u1s1-cli 1.2.7 → 1.2.9

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/api.js CHANGED
@@ -3,6 +3,11 @@ import { authorizedFetch } from "./device-auth.js";
3
3
  /** 401:凭证失效(设备被移除/换过钥匙),调用方应引导重新登录而不是继续硬跑。 */
4
4
  export class AuthError extends Error {
5
5
  }
6
+ /** 网关错误壳统一是 { error: { message } };解析不出来时回退到状态码提示。 */
7
+ async function errorMessageFromResponse(resp, fallback) {
8
+ const body = (await resp.json().catch(() => null));
9
+ return body?.error?.message ?? fallback;
10
+ }
6
11
  export async function fetchModels(cfg) {
7
12
  let resp;
8
13
  try {
@@ -17,7 +22,7 @@ export async function fetchModels(cfg) {
17
22
  if (resp.status === 401)
18
23
  throw new AuthError("登录已失效,请重新运行 u1s1 login");
19
24
  if (!resp.ok)
20
- throw new Error(`服务端返回 ${resp.status},稍后再试`);
25
+ throw new Error(await errorMessageFromResponse(resp, `服务端返回 ${resp.status},稍后再试`));
21
26
  const body = (await resp.json());
22
27
  return { models: body.data, features: body.features ?? {}, announcement: body.announcement };
23
28
  }
@@ -37,7 +42,7 @@ export async function fetchUserEndpoints(cfg) {
37
42
  throw new Error(`连不上 ${cfg.baseUrl}`);
38
43
  }
39
44
  if (!resp.ok)
40
- throw new Error(`服务端返回 ${resp.status}`);
45
+ throw new Error(await errorMessageFromResponse(resp, `服务端返回 ${resp.status}`));
41
46
  const body = (await resp.json());
42
47
  return Array.isArray(body.endpoints) ? body.endpoints : [];
43
48
  }
@@ -76,10 +81,8 @@ export async function searchWeb(cfg, input) {
76
81
  }
77
82
  if (resp.status === 401)
78
83
  throw new Error("登录已失效,请重新运行 u1s1 login");
79
- if (!resp.ok) {
80
- const body = (await resp.json().catch(() => null));
81
- throw new Error(body?.error?.message ?? `搜索服务返回 ${resp.status},稍后再试`);
82
- }
84
+ if (!resp.ok)
85
+ throw new Error(await errorMessageFromResponse(resp, `搜索服务返回 ${resp.status},稍后再试`));
83
86
  return (await resp.json());
84
87
  }
85
88
  /** web_fetch 直连失败时的回退:网关用 Cloudflare Browser Rendering 渲染后转 markdown。 */
@@ -103,10 +106,8 @@ export async function renderPage(cfg, url, signal) {
103
106
  }
104
107
  if (resp.status === 401)
105
108
  throw new Error("登录已失效,请重新运行 u1s1 login");
106
- if (!resp.ok) {
107
- const body = (await resp.json().catch(() => null));
108
- throw new Error(body?.error?.message ?? `渲染服务返回 ${resp.status}`);
109
- }
109
+ if (!resp.ok)
110
+ throw new Error(await errorMessageFromResponse(resp, `渲染服务返回 ${resp.status}`));
110
111
  return (await resp.json());
111
112
  }
112
113
  const IMAGE_URL_HEADER = "x-u1s1-image-url";
@@ -178,10 +179,8 @@ export async function generateImage(cfg, req, signal) {
178
179
  }
179
180
  if (resp.status === 401)
180
181
  throw new Error("登录已失效,请重新运行 u1s1 login");
181
- if (!resp.ok) {
182
- const body = (await resp.json().catch(() => null));
183
- throw new Error(body?.error?.message ?? `图片生成服务返回 ${resp.status},稍后再试`);
184
- }
182
+ if (!resp.ok)
183
+ throw new Error(await errorMessageFromResponse(resp, `图片生成服务返回 ${resp.status},稍后再试`));
185
184
  return readGeneratedImageResponse(resp);
186
185
  }
