u1s1-cli 0.12.1 → 0.13.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.
@@ -46,6 +46,7 @@ export declare function toProviderModels(models: ModelDef[]): {
46
46
  * 这是 pi 自带 llama provider 的同款兜底,本地服务会忽略 Authorization 头。
47
47
  */
48
48
  export declare function endpointProviderEntry(ep: CustomEndpoint): Record<string, unknown>;
49
+ export declare function scrubForeignProviderEnv(): void;
49
50
  /** 把各端点密钥放进环境(TUI 直接 process.env;web 传给子进程)。 */
50
51
  export declare function endpointKeyEnv(): Record<string, string>;
51
52
  export declare function ensureProviderModels(cfg: CliConfig): void;
@@ -160,6 +160,56 @@ export function endpointProviderEntry(ep) {
160
160
  models: toProviderModels(ep.models),
161
161
  };
162
162
  }
163
+ /**
164
+ * pi 会把环境里的厂商密钥(ANTHROPIC_API_KEY 等)当作「已配置的 provider」,
165
+ * /model 里冒出一大串没在用的模型(u1s1 又拦了 /login,配了也没法管理)。
166
+ * u1s1 的口径是:官方模型 + 自定义端点,所以启动 pi 前把这批**纯 LLM 厂商**
167
+ * 密钥从 pi 进程环境里剔掉。名单来自 pi-ai env-api-keys.ts;刻意保留
168
+ * AWS_WEB_IDENTITY_TOKEN_FILE / HF_TOKEN / GOOGLE_CLOUD_API_KEY /
169
+ * CLOUDFLARE_API_KEY 这类通用凭据 —— agent 终端里的其他工具还要用。
170
+ * 注意 agent 的 Bash 子进程也继承不到被剔的变量;项目 .env 文件不受影响。
171
+ */
172
+ const FOREIGN_PROVIDER_ENV_KEYS = [
173
+ "AI_GATEWAY_API_KEY",
174
+ "ANTHROPIC_API_KEY",
175
+ "ANTHROPIC_AUTH_TOKEN",
176
+ "ANTHROPIC_OAUTH_TOKEN",
177
+ "ANT_LING_API_KEY",
178
+ "AWS_BEARER_TOKEN_BEDROCK",
179
+ "AZURE_OPENAI_API_KEY",
180
+ "BASETEN_API_KEY",
181
+ "CEREBRAS_API_KEY",
182
+ "COPILOT_GITHUB_TOKEN",
183
+ "DEEPSEEK_API_KEY",
184
+ "FIREWORKS_API_KEY",
185
+ "GEMINI_API_KEY",
186
+ "GROQ_API_KEY",
187
+ "KIMI_API_KEY",
188
+ "MINIMAX_API_KEY",
189
+ "MINIMAX_CN_API_KEY",
190
+ "MISTRAL_API_KEY",
191
+ "MOONSHOT_API_KEY",
192
+ "NVIDIA_API_KEY",
193
+ "OPENAI_API_KEY",
194
+ "OPENCODE_API_KEY",
195
+ "OPENROUTER_API_KEY",
196
+ "QWEN_TOKEN_PLAN_API_KEY",
197
+ "QWEN_TOKEN_PLAN_CN_API_KEY",
198
+ "RADIUS_API_KEY",
199
+ "TOGETHER_API_KEY",
200
+ "XAI_API_KEY",
201
+ "XIAOMI_API_KEY",
202
+ "XIAOMI_TOKEN_PLAN_AMS_API_KEY",
203
+ "XIAOMI_TOKEN_PLAN_CN_API_KEY",
204
+ "XIAOMI_TOKEN_PLAN_SGP_API_KEY",
205
+ "ZAI_API_KEY",
206
+ "ZAI_CODING_CN_API_KEY",
207
+ ];
208
+ export function scrubForeignProviderEnv() {
209
+ for (const key of FOREIGN_PROVIDER_ENV_KEYS) {
210
+ delete process.env[key];
211
+ }
212
+ }
163
213
  /** 把各端点密钥放进环境(TUI 直接 process.env;web 传给子进程)。 */
