u1s1-cli 1.13.0 → 1.13.2

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.
@@ -14,11 +14,22 @@ export declare function ensureBrandPrompt(shell: ShellDoctorResult): void;
14
14
  * them while a diverged user value becomes user-owned.
15
15
  */
16
16
  export declare function applyModelThinkingDefaults(settings: Record<string, unknown>, models: ModelDef[]): boolean;
17
+ /** pi 1.0 的 DEFAULT_TOOL_NAMES(未导出,测试里对过 pi 源码)。 */
18
+ export declare const PI_DEFAULT_TOOL_NAMES: string[];
17
19
  /**
18
- * pi 内置 codemode 默认不开,u1s1 给每个用户开一次(`+codemode` 只加不替换默认工具)。
19
- * 标记写下后不再碰:用户在 defaultTools 里删掉或写 -codemode 都算用户的选择。
20
+ * pi 内置 codemode 默认不开,u1s1 给每个用户开一次。标记写下后不再碰:用户删掉
21
+ * codemode 都算用户的选择。写成完整工具名列表而不是 `+codemode`,原因见
22
+ * normalizeDefaultTools。
20
23
  */
21
24
  export declare function applyCodemodeDefault(settings: Record<string, unknown>): boolean;
25
+ /**
26
+ * 每次启动把 defaultTools 里的 `+name` / `-name` 按 pi 1.0 的规则展开成完整工具名列表。
27
+ * Desktop App ≤0.1.23 内置的 pi 0.87.1 和 CLI 共用这份 settings.json,它不认增减写法,
28
+ * 会把 `["+codemode"]` 当成「只开一个叫 +codemode 的工具」,结果旧对话一个工具都没有。
29
+ * 展开后 pi 1.0 解析结果不变(项目级的 +/- 照样叠在上面),旧版也能读懂;
30
+ * 1.13.0 写下的 `["+codemode"]` 由此修复。
31
+ */
32
+ export declare function normalizeDefaultTools(settings: Record<string, unknown>): boolean;
22
33
  /**
23
34
  * pi 1.0 内置 llama.cpp 扩展(本地模型 provider + /llama),u1s1 走网关用不上,默认关一次。
24
35
  * 用户在 extensions 里自己写过 builtin:llama.cpp 的任何开关就不动;标记写下后同样不再碰。
@@ -131,21 +131,61 @@ export function applyModelThinkingDefaults(settings, models) {
131
131
  }
132
132
  return changed;
133
133
  }
134
+ /** pi 1.0 的 DEFAULT_TOOL_NAMES(未导出,测试里对过 pi 源码)。 */
135
+ export const PI_DEFAULT_TOOL_NAMES = ["read", "bash", "edit", "write"];
136
+ function isToolModifier(entry) {
137
+ return entry.startsWith("+") || entry.startsWith("-");
138
+ }
139
+ /** 与 pi 1.0 resolveDefaultTools 同算法:纯名字替换默认集,再按顺序应用 +name / -name。 */
140
+ function resolveDefaultTools(entries) {
141
+ const plain = entries.filter((entry) => !isToolModifier(entry));
142
+ const tools = plain.length > 0 || entries.length === 0 ? plain : [...PI_DEFAULT_TOOL_NAMES];
143
+ for (const entry of entries) {
144
+ if (!isToolModifier(entry))
145
+ continue;
146
+ const name = entry.slice(1);
147
+ const index = tools.indexOf(name);
148
+ if (entry.startsWith("+") && index === -1 && name)
149
+ tools.push(name);
150
+ else if (entry.startsWith("-") && index !== -1)
151
+ tools.splice(index, 1);
152
+ }
153
+ return tools;
154
+ }
155
+ function stringList(value) {
156
+ return Array.isArray(value) ? value.filter((t) => typeof t === "string") : undefined;
157
+ }
134
158
  /**
135
- * pi 内置 codemode 默认不开,u1s1 给每个用户开一次(`+codemode` 只加不替换默认工具)。
136
- * 标记写下后不再碰:用户在 defaultTools 里删掉或写 -codemode 都算用户的选择。
159
+ * pi 内置 codemode 默认不开,u1s1 给每个用户开一次。标记写下后不再碰:用户删掉
160
+ * codemode 都算用户的选择。写成完整工具名列表而不是 `+codemode`,原因见
161
+ * normalizeDefaultTools。
137
162
  */
