u1s1-cli 0.7.1 → 0.8.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.js +15 -0
- package/dist/config.js +6 -2
- package/dist/index.js +89 -14
- package/dist/style.js +12 -7
- package/dist/update.js +5 -5
- package/package.json +1 -1
package/dist/agent-setup.js
CHANGED
|
@@ -45,6 +45,21 @@ export function ensureDefaultSettings() {
|
|
|
45
45
|
settings["hideThinkingBlock"] = true;
|
|
46
46
|
changed = true;
|
|
47
47
|
}
|
|
48
|
+
// 启动时是否显示品牌横幅(logo + 版本号/目录/提示)
|
|
49
|
+
if (!("showStartupBanner" in settings)) {
|
|
50
|
+
settings["showStartupBanner"] = true;
|
|
51
|
+
changed = true;
|
|
52
|
+
}
|
|
53
|
+
// 静默启动:不打印 [Context]/[Skills]/[Extensions] 等资源加载信息
|
|
54
|
+
if (!("quietStartup" in settings)) {
|
|
55
|
+
settings["quietStartup"] = true;
|
|
56
|
+
changed = true;
|
|
57
|
+
}
|
|
58
|
+
// 启动时自动检查并更新新版
|
|
59
|
+
if (!("autoUpdate" in settings)) {
|
|
60
|
+
settings["autoUpdate"] = true;
|
|
61
|
+
changed = true;
|
|
62
|
+
}
|
|
48
63
|
// ≤0.4.0 shipped branded themes and forced them as default; the files are
|
|
49
64
|
// gone now, so a settings.json still pointing at them must fall back to
|
|
50
65
|
// pi's default theme.
|
package/dist/config.js
CHANGED
|
@@ -38,7 +38,7 @@ export const MODELS = [
|
|
|
38
38
|
aliases: ["deepseek", "flash", "v4-flash"],
|
|
39
39
|
reasoning: false,
|
|
40
40
|
contextWindow: 1_048_576,
|
|
41
|
-
maxTokens:
|
|
41
|
+
maxTokens: 384_000,
|
|
42
42
|
cost: { input: 0.14, output: 0.28, cacheRead: 0, cacheWrite: 0 },
|
|
43
43
|
note: "默认 · 便宜大碗,日常写代码首选",
|
|
44
44
|
},
|
|
@@ -67,7 +67,7 @@ export const u1s1Dir = join(homedir(), ".u1s1");
|
|
|
67
67
|
const configFile = join(u1s1Dir, "config.json");
|
|
68
68
|
/** pi keeps auth/models/settings/sessions under this dir — isolated from any real pi install. */
|
|
69
69
|
export const agentDir = join(u1s1Dir, "agent");
|
|
70
|
-
const agentSettingsFile = join(agentDir, "settings.json");
|
|
70
|
+
export const agentSettingsFile = join(agentDir, "settings.json");
|
|
71
71
|
function readJsonFile(path) {
|
|
72
72
|
if (!existsSync(path))
|
|
73
73
|
return undefined;
|
|
@@ -78,6 +78,10 @@ function readJsonFile(path) {
|
|
|
78
78
|
return undefined;
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
+
/** Read u1s1 agent settings (settings.json). Returns empty object if missing or invalid. */
|
|
82
|
+
export function readSettings() {
|
|
83
|
+
return (readJsonFile(agentSettingsFile) ?? {});
|
|
84
|
+
}
|
|
81
85
|
/** Model last chosen in-session via /model (pi writes this). */
|
|
82
86
|
export function readAgentDefaultModel() {
|
|
83
87
|
const settings = readJsonFile(agentSettingsFile);
|
package/dist/index.js
CHANGED
|
@@ -4,11 +4,70 @@ import { writeFileSync } from "node:fs";
|
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { cleanupBrandThemes, ensureBrandPrompt, ensureDefaultSettings, ensureProviderModels, } from "./agent-setup.js";
|
|
6
6
|
import { printConsoleBanner } from "./brand.js";
|
|
7
|
-
import { agentDir, apiModelToDef, loadConfig, MODELS, persistPreferredModel, PROVIDER_ID, resolvePreferredModel, setModelsFromApi, } from "./config.js";
|
|
7
|
+
import { agentDir, apiModelToDef, loadConfig, MODELS, persistPreferredModel, PROVIDER_ID, readSettings, resolvePreferredModel, setModelsFromApi, } from "./config.js";
|
|
8
8
|
import { applyBrandUi } from "./style.js";
|
|
9
9
|
import { fetchModels } from "./api.js";
|
|
10
10
|
const require = createRequire(import.meta.url);
|
|
11
11
|
const VERSION = require("../package.json").version;
|
|
12
|
+
const PACKAGE_NAME = "u1s1-cli";
|
|
13
|
+
/**
|
|
14
|
+
* 启动时自动检查 npm 最新版,发现更新就静默安装。
|
|
15
|
+
* 不阻塞启动流程,失败也不报错(留到手动 `u1s1 update`)。
|
|
16
|
+
*/
|
|
17
|
+
async function checkAndAutoUpdate() {
|
|
18
|
+
const settings = readSettings();
|
|
19
|
+
if (settings.autoUpdate === false)
|
|
20
|
+
return;
|
|
21
|
+
let latest;
|
|
22
|
+
try {
|
|
23
|
+
const url = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
24
|
+
const res = await fetch(url, {
|
|
25
|
+
headers: { Accept: "application/json" },
|
|
26
|
+
signal: AbortSignal.timeout(5_000),
|
|
27
|
+
});
|
|
28
|
+
if (res.ok) {
|
|
29
|
+
const body = (await res.json());
|
|
30
|
+
latest = body.version;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return; // 网络不可用,静默跳过
|
|
35
|
+
}
|
|
36
|
+
if (!latest)
|
|
37
|
+
return;
|
|
38
|
+
const pa = VERSION.split(".").map(Number);
|
|
39
|
+
const pb = latest.split(".").map(Number);
|
|
40
|
+
let newer = false;
|
|
41
|
+
for (let i = 0; i < 3; i++) {
|
|
42
|
+
const diff = (pb[i] ?? 0) - (pa[i] ?? 0);
|
|
43
|
+
if (diff > 0) {
|
|
44
|
+
newer = true;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
if (diff < 0)
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
if (!newer)
|
|
51
|
+
return;
|
|
52
|
+
// 检测包管理器
|
|
53
|
+
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
54
|
+
const pm = userAgent.startsWith("pnpm") ? "pnpm"
|
|
55
|
+
: userAgent.startsWith("yarn") ? "yarn"
|
|
56
|
+
: userAgent.startsWith("bun") ? "bun"
|
|
57
|
+
: "npm";
|
|
58
|
+
const installCmd = pm === "npm"
|
|
59
|
+
? `npm install -g ${PACKAGE_NAME}@latest`
|
|
60
|
+
: `${pm} add -g ${PACKAGE_NAME}@latest`;
|
|
61
|
+
try {
|
|
62
|
+
// 用同步 execSync 确保安装完成后再进交互
|
|
63
|
+
const { execSync } = await import("node:child_process");
|
|
64
|
+
execSync(installCmd, { stdio: "pipe", timeout: 60_000 });
|
|
65
|
+
console.log(` ✨ 已自动更新到 v${latest},下次启动生效`);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// 自动更新失败不阻塞,用户可手动 `u1s1 update`
|
|
69
|
+
}
|
|
70
|
+
}
|
|
12
71
|
/**
|
|
13
72
|
* tmux 默认不转发键盘修饰键,会把 Shift+Enter 当成普通回车发出去,消息没写完就被发送。
|
|
14
73
|
* 只开 extended-keys 不够:默认 terminal-features 里 xterm* 没有 extkeys,tmux 根本不会
|
|
@@ -74,6 +133,22 @@ async function runAgent(cfg, args) {
|
|
|
74
133
|
// Shift+Enter,输入框就会换行,真·Alt+Enter(\x1b[13;3u)不受影响。
|
|
75
134
|
class ShiftEnterEditor extends CustomEditor {
|
|
76
135
|
handleInput(data) {
|
|
136
|
+
// 拦截 /login 和 /logout,不让 pi 弹出内置供应商列表
|
|
137
|
+
if (data === "\r" || data === "\n") {
|
|
138
|
+
const text = this.getText().trim();
|
|
139
|
+
if (text.startsWith("/login")) {
|
|
140
|
+
console.log(" u1s1 不需要额外登录,直接用 /model 切换模型即可");
|
|
141
|
+
this.setText("");
|
|
142
|
+
this.addToHistory?.(text);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (text === "/logout") {
|
|
146
|
+
console.log(" u1s1 统一使用同一个 API Key,不需要单独登出");
|
|
147
|
+
this.setText("");
|
|
148
|
+
this.addToHistory?.(text);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
77
152
|
super.handleInput(data === "\x1b\r" ? "\x1b[13;2u" : data);
|
|
78
153
|
}
|
|
79
154
|
}
|
|
@@ -89,6 +164,17 @@ async function runAgent(cfg, args) {
|
|
|
89
164
|
ctx.shutdown();
|
|
90
165
|
},
|
|
91
166
|
});
|
|
167
|
+
// /clear: 清空当前对话上下文,开始新会话
|
|
168
|
+
pi.registerCommand("clear", {
|
|
169
|
+
description: "清除当前对话上下文,开始新会话",
|
|
170
|
+
handler: async (_args, ctx) => {
|
|
171
|
+
await ctx.newSession({
|
|
172
|
+
withSession: async (ctx) => {
|
|
173
|
+
ctx.ui.notify("🗑️ 上下文已清除", "info");
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
},
|
|
177
|
+
});
|
|
92
178
|
pi.registerProvider(PROVIDER_ID, {
|
|
93
179
|
name: "u1s1",
|
|
94
180
|
baseUrl: cfg.baseUrl,
|
|
@@ -114,19 +200,6 @@ async function runAgent(cfg, args) {
|
|
|
114
200
|
return;
|
|
115
201
|
persistPreferredModel(loadConfig(), event.model.id);
|
|
116
202
|
});
|
|
117
|
-
// 拦截 /login 和 /logout,不让 pi 弹出内置供应商列表
|
|
118
|
-
pi.on("input", (event, ctx) => {
|
|
119
|
-
const text = event.text.trim();
|
|
120
|
-
if (text.startsWith("/login")) {
|
|
121
|
-
ctx.ui.notify("u1s1 不需要额外登录,直接用 /model 切换模型即可", "info");
|
|
122
|
-
return { action: "handled" };
|
|
123
|
-
}
|
|
124
|
-
if (text.startsWith("/logout")) {
|
|
125
|
-
ctx.ui.notify("u1s1 统一使用同一个 API Key,不需要单独登出", "info");
|
|
126
|
-
return { action: "handled" };
|
|
127
|
-
}
|
|
128
|
-
return { action: "continue" };
|
|
129
|
-
});
|
|
130
203
|
pi.on("session_start", (_event, ctx) => {
|
|
131
204
|
const previous = ctx.ui.getEditorComponent();
|
|
132
205
|
ctx.ui.setEditorComponent((tui, theme, keybindings) => {
|
|
@@ -207,6 +280,8 @@ async function run() {
|
|
|
207
280
|
}
|
|
208
281
|
const { ensureAuth } = await import("./login.js");
|
|
209
282
|
const cfg = await ensureAuth();
|
|
283
|
+
// 在后台检查更新(非阻塞,不影响启动速度)
|
|
284
|
+
void checkAndAutoUpdate();
|
|
210
285
|
await runAgent(cfg, args);
|
|
211
286
|
}
|
|
212
287
|
run().catch((e) => {
|
package/dist/style.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { basename } from "node:path";
|
|
2
2
|
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
|
+
import { readSettings } from "./config.js";
|
|
3
4
|
import { renderBrandHeader } from "./brand.js";
|
|
4
5
|
/**
|
|
5
6
|
* Brand chrome is just the startup hero + window title; everything else
|
|
@@ -9,13 +10,17 @@ export function applyBrandUi(pi, version) {
|
|
|
9
10
|
pi.on("session_start", async (_event, ctx) => {
|
|
10
11
|
if (ctx.mode !== "tui")
|
|
11
12
|
return;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
// 检查设置:关掉就不显示启动横幅
|
|
14
|
+
const settings = readSettings();
|
|
15
|
+
if (settings.showStartupBanner !== false) {
|
|
16
|
+
ctx.ui.setHeader((_tui, theme) => ({
|
|
17
|
+
render(width) {
|
|
18
|
+
// pi-tui crashes on lines wider than the terminal, so truncate defensively.
|
|
19
|
+
return renderBrandHeader(theme, version, process.cwd(), width).map((line) => truncateToWidth(line, width));
|
|
20
|
+
},
|
|
21
|
+
invalidate() { },
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
19
24
|
ctx.ui.setTitle(`u1s1 — ${basename(process.cwd())}`);
|
|
20
25
|
});
|
|
21
26
|
}
|
package/dist/update.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
const require = createRequire(import.meta.url);
|
|
4
|
-
const VERSION = require("../package.json").version;
|
|
5
|
-
const PACKAGE_NAME = "u1s1-cli";
|
|
4
|
+
export const VERSION = require("../package.json").version;
|
|
5
|
+
export const PACKAGE_NAME = "u1s1-cli";
|
|
6
6
|
/** Detect the package manager that installed u1s1. */
|
|
7
|
-
function detectPackageManager() {
|
|
7
|
+
export function detectPackageManager() {
|
|
8
8
|
// Check common global install locations for clues
|
|
9
9
|
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
10
10
|
if (userAgent.startsWith("pnpm"))
|
|
@@ -16,7 +16,7 @@ function detectPackageManager() {
|
|
|
16
16
|
return "npm";
|
|
17
17
|
}
|
|
18
18
|
/** Fetch the latest published version from npm registry. */
|
|
19
|
-
async function getLatestVersion() {
|
|
19
|
+
export async function getLatestVersion() {
|
|
20
20
|
const url = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
21
21
|
try {
|
|
22
22
|
const res = await fetch(url, {
|
|
@@ -32,7 +32,7 @@ async function getLatestVersion() {
|
|
|
32
32
|
return undefined;
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
-
function compareVersions(a, b) {
|
|
35
|
+
export function compareVersions(a, b) {
|
|
36
36
|
const pa = a.split(".").map(Number);
|
|
37
37
|
const pb = b.split(".").map(Number);
|
|
38
38
|
for (let i = 0; i < 3; i++) {
|