u1s1-cli 0.20.2 → 1.1.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/agent-setup.d.ts +2 -2
- package/dist/agent-setup.js +2 -2
- package/dist/deploy.d.ts +8 -0
- package/dist/deploy.js +70 -15
- package/dist/index.js +4 -12
- package/dist/web.d.ts +3 -9
- package/dist/web.js +10 -74
- package/dist/webui-brand.js +3 -3
- package/package.json +1 -5
- package/dist/shortcut.d.ts +0 -1
- package/dist/shortcut.js +0 -106
- package/webui-dist/assets/index-Cz5vuDzv.js +0 -128
- package/webui-dist/assets/index-DICLz-bf.css +0 -41
- package/webui-dist/favicon.svg +0 -4
- package/webui-dist/icon.ico +0 -0
- package/webui-dist/index.html +0 -15
package/dist/agent-setup.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export declare function ensureDefaultSettings(): void;
|
|
|
7
7
|
/** ≤0.4.0 wrote u1s1-dark/u1s1-light into the pi themes dir; remove them. */
|
|
8
8
|
export declare function cleanupBrandThemes(): void;
|
|
9
9
|
/**
|
|
10
|
-
* u1s1 provider for SDK-based frontends (
|
|
10
|
+
* u1s1 provider for SDK-based frontends (Desktop App): pi composes
|
|
11
11
|
* <agentDir>/models.json above registered providers, so this exposes the same
|
|
12
12
|
* models as the TUI's in-process extension without patching the frontend.
|
|
13
13
|
* apiKey stays an env reference — the launcher sets U1S1_API_KEY, the file
|
|
@@ -15,7 +15,7 @@ export declare function cleanupBrandThemes(): void;
|
|
|
15
15
|
* propagate; other providers a user may have added are preserved.
|
|
16
16
|
*/
|
|
17
17
|
/**
|
|
18
|
-
* 联网工具经 <agentDir>/extensions 投影:pi 的资源加载器对 TUI 和
|
|
18
|
+
* 联网工具经 <agentDir>/extensions 投影:pi 的资源加载器对 TUI 和 Desktop App
|
|
19
19
|
* 都会扫描这个目录,两个前端从此共用同一份工具注册,不用各接一遍。
|
|
20
20
|
* 文件每次启动重写(baseUrl/服务端开关变化随之生效);apiKey 走 U1S1_API_KEY
|
|
21
21
|
* 环境变量,文件里不落密钥。U1S1_TOOLS_VIA_EXTENSION 守卫:旧版 CLI 仍在
|
package/dist/agent-setup.js
CHANGED
|
@@ -141,7 +141,7 @@ export function cleanupBrandThemes() {
|
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
/**
|
|
144
|
-
* u1s1 provider for SDK-based frontends (
|
|
144
|
+
* u1s1 provider for SDK-based frontends (Desktop App): pi composes
|
|
145
145
|
* <agentDir>/models.json above registered providers, so this exposes the same
|
|
146
146
|
* models as the TUI's in-process extension without patching the frontend.
|
|
147
147
|
* apiKey stays an env reference — the launcher sets U1S1_API_KEY, the file
|
|
@@ -149,7 +149,7 @@ export function cleanupBrandThemes() {
|
|
|
149
149
|
* propagate; other providers a user may have added are preserved.
|
|
150
150
|
*/
|
|
151
151
|
/**
|
|
152
|
-
* 联网工具经 <agentDir>/extensions 投影:pi 的资源加载器对 TUI 和
|
|
152
|
+
* 联网工具经 <agentDir>/extensions 投影:pi 的资源加载器对 TUI 和 Desktop App
|
|
153
153
|
* 都会扫描这个目录,两个前端从此共用同一份工具注册,不用各接一遍。
|
|
154
154
|
* 文件每次启动重写(baseUrl/服务端开关变化随之生效);apiKey 走 U1S1_API_KEY
|
|
155
155
|
* 环境变量,文件里不落密钥。U1S1_TOOLS_VIA_EXTENSION 守卫:旧版 CLI 仍在
|
package/dist/deploy.d.ts
CHANGED
|
@@ -1,2 +1,10 @@
|
|
|
1
1
|
import { type CliConfig } from "./config.js";
|
|
2
|
+
type SiteVisibility = "public" | "private";
|
|
3
|
+
interface DeployArgs {
|
|
4
|
+
name?: string;
|
|
5
|
+
dirArg?: string;
|
|
6
|
+
visibility?: SiteVisibility;
|
|
7
|
+
}
|
|
8
|
+
export declare function parseDeployArgs(args: string[]): DeployArgs;
|
|
2
9
|
export declare function deployCommand(cfg: CliConfig, args: string[]): Promise<void>;
|
|
10
|
+
export {};
|
package/dist/deploy.js
CHANGED
|
@@ -84,6 +84,37 @@ function fmtBytes(n) {
|
|
|
84
84
|
return `${Math.round(n / 1024)}KB`;
|
|
85
85
|
return `${n}B`;
|
|
86
86
|
}
|
|
87
|
+
export function parseDeployArgs(args) {
|
|
88
|
+
const parsed = {};
|
|
89
|
+
for (let i = 0; i < args.length; i++) {
|
|
90
|
+
const arg = args[i];
|
|
91
|
+
if (arg === "--name" || arg === "-n") {
|
|
92
|
+
const value = args[++i];
|
|
93
|
+
if (!value || value.startsWith("-"))
|
|
94
|
+
throw new Error(`${arg} 后面需要站点名`);
|
|
95
|
+
parsed.name = value.toLowerCase();
|
|
96
|
+
}
|
|
97
|
+
else if (arg.startsWith("--name=")) {
|
|
98
|
+
parsed.name = arg.slice(7).toLowerCase();
|
|
99
|
+
if (!parsed.name)
|
|
100
|
+
throw new Error("--name 后面需要站点名");
|
|
101
|
+
}
|
|
102
|
+
else if (arg === "--public" || arg === "--private") {
|
|
103
|
+
const visibility = arg.slice(2);
|
|
104
|
+
if (parsed.visibility && parsed.visibility !== visibility) {
|
|
105
|
+
throw new Error("--public 和 --private 不能同时使用");
|
|
106
|
+
}
|
|
107
|
+
parsed.visibility = visibility;
|
|
108
|
+
}
|
|
109
|
+
else if (arg.startsWith("-")) {
|
|
110
|
+
throw new Error(`不认识的 deploy 参数:${arg}`);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
parsed.dirArg = arg;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return parsed;
|
|
117
|
+
}
|
|
87
118
|
async function api(cfg, method, path, body) {
|
|
88
119
|
let resp;
|
|
89
120
|
try {
|
|
@@ -144,8 +175,26 @@ async function promptSiteName(def) {
|
|
|
144
175
|
rl.close();
|
|
145
176
|
}
|
|
146
177
|
}
|
|
178
|
+
async function promptVisibility() {
|
|
179
|
+
if (!process.stdin.isTTY)
|
|
180
|
+
return "private";
|
|
181
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
182
|
+
try {
|
|
183
|
+
while (true) {
|
|
184
|
+
const answer = (await rl.question(" 发布方式 [1] 公开(进入作品社区) [2] 私密(仅自己登录后可看,默认):")).trim().toLowerCase();
|
|
185
|
+
if (!answer || answer === "2" || answer === "private" || answer === "私密")
|
|
186
|
+
return "private";
|
|
187
|
+
if (answer === "1" || answer === "public" || answer === "公开")
|
|
188
|
+
return "public";
|
|
189
|
+
console.log(" 请输入 1 或 2");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
finally {
|
|
193
|
+
rl.close();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
147
196
|
export async function deployCommand(cfg, args) {
|
|
148
|
-
// 参数:[dir] [--name xxx];u1s1 deploy list 列出已有站点
|
|
197
|
+
// 参数:[dir] [--name xxx] [--public|--private];u1s1 deploy list 列出已有站点
|
|
149
198
|
if (args[0] === "list") {
|
|
150
199
|
const { sites } = await api(cfg, "GET", "/deploy/sites");
|
|
151
200
|
if (!sites.length) {
|
|
@@ -154,22 +203,14 @@ export async function deployCommand(cfg, args) {
|
|
|
154
203
|
}
|
|
155
204
|
console.log("");
|
|
156
205
|
for (const s of sites) {
|
|
157
|
-
|
|
206
|
+
const visibility = s.visibility === "private" ? "私密" : s.community_listed ? "公开(社区)" : "公开(未展示)";
|
|
207
|
+
console.log(` ${s.deployed ? "●" : "○"} ${s.url} ${visibility} · ${fmtBytes(s.total_bytes)} · ${s.updated_at} UTC`);
|
|
158
208
|
}
|
|
159
209
|
console.log("");
|
|
160
210
|
return;
|
|
161
211
|
}
|
|
162
|
-
|
|
163
|
-
let dirArg;
|
|
164
|
-
for (let i = 0; i < args.length; i++) {
|
|
165
|
-
const a = args[i];
|
|
166
|
-
if (a === "--name" || a === "-n")
|
|
167
|
-
name = args[++i]?.toLowerCase();
|
|
168
|
-
else if (a.startsWith("--name="))
|
|
169
|
-
name = a.slice(7).toLowerCase();
|
|
170
|
-
else if (!a.startsWith("-"))
|
|
171
|
-
dirArg = a;
|
|
172
|
-
}
|
|
212
|
+
const parsed = parseDeployArgs(args);
|
|
213
|
+
let { name, dirArg, visibility } = parsed;
|
|
173
214
|
const dir = resolveSiteDir(dirArg);
|
|
174
215
|
const files = collectFiles(dir);
|
|
175
216
|
if (!files.some((f) => f.path === "index.html")) {
|
|
@@ -187,6 +228,10 @@ export async function deployCommand(cfg, args) {
|
|
|
187
228
|
const projectName = BUILD_DIRS.includes(basename(dir)) ? basename(resolve(dir, "..")) : basename(dir);
|
|
188
229
|
name = await promptSiteName(slugify(projectName));
|
|
189
230
|
}
|
|
231
|
+
// 同一目录再次部署时保持上次设置;首次部署显式选择。CI/脚本没有交互时
|
|
232
|
+
// 取更安全的 private,用户仍可用 --public 明确公开。
|
|
233
|
+
if (!visibility && remembered !== name)
|
|
234
|
+
visibility = await promptVisibility();
|
|
190
235
|
let start;
|
|
191
236
|
for (let attempt = 0; !start; attempt++) {
|
|
192
237
|
try {
|
|
@@ -222,12 +267,22 @@ export async function deployCommand(cfg, args) {
|
|
|
222
267
|
const fin = await api(cfg, "POST", "/deploy/finish", {
|
|
223
268
|
site: start.site,
|
|
224
269
|
deploy_id: start.deploy_id,
|
|
270
|
+
visibility,
|
|
225
271
|
});
|
|
226
272
|
rememberSite(dir, start.site);
|
|
227
273
|
console.log(` ✅ 部署完成,${fin.file_count} 个文件已上线`);
|
|
228
274
|
console.log("");
|
|
229
|
-
console.log(` 🌐 ${fin.url}`);
|
|
275
|
+
console.log(` ${fin.visibility === "private" ? "🔒" : "🌐"} ${fin.url}`);
|
|
230
276
|
console.log("");
|
|
231
|
-
|
|
277
|
+
if (fin.visibility === "private") {
|
|
278
|
+
console.log(" 私密站点仅你可见,请从 https://u1s1.io/dashboard#sec-sites 打开。");
|
|
279
|
+
}
|
|
280
|
+
else if (fin.community_listed) {
|
|
281
|
+
console.log(" 已进入 https://u1s1.io/community ,把网址发给朋友就能看。");
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
console.log(" 公开链接可直接访问;运行 u1s1 deploy --public 可明确加入作品社区。");
|
|
285
|
+
}
|
|
286
|
+
console.log(" 改完代码再跑一次 u1s1 deploy 即可更新。");
|
|
232
287
|
console.log("");
|
|
233
288
|
}
|
package/dist/index.js
CHANGED
|
@@ -171,7 +171,7 @@ async function runAgent(cfg, args) {
|
|
|
171
171
|
ensureBrandPrompt(await shellReady);
|
|
172
172
|
ensureProviderModels(officialCfg);
|
|
173
173
|
ensureWorkflowPromptTemplate();
|
|
174
|
-
// 联网工具经 agentDir/extensions 投影,TUI 和
|
|
174
|
+
// 联网工具经 agentDir/extensions 投影,TUI 和 Desktop App 共用一份注册
|
|
175
175
|
writeWebToolsExtension(officialCfg, {
|
|
176
176
|
webSearch: webSearchEnabled,
|
|
177
177
|
webFetchRender: webFetchRenderEnabled,
|
|
@@ -314,20 +314,12 @@ async function run() {
|
|
|
314
314
|
}
|
|
315
315
|
if (cmd === "--help" || cmd === "-h") {
|
|
316
316
|
printConsoleBanner(VERSION);
|
|
317
|
-
console.log(" u1s1 命令:
|
|
317
|
+
console.log(" u1s1 命令:deploy(发布网页,可选 --public / --private)· login / logout · model · usage · update · import · bench");
|
|
318
318
|
console.log("");
|
|
319
319
|
}
|
|
320
320
|
if (cmd === "web") {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const { createWebShortcut } = await import("./shortcut.js");
|
|
324
|
-
createWebShortcut();
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
const { ensureAuth } = await import("./login.js");
|
|
328
|
-
const cfg = await ensureAuth();
|
|
329
|
-
const { webCommand } = await import("./web.js");
|
|
330
|
-
await webCommand(cfg, args.slice(1));
|
|
321
|
+
console.error("u1s1 web 已下线,请使用 Desktop App:https://u1s1.io/#download");
|
|
322
|
+
process.exitCode = 1;
|
|
331
323
|
return;
|
|
332
324
|
}
|
|
333
325
|
if (cmd === "deploy") {
|
package/dist/web.d.ts
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import { type CliConfig } from "./config.js";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*/
|
|
7
|
-
/**
|
|
8
|
-
* 启动 pi-web-ui 服务器前的公共准备:品牌 prompt、模型列表、auth.json 凭据、
|
|
9
|
-
* 联网工具扩展、默认模型,并返回子进程需要的环境变量。CLI(`u1s1 web`)和
|
|
10
|
-
* 桌面版 App(packages/app,经 "u1s1-cli/embed" 引入)共用这一份逻辑。
|
|
3
|
+
* Desktop App 启动 agent server 前的公共准备:品牌 prompt、模型列表、
|
|
4
|
+
* auth.json 凭据、联网工具扩展、默认模型,并返回子进程需要的环境变量。
|
|
5
|
+
* 由 App 经 "u1s1-cli/embed" 引入;CLI 本身不提供浏览器版入口。
|
|
11
6
|
*/
|
|
12
7
|
export declare function prepareWebEnv(cfg: CliConfig): Promise<Record<string, string>>;
|
|
13
|
-
export declare function webCommand(cfg: CliConfig, args: string[]): Promise<void>;
|
package/dist/web.js
CHANGED
|
@@ -1,31 +1,22 @@
|
|
|
1
|
-
import { spawn, spawnSync } from "node:child_process";
|
|
2
1
|
import { mkdirSync } from "node:fs";
|
|
3
|
-
import {
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
2
|
+
import { join } from "node:path";
|
|
5
3
|
import { cleanupBrandThemes, endpointKeyEnv, ensureAuthCredential, ensureBrandPrompt, ensureWorkflowPromptTemplate, ensureDefaultSettings, ensureProviderModels, scrubForeignProviderEnv, writeAttributionExtension, writeWebToolsExtension, } from "./agent-setup.js";
|
|
6
4
|
import { agentDir, apiModelToDef, resolvePreferredModel, setModelsFromApi, u1s1Dir, writeAgentDefaultModel, } from "./config.js";
|
|
7
5
|
import { ensureSearchTools } from "./search-tools.js";
|
|
8
6
|
import { ensureUsableShell } from "./shell-doctor.js";
|
|
9
|
-
import { applyWebUiBranding, applyWebUiFrontend } from "./webui-brand.js";
|
|
10
7
|
import { fetchModels, loadCustomEndpoints } from "./api.js";
|
|
11
8
|
import { ensureSigningProxy } from "./device-auth.js";
|
|
12
|
-
const require = createRequire(import.meta.url);
|
|
13
9
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*/
|
|
18
|
-
/**
|
|
19
|
-
* 启动 pi-web-ui 服务器前的公共准备:品牌 prompt、模型列表、auth.json 凭据、
|
|
20
|
-
* 联网工具扩展、默认模型,并返回子进程需要的环境变量。CLI(`u1s1 web`)和
|
|
21
|
-
* 桌面版 App(packages/app,经 "u1s1-cli/embed" 引入)共用这一份逻辑。
|
|
10
|
+
* Desktop App 启动 agent server 前的公共准备:品牌 prompt、模型列表、
|
|
11
|
+
* auth.json 凭据、联网工具扩展、默认模型,并返回子进程需要的环境变量。
|
|
12
|
+
* 由 App 经 "u1s1-cli/embed" 引入;CLI 本身不提供浏览器版入口。
|
|
22
13
|
*/
|
|
23
14
|
export async function prepareWebEnv(cfg) {
|
|
24
15
|
cleanupBrandThemes();
|
|
25
16
|
ensureDefaultSettings();
|
|
26
17
|
// Windows shell 体检异步先行,与取模型列表并行,写 brand prompt 前 await
|
|
27
18
|
const shellReady = ensureUsableShell();
|
|
28
|
-
//
|
|
19
|
+
// App 的 agent 会话同样依赖 fd/rg(同一个 agentDir);与取模型列表并行
|
|
29
20
|
const searchToolsReady = ensureSearchTools(cfg);
|
|
30
21
|
// Fetch model list from server; fall back to built-in MODELS on error.
|
|
31
22
|
// 服务端没开搜索(或老网关没有 features 字段)时不注册 web_search
|
|
@@ -47,10 +38,10 @@ export async function prepareWebEnv(cfg) {
|
|
|
47
38
|
console.error(" 获取模型列表失败,使用内置列表:", e.message);
|
|
48
39
|
}
|
|
49
40
|
await endpointsReady;
|
|
50
|
-
const signing = await ensureSigningProxy(cfg, "
|
|
41
|
+
const signing = await ensureSigningProxy(cfg, "desktop");
|
|
51
42
|
const officialCfg = { ...cfg, baseUrl: signing.baseUrl, apiKey: signing.localKey };
|
|
52
43
|
ensureBrandPrompt(await shellReady);
|
|
53
|
-
// /workflow 提示词模板与 TUI 同源,
|
|
44
|
+
// /workflow 提示词模板与 TUI 同源,Desktop App 也要有
|
|
54
45
|
ensureWorkflowPromptTemplate();
|
|
55
46
|
ensureProviderModels(officialCfg);
|
|
56
47
|
// pi-web-ui 靠 auth.json 判断「已配置」,否则网页会弹 pi 安装引导
|
|
@@ -63,13 +54,13 @@ export async function prepareWebEnv(cfg) {
|
|
|
63
54
|
});
|
|
64
55
|
// OpenRouter 应用归因:直连流量计入 u1s1 的公开排行(与 TUI 同源)
|
|
65
56
|
writeAttributionExtension();
|
|
66
|
-
//
|
|
57
|
+
// Desktop App 新会话从 settings.json 的 defaultModel 取模型(TUI 是每次传 --model),
|
|
67
58
|
// 确保它有值;resolvePreferredModel 优先尊重已有的 in-session 选择,不会回退覆盖。
|
|
68
59
|
const pref = resolvePreferredModel(cfg);
|
|
69
60
|
writeAgentDefaultModel(pref.provider, pref.id);
|
|
70
61
|
const dataDir = join(u1s1Dir, "web");
|
|
71
62
|
mkdirSync(dataDir, { recursive: true });
|
|
72
|
-
//
|
|
63
|
+
// App 子进程按 {...process.env, ...返回值} 起,这里剔掉即对子进程生效
|
|
73
64
|
scrubForeignProviderEnv();
|
|
74
65
|
await searchToolsReady;
|
|
75
66
|
return {
|
|
@@ -77,62 +68,7 @@ export async function prepareWebEnv(cfg) {
|
|
|
77
68
|
U1S1_API_KEY: officialCfg.apiKey,
|
|
78
69
|
U1S1_TOOLS_VIA_EXTENSION: "1",
|
|
79
70
|
PI_WEB_DATA_DIR: dataDir,
|
|
80
|
-
// 自定义端点的密钥经环境变量传给
|
|
71
|
+
// 自定义端点的密钥经环境变量传给 App 子进程(models.json 里只有 $VAR 引用)
|
|
81
72
|
...endpointKeyEnv(),
|
|
82
73
|
};
|
|
83
74
|
}
|
|
84
|
-
export async function webCommand(cfg, args) {
|
|
85
|
-
// pi-web-ui 的 server install/shortcut 生成的服务定义只固化 PORT/CWD,不带
|
|
86
|
-
// PI_CODING_AGENT_DIR 和 U1S1_API_KEY —— 装出来的自启服务是坏的(丢登录态、
|
|
87
|
-
// 会话落到 ~/.pi)。在上游支持透传 env 或我们自己生成服务定义前,直接拦掉。
|
|
88
|
-
if (args[0] === "server") {
|
|
89
|
-
console.error("u1s1 web 暂不支持 server 子命令(开机自启/桌面图标),后续版本会加上。");
|
|
90
|
-
console.error("现在请直接运行 u1s1 web,Ctrl+C 停止。");
|
|
91
|
-
process.exitCode = 1;
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
// pi-web-ui 是 optionalDependency:装 CLI 时它编译/下载失败不会拖垮整个安装,
|
|
95
|
-
// 代价是这里可能不存在 —— 给出人话指引而不是 Cannot find module。
|
|
96
|
-
// (桌面版 App 上线后这里的兜底会改为指向 App 下载,CLI 届时不再携带 pi-web-ui。)
|
|
97
|
-
let bin;
|
|
98
|
-
try {
|
|
99
|
-
bin = require.resolve("pi-web-ui/bin/pi-web-ui.mjs");
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
102
|
-
console.error("网页版组件(pi-web-ui)没有安装成功,通常是安装时网络不稳或缺少编译工具。");
|
|
103
|
-
console.error("请重新安装试试:npm install -g u1s1-cli");
|
|
104
|
-
console.error("Linux 用户可能还需要:sudo apt install -y build-essential(或对应发行版的 gcc/make)");
|
|
105
|
-
process.exitCode = 1;
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
// 终端面板依赖原生模块 node-pty。Windows/macOS 走官方 prebuilds 一定在;
|
|
109
|
-
// Linux 便携包/无 gcc 环境可能缺编译产物,而服务器是顶层 import —— 缺了会
|
|
110
|
-
// 整个起不来。先探测,坏了就说人话。
|
|
111
|
-
const probe = spawnSync(process.execPath, ["-e", "require(require.resolve('node-pty',{paths:[process.argv[1]]}))", dirname(bin)], { timeout: 10_000 });
|
|
112
|
-
if (probe.status !== 0) {
|
|
113
|
-
console.error("网页版的终端组件(node-pty)在这台机器上没有编译成功,网页版暂时起不来。");
|
|
114
|
-
console.error("Linux 用户先装编译工具再重装:sudo apt install -y build-essential && npm install -g u1s1-cli");
|
|
115
|
-
process.exitCode = 1;
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
const webEnv = await prepareWebEnv(cfg);
|
|
119
|
-
// 首选 u1s1 自有前端(Codex 布局);产物缺失时回退为上游界面 + 品牌补丁
|
|
120
|
-
if (!applyWebUiFrontend(bin))
|
|
121
|
-
applyWebUiBranding(bin);
|
|
122
|
-
console.log("u1s1 网页版启动中… 浏览器会自动打开;关闭请回到这里按 Ctrl+C。");
|
|
123
|
-
const child = spawn(process.execPath, [bin, ...args], {
|
|
124
|
-
stdio: "inherit",
|
|
125
|
-
env: { ...process.env, ...webEnv },
|
|
126
|
-
});
|
|
127
|
-
// Ctrl+C 由子进程负责优雅停机(同进程组,信号会直达);父进程只等退出码,
|
|
128
|
-
// 避免父进程先死导致孤儿服务器。
|
|
129
|
-
const ignore = () => { };
|
|
130
|
-
process.on("SIGINT", ignore);
|
|
131
|
-
process.on("SIGTERM", ignore);
|
|
132
|
-
await new Promise((resolve) => {
|
|
133
|
-
child.on("exit", (code) => {
|
|
134
|
-
process.exitCode = code ?? 0;
|
|
135
|
-
resolve();
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
}
|
package/dist/webui-brand.js
CHANGED
|
@@ -29,8 +29,8 @@ export function applyWebUiFrontend(binPath) {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
|
-
* pi-web-ui 前端产物的启动时品牌补丁。不 fork
|
|
33
|
-
*
|
|
32
|
+
* pi-web-ui 前端产物的启动时品牌补丁。不 fork 上游,只在 Desktop App
|
|
33
|
+
* 启动前改它 web/dist 里的静态文件:重写 <title>、换 favicon、注入
|
|
34
34
|
* 品牌脚本和一份 Codex 风格的主题 CSS(u1s1-theme.css),把整个界面换成
|
|
35
35
|
* u1s1 的视觉:石墨底色、品牌蓝 #4ea1ff、幽灵化顶栏、居中内容列。
|
|
36
36
|
*
|
|
@@ -40,7 +40,7 @@ export function applyWebUiFrontend(binPath) {
|
|
|
40
40
|
* 原地写会污染 store;unlink 后新写的是独立 inode,npm 平铺安装则无所谓。
|
|
41
41
|
*/
|
|
42
42
|
const PATCH_MARK = "u1s1-brand-v2";
|
|
43
|
-
const PAGE_TITLE = "u1s1
|
|
43
|
+
const PAGE_TITLE = "u1s1 Desktop App — 有一说一";
|
|
44
44
|
const FAVICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
45
45
|
<rect width="64" height="64" rx="14" fill="#101418"/>
|
|
46
46
|
<text x="32" y="42" font-family="ui-monospace,Menlo,Consolas,monospace" font-size="26" font-weight="700" fill="#4ea1ff" text-anchor="middle">u1</text>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "u1s1-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "u1s1 — 有一说一,最省心的 AI 编程搭子。终端里用中文说需求,AI 帮你读文件、改代码、跑命令。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"dist",
|
|
18
|
-
"webui-dist",
|
|
19
18
|
"bench",
|
|
20
19
|
"scripts"
|
|
21
20
|
],
|
|
@@ -47,9 +46,6 @@
|
|
|
47
46
|
"@earendil-works/pi-tui": "0.84.3",
|
|
48
47
|
"typebox": "1.3.7"
|
|
49
48
|
},
|
|
50
|
-
"optionalDependencies": {
|
|
51
|
-
"pi-web-ui": "0.26.0"
|
|
52
|
-
},
|
|
53
49
|
"devDependencies": {
|
|
54
50
|
"@types/node": "^22.20.1",
|
|
55
51
|
"tsx": "^4.23.12",
|
package/dist/shortcut.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function createWebShortcut(): void;
|
package/dist/shortcut.js
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { chmodSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
/**
|
|
7
|
-
* `u1s1 web shortcut` — 在桌面创建「u1s1 网页版」图标,双击即启动网页版。
|
|
8
|
-
* 面向完全不用终端的用户:安装脚本会自动调用,之后的登录(浏览器 device flow)、
|
|
9
|
-
* 聊天全程不需要终端知识。
|
|
10
|
-
*
|
|
11
|
-
* 图标里嵌的是 node 与 CLI 入口的绝对路径 —— GUI 双击环境(macOS .command、
|
|
12
|
-
* Linux .desktop、Windows .lnk)拿不到 npm 全局 bin 的 PATH。
|
|
13
|
-
* 工作目录固定 ~/u1s1(自动创建):给新手一个可预期的「我的项目」目录,
|
|
14
|
-
* 网页里仍可切换;有历史工作区时 UI 会自己恢复上次的。
|
|
15
|
-
*/
|
|
16
|
-
const SHORTCUT_NAME = "u1s1 网页版";
|
|
17
|
-
function cliEntry() {
|
|
18
|
-
return fileURLToPath(new URL("./index.js", import.meta.url));
|
|
19
|
-
}
|
|
20
|
-
function defaultWorkspace() {
|
|
21
|
-
const dir = join(homedir(), "u1s1");
|
|
22
|
-
mkdirSync(dir, { recursive: true });
|
|
23
|
-
return dir;
|
|
24
|
-
}
|
|
25
|
-
function linuxDesktopDir() {
|
|
26
|
-
const out = spawnSync("xdg-user-dir", ["DESKTOP"], { encoding: "utf8", timeout: 3000 });
|
|
27
|
-
const dir = out.status === 0 ? out.stdout.trim() : "";
|
|
28
|
-
return dir && dir !== homedir() ? dir : join(homedir(), "Desktop");
|
|
29
|
-
}
|
|
30
|
-
function createLinux(node, entry, cwd) {
|
|
31
|
-
const desktop = `[Desktop Entry]
|
|
32
|
-
Type=Application
|
|
33
|
-
Name=${SHORTCUT_NAME}
|
|
34
|
-
Comment=有一说一 — 说人话的 AI 编程搭子(浏览器版)
|
|
35
|
-
Exec="${node}" "${entry}" web --cwd "${cwd}"
|
|
36
|
-
Terminal=true
|
|
37
|
-
Categories=Development;
|
|
38
|
-
`;
|
|
39
|
-
const appsDir = join(homedir(), ".local", "share", "applications");
|
|
40
|
-
mkdirSync(appsDir, { recursive: true });
|
|
41
|
-
const appFile = join(appsDir, "u1s1-web.desktop");
|
|
42
|
-
writeFileSync(appFile, desktop);
|
|
43
|
-
chmodSync(appFile, 0o755);
|
|
44
|
-
const desktopDir = linuxDesktopDir();
|
|
45
|
-
let placed = appFile;
|
|
46
|
-
if (existsSync(desktopDir)) {
|
|
47
|
-
placed = join(desktopDir, "u1s1-web.desktop");
|
|
48
|
-
writeFileSync(placed, desktop);
|
|
49
|
-
chmodSync(placed, 0o755);
|
|
50
|
-
// GNOME 桌面图标需要 trusted 标记才可双击;失败无妨(应用菜单入口仍可用)
|
|
51
|
-
spawnSync("gio", ["set", placed, "metadata::trusted", "true"], { timeout: 3000 });
|
|
52
|
-
}
|
|
53
|
-
return placed;
|
|
54
|
-
}
|
|
55
|
-
function createMac(node, entry, cwd) {
|
|
56
|
-
const file = join(homedir(), "Desktop", `${SHORTCUT_NAME}.command`);
|
|
57
|
-
writeFileSync(file, `#!/bin/bash
|
|
58
|
-
# 双击启动 u1s1 网页版;关闭这个窗口即停止
|
|
59
|
-
exec "${node}" "${entry}" web --cwd "${cwd}"
|
|
60
|
-
`);
|
|
61
|
-
chmodSync(file, 0o755);
|
|
62
|
-
return file;
|
|
63
|
-
}
|
|
64
|
-
function createWindows(node, entry, cwd) {
|
|
65
|
-
// Desktop 路径交给 .NET 解析(兼容 OneDrive 重定向);.lnk 目标是 node.exe。
|
|
66
|
-
const psQuote = (s) => `'${s.replace(/'/g, "''")}'`;
|
|
67
|
-
const script = [
|
|
68
|
-
`$desktop = [Environment]::GetFolderPath('Desktop')`,
|
|
69
|
-
`$lnk = Join-Path $desktop ${psQuote(`${SHORTCUT_NAME}.lnk`)}`,
|
|
70
|
-
`$ws = New-Object -ComObject WScript.Shell`,
|
|
71
|
-
`$s = $ws.CreateShortcut($lnk)`,
|
|
72
|
-
`$s.TargetPath = ${psQuote(node)}`,
|
|
73
|
-
`$s.Arguments = ('"' + ${psQuote(entry)} + '" web --cwd "' + ${psQuote(cwd)} + '"')`,
|
|
74
|
-
`$s.WorkingDirectory = ${psQuote(cwd)}`,
|
|
75
|
-
`$s.Description = ${psQuote("有一说一 — 说人话的 AI 编程搭子(浏览器版)")}`,
|
|
76
|
-
`$s.Save()`,
|
|
77
|
-
`Write-Output $lnk`,
|
|
78
|
-
].join("; ");
|
|
79
|
-
const out = spawnSync("powershell", ["-NoProfile", "-NonInteractive", "-Command", script], {
|
|
80
|
-
encoding: "utf8",
|
|
81
|
-
timeout: 20_000,
|
|
82
|
-
});
|
|
83
|
-
if (out.status !== 0) {
|
|
84
|
-
throw new Error(`PowerShell 创建快捷方式失败:${(out.stderr || "").trim().slice(0, 200)}`);
|
|
85
|
-
}
|
|
86
|
-
return out.stdout.trim().split(/\r?\n/).pop() ?? "桌面";
|
|
87
|
-
}
|
|
88
|
-
export function createWebShortcut() {
|
|
89
|
-
const node = process.execPath;
|
|
90
|
-
const entry = cliEntry();
|
|
91
|
-
const cwd = defaultWorkspace();
|
|
92
|
-
try {
|
|
93
|
-
const placed = process.platform === "darwin"
|
|
94
|
-
? createMac(node, entry, cwd)
|
|
95
|
-
: process.platform === "win32"
|
|
96
|
-
? createWindows(node, entry, cwd)
|
|
97
|
-
: createLinux(node, entry, cwd);
|
|
98
|
-
console.log(`✓ 已在桌面创建「${SHORTCUT_NAME}」图标:${placed}`);
|
|
99
|
-
console.log(" 双击即可在浏览器里使用 u1s1;项目文件默认放在 ~/u1s1 文件夹。");
|
|
100
|
-
}
|
|
101
|
-
catch (e) {
|
|
102
|
-
console.error(`创建桌面图标失败:${e instanceof Error ? e.message : String(e)}`);
|
|
103
|
-
console.error("可以先手动使用:打开终端输入 u1s1 web");
|
|
104
|
-
process.exitCode = 1;
|
|
105
|
-
}
|
|
106
|
-
}
|