138
163
  export function applyCodemodeDefault(settings) {
139
164
  if (settings[CODEMODE_DEFAULT_MARKER] === true)
140
165
  return false;
141
- const tools = Array.isArray(settings["defaultTools"])
142
- ? settings["defaultTools"].filter((t) => typeof t === "string")
143
- : [];
144
- if (!tools.some((t) => t.replace(/^[+-]/, "") === "codemode"))
145
- settings["defaultTools"] = [...tools, "+codemode"];
166
+ const tools = stringList(settings["defaultTools"]);
167
+ if (!tools?.some((t) => t.replace(/^[+-]/, "") === "codemode")) {
168
+ // 没写过 defaultTools 是 pi 默认集;空列表是用户明确不开工具,两者不同
169
+ const current = tools === undefined ? [...PI_DEFAULT_TOOL_NAMES] : resolveDefaultTools(tools);
170
+ settings["defaultTools"] = [...current, "codemode"];
171
+ }
146
172
  settings[CODEMODE_DEFAULT_MARKER] = true;
147
173
  return true;
148
174
  }
175
+ /**
176
+ * 每次启动把 defaultTools 里的 `+name` / `-name` 按 pi 1.0 的规则展开成完整工具名列表。
177
+ * Desktop App ≤0.1.23 内置的 pi 0.87.1 和 CLI 共用这份 settings.json,它不认增减写法,
178
+ * 会把 `["+codemode"]` 当成「只开一个叫 +codemode 的工具」,结果旧对话一个工具都没有。
179
+ * 展开后 pi 1.0 解析结果不变(项目级的 +/- 照样叠在上面),旧版也能读懂;
180
+ * 1.13.0 写下的 `["+codemode"]` 由此修复。
181
+ */
182
+ export function normalizeDefaultTools(settings) {
183
+ const tools = stringList(settings["defaultTools"]);
184
+ if (!tools?.some(isToolModifier))
185
+ return false;
186
+ settings["defaultTools"] = resolveDefaultTools(tools);
187
+ return true;
188
+ }
149
189
  /**
150
190
  * pi 1.0 内置 llama.cpp 扩展(本地模型 provider + /llama),u1s1 走网关用不上,默认关一次。
151
191
  * 用户在 extensions 里自己写过 builtin:llama.cpp 的任何开关就不动;标记写下后同样不再碰。
@@ -217,6 +257,8 @@ export function ensureDefaultSettings(models = []) {
217
257
  }
218
258
  if (applyCodemodeDefault(settings))
219
259
  changed = true;
260
+ if (normalizeDefaultTools(settings))
261
+ changed = true;
220
262
  if (applyLlamaDisabledDefault(settings))
221
263
  changed = true;
222
264
  if (applyModelThinkingDefaults(settings, models))
package/dist/config.js CHANGED
@@ -25,9 +25,13 @@ export function isPortableInstall() {
25
25
  /** 默认连中国站;国际站由安装脚本写入 baseUrl 或 `u1s1 login --site global` 切换。 */
26
26
  export const DEFAULT_BASE_URL = SITES.cn.apiBase;
27
27
  export const PROVIDER_ID = "u1s1";
