u1s1-cli 0.2.0 → 0.4.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/index.js CHANGED
@@ -1,8 +1,12 @@
1
1
  #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
2
3
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
4
  import { join } from "node:path";
4
5
  import { createRequire } from "node:module";
5
- import { agentDir, DEFAULT_MODEL_ID, loadConfig, MODELS, PROVIDER_ID } from "./config.js";
6
+ import { printConsoleBanner } from "./brand.js";
7
+ import { agentDir, loadConfig, MODELS, persistPreferredModel, PROVIDER_ID, resolvePreferredModel, } from "./config.js";
8
+ import { applyBrandUi } from "./style.js";
9
+ import { DEFAULT_THEME_SETTING, ensureBrandThemes } from "./themes.js";
6
10
  const require = createRequire(import.meta.url);
7
11
  const VERSION = require("../package.json").version;
8
12
  const BRAND_APPEND = `## u1s1
@@ -21,16 +25,113 @@ function ensureBrandPrompt() {
21
25
  writeFileSync(p, BRAND_APPEND);
22
26
  }
23
27
  }
28
+ /** Defaults that don't overwrite values the user already set. */
29
+ function ensureDefaultSettings() {
30
+ mkdirSync(agentDir, { recursive: true });
31
+ const p = join(agentDir, "settings.json");
32
+ let settings = {};
33
+ if (existsSync(p)) {
34
+ try {
35
+ settings = JSON.parse(readFileSync(p, "utf8"));
36
+ }
37
+ catch {
38
+ return;
39
+ }
40
+ }
41
+ let changed = false;
42
+ if (!("hideThinkingBlock" in settings)) {
43
+ settings.hideThinkingBlock = true;
44
+ changed = true;
45
+ }
46
+ // Default / leftover Pi themes → u1s1 Claude-like pair. Custom names stay put.
47
+ const theme = settings.theme;
48
+ if (theme === undefined || theme === "dark" || theme === "light") {
49
+ settings.theme = DEFAULT_THEME_SETTING;
50
+ changed = true;
51
+ }
52
+ // Pi default is one-at-a-time (one queued message per turn). u1s1 delivers
53
+ // all queued messages together so burst typing isn't split across turns.
54
+ // Only fill in missing keys so an explicit /settings choice still sticks.
55
+ for (const key of ["steeringMode", "followUpMode"]) {
56
+ if (!(key in settings)) {
57
+ settings[key] = "all";
58
+ changed = true;
59
+ }
60
+ }
61
+ if (changed)
62
+ writeFileSync(p, JSON.stringify(settings, null, 2) + "\n");
63
+ }
64
+ /**
65
+ * tmux 默认不转发键盘修饰键,会把 Shift+Enter 当成普通回车发出去,消息没写完就被发送。
66
+ * 只开 extended-keys 不够:默认 terminal-features 里 xterm* 没有 extkeys,tmux 根本不会
67
+ * 向外层终端要 modifyOtherKeys,S-Enter 绑定也就永远触发不了。
68
+ * 启动时自动做这些事(运行时生效,不写用户 tmux.conf;失败则静默忽略,仍可用 Ctrl+J 换行):
69
+ * 1. 开启 extended-keys,并给当前终端补上 extkeys;
70
+ * 2. tmux 3.5+ 再切到 csi-u(3.4 及以下没有这个选项,忽略即可);
71
+ * 3. 兜底把 S-Enter / C-Enter 原样转成 pi 认得的 CSI-u 序列。
72
+ */
73
+ function ensureTmuxKeyboardProtocol() {
74
+ if (!process.env.TMUX)
75
+ return;
76
+ const run = (args) => spawnSync("tmux", args, { timeout: 1500, encoding: "utf8" });
77
+ if (run(["-V"]).status !== 0)
78
+ return; // tmux 不可用(如沙箱)则跳过
79
+ // 当前会话 + 全局默认都开,避免只改 -g 时已有会话仍是 off
80
+ run(["set-option", "-g", "extended-keys", "on"]);
81
+ run(["set-option", "extended-keys", "on"]);
82
+ // 3.5+; 3.4 会失败,忽略
83
+ run(["set-option", "-g", "extended-keys-format", "csi-u"]);
84
+ run(["set-option", "extended-keys-format", "csi-u"]);
85
+ const features = run(["show", "-gv", "terminal-features"]);
86
+ if (features.status === 0 && !/\bextkeys\b/.test(features.stdout ?? "")) {
87
+ run(["set-option", "-ga", "terminal-features", "xterm*:extkeys"]);
88
+ }
89
+ // 已挂上的客户端不会自动重读 terminal-features;直接往当前客户端 tty
90
+ // 发 modifyOtherKeys,让外层终端立刻开始区分 Shift+Enter。
91
+ const tty = run(["display-message", "-p", "#{client_tty}"]).stdout?.trim();
92
+ if (tty) {
93
+ try {
94
+ writeFileSync(tty, "\x1b[>4;2m");
95
+ }
96
+ catch {
97
+ // 没权限或不是 tty 就忽略
98
+ }
99
+ }
100
+ // -l: 按字面量发送,避免 send-keys 把转义序列拆成一串普通按键
101
+ run(["bind-key", "-n", "S-Enter", "send-keys", "-l", "\x1b[13;2u"]);
102
+ run(["bind-key", "-n", "C-Enter", "send-keys", "-l", "\x1b[13;5u"]);
103
+ }
24
104
  async function runAgent(cfg, args) {
105
+ ensureBrandThemes();
25
106
  ensureBrandPrompt();
107
+ ensureDefaultSettings();
108
+ ensureTmuxKeyboardProtocol();
26
109
  // must be set before pi reads them (getAgentDir() reads at call time, env at import is fine too)
27
110
  process.env["PI_CODING_AGENT_DIR"] = agentDir;
28
111
  process.env["U1S1_API_KEY"] = cfg.apiKey;
29
- const { main } = await import("@earendil-works/pi-coding-agent");
112
+ // hide pi's own "Run pi update" banner; users should run `u1s1 update` instead
113
+ process.env["PI_SKIP_VERSION_CHECK"] = "1";
114
+ const { CustomEditor, main } = await import("@earendil-works/pi-coding-agent");
115
+ // 网页终端(如 Taikula)把 Shift+Enter 发成 ESC+CR(\x1b\r)。pi 在没开 Kitty
116
+ // 协议时把它当成 Alt+Enter:空闲就直接发送,忙碌才排队。先改成 CSI-u 的
117
+ // Shift+Enter,输入框就会换行,真·Alt+Enter(\x1b[13;3u)不受影响。
118
+ class ShiftEnterEditor extends CustomEditor {
119
+ handleInput(data) {
120
+ super.handleInput(data === "\x1b\r" ? "\x1b[13;2u" : data);
121
+ }
122
+ }
30
123
  const extension = [
31
124
  {
32
125
  name: "u1s1",
33
126
  factory: (pi) => {
127
+ applyBrandUi(pi, VERSION);
128
+ // pi 内置只有 /quit;补一个更常见的 /exit,方便新手退出
129
+ pi.registerCommand("exit", {
130
+ description: "退出 u1s1",
131
+ handler: async (_args, ctx) => {
132
+ ctx.shutdown();
133
+ },
134
+ });
34
135
  pi.registerProvider(PROVIDER_ID, {
35
136
  name: "u1s1",
36
137
  baseUrl: cfg.baseUrl,
@@ -46,11 +147,35 @@ async function runAgent(cfg, args) {
46
147
  maxTokens: m.maxTokens,
47
148
  })),
48
149
  });
150
+ // /model and Ctrl+P already write pi settings; also keep ~/.u1s1/config.json in sync
151
+ pi.on("model_select", (event) => {
152
+ if (event.source === "restore")
153
+ return;
154
+ if (event.model.provider !== PROVIDER_ID)
155
+ return;
156
+ if (!MODELS.some((m) => m.id === event.model.id))
157
+ return;
158
+ persistPreferredModel(loadConfig(), event.model.id);
159
+ });
160
+ pi.on("session_start", (_event, ctx) => {
161
+ const previous = ctx.ui.getEditorComponent();
162
+ ctx.ui.setEditorComponent((tui, theme, keybindings) => {
163
+ if (previous) {
164
+ const inner = previous(tui, theme, keybindings);
165
+ const orig = inner.handleInput.bind(inner);
166
+ inner.handleInput = (data) => orig(data === "\x1b\r" ? "\x1b[13;2u" : data);
167
+ return inner;
168
+ }
169
+ return new ShiftEnterEditor(tui, theme, keybindings);
170
+ });
171
+ });
49
172
  },
50
173
  },
51
174
  ];
52
175
  const hasModelArg = args.some((a) => a === "--model" || a.startsWith("--model=") || a === "--provider");
53
- const defaultModel = cfg.model ?? DEFAULT_MODEL_ID;
176
+ const defaultModel = resolvePreferredModel(cfg.model);
177
+ if (cfg.model !== defaultModel)
178
+ persistPreferredModel(cfg, defaultModel);
54
179
  const finalArgs = hasModelArg ? args : ["--model", `${PROVIDER_ID}/${defaultModel}`, ...args];
55
180
  await main(finalArgs, { extensionFactories: extension });
56
181
  }
@@ -61,6 +186,9 @@ async function run() {
61
186
  console.log(`u1s1 v${VERSION}`);
62
187
  return;
63
188
  }
189
+ if (cmd === "--help" || cmd === "-h") {
190
+ printConsoleBanner(VERSION);
191
+ }
64
192
  if (cmd === "login") {
65
193
  const { login } = await import("./login.js");
66
194
  await login(args[1]);
@@ -82,6 +210,16 @@ async function run() {
82
210
  console.log("已退出登录。");
83
211
  return;
84
212
  }
213
+ if (cmd === "update") {
214
+ const { update } = await import("./update.js");
215
+ await update();
216
+ return;
217
+ }
218
+ if (cmd === "import") {
219
+ const { importCommand } = await import("./import/index.js");
220
+ await importCommand(args.slice(1));
221
+ return;
222
+ }
85
223
  const { ensureAuth } = await import("./login.js");
86
224
  const cfg = await ensureAuth();
87
225
  await runAgent(cfg, args);
package/dist/login.js CHANGED
@@ -1,7 +1,10 @@
1
+ import { createRequire } from "node:module";
1
2
  import { createInterface } from "node:readline/promises";
2
3
  import { fetchMe } from "./api.js";
4
+ import { DASHBOARD_URL, printConsoleBanner } from "./brand.js";
3
5
  import { loadConfig, saveConfig } from "./config.js";
4
- const DASHBOARD_URL = "https://u1s1.io/dashboard";
6
+ const require = createRequire(import.meta.url);
7
+ const VERSION = require("../package.json").version;
5
8
  function tryOpenBrowser(url) {
6
9
  import("node:child_process")
7
10
  .then(({ spawn }) => {
@@ -14,9 +17,9 @@ export async function login(keyArg) {
14
17
  const cfg = loadConfig();
15
18
  let key = keyArg?.trim();
16
19
  if (!key) {
17
- console.log("");
18
- console.log(" u1s1 需要一把 API Key(免费):");
19
- console.log(` 1. 打开 ${DASHBOARD_URL} 注册/登录(30 秒,送每月 $2 额度)`);
20
+ printConsoleBanner(VERSION);
21
+ console.log(" 需要一把 API Key(免费):");
22
+ console.log(` 1. 打开 ${DASHBOARD_URL} 注册/登录(30 秒,送 $10 额度)`);
20
23
  console.log(" 2. 复制你的 API Key,粘贴到下面");
21
24
  console.log("");
22
25
  tryOpenBrowser(DASHBOARD_URL);
@@ -34,7 +37,7 @@ export async function login(keyArg) {
34
37
  process.exit(1);
35
38
  });
36
39
  saveConfig(next);
37
- console.log(` ✓ 登录成功${me.email ? `(${me.email})` : ""},本月还剩 $${me.remaining_usd} 额度。`);
40
+ console.log(` ✓ 登录成功${me.email ? `(${me.email})` : ""},还剩 $${me.remaining_usd} 额度。`);
38
41
  return next;
39
42
  }
40
43
  /** Returns a config that definitely has an apiKey, prompting the user if needed. */
package/dist/model.js CHANGED
@@ -1,7 +1,7 @@
1
- import { DEFAULT_MODEL_ID, loadConfig, MODELS, resolveModel, saveConfig } from "./config.js";
1
+ import { loadConfig, MODELS, persistPreferredModel, resolveModel, resolvePreferredModel } from "./config.js";
2
2
  export async function modelCommand(nameOrAlias) {
3
3
  const cfg = loadConfig();
4
- const current = cfg.model ?? DEFAULT_MODEL_ID;
4
+ const current = resolvePreferredModel(cfg.model);
5
5
  if (!nameOrAlias) {
6
6
  console.log("");
7
7
  for (const m of MODELS) {
@@ -10,7 +10,7 @@ export async function modelCommand(nameOrAlias) {
10
10
  console.log(` ${m.note} · $${m.cost.input}/$${m.cost.output} 每百万 token`);
11
11
  }
12
12
  console.log("");
13
- console.log(" 切换:u1s1 model grok / u1s1 model deepseek(也可在对话里用 /model 临时切)");
13
+ console.log(" 切换:u1s1 model grok / u1s1 model deepseek(对话里 /model 同样会记住)");
14
14
  console.log("");
15
15
  return;
16
16
  }
@@ -19,6 +19,6 @@ export async function modelCommand(nameOrAlias) {
19
19
  console.error(` 没有叫「${nameOrAlias}」的模型,可选:${MODELS.map((x) => x.aliases[0]).join(" / ")}`);
20
20
  process.exit(1);
21
21
  }
22
- saveConfig({ ...cfg, model: m.id });
22
+ persistPreferredModel(cfg, m.id);
23
23
  console.log(` ✓ 默认模型已切到 ${m.name}(${m.note})`);
24
24
  }
package/dist/style.js ADDED
@@ -0,0 +1,21 @@
1
+ import { basename } from "node:path";
2
+ import { truncateToWidth } from "@earendil-works/pi-tui";
3
+ import { renderBrandHeader } from "./brand.js";
4
+ /**
5
+ * Brand chrome is just the startup hero + window title; everything else
6
+ * (tool rows, thinking blocks, spinner) stays Pi's default UI.
7
+ */
8
+ export function applyBrandUi(pi, version) {
9
+ pi.on("session_start", async (_event, ctx) => {
10
+ if (ctx.mode !== "tui")
11
+ return;
12
+ ctx.ui.setHeader((_tui, theme) => ({
13
+ render(width) {
14
+ // pi-tui crashes on lines wider than the terminal, so truncate defensively.
15
+ return renderBrandHeader(theme, version, process.cwd(), width).map((line) => truncateToWidth(line, width));
16
+ },
17
+ invalidate() { },
18
+ }));
19
+ ctx.ui.setTitle(`u1s1 — ${basename(process.cwd())}`);
20
+ });
21
+ }
package/dist/themes.js ADDED
@@ -0,0 +1,177 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { agentDir } from "./config.js";
4
+ /**
5
+ * Claude Code-like: warm terracotta accent, no gray message/tool boxes,
6
+ * text sits on the terminal background instead of Codex-style cards.
7
+ */
8
+ export const DARK_THEME = {
9
+ name: "u1s1-dark",
10
+ vars: {
11
+ orange: "#d97757",
12
+ orangeSoft: "#e8a87c",
13
+ text: "#e8e4db",
14
+ muted: "#9a9488",
15
+ dim: "#6b665e",
16
+ green: "#8fbc8f",
17
+ red: "#e07a7a",
18
+ yellow: "#d4a574",
19
+ blue: "#89b4c4",
20
+ selectedBg: "#3a342e",
21
+ userMsgBg: "#34312c",
22
+ },
23
+ colors: {
24
+ accent: "orange",
25
+ border: "orangeSoft",
26
+ borderAccent: "orange",
27
+ borderMuted: "dim",
28
+ success: "green",
29
+ error: "red",
30
+ warning: "yellow",
31
+ muted: "muted",
32
+ dim: "dim",
33
+ text: "text",
34
+ thinkingText: "muted",
35
+ selectedBg: "selectedBg",
36
+ scrollbarThumb: "selectedBg",
37
+ searchMatchBg: "selectedBg",
38
+ searchMatchText: "text",
39
+ userMessageBg: "userMsgBg",
40
+ userMessageText: "text",
41
+ customMessageBg: "userMsgBg",
42
+ customMessageText: "text",
43
+ customMessageLabel: "orangeSoft",
44
+ toolPendingBg: "",
45
+ toolSuccessBg: "",
46
+ toolErrorBg: "",
47
+ toolTitle: "text",
48
+ toolOutput: "muted",
49
+ mdHeading: "orangeSoft",
50
+ mdLink: "blue",
51
+ mdLinkUrl: "dim",
52
+ mdCode: "orangeSoft",
53
+ mdCodeBlock: "text",
54
+ mdCodeBlockBorder: "dim",
55
+ mdQuote: "muted",
56
+ mdQuoteBorder: "orange",
57
+ mdHr: "dim",
58
+ mdListBullet: "orange",
59
+ toolDiffAdded: "green",
60
+ toolDiffRemoved: "red",
61
+ toolDiffContext: "muted",
62
+ syntaxComment: "muted",
63
+ syntaxKeyword: "orangeSoft",
64
+ syntaxFunction: "orange",
65
+ syntaxVariable: "text",
66
+ syntaxString: "green",
67
+ syntaxNumber: "yellow",
68
+ syntaxType: "blue",
69
+ syntaxOperator: "muted",
70
+ syntaxPunctuation: "dim",
71
+ thinkingOff: "dim",
72
+ thinkingMinimal: "muted",
73
+ thinkingLow: "blue",
74
+ thinkingMedium: "orangeSoft",
75
+ thinkingHigh: "orange",
76
+ thinkingXhigh: "yellow",
77
+ thinkingMax: "red",
78
+ bashMode: "green",
79
+ },
80
+ export: {
81
+ pageBg: "#1c1917",
82
+ cardBg: "#292524",
83
+ infoBg: "#3d3429",
84
+ },
85
+ };
86
+ export const LIGHT_THEME = {
87
+ name: "u1s1-light",
88
+ vars: {
89
+ orange: "#c45c38",
90
+ orangeSoft: "#b86a45",
91
+ text: "#2c2825",
92
+ muted: "#6f6a63",
93
+ dim: "#9a9488",
94
+ green: "#3d7a4a",
95
+ red: "#c45c5c",
96
+ yellow: "#9a6b2f",
97
+ blue: "#3d6f8a",
98
+ selectedBg: "#efe8e0",
99
+ userMsgBg: "#e8e4dc",
100
+ },
101
+ colors: {
102
+ accent: "orange",
103
+ border: "orangeSoft",
104
+ borderAccent: "orange",
105
+ borderMuted: "dim",
106
+ success: "green",
107
+ error: "red",
108
+ warning: "yellow",
109
+ muted: "muted",
110
+ dim: "dim",
111
+ text: "text",
112
+ thinkingText: "muted",
113
+ selectedBg: "selectedBg",
114
+ scrollbarThumb: "selectedBg",
115
+ searchMatchBg: "selectedBg",
116
+ searchMatchText: "text",
117
+ userMessageBg: "userMsgBg",
118
+ userMessageText: "text",
119
+ customMessageBg: "userMsgBg",
120
+ customMessageText: "text",
121
+ customMessageLabel: "orangeSoft",
122
+ toolPendingBg: "",
123
+ toolSuccessBg: "",
124
+ toolErrorBg: "",
125
+ toolTitle: "text",
126
+ toolOutput: "muted",
127
+ mdHeading: "orange",
128
+ mdLink: "blue",
129
+ mdLinkUrl: "dim",
130
+ mdCode: "orangeSoft",
131
+ mdCodeBlock: "text",
132
+ mdCodeBlockBorder: "dim",
133
+ mdQuote: "muted",
134
+ mdQuoteBorder: "orange",
135
+ mdHr: "dim",
136
+ mdListBullet: "orange",
137
+ toolDiffAdded: "green",
138
+ toolDiffRemoved: "red",
139
+ toolDiffContext: "muted",
140
+ syntaxComment: "muted",
141
+ syntaxKeyword: "orange",
142
+ syntaxFunction: "orangeSoft",
143
+ syntaxVariable: "text",
144
+ syntaxString: "green",
145
+ syntaxNumber: "yellow",
146
+ syntaxType: "blue",
147
+ syntaxOperator: "muted",
148
+ syntaxPunctuation: "dim",
149
+ thinkingOff: "dim",
150
+ thinkingMinimal: "muted",
151
+ thinkingLow: "blue",
152
+ thinkingMedium: "orangeSoft",
153
+ thinkingHigh: "orange",
154
+ thinkingXhigh: "yellow",
155
+ thinkingMax: "red",
156
+ bashMode: "green",
157
+ },
158
+ export: {
159
+ pageBg: "#faf7f2",
160
+ cardBg: "#ffffff",
161
+ infoBg: "#fff4e8",
162
+ },
163
+ };
164
+ const THEMES = [DARK_THEME, LIGHT_THEME];
165
+ export const DEFAULT_THEME_SETTING = "u1s1-light/u1s1-dark";
166
+ /** Keep ~/.u1s1/agent/themes in sync so Pi picks them up as normal theme files. */
167
+ export function ensureBrandThemes() {
168
+ const dir = join(agentDir, "themes");
169
+ mkdirSync(dir, { recursive: true });
170
+ for (const theme of THEMES) {
171
+ const path = join(dir, `${theme.name}.json`);
172
+ const body = `${JSON.stringify(theme, null, 2)}\n`;
173
+ if (!existsSync(path) || readFileSync(path, "utf8") !== body) {
174
+ writeFileSync(path, body);
175
+ }
176
+ }
177
+ }
package/dist/update.js ADDED
@@ -0,0 +1,69 @@
1
+ import { execSync } from "node:child_process";
2
+ import { createRequire } from "node:module";
3
+ const require = createRequire(import.meta.url);
4
+ const VERSION = require("../package.json").version;
5
+ const PACKAGE_NAME = "u1s1-cli";
6
+ /** Detect the package manager that installed u1s1. */
7
+ function detectPackageManager() {
8
+ // Check common global install locations for clues
9
+ const userAgent = process.env.npm_config_user_agent ?? "";
10
+ if (userAgent.startsWith("pnpm"))
11
+ return "pnpm";
12
+ if (userAgent.startsWith("yarn"))
13
+ return "yarn";
14
+ if (userAgent.startsWith("bun"))
15
+ return "bun";
16
+ return "npm";
17
+ }
18
+ /** Fetch the latest published version from npm registry. */
19
+ async function getLatestVersion() {
20
+ const url = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
21
+ try {
22
+ const res = await fetch(url, {
23
+ headers: { Accept: "application/json" },
24
+ signal: AbortSignal.timeout(10_000),
25
+ });
26
+ if (!res.ok)
27
+ return undefined;
28
+ const body = (await res.json());
29
+ return body.version;
30
+ }
31
+ catch {
32
+ return undefined;
33
+ }
34
+ }
35
+ function compareVersions(a, b) {
36
+ const pa = a.split(".").map(Number);
37
+ const pb = b.split(".").map(Number);
38
+ for (let i = 0; i < 3; i++) {
39
+ const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
40
+ if (diff !== 0)
41
+ return diff;
42
+ }
43
+ return 0;
44
+ }
45
+ export async function update() {
46
+ console.log("正在检查更新…");
47
+ const latest = await getLatestVersion();
48
+ if (!latest) {
49
+ console.error("无法获取最新版本。请检查网络连接后重试。");
50
+ process.exit(1);
51
+ }
52
+ if (compareVersions(latest, VERSION) <= 0) {
53
+ console.log(`当前 v${VERSION} 已是最新版本 ✓`);
54
+ return;
55
+ }
56
+ const pm = detectPackageManager();
57
+ console.log(`发现新版本 v${latest} (当前 v${VERSION})`);
58
+ console.log(`正在用 ${pm} 更新 ${PACKAGE_NAME}…`);
59
+ try {
60
+ const installCmd = pm === "npm" ? `npm install -g ${PACKAGE_NAME}@latest` : `${pm} add -g ${PACKAGE_NAME}@latest`;
61
+ execSync(installCmd, { stdio: "inherit" });
62
+ console.log(`\n✅ 已更新到 v${latest},重启 u1s1 后生效。`);
63
+ }
64
+ catch {
65
+ console.error("\n自动更新失败。请手动运行:");
66
+ console.error(` ${pm === "npm" ? "npm install -g" : `${pm} add -g`} ${PACKAGE_NAME}@latest`);
67
+ process.exit(1);
68
+ }
69
+ }
package/dist/usage.js CHANGED
@@ -14,14 +14,14 @@ export async function usage() {
14
14
  console.error(e.message);
15
15
  process.exit(1);
16
16
  });
17
- const total = me.monthly_free_usd + me.bonus_balance_usd;
18
- const remainRatio = total > 0 ? me.remaining_usd / total : 0;
17
+ const remain = me.remaining_usd;
18
+ const spent = me.mtd_usd;
19
+ const denom = remain + spent;
20
+ const remainRatio = denom > 0 ? remain / denom : 0;
19
21
  console.log("");
20
22
  console.log(` 账号 ${me.email ?? "(未绑定邮箱)"}`);
21
- console.log(` 本月已用 $${me.mtd_usd}`);
22
- console.log(` 剩余额度 $${me.remaining_usd} ${bar(remainRatio)}`);
23
- if (me.bonus_balance_usd > 0)
24
- console.log(` 其中加量 $${me.bonus_balance_usd}(邀请所得,不过期)`);
23
+ console.log(` 本月已用 $${spent}`);
24
+ console.log(` 剩余额度 $${remain} ${bar(remainRatio)}`);
25
25
  console.log("");
26
26
  console.log(" 邀请朋友双方各得加量 → https://u1s1.io/dashboard");
27
27
  console.log("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u1s1-cli",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "u1s1 — 有一说一,最省心的 AI 编程搭子。终端里用中文说需求,AI 帮你读文件、改代码、跑命令。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,7 +15,8 @@
15
15
  "scripts": {
16
16
  "build": "tsc",
17
17
  "dev": "tsx src/index.ts",
18
- "typecheck": "tsc --noEmit"
18
+ "typecheck": "tsc --noEmit",
19
+ "prepublishOnly": "npm run build"
19
20
  },
20
21
  "keywords": [
21
22
  "ai",
@@ -27,11 +28,12 @@
27
28
  "license": "MIT",
28
29
  "homepage": "https://u1s1.io",
29
30
  "dependencies": {
30
- "@earendil-works/pi-coding-agent": "0.84.1"
31
+ "@earendil-works/pi-coding-agent": "0.84.2",
32
+ "@earendil-works/pi-tui": "0.84.2"
31
33
  },
32
34
  "devDependencies": {
33
- "typescript": "^5.9.2",
35
+ "@types/node": "^22.15.0",
34
36
  "tsx": "^4.20.0",
35
- "@types/node": "^22.15.0"
37
+ "typescript": "^5.9.2"
36
38
  }
37
39
  }