164
214
  export function endpointKeyEnv() {
165
215
  const env = {};
package/dist/api.d.ts CHANGED
@@ -14,6 +14,22 @@ export interface MeResponse {
14
14
  remaining_usd: number;
15
15
  /** USD→默认模型 Token 折算率;老网关没有该字段,展示时需兜底回 $ */
16
16
  tokens_per_usd?: number;
17
+ /** 用量包(新网关);老网关没有该字段,展示时退回 daily_free_* 旧口径 */
18
+ packages?: MePackage[];
19
+ /** 可领取的免费包:'first' 首月包 / 'renew' 年度包 / null 已有生效中的 */
20
+ free_claim?: "first" | "renew" | null;
21
+ }
22
+ export interface MePackage {
23
+ id: number;
24
+ kind: string;
25
+ scope: string;
26
+ daily_tokens: number | null;
27
+ total_tokens: number | null;
28
+ used_today: number;
29
+ used_tokens: number;
30
+ remaining: number;
31
+ expires_at: string | null;
32
+ note: string | null;
17
33
  }
18
34
  export interface ApiModel {
19
35
  id: string;
package/dist/config.js CHANGED
@@ -58,7 +58,7 @@ export const MODELS = [
58
58
  contextWindow: 1_048_576,
59
59
  maxTokens: 384_000,
60
60
  cost: { input: 0.14, output: 0.28, cacheRead: 0, cacheWrite: 0 },
61
- note: "默认 · 每日 $2 免费,日常写代码首选",
61
+ note: "默认 · 免费用量包额度内免费,日常写代码首选",
62
62
  },
63
63
  {
64
64
  id: "x-ai/grok-4.6",
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync } from "node:child_process";
3
3
  import { writeFileSync } from "node:fs";
4
- import { cleanupBrandThemes, endpointKeyEnv, endpointProviderEntry, ensureBrandPrompt, ensureDefaultSettings, ensureProviderModels, toProviderModels, writeWebToolsExtension, } from "./agent-setup.js";
4
+ import { cleanupBrandThemes, endpointKeyEnv, endpointProviderEntry, ensureBrandPrompt, ensureDefaultSettings, ensureProviderModels, scrubForeignProviderEnv, toProviderModels, writeWebToolsExtension, } from "./agent-setup.js";
5
5
  import { printConsoleBanner } from "./brand.js";
6
6
  import { agentDir, apiModelToDef, CUSTOM_ENDPOINTS, isPortableInstall, loadConfig, MODELS, persistPreferredModel, PROVIDER_ID, readSettings, refValid, resolvePreferredModel, setModelsFromApi, VERSION, } from "./config.js";
7
7
  import { ensureSearchTools } from "./search-tools.js";
@@ -145,6 +145,8 @@ async function runAgent(cfg, args) {
145
145
  process.env["U1S1_TOOLS_VIA_EXTENSION"] = "1";
146
146
  // 自定义端点的密钥走环境变量引用(models.json 里只有 $VAR,不落明文)
147
147
  Object.assign(process.env, endpointKeyEnv());
148
+ // 剔掉环境里的其他厂商密钥,/model 只展示 u1s1 官方模型 + 自定义端点
149
+ scrubForeignProviderEnv();
148
150
  // hide pi's own "Run pi update" banner; users should run `u1s1 update` instead
149
151
  process.env["PI_SKIP_VERSION_CHECK"] = "1";
150
152
  await searchToolsReady;
package/dist/login.js CHANGED
@@ -74,7 +74,7 @@ async function promptForKey() {
74
74
  process.exit(1);
75
75
  }
76
76
  console.log(" 需要一把 API Key(免费):");
77
- console.log(` 1. 打开 ${DASHBOARD_URL} 注册/登录(30 秒,DeepSeek 每天免费 $2)`);
77
+ console.log(` 1. 打开 ${DASHBOARD_URL} 注册/登录(30 秒,注册领免费用量包:首月每天 1 亿 Token)`);
78
78
  console.log(" 2. 复制你的 API Key,粘贴到下面");
79
79
  console.log("");
80
80
  tryOpenBrowser(DASHBOARD_URL);
@@ -91,7 +91,7 @@ export async function login(keyArg) {
91
91
  const origin = apiOrigin(cfg);
92
92
  const start = await startDeviceLogin(origin);
93
93
  if (start) {
94
- console.log(" 用浏览器登录(DeepSeek 每天免费 $2):");
94
+ console.log(" 用浏览器登录(注册领免费用量包,首月每天 1 亿 Token):");
95
95
  console.log("");
96
96
  console.log(` ${start.verify_url}`);
97
97
  console.log("");
@@ -119,7 +119,29 @@ export async function login(keyArg) {
119
119
  process.exit(1);
120
120
  });
121
121
  saveConfig(next);
122
- console.log(` ✓ 登录成功${me.email ? `(${me.email})` : ""},今日免费还剩 $${me.daily_free_remaining_usd},永久余额 $${me.remaining_usd}。`);
122
+ const tpu = me.tokens_per_usd ?? 0;
123
+ let quotaNote;
124
+ if (me.free_claim) {
125
+ // 新网关且免费包可领/可续,登录成功时顺手提醒
126
+ quotaNote =
127
+ me.free_claim === "first"
128
+ ? "免费用量包还没领,去 https://u1s1.io/dashboard 点「领取」(首月每天 1 亿 Token)"
129
+ : "免费用量包到期了,去 https://u1s1.io/dashboard 续领(每天 5000 万 Token)";
130
+ }
131
+ else if (tpu > 0) {
132
+ const tok = (usd) => {
133
+ const t = usd * tpu;
134
+ if (t >= 1e8)
135
+ return `${Math.round((t / 1e8) * 10) / 10} 亿`;
136
+ return `${Math.round(t / 1e4).toLocaleString("en-US")} 万`;
137
+ };
138
+ quotaNote = `今日免费还剩约 ${tok(me.daily_free_remaining_usd)} Token`;
139
+ }
140
+ else {
141
+ // 老网关:没有 tokens_per_usd,退回金额显示
142
+ quotaNote = `今日免费还剩 $${me.daily_free_remaining_usd},永久余额 $${me.remaining_usd}`;
143
+ }
144
+ console.log(` ✓ 登录成功${me.email ? `(${me.email})` : ""},${quotaNote}。`);
123
145
  return next;
124
146
  }
125
147
  /** Returns a config that definitely has an apiKey, prompting the user if needed. */
package/dist/update.js CHANGED
@@ -1,5 +1,8 @@
1
- import { execSync } from "node:child_process";
1
+ import { execSync, spawn } from "node:child_process";
2
+ import { mkdtempSync, writeFileSync } from "node:fs";
2
3
  import { createRequire } from "node:module";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
3
6
  import { isPortableInstall } from "./config.js";
4
7
  const require = createRequire(import.meta.url);
5
8
  const pkg = require("../package.json");
@@ -49,6 +52,55 @@ export function compareVersions(a, b) {
49
52
  }
50
53
  return 0;
51
54
  }
55
+ /**
56
+ * 便携版原地升级:下载官网安装脚本并替用户执行,整包替换安装目录。
57
+ *
58
+ * - Linux/macOS:同进程跑 bash 安装脚本。替换正在运行的文件在 unix 上安全
59
+ * (旧 inode 继续存活到进程退出)。
60
+ * - Windows:正在运行的 node.exe 锁着自己,同进程装不了 —— 弹一个新的
61
+ * PowerShell 窗口跑 install.ps1(脚本会先结束安装目录里残留的 node 进程,
62
+ * 包括本进程,属预期),本进程立刻退出把文件锁让出来。
63
+ */
64
+ async function portableSelfUpdate(latest) {
65
+ if (process.platform === "win32") {
66
+ console.log(`正在打开更新窗口(升级到 v${latest})…`);
67
+ console.log("更新在新窗口里进行,本窗口会退出;装完后重新打开终端运行 u1s1 即可。");
68
+ // 命令写进临时 .ps1 再 start,避开 cmd/start 对内联命令的引号地狱;
69
+ // start 的第一个引号参数是窗口标题,必须占位,否则带空格的路径会被吞掉
70
+ const tmp = join(mkdtempSync(join(tmpdir(), "u1s1-update-")), "update.ps1");
71
+ writeFileSync(tmp, "irm https://u1s1.io/releases/install.ps1 | iex\n");
72
+ const child = spawn("cmd.exe", ["/c", "start", "u1s1", "powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-NoExit", "-File", tmp], { detached: true, stdio: "ignore" });
73
+ child.unref();
74
+ // 立刻退出释放 node.exe 的文件锁,让安装器能替换安装目录
75
+ process.exit(0);
76
+ }
77
+ console.log(`正在下载安装脚本并升级到 v${latest}…`);
78
+ let script;
79
+ try {
80
+ const res = await fetch("https://u1s1.io/releases/install.sh", {
81
+ signal: AbortSignal.timeout(30_000),
82
+ });
83
+ if (!res.ok)
84
+ throw new Error(`HTTP ${res.status}`);
85
+ script = await res.text();
86
+ }
87
+ catch {
88
+ console.error("安装脚本下载失败。请手动运行:");
89
+ console.error(" curl -fsSL https://u1s1.io/releases/install.sh | bash");
90
+ process.exit(1);
91
+ }
92
+ const tmp = join(mkdtempSync(join(tmpdir(), "u1s1-update-")), "install.sh");
93
+ writeFileSync(tmp, script, { mode: 0o755 });
94
+ try {
95
+ execSync(`bash ${JSON.stringify(tmp)}`, { stdio: "inherit" });
96
+ console.log(`\n✅ 已升级到 v${latest},重新运行 u1s1 生效。`);
97
+ }
98
+ catch {
99
+ console.error("\n自动升级失败。请手动运行:");
100
+ console.error(" curl -fsSL https://u1s1.io/releases/install.sh | bash");
101
+ process.exit(1);
102
+ }
103
+ }
52
104
  export async function update() {
53
105
  console.log("正在检查更新…");
54
106
  const latest = await getLatestVersion();
@@ -62,15 +114,9 @@ export async function update() {
62
114
  }
63
115
  console.log(`发现新版本 v${latest} (当前 v${VERSION})`);
64
116
  // 便携版:npm 装到全局目录,但 PATH 先命中便携目录,怎么更都还是旧版。
65
- // 唯一正确的升级方式是重跑官网安装命令整包替换,这里不再碰 npm。
117
+ // 正确方式是重跑官网安装脚本整包替换 —— 这里直接替用户跑,不再让人手抄命令。
66
118
  if (isPortableInstall()) {
67
- const isWin = process.platform === "win32";
68
- console.log("");
69
- console.log("你装的是便携版(自带 Node),npm 更新不了它。升级分两步:");
70
- console.log(" 1. 关闭所有 u1s1 窗口");
71
- console.log(isWin
72
- ? " 2. 打开 PowerShell 运行: irm https://u1s1.io/releases/install.ps1 | iex"
73
- : " 2. 在终端运行: curl -fsSL https://u1s1.io/releases/install.sh | bash");
119
+ await portableSelfUpdate(latest);
74
120
  return;
75
121
  }
76
122
  const pm = detectPackageManager();
package/dist/usage.js CHANGED
@@ -35,6 +35,46 @@ export async function usage() {
35
35
  const tpu = me.tokens_per_usd ?? 0;
36
36
  console.log("");
37
37
  console.log(` 账号 ${me.email ?? "(未绑定邮箱)"}`);
38
+ if (me.packages) {
39
+ // 新网关:按用量包逐个展示,token 原生数字
40
+ const PKG_LABEL = {
41
+ free_first: "首月免费包",
42
+ free_yearly: "年度免费包",
43
+ invite: "邀请赠送",
44
+ topup_daily: "每日加量包",
45
+ admin_grant: "官方赠送",
46
+ };
47
+ if (me.packages.length === 0)
48
+ console.log(" 用量包 (无生效中的用量包)");
49
+ for (const p of me.packages) {
50
+ const daily = p.daily_tokens != null;
51
+ const total = daily ? (p.daily_tokens ?? 0) : (p.total_tokens ?? 0);
52
+ const ratio = total > 0 ? p.remaining / total : 0;
53
+ const label = (PKG_LABEL[p.kind] ?? p.kind).padEnd(5, " ");
54
+ const per = daily ? "/天" : "";
55
+ console.log(` ${label} 还剩 ${fmtTokensCn(p.remaining)} / ${fmtTokensCn(total)}${per} ${bar(ratio)}`);
56
+ const scopeNote = p.scope === "free" ? "仅默认模型和搜索 · 0 点恢复" : "全模型可用";
57
+ console.log(` ${scopeNote} · ${p.expires_at ? `${p.expires_at.slice(0, 10)} 到期` : "永不过期"}`);
58
+ }
59
+ if (me.bonus_balance_usd > 0) {
60
+ const balText = tpu > 0 ? `${fmtTokensCn(me.bonus_balance_usd * tpu)} Token` : `$${me.bonus_balance_usd}`;
61
+ console.log(` 余额(按量) ${balText}`);
62
+ }
63
+ console.log(` 本月已用 约 ${tpu > 0 ? `${fmtTokensCn(me.mtd_usd * tpu)} Token` : ""}($${me.mtd_usd.toFixed(2)})`);
64
+ console.log("");
65
+ if (me.free_claim === "first") {
66
+ console.log(" → 你有免费用量包没领:首月每天 1 亿 Token,去 https://u1s1.io/dashboard 点「领取」");
67
+ }
68
+ else if (me.free_claim === "renew") {
69
+ console.log(" → 免费用量包到期了,去 https://u1s1.io/dashboard 续领:每天 5000 万 Token,一年有效");
70
+ }
71
+ else {
72
+ console.log(" 免费包每天 0 点恢复;邀请朋友双方各得一次性 1 亿 Token 包 → https://u1s1.io/dashboard");
73
+ }
74
+ console.log(" Token 数按默认模型单价折算,为约数;用更贵的模型时消耗更快。");
75
+ console.log("");
76
+ return;
77
+ }
38
78
  if (tpu > 0) {
39
79
  const tok = (usd) => `${fmtTokensCn(usd * tpu)} Token`;
40
80
  console.log(` 今日免费 还剩 ${fmtTokensCn(freeRemain * tpu)} / ${tok(freeTotal)} ${bar(freeRatio)}`);
package/dist/web.js CHANGED
@@ -2,7 +2,7 @@ import { spawn, spawnSync } from "node:child_process";
2
2
  import { mkdirSync } from "node:fs";
3
3
  import { createRequire } from "node:module";
4
4
  import { dirname, join } from "node:path";
5
- import { cleanupBrandThemes, endpointKeyEnv, ensureAuthCredential, ensureBrandPrompt, ensureDefaultSettings, ensureProviderModels, writeWebToolsExtension, } from "./agent-setup.js";
5
+ import { cleanupBrandThemes, endpointKeyEnv, ensureAuthCredential, ensureBrandPrompt, ensureDefaultSettings, ensureProviderModels, scrubForeignProviderEnv, writeWebToolsExtension, } from "./agent-setup.js";
6
6
  import { agentDir, apiModelToDef, resolvePreferredModel, setModelsFromApi, u1s1Dir, writeAgentDefaultModel, } from "./config.js";
7
7
  import { ensureSearchTools } from "./search-tools.js";
8
8
  import { applyWebUiBranding, applyWebUiFrontend } from "./webui-brand.js";
@@ -49,6 +49,8 @@ export async function prepareWebEnv(cfg) {
49
49
  writeAgentDefaultModel(pref.provider, pref.id);
50
50
  const dataDir = join(u1s1Dir, "web");
51
51
  mkdirSync(dataDir, { recursive: true });
52
+ // web 子进程按 {...process.env, ...返回值} 起,这里剔掉即对子进程生效
53
+ scrubForeignProviderEnv();
52
54
  await searchToolsReady;
53
55
  return {
54
56
  PI_CODING_AGENT_DIR: agentDir,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u1s1-cli",
3
- "version": "0.12.1",
3
+ "version": "0.13.1",
4
4
  "description": "u1s1 — 有一说一,最省心的 AI 编程搭子。终端里用中文说需求,AI 帮你读文件、改代码、跑命令。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -47,8 +47,8 @@
47
47
  "pi-web-ui": "0.26.0"
48
48
  },
49
49
  "devDependencies": {
50
- "@types/node": "^22.15.0",
51
- "tsx": "^4.20.0",
52
- "typescript": "^5.9.2"
50
+ "@types/node": "^22.20.1",
51
+ "tsx": "^4.23.12",
52
+ "typescript": "^5.9.3"
53
53
  }
54
54
  }