28
- /** Make a short alias from a model id by stripping common prefixes. */
28
+ /**
29
+ * Make a short alias from a model id by stripping common prefixes:厂商前缀
30
+ * `anthropic.claude-…`(前缀只含字母数字、点后接字母)和 `vendor/model`。
31
+ * 版本号里的点不算前缀:claude-fable-5.1 以前被截成「1」。
32
+ */
29
33
  function defaultAliases(id) {
30
- const short = id.replace(/^[^/]+\./, "").replace(/^[^/]+\//, "");
34
+ const short = id.replace(/^[a-z0-9]+\.(?=[a-z])/i, "").replace(/^[^/]+\//, "");
31
35
  const parts = short.split(/[-_.:]/);
32
36
  const aliases = [short, ...parts.filter((p) => p.length > 1)];
33
37
  return [...new Set(aliases)];
package/dist/index.js CHANGED
@@ -544,6 +544,11 @@ async function runAgent(cfg, args) {
544
544
  ];
545
545
  const hasModelArg = args.some((a) => a === "--model" || a.startsWith("--model=") || a === "--provider");
546
546
  const pref = resolvePreferredModel(cfg);
547
+ // 拿到了目录但本站一个模型都没上(国际站上线初期就是这样):别让 pi 报「Model not found」
548
+ if (!hasModelArg && modelsResp && MODELS.length === 0 && pref.provider === PROVIDER_ID) {
549
+ console.error(tr(` ${siteOf(cfg).origin} 暂时还没有可用的模型,请稍后再试。`, ` No models are available on ${siteOf(cfg).origin} yet. Please try again later.`));
550
+ process.exit(1);
551
+ }
547
552
  if (cfg.model !== pref.id || (cfg.modelProvider ?? PROVIDER_ID) !== pref.provider) {
548
553
  persistPreferredModel(cfg, pref.provider, pref.id);
549
554
  }
@@ -59,10 +59,13 @@ export declare function loadMcpConfigDetailed(path?: string): ReturnType<typeof
59
59
  */
60
60
  export declare function updateMcpServers(changes: Record<string, McpServerConfig | undefined>, path?: string): void;
61
61
  /**
62
- * 把 ≤1.12 的 ~/.u1s1/mcp.json 并进 pi 读的 agent/mcp.json(同名以新文件为准),
63
- * 旧文件改名留底。条目原样搬(补上 direct,保持原来工具直接给模型),格式本来就一致;
64
- * sse 等 pi 不支持的条目由 pi 报错跳过。
65
- * 返回搬过去的服务名;没有旧文件时什么也不做。
62
+ * 把 ≤1.12 的 ~/.u1s1/mcp.json 并进 pi 读的 agent/mcp.json(同名以新文件为准)。
63
+ * 条目原样搬(补上 direct,保持原来工具直接给模型),格式本来就一致;sse 等 pi 不支持的
64
+ * 条目由 pi 报错跳过。返回搬过去的服务名;没有旧文件时什么也不做。
65
+ *
66
+ * 旧文件留在原处:Desktop App ≤0.1.23 内置 CLI 1.12,和 CLI 共用 ~/.u1s1,MCP 只读这份。
67
+ * 另抄一份 mcp.json.migrated 当「已迁移」标记,之后不再合并,免得用户在新文件里删掉的
68
+ * 服务又被加回来。1.13.0 是把旧文件改名成 .migrated 的,这里从它复原旧文件给桌面版用。
66
69
  */
67
70
  export declare function migrateLegacyMcpConfig(legacyPath?: string, path?: string): string[];
68
71
  export declare function loadMcpToolCache(path?: string): McpToolCache;
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync, renameSync } from "node:fs";
1
+ import { existsSync, readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { agentDir, u1s1Dir, writePrivateFile } from "../config.js";
4
4
  /** u1s1 mcp add 收的服务名:比 pi 的规则更严,工具名前缀短且全小写。 */
@@ -131,14 +131,24 @@ export function updateMcpServers(changes, path = mcpConfigPath()) {
131
131
  writePrivateFile(path, `${JSON.stringify({ ...existing, mcpServers: servers }, null, 2)}\n`);
132
132
  }
133
133
  /**
134
- * 把 ≤1.12 的 ~/.u1s1/mcp.json 并进 pi 读的 agent/mcp.json(同名以新文件为准),
135
- * 旧文件改名留底。条目原样搬(补上 direct,保持原来工具直接给模型),格式本来就一致;
136
- * sse 等 pi 不支持的条目由 pi 报错跳过。
137
- * 返回搬过去的服务名;没有旧文件时什么也不做。
134
+ * 把 ≤1.12 的 ~/.u1s1/mcp.json 并进 pi 读的 agent/mcp.json(同名以新文件为准)。
135
+ * 条目原样搬(补上 direct,保持原来工具直接给模型),格式本来就一致;sse 等 pi 不支持的
136
+ * 条目由 pi 报错跳过。返回搬过去的服务名;没有旧文件时什么也不做。
137
+ *
138
+ * 旧文件留在原处:Desktop App ≤0.1.23 内置 CLI 1.12,和 CLI 共用 ~/.u1s1,MCP 只读这份。
139
+ * 另抄一份 mcp.json.migrated 当「已迁移」标记,之后不再合并,免得用户在新文件里删掉的
140
+ * 服务又被加回来。1.13.0 是把旧文件改名成 .migrated 的,这里从它复原旧文件给桌面版用。
138
141
  */
139
142
  export function migrateLegacyMcpConfig(legacyPath = legacyMcpConfigPath(), path = mcpConfigPath()) {
143
+ const marker = `${legacyPath}.migrated`;
144
+ if (existsSync(marker)) {
145
+ if (!existsSync(legacyPath))
146
+ writePrivateFile(legacyPath, readFileSync(marker, "utf8"));
147
+ return [];
148
+ }
140
149
  if (!existsSync(legacyPath))
141
150
  return [];
151
+ const raw = readFileSync(legacyPath, "utf8");
142
152
  const legacy = asRecord(asRecord(readJson(legacyPath))?.["mcpServers"]) ?? {};
143
153
  const existing = asRecord(readJson(path)) ?? {};
144
154
  const servers = { ...asRecord(existing["mcpServers"]) };
@@ -152,7 +162,7 @@ export function migrateLegacyMcpConfig(legacyPath = legacyMcpConfigPath(), path
152
162
  }
153
163
  if (moved.length)
154
164
  writePrivateFile(path, `${JSON.stringify({ ...existing, mcpServers: servers }, null, 2)}\n`);
155
- renameSync(legacyPath, `${legacyPath}.migrated`);
165
+ writePrivateFile(marker, raw);
156
166
  return moved;
157
167
  }
158
168
  export function loadMcpToolCache(path = mcpCachePath()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u1s1-cli",
3
- "version": "1.13.0",
3
+ "version": "1.13.2",
4
4
  "description": "u1s1 — 有一说一,最省心的 AI 编程搭子。终端里用中文说需求,AI 帮你读文件、改代码、跑命令。",
5
5
  "type": "module",
6
6
  "bin": {