187
186
  export async function fetchMe(cfg) {
@@ -200,6 +199,6 @@ export async function fetchMe(cfg) {
200
199
  if (resp.status === 401)
201
200
  throw new Error("登录已失效,请重新运行 u1s1 login");
202
201
  if (!resp.ok)
203
- throw new Error(`服务端返回 ${resp.status},稍后再试`);
202
+ throw new Error(await errorMessageFromResponse(resp, `服务端返回 ${resp.status},稍后再试`));
204
203
  return (await resp.json());
205
204
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u1s1-cli",
3
- "version": "1.2.7",
3
+ "version": "1.2.9",
4
4
  "description": "u1s1 — 有一说一,最省心的 AI 编程搭子。终端里用中文说需求,AI 帮你读文件、改代码、跑命令。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -42,8 +42,8 @@
42
42
  "license": "MIT",
43
43
  "homepage": "https://u1s1.io",
44
44
  "dependencies": {
45
- "@earendil-works/pi-coding-agent": "0.84.3",
46
- "@earendil-works/pi-tui": "0.84.3",
45
+ "@earendil-works/pi-coding-agent": "0.84.4",
46
+ "@earendil-works/pi-tui": "0.84.4",
47
47
  "typebox": "1.3.7"
48
48
  },
49
49
  "devDependencies": {
@@ -12,7 +12,7 @@
12
12
  * - Never fails installation; mismatches only emit a notice and exit 0
13
13
  */
14
14
 
15
- import { readFileSync, writeFileSync, existsSync } from "node:fs";
15
+ import { readFileSync, writeFileSync, existsSync, realpathSync } from "node:fs";
16
16
  import { join, dirname } from "node:path";
17
17
  import { fileURLToPath } from "node:url";
18
18
  import { createRequire } from "node:module";
@@ -21,21 +21,40 @@ const pkgRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
21
21
 
22
22
  // npm may hoist dependencies above the package, especially for global installs.
23
23
  // Resolve packages through Node first, then walk upward as a fallback.
24
- function findPackageDir(packageName) {
24
+ function addPackageDir(found, candidate) {
25
+ if (!existsSync(join(candidate, "package.json"))) return;
26
+ try {
27
+ found.add(realpathSync(candidate));
28
+ } catch {
29
+ found.add(candidate);
30
+ }
31
+ }
32
+
33
+ function findPackageDirs(packageName) {
34
+ const found = new Set();
25
35
  try {
26
36
  const req = createRequire(join(pkgRoot, "package.json"));
27
- return dirname(req.resolve(`${packageName}/package.json`));
37
+ addPackageDir(found, dirname(req.resolve(`${packageName}/package.json`)));
28
38
  } catch {
29
- const [scope, name] = packageName.split("/");
30
- let dir = pkgRoot;
31
- while (true) {
32
- const candidate = join(dir, "node_modules", scope, name);
33
- if (existsSync(candidate)) return candidate;
34
- const parent = dirname(dir);
35
- if (parent === dir) return null;
36
- dir = parent;
37
- }
39
+ // The package may block package.json through exports; the upward scan below still finds it.
38
40
  }
41
+ const [scope, name] = packageName.split("/");
42
+ let dir = pkgRoot;
43
+ while (true) {
44
+ addPackageDir(found, join(dir, "node_modules", scope, name));
45
+ const parent = dirname(dir);
46
+ if (parent === dir) break;
47
+ dir = parent;
48
+ }
49
+ return [...found];
50
+ }
51
+
52
+ function findTuiDirs() {
53
+ const found = new Set(findPackageDirs("@earendil-works/pi-tui"));
54
+ for (const codingAgentDir of findPackageDirs("@earendil-works/pi-coding-agent")) {
55
+ addPackageDir(found, join(codingAgentDir, "node_modules", "@earendil-works", "pi-tui"));
56
+ }
57
+ return [...found];
39
58
  }
40
59
 
41
60
  // Each tuple is [original, replacement], matching the pnpm coding-agent patch.
@@ -58,88 +77,90 @@ const COMPACT_UI_REPLACEMENTS = [
58
77
  ];
59
78
 
60
79
  function patchCompactUi() {
61
- const piDir = findPackageDir("@earendil-works/pi-coding-agent");
62
- const target = piDir
63
- ? join(piDir, "dist", "modes", "interactive", "components", "assistant-message.js")
64
- : null;
65
- if (!target) {
80
+ const piDirs = findPackageDirs("@earendil-works/pi-coding-agent");
81
+ if (piDirs.length === 0) {
66
82
  console.log("[u1s1] 提示: 未找到 @earendil-works/pi-coding-agent,精简 UI 空行补丁未应用(不影响使用)");
67
83
  return;
68
84
  }
69
- try {
70
- let text = readFileSync(target, "utf8");
71
- if (text.includes("!this.hideThinkingBlock && c.thinking.trim()")) return;
85
+ for (const piDir of piDirs) {
86
+ const target = join(piDir, "dist", "modes", "interactive", "components", "assistant-message.js");
87
+ try {
88
+ let text = readFileSync(target, "utf8");
89
+ if (text.includes("!this.hideThinkingBlock && c.thinking.trim()")) continue;
72
90
 
73
- let applied = 0;
74
- for (const [from, to] of COMPACT_UI_REPLACEMENTS) {
75
- if (text.includes(to)) {
91
+ let applied = 0;
92
+ for (const [from, to] of COMPACT_UI_REPLACEMENTS) {
93
+ if (text.includes(to)) {
94
+ applied++;
95
+ continue;
96
+ }
97
+ if (!text.includes(from)) continue;
98
+ text = text.split(from).join(to);
76
99
  applied++;
77
- continue;
78
100
  }
79
- if (!text.includes(from)) continue;
80
- text = text.split(from).join(to);
81
- applied++;
82
- }
83
101
 
84
- if (applied < COMPACT_UI_REPLACEMENTS.length) {
85
- console.log(
86
- `[u1s1] 提示: pi 版本可能已更新,精简 UI 空行补丁只应用了 ${applied}/${COMPACT_UI_REPLACEMENTS.length} 处(不影响使用)`,
87
- );
102
+ if (applied < COMPACT_UI_REPLACEMENTS.length) {
103
+ console.log(
104
+ `[u1s1] 提示: pi 版本可能已更新,精简 UI 空行补丁只应用了 ${applied}/${COMPACT_UI_REPLACEMENTS.length} 处(不影响使用)`,
105
+ );
106
+ }
107
+ writeFileSync(target, text);
108
+ } catch {
109
+ continue;
88
110
  }
89
- writeFileSync(target, text);
90
- } catch {
91
- return;
92
111
  }
93
112
  }
94
113
 
95
114
  function patchMainScreenAutowrap() {
96
- const tuiDir = findPackageDir("@earendil-works/pi-tui");
97
- const target = tuiDir ? join(tuiDir, "dist", "tui-main-screen.js") : null;
98
- if (!target) {
115
+ const tuiDirs = findTuiDirs();
116
+ if (tuiDirs.length === 0) {
99
117
  console.log("[u1s1] 提示: 未找到 @earendil-works/pi-tui,Windows 重绘补丁未应用(不影响安装)");
100
118
  return;
101
119
  }
102
- try {
103
- let text = readFileSync(target, "utf8");
104
- if (text.includes('const DISABLE_AUTOWRAP = "\\x1b[?7l";')) return;
120
+ for (const tuiDir of tuiDirs) {
121
+ const target = join(tuiDir, "dist", "tui-main-screen.js");
122
+ try {
123
+ let text = readFileSync(target, "utf8");
124
+ if (text.includes('const DISABLE_AUTOWRAP = "\\x1b[?7l";')) continue;
105
125
 
106
- const rules = [
107
- {
108
- from: 'const KITTY_SEQUENCE_PREFIX = "\\x1b_G";',
109
- to: `const KITTY_SEQUENCE_PREFIX = "\\x1b_G";
126
+ const rules = [
127
+ {
128
+ from: 'const KITTY_SEQUENCE_PREFIX = "\\x1b_G";',
129
+ to: `const KITTY_SEQUENCE_PREFIX = "\\x1b_G";
110
130
  // Windows ConPTY eagerly wraps full-width lines, which desynchronizes cursor-row tracking.
111
131
  const DISABLE_AUTOWRAP = "\\x1b[?7l";
112
132
  const ENABLE_AUTOWRAP = "\\x1b[?7h";`,
113
- expected: 1,
114
- },
115
- {
116
- from: 'let buffer = "\\x1b[?2026h";',
117
- to: 'let buffer = "\\x1b[?2026h" + DISABLE_AUTOWRAP;',
118
- expected: 3,
119
- },
120
- {
121
- from: 'buffer += "\\x1b[?2026l";',
122
- to: 'buffer += ENABLE_AUTOWRAP + "\\x1b[?2026l";',
123
- expected: 3,
124
- },
125
- ];
126
- let applied = 0;
127
- let expected = 0;
128
- for (const rule of rules) {
129
- expected += rule.expected;
130
- const occurrences = text.split(rule.from).length - 1;
131
- if (occurrences !== rule.expected) continue;
132
- text = text.split(rule.from).join(rule.to);
133
- applied += occurrences;
134
- }
133
+ expected: 1,
134
+ },
135
+ {
136
+ from: 'output.append("\\x1b[?2026h");',
137
+ to: 'output.append("\\x1b[?2026h" + DISABLE_AUTOWRAP);',
138
+ expected: 3,
139
+ },
140
+ {
141
+ from: 'output.append("\\x1b[?2026l");',
142
+ to: 'output.append(ENABLE_AUTOWRAP + "\\x1b[?2026l");',
143
+ expected: 3,
144
+ },
145
+ ];
146
+ let applied = 0;
147
+ let expected = 0;
148
+ for (const rule of rules) {
149
+ expected += rule.expected;
150
+ const occurrences = text.split(rule.from).length - 1;
151
+ if (occurrences !== rule.expected) continue;
152
+ text = text.split(rule.from).join(rule.to);
153
+ applied += occurrences;
154
+ }
135
155
 
136
- if (applied !== expected) {
137
- console.log(`[u1s1] 提示: pi-tui 版本可能已更新,Windows 重绘补丁只应用了 ${applied}/${expected} 处(不影响安装)`);
138
- return;
156
+ if (applied !== expected) {
157
+ console.log(`[u1s1] 提示: pi-tui 版本可能已更新,Windows 重绘补丁只应用了 ${applied}/${expected} 处(不影响安装)`);
158
+ continue;
159
+ }
160
+ writeFileSync(target, text);
161
+ } catch {
162
+ continue;
139
163
  }
140
- writeFileSync(target, text);
141
- } catch {
142
- return;
143
164
  }
144
165
  }
145
166