u1s1-cli 0.13.3 → 0.13.5
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/agent-setup.d.ts +2 -1
- package/dist/agent-setup.js +35 -3
- package/dist/index.js +2 -1
- package/dist/shell-doctor.d.ts +22 -0
- package/dist/shell-doctor.js +129 -0
- package/dist/update.js +19 -8
- package/dist/web.js +2 -1
- package/package.json +1 -1
package/dist/agent-setup.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type CliConfig, type CustomEndpoint, type ModelDef } from "./config.js";
|
|
2
|
+
import type { ShellDoctorResult } from "./shell-doctor.js";
|
|
2
3
|
/** pi auto-appends <agentDir>/APPEND_SYSTEM.md to its system prompt — our branding hook. */
|
|
3
|
-
export declare function ensureBrandPrompt(): void;
|
|
4
|
+
export declare function ensureBrandPrompt(shell: ShellDoctorResult): void;
|
|
4
5
|
/** Defaults that don't overwrite values the user already set. */
|
|
5
6
|
export declare function ensureDefaultSettings(): void;
|
|
6
7
|
/** ≤0.4.0 wrote u1s1-dark/u1s1-light into the pi themes dir; remove them. */
|
package/dist/agent-setup.js
CHANGED
|
@@ -10,12 +10,44 @@ const BRAND_APPEND = `## u1s1
|
|
|
10
10
|
- 改动前先说明打算做什么,改完用一两句话总结改了哪里。
|
|
11
11
|
- 用户描述模糊时,先猜最可能的意图并确认,不要长篇追问。
|
|
12
12
|
`;
|
|
13
|
+
const EXEC_RULES = `
|
|
14
|
+
## 命令执行
|
|
15
|
+
|
|
16
|
+
- 能用 bash 工具执行的命令,自己执行并展示输出,不要让用户手动执行。
|
|
17
|
+
- 只有需要管理员权限、交互式输入(登录、装系统组件等),或必须开新终端窗口的操作,才让用户执行,并给出可直接粘贴的完整命令。
|
|
18
|
+
`;
|
|
19
|
+
/**
|
|
20
|
+
* pi 的系统提示不含任何 OS/平台信息,模型只能拿 uname 之类盲探,Windows 上
|
|
21
|
+
* 第一步就撞墙。这里按 shell 体检结果告诉模型:系统是什么、命令实际跑在哪、
|
|
22
|
+
* 坏了该怎么引导用户修(而不是反复重试)。
|
|
23
|
+
*/
|
|
24
|
+
function envSection(shell) {
|
|
25
|
+
if (shell.status === "posix") {
|
|
26
|
+
return `\n## 运行环境\n\n- 操作系统: ${process.platform === "darwin" ? "macOS" : "Linux"}。\n`;
|
|
27
|
+
}
|
|
28
|
+
const lines = ["", "## 运行环境", "", "- 操作系统: Windows。环境信息以本节为准,不要用 uname 探测。"];
|
|
29
|
+
switch (shell.status) {
|
|
30
|
+
case "bash":
|
|
31
|
+
lines.push(`- 命令通过 bash 执行(${shell.shellPath}),ls/grep/curl 等 POSIX 工具可用。`, '- 查 Windows 原生信息或调 Windows 程序,用 cmd.exe /c "..." 或 powershell.exe -NoProfile -Command "..."。', "- 默认在 Windows 本机干活;只有用户要求或任务确实需要 Linux 时才考虑 WSL。");
|
|
32
|
+
break;
|
|
33
|
+
case "wsl":
|
|
34
|
+
lines.push("- 注意:命令通过 WSL 的 bash 执行,实际跑在 Linux 发行版里,不是 Windows 本机。", "- 访问 Windows 文件用 /mnt/c/... 路径;调 Windows 程序用带 .exe 的名字(cmd.exe、powershell.exe)。", "- 适时提醒用户:安装 Git for Windows(PowerShell 运行 winget install --id Git.Git)并重启 u1s1,命令就能回到 Windows 本机执行。");
|
|
35
|
+
break;
|
|
36
|
+
case "none":
|
|
37
|
+
lines.push("- 严重:当前没有可用的 bash,bash 工具执行任何命令都会立刻失败,反复重试没有意义。", "- 第一要务是引导用户修复:让用户在 PowerShell 运行 winget install --id Git.Git 安装 Git for Windows,装完重启 u1s1。", "- 修复之前只用读写文件等其他工具帮用户。");
|
|
38
|
+
break;
|
|
39
|
+
case "unknown":
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
return lines.join("\n") + "\n";
|
|
43
|
+
}
|
|
13
44
|
/** pi auto-appends <agentDir>/APPEND_SYSTEM.md to its system prompt — our branding hook. */
|
|
14
|
-
export function ensureBrandPrompt() {
|
|
45
|
+
export function ensureBrandPrompt(shell) {
|
|
15
46
|
mkdirSync(agentDir, { recursive: true });
|
|
47
|
+
const content = BRAND_APPEND + envSection(shell) + EXEC_RULES;
|
|
16
48
|
const p = join(agentDir, "APPEND_SYSTEM.md");
|
|
17
|
-
if (!existsSync(p) || readFileSync(p, "utf8") !==
|
|
18
|
-
writeFileSync(p,
|
|
49
|
+
if (!existsSync(p) || readFileSync(p, "utf8") !== content) {
|
|
50
|
+
writeFileSync(p, content);
|
|
19
51
|
}
|
|
20
52
|
}
|
|
21
53
|
/** Defaults that don't overwrite values the user already set. */
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { cleanupBrandThemes, endpointKeyEnv, endpointProviderEntry, ensureBrandP
|
|
|
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";
|
|
8
|
+
import { ensureUsableShell } from "./shell-doctor.js";
|
|
8
9
|
import { applyBrandUi, setUpdateNotice } from "./style.js";
|
|
9
10
|
import { offerStarterTemplates } from "./templates.js";
|
|
10
11
|
import { fetchModels, loadCustomEndpoints } from "./api.js";
|
|
@@ -133,7 +134,7 @@ function ensureTmuxKeyboardProtocol() {
|
|
|
133
134
|
}
|
|
134
135
|
async function runAgent(cfg, args) {
|
|
135
136
|
cleanupBrandThemes();
|
|
136
|
-
ensureBrandPrompt();
|
|
137
|
+
ensureBrandPrompt(ensureUsableShell());
|
|
137
138
|
ensureDefaultSettings();
|
|
138
139
|
// 预 seed fd/rg(国内直连 GitHub 不通,pi 自己下不动);与取模型列表并行,
|
|
139
140
|
// 但必须在进 pi 之前就位,否则 pi 会自己去 GitHub 下载
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windows shell 体检。pi 的 shell 兜底顺序是 Program Files 的 Git Bash →
|
|
3
|
+
* PATH 上第一个 bash.exe,后者经常命中 System32 的 WSL 转发器:默认发行版
|
|
4
|
+
* 没有 /bin/bash 时(装过 Docker Desktop 的机器把 docker-desktop 当默认很
|
|
5
|
+
* 常见),所有命令 0.1s 内失败,stderr 还是 UTF-16 乱码,模型只能干瞪眼。
|
|
6
|
+
* 这里在进 pi 之前把一个「验过活」的 bash 写进 settings.json 的 shellPath;
|
|
7
|
+
* 找不到就当场把修复指引打给用户,并让品牌 prompt 告知模型别撞墙。
|
|
8
|
+
*/
|
|
9
|
+
export type ShellDoctorResult = {
|
|
10
|
+
status: "posix";
|
|
11
|
+
} | {
|
|
12
|
+
status: "bash";
|
|
13
|
+
shellPath: string;
|
|
14
|
+
} | {
|
|
15
|
+
status: "wsl";
|
|
16
|
+
shellPath: string;
|
|
17
|
+
} | {
|
|
18
|
+
status: "none";
|
|
19
|
+
} | {
|
|
20
|
+
status: "unknown";
|
|
21
|
+
};
|
|
22
|
+
export declare function ensureUsableShell(): ShellDoctorResult;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { agentDir, agentSettingsFile } from "./config.js";
|
|
5
|
+
/** System32/Sysnative 的 bash.exe 是 WSL 转发器,不是真 bash(与 pi 同款判断)。 */
|
|
6
|
+
function isWslRelayBash(p) {
|
|
7
|
+
const normalized = p.replace(/\//g, "\\").toLowerCase();
|
|
8
|
+
return /^[a-z]:\\windows\\(?:system32|sysnative)\\bash\.exe$/.test(normalized);
|
|
9
|
+
}
|
|
10
|
+
/** 真跑一条 echo 验活。WSL 转发器不支持 -c 传参(pi 对它走 stdin),探活同款。 */
|
|
11
|
+
function bashWorks(bashPath) {
|
|
12
|
+
const viaStdin = isWslRelayBash(bashPath);
|
|
13
|
+
try {
|
|
14
|
+
const res = spawnSync(bashPath, viaStdin ? ["-s"] : ["-c", "echo u1s1-shell-ok"], {
|
|
15
|
+
input: viaStdin ? "echo u1s1-shell-ok\n" : undefined,
|
|
16
|
+
encoding: "utf-8",
|
|
17
|
+
timeout: 5000,
|
|
18
|
+
windowsHide: true,
|
|
19
|
+
});
|
|
20
|
+
// WSL 转发器可能按 UTF-16LE 输出(ASCII 字符间夹 NUL),剔掉再找标记
|
|
21
|
+
return res.status === 0 && (res.stdout ?? "").replace(/\u0000/g, "").includes("u1s1-shell-ok");
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function runWhere(name) {
|
|
28
|
+
try {
|
|
29
|
+
const res = spawnSync("where", [name], { encoding: "utf-8", timeout: 5000, windowsHide: true });
|
|
30
|
+
if (res.status !== 0 || !res.stdout)
|
|
31
|
+
return [];
|
|
32
|
+
return res.stdout
|
|
33
|
+
.trim()
|
|
34
|
+
.split(/\r?\n/)
|
|
35
|
+
.map((l) => l.trim())
|
|
36
|
+
.filter(Boolean);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Git Bash 候选位置:pi 只认 Program Files 两处,漏掉 winget 默认的用户级
|
|
44
|
+
* 安装(%LOCALAPPDATA%\Programs\Git)和 scoop;再从 git.exe 反推兜住
|
|
45
|
+
* portable 安装。只返回磁盘上真实存在的路径。
|
|
46
|
+
*/
|
|
47
|
+
function candidateGitBashPaths() {
|
|
48
|
+
const out = [];
|
|
49
|
+
const add = (p) => {
|
|
50
|
+
if (!out.includes(p))
|
|
51
|
+
out.push(p);
|
|
52
|
+
};
|
|
53
|
+
for (const [envKey, ...rest] of [
|
|
54
|
+
["ProgramFiles", "Git", "bin", "bash.exe"],
|
|
55
|
+
["ProgramFiles(x86)", "Git", "bin", "bash.exe"],
|
|
56
|
+
["LOCALAPPDATA", "Programs", "Git", "bin", "bash.exe"],
|
|
57
|
+
["USERPROFILE", "scoop", "apps", "git", "current", "bin", "bash.exe"],
|
|
58
|
+
]) {
|
|
59
|
+
const base = process.env[envKey];
|
|
60
|
+
if (base)
|
|
61
|
+
add(join(base, ...rest));
|
|
62
|
+
}
|
|
63
|
+
// git.exe 在 <root>\cmd、<root>\bin 或 <root>\mingw64\bin 下,向上找 root
|
|
64
|
+
for (const gitPath of runWhere("git.exe")) {
|
|
65
|
+
const dir = dirname(gitPath);
|
|
66
|
+
for (const root of [dirname(dir), dirname(dirname(dir))]) {
|
|
67
|
+
add(join(root, "bin", "bash.exe"));
|
|
68
|
+
add(join(root, "usr", "bin", "bash.exe"));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return out.filter((p) => existsSync(p));
|
|
72
|
+
}
|
|
73
|
+
export function ensureUsableShell() {
|
|
74
|
+
if (process.platform !== "win32")
|
|
75
|
+
return { status: "posix" };
|
|
76
|
+
mkdirSync(agentDir, { recursive: true });
|
|
77
|
+
let settings = {};
|
|
78
|
+
if (existsSync(agentSettingsFile)) {
|
|
79
|
+
try {
|
|
80
|
+
settings = JSON.parse(readFileSync(agentSettingsFile, "utf8"));
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return { status: "unknown" }; // 不动损坏的文件,pi 启动时会自己报
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// 已配置且文件还在:信它,不每次启动都探活(坏了 pi 会在会话里报错)
|
|
87
|
+
const configured = settings["shellPath"];
|
|
88
|
+
if (typeof configured === "string" && configured && existsSync(configured)) {
|
|
89
|
+
return isWslRelayBash(configured)
|
|
90
|
+
? { status: "wsl", shellPath: configured }
|
|
91
|
+
: { status: "bash", shellPath: configured };
|
|
92
|
+
}
|
|
93
|
+
// shellPath 缺失或指向已卸载的文件:探测 + 验活,找到就固定下来
|
|
94
|
+
for (const candidate of candidateGitBashPaths()) {
|
|
95
|
+
if (bashWorks(candidate)) {
|
|
96
|
+
settings["shellPath"] = candidate;
|
|
97
|
+
writeFileSync(agentSettingsFile, JSON.stringify(settings, null, 2) + "\n");
|
|
98
|
+
if (typeof configured === "string") {
|
|
99
|
+
console.error(` 原 shellPath 已失效,自动改用: ${candidate}`);
|
|
100
|
+
}
|
|
101
|
+
return { status: "bash", shellPath: candidate };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// 没有 Git Bash:看 PATH 上还有什么(与 pi 的兜底一致,但这里要验活)
|
|
105
|
+
const fallback = runWhere("bash.exe").find((p) => existsSync(p));
|
|
106
|
+
if (fallback && bashWorks(fallback)) {
|
|
107
|
+
if (isWslRelayBash(fallback)) {
|
|
108
|
+
// 能用但跑在 Linux 里。不写进 settings:一旦用户装了 Git Bash,
|
|
109
|
+
// 下次启动就能自动切回 Windows 本机执行。
|
|
110
|
+
console.error(" ⚠ 未找到 Git Bash,将使用 WSL 的 bash:命令会在 WSL 的 Linux 里执行,不是 Windows 本机。");
|
|
111
|
+
console.error(" 建议安装 Git for Windows(PowerShell 运行: winget install --id Git.Git)后重启 u1s1。");
|
|
112
|
+
return { status: "wsl", shellPath: fallback };
|
|
113
|
+
}
|
|
114
|
+
settings["shellPath"] = fallback;
|
|
115
|
+
writeFileSync(agentSettingsFile, JSON.stringify(settings, null, 2) + "\n");
|
|
116
|
+
return { status: "bash", shellPath: fallback };
|
|
117
|
+
}
|
|
118
|
+
if (fallback) {
|
|
119
|
+
console.error(` ⚠ 检测到的 bash(${fallback})无法执行命令,u1s1 的命令工具会全部失败。`);
|
|
120
|
+
if (isWslRelayBash(fallback)) {
|
|
121
|
+
console.error(" 它是 WSL 转发器,当前默认发行版里没有 /bin/bash(docker-desktop 被设为默认时就会这样)。");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
console.error(" ⚠ 没找到 bash,u1s1 无法执行命令。");
|
|
126
|
+
}
|
|
127
|
+
console.error(" 修复:PowerShell 里运行 winget install --id Git.Git 安装 Git for Windows,装完重启 u1s1。");
|
|
128
|
+
return { status: "none" };
|
|
129
|
+
}
|
package/dist/update.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execSync,
|
|
1
|
+
import { execSync, spawnSync } from "node:child_process";
|
|
2
2
|
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
@@ -65,14 +65,25 @@ async function portableSelfUpdate(latest) {
|
|
|
65
65
|
if (process.platform === "win32") {
|
|
66
66
|
console.log(`正在打开更新窗口(升级到 v${latest})…`);
|
|
67
67
|
console.log("更新在新窗口里进行,本窗口会退出;装完后重新打开终端运行 u1s1 即可。");
|
|
68
|
+
console.log("若新窗口没有出现,可手动在 PowerShell 运行: irm https://u1s1.io/releases/install.ps1 | iex");
|
|
68
69
|
// 不走 `cmd /c start`:start 只把**带引号**的首参当窗口标题,而 node 对
|
|
69
|
-
// 无空格参数不加引号,标题会被当成命令执行(0.13.1 翻车现场)
|
|
70
|
-
// Windows 上 detached
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
// 无空格参数不加引号,标题会被当成命令执行(0.13.1 翻车现场)。也不走
|
|
71
|
+
// spawn detached:Windows 上 detached 是 DETACHED_PROCESS——子进程**没有**
|
|
72
|
+
// 控制台,窗口根本不出现,0.13.2-0.13.4 的更新其实在隐形 powershell 里
|
|
73
|
+
// 跑完的(当时的注释把这个标志理解反了)。可靠开窗走 ShellExecute 语义:
|
|
74
|
+
// 同步跑一个无窗 launcher,由它 Start-Process 弹真窗口(ShellExecute 出的
|
|
75
|
+
// 进程完全独立,不随本进程退出)。路径经 $PSScriptRoot 传递,不往命令行
|
|
76
|
+
// 拼字符串,没有引号二次解析问题。
|
|
77
|
+
const dir = mkdtempSync(join(tmpdir(), "u1s1-update-"));
|
|
78
|
+
writeFileSync(join(dir, "update.ps1"), "irm https://u1s1.io/releases/install.ps1 | iex\n");
|
|
79
|
+
writeFileSync(join(dir, "launch.ps1"), "$inner = '-NoProfile -ExecutionPolicy Bypass -NoExit -File \"' + $PSScriptRoot + '\\update.ps1\"'\n" +
|
|
80
|
+
"Start-Process powershell -ArgumentList $inner\n");
|
|
81
|
+
const res = spawnSync("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", join(dir, "launch.ps1")], { stdio: "ignore", windowsHide: true, timeout: 30_000 });
|
|
82
|
+
if (res.error || res.status !== 0) {
|
|
83
|
+
console.error("无法拉起更新窗口。请手动在 PowerShell 里运行:");
|
|
84
|
+
console.error(" irm https://u1s1.io/releases/install.ps1 | iex");
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
76
87
|
// 立刻退出释放 node.exe 的文件锁,让安装器能替换安装目录
|
|
77
88
|
process.exit(0);
|
|
78
89
|
}
|
package/dist/web.js
CHANGED
|
@@ -5,6 +5,7 @@ import { dirname, join } from "node:path";
|
|
|
5
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
|
+
import { ensureUsableShell } from "./shell-doctor.js";
|
|
8
9
|
import { applyWebUiBranding, applyWebUiFrontend } from "./webui-brand.js";
|
|
9
10
|
import { fetchModels, loadCustomEndpoints } from "./api.js";
|
|
10
11
|
const require = createRequire(import.meta.url);
|
|
@@ -20,7 +21,7 @@ const require = createRequire(import.meta.url);
|
|
|
20
21
|
*/
|
|
21
22
|
export async function prepareWebEnv(cfg) {
|
|
22
23
|
cleanupBrandThemes();
|
|
23
|
-
ensureBrandPrompt();
|
|
24
|
+
ensureBrandPrompt(ensureUsableShell());
|
|
24
25
|
ensureDefaultSettings();
|
|
25
26
|
// 网页版/App 的 agent 会话同样依赖 fd/rg(同一个 agentDir);与取模型列表并行
|
|
26
27
|
const searchToolsReady = ensureSearchTools(cfg);
|