u1s1-cli 0.2.0 → 0.4.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/README.md +7 -5
- package/dist/brand.js +77 -0
- package/dist/config.js +47 -9
- package/dist/import/claude.js +372 -0
- package/dist/import/codex.js +362 -0
- package/dist/import/index.js +354 -0
- package/dist/import/types.js +1 -0
- package/dist/import/util.js +231 -0
- package/dist/import/write.js +95 -0
- package/dist/index.js +141 -3
- package/dist/login.js +8 -5
- package/dist/model.js +4 -4
- package/dist/style.js +21 -0
- package/dist/themes.js +177 -0
- package/dist/update.js +69 -0
- package/dist/usage.js +6 -6
- package/package.json +7 -5
package/dist/index.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
2
3
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
4
|
import { join } from "node:path";
|
|
4
5
|
import { createRequire } from "node:module";
|
|
5
|
-
import {
|
|
6
|
+
import { printConsoleBanner } from "./brand.js";
|
|
7
|
+
import { agentDir, loadConfig, MODELS, persistPreferredModel, PROVIDER_ID, resolvePreferredModel, } from "./config.js";
|
|
8
|
+
import { applyBrandUi } from "./style.js";
|
|
9
|
+
import { DEFAULT_THEME_SETTING, ensureBrandThemes } from "./themes.js";
|
|
6
10
|
const require = createRequire(import.meta.url);
|
|
7
11
|
const VERSION = require("../package.json").version;
|
|
8
12
|
const BRAND_APPEND = `## u1s1
|
|
@@ -21,16 +25,113 @@ function ensureBrandPrompt() {
|
|
|
21
25
|
writeFileSync(p, BRAND_APPEND);
|
|
22
26
|
}
|
|
23
27
|
}
|
|
28
|
+
/** Defaults that don't overwrite values the user already set. */
|
|
29
|
+
function ensureDefaultSettings() {
|
|
30
|
+
mkdirSync(agentDir, { recursive: true });
|
|
31
|
+
const p = join(agentDir, "settings.json");
|
|
32
|
+
let settings = {};
|
|
33
|
+
if (existsSync(p)) {
|
|
34
|
+
try {
|
|
35
|
+
settings = JSON.parse(readFileSync(p, "utf8"));
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
let changed = false;
|
|
42
|
+
if (!("hideThinkingBlock" in settings)) {
|
|
43
|
+
settings.hideThinkingBlock = true;
|
|
44
|
+
changed = true;
|
|
45
|
+
}
|
|
46
|
+
// Default / leftover Pi themes → u1s1 Claude-like pair. Custom names stay put.
|
|
47
|
+
const theme = settings.theme;
|
|
48
|
+
if (theme === undefined || theme === "dark" || theme === "light") {
|
|
49
|
+
settings.theme = DEFAULT_THEME_SETTING;
|
|
50
|
+
changed = true;
|
|
51
|
+
}
|
|
52
|
+
// Pi default is one-at-a-time (one queued message per turn). u1s1 delivers
|
|
53
|
+
// all queued messages together so burst typing isn't split across turns.
|
|
54
|
+
// Only fill in missing keys so an explicit /settings choice still sticks.
|
|
55
|
+
for (const key of ["steeringMode", "followUpMode"]) {
|
|
56
|
+
if (!(key in settings)) {
|
|
57
|
+
settings[key] = "all";
|
|
58
|
+
changed = true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (changed)
|
|
62
|
+
writeFileSync(p, JSON.stringify(settings, null, 2) + "\n");
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* tmux 默认不转发键盘修饰键,会把 Shift+Enter 当成普通回车发出去,消息没写完就被发送。
|
|
66
|
+
* 只开 extended-keys 不够:默认 terminal-features 里 xterm* 没有 extkeys,tmux 根本不会
|
|
67
|
+
* 向外层终端要 modifyOtherKeys,S-Enter 绑定也就永远触发不了。
|
|
68
|
+
* 启动时自动做这些事(运行时生效,不写用户 tmux.conf;失败则静默忽略,仍可用 Ctrl+J 换行):
|
|
69
|
+
* 1. 开启 extended-keys,并给当前终端补上 extkeys;
|
|
70
|
+
* 2. tmux 3.5+ 再切到 csi-u(3.4 及以下没有这个选项,忽略即可);
|
|
71
|
+
* 3. 兜底把 S-Enter / C-Enter 原样转成 pi 认得的 CSI-u 序列。
|
|
72
|
+
*/
|
|
73
|
+
function ensureTmuxKeyboardProtocol() {
|
|
74
|
+
if (!process.env.TMUX)
|
|
75
|
+
return;
|
|
76
|
+
const run = (args) => spawnSync("tmux", args, { timeout: 1500, encoding: "utf8" });
|
|
77
|
+
if (run(["-V"]).status !== 0)
|
|
78
|
+
return; // tmux 不可用(如沙箱)则跳过
|
|
79
|
+
// 当前会话 + 全局默认都开,避免只改 -g 时已有会话仍是 off
|
|
80
|
+
run(["set-option", "-g", "extended-keys", "on"]);
|
|
81
|
+
run(["set-option", "extended-keys", "on"]);
|
|
82
|
+
// 3.5+; 3.4 会失败,忽略
|
|
83
|
+
run(["set-option", "-g", "extended-keys-format", "csi-u"]);
|
|
84
|
+
run(["set-option", "extended-keys-format", "csi-u"]);
|
|
85
|
+
const features = run(["show", "-gv", "terminal-features"]);
|
|
86
|
+
if (features.status === 0 && !/\bextkeys\b/.test(features.stdout ?? "")) {
|
|
87
|
+
run(["set-option", "-ga", "terminal-features", "xterm*:extkeys"]);
|
|
88
|
+
}
|
|
89
|
+
// 已挂上的客户端不会自动重读 terminal-features;直接往当前客户端 tty
|
|
90
|
+
// 发 modifyOtherKeys,让外层终端立刻开始区分 Shift+Enter。
|
|
91
|
+
const tty = run(["display-message", "-p", "#{client_tty}"]).stdout?.trim();
|
|
92
|
+
if (tty) {
|
|
93
|
+
try {
|
|
94
|
+
writeFileSync(tty, "\x1b[>4;2m");
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// 没权限或不是 tty 就忽略
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// -l: 按字面量发送,避免 send-keys 把转义序列拆成一串普通按键
|
|
101
|
+
run(["bind-key", "-n", "S-Enter", "send-keys", "-l", "\x1b[13;2u"]);
|
|
102
|
+
run(["bind-key", "-n", "C-Enter", "send-keys", "-l", "\x1b[13;5u"]);
|
|
103
|
+
}
|
|
24
104
|
async function runAgent(cfg, args) {
|
|
105
|
+
ensureBrandThemes();
|
|
25
106
|
ensureBrandPrompt();
|
|
107
|
+
ensureDefaultSettings();
|
|
108
|
+
ensureTmuxKeyboardProtocol();
|
|
26
109
|
// must be set before pi reads them (getAgentDir() reads at call time, env at import is fine too)
|
|
27
110
|
process.env["PI_CODING_AGENT_DIR"] = agentDir;
|
|
28
111
|
process.env["U1S1_API_KEY"] = cfg.apiKey;
|
|
29
|
-
|
|
112
|
+
// hide pi's own "Run pi update" banner; users should run `u1s1 update` instead
|
|
113
|
+
process.env["PI_SKIP_VERSION_CHECK"] = "1";
|
|
114
|
+
const { CustomEditor, main } = await import("@earendil-works/pi-coding-agent");
|
|
115
|
+
// 网页终端(如 Taikula)把 Shift+Enter 发成 ESC+CR(\x1b\r)。pi 在没开 Kitty
|
|
116
|
+
// 协议时把它当成 Alt+Enter:空闲就直接发送,忙碌才排队。先改成 CSI-u 的
|
|
117
|
+
// Shift+Enter,输入框就会换行,真·Alt+Enter(\x1b[13;3u)不受影响。
|
|
118
|
+
class ShiftEnterEditor extends CustomEditor {
|
|
119
|
+
handleInput(data) {
|
|
120
|
+
super.handleInput(data === "\x1b\r" ? "\x1b[13;2u" : data);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
30
123
|
const extension = [
|
|
31
124
|
{
|
|
32
125
|
name: "u1s1",
|
|
33
126
|
factory: (pi) => {
|
|
127
|
+
applyBrandUi(pi, VERSION);
|
|
128
|
+
// pi 内置只有 /quit;补一个更常见的 /exit,方便新手退出
|
|
129
|
+
pi.registerCommand("exit", {
|
|
130
|
+
description: "退出 u1s1",
|
|
131
|
+
handler: async (_args, ctx) => {
|
|
132
|
+
ctx.shutdown();
|
|
133
|
+
},
|
|
134
|
+
});
|
|
34
135
|
pi.registerProvider(PROVIDER_ID, {
|
|
35
136
|
name: "u1s1",
|
|
36
137
|
baseUrl: cfg.baseUrl,
|
|
@@ -46,11 +147,35 @@ async function runAgent(cfg, args) {
|
|
|
46
147
|
maxTokens: m.maxTokens,
|
|
47
148
|
})),
|
|
48
149
|
});
|
|
150
|
+
// /model and Ctrl+P already write pi settings; also keep ~/.u1s1/config.json in sync
|
|
151
|
+
pi.on("model_select", (event) => {
|
|
152
|
+
if (event.source === "restore")
|
|
153
|
+
return;
|
|
154
|
+
if (event.model.provider !== PROVIDER_ID)
|
|
155
|
+
return;
|
|
156
|
+
if (!MODELS.some((m) => m.id === event.model.id))
|
|
157
|
+
return;
|
|
158
|
+
persistPreferredModel(loadConfig(), event.model.id);
|
|
159
|
+
});
|
|
160
|
+
pi.on("session_start", (_event, ctx) => {
|
|
161
|
+
const previous = ctx.ui.getEditorComponent();
|
|
162
|
+
ctx.ui.setEditorComponent((tui, theme, keybindings) => {
|
|
163
|
+
if (previous) {
|
|
164
|
+
const inner = previous(tui, theme, keybindings);
|
|
165
|
+
const orig = inner.handleInput.bind(inner);
|
|
166
|
+
inner.handleInput = (data) => orig(data === "\x1b\r" ? "\x1b[13;2u" : data);
|
|
167
|
+
return inner;
|
|
168
|
+
}
|
|
169
|
+
return new ShiftEnterEditor(tui, theme, keybindings);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
49
172
|
},
|
|
50
173
|
},
|
|
51
174
|
];
|
|
52
175
|
const hasModelArg = args.some((a) => a === "--model" || a.startsWith("--model=") || a === "--provider");
|
|
53
|
-
const defaultModel = cfg.model
|
|
176
|
+
const defaultModel = resolvePreferredModel(cfg.model);
|
|
177
|
+
if (cfg.model !== defaultModel)
|
|
178
|
+
persistPreferredModel(cfg, defaultModel);
|
|
54
179
|
const finalArgs = hasModelArg ? args : ["--model", `${PROVIDER_ID}/${defaultModel}`, ...args];
|
|
55
180
|
await main(finalArgs, { extensionFactories: extension });
|
|
56
181
|
}
|
|
@@ -61,6 +186,9 @@ async function run() {
|
|
|
61
186
|
console.log(`u1s1 v${VERSION}`);
|
|
62
187
|
return;
|
|
63
188
|
}
|
|
189
|
+
if (cmd === "--help" || cmd === "-h") {
|
|
190
|
+
printConsoleBanner(VERSION);
|
|
191
|
+
}
|
|
64
192
|
if (cmd === "login") {
|
|
65
193
|
const { login } = await import("./login.js");
|
|
66
194
|
await login(args[1]);
|
|
@@ -82,6 +210,16 @@ async function run() {
|
|
|
82
210
|
console.log("已退出登录。");
|
|
83
211
|
return;
|
|
84
212
|
}
|
|
213
|
+
if (cmd === "update") {
|
|
214
|
+
const { update } = await import("./update.js");
|
|
215
|
+
await update();
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (cmd === "import") {
|
|
219
|
+
const { importCommand } = await import("./import/index.js");
|
|
220
|
+
await importCommand(args.slice(1));
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
85
223
|
const { ensureAuth } = await import("./login.js");
|
|
86
224
|
const cfg = await ensureAuth();
|
|
87
225
|
await runAgent(cfg, args);
|
package/dist/login.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
import { createInterface } from "node:readline/promises";
|
|
2
3
|
import { fetchMe } from "./api.js";
|
|
4
|
+
import { DASHBOARD_URL, printConsoleBanner } from "./brand.js";
|
|
3
5
|
import { loadConfig, saveConfig } from "./config.js";
|
|
4
|
-
const
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
const VERSION = require("../package.json").version;
|
|
5
8
|
function tryOpenBrowser(url) {
|
|
6
9
|
import("node:child_process")
|
|
7
10
|
.then(({ spawn }) => {
|
|
@@ -14,9 +17,9 @@ export async function login(keyArg) {
|
|
|
14
17
|
const cfg = loadConfig();
|
|
15
18
|
let key = keyArg?.trim();
|
|
16
19
|
if (!key) {
|
|
17
|
-
|
|
18
|
-
console.log("
|
|
19
|
-
console.log(` 1. 打开 ${DASHBOARD_URL} 注册/登录(30
|
|
20
|
+
printConsoleBanner(VERSION);
|
|
21
|
+
console.log(" 需要一把 API Key(免费):");
|
|
22
|
+
console.log(` 1. 打开 ${DASHBOARD_URL} 注册/登录(30 秒,送 $10 额度)`);
|
|
20
23
|
console.log(" 2. 复制你的 API Key,粘贴到下面");
|
|
21
24
|
console.log("");
|
|
22
25
|
tryOpenBrowser(DASHBOARD_URL);
|
|
@@ -34,7 +37,7 @@ export async function login(keyArg) {
|
|
|
34
37
|
process.exit(1);
|
|
35
38
|
});
|
|
36
39
|
saveConfig(next);
|
|
37
|
-
console.log(` ✓ 登录成功${me.email ? `(${me.email})` : ""}
|
|
40
|
+
console.log(` ✓ 登录成功${me.email ? `(${me.email})` : ""},还剩 $${me.remaining_usd} 额度。`);
|
|
38
41
|
return next;
|
|
39
42
|
}
|
|
40
43
|
/** Returns a config that definitely has an apiKey, prompting the user if needed. */
|
package/dist/model.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { loadConfig, MODELS, persistPreferredModel, resolveModel, resolvePreferredModel } from "./config.js";
|
|
2
2
|
export async function modelCommand(nameOrAlias) {
|
|
3
3
|
const cfg = loadConfig();
|
|
4
|
-
const current = cfg.model
|
|
4
|
+
const current = resolvePreferredModel(cfg.model);
|
|
5
5
|
if (!nameOrAlias) {
|
|
6
6
|
console.log("");
|
|
7
7
|
for (const m of MODELS) {
|
|
@@ -10,7 +10,7 @@ export async function modelCommand(nameOrAlias) {
|
|
|
10
10
|
console.log(` ${m.note} · $${m.cost.input}/$${m.cost.output} 每百万 token`);
|
|
11
11
|
}
|
|
12
12
|
console.log("");
|
|
13
|
-
console.log(" 切换:u1s1 model grok / u1s1 model deepseek(
|
|
13
|
+
console.log(" 切换:u1s1 model grok / u1s1 model deepseek(对话里 /model 同样会记住)");
|
|
14
14
|
console.log("");
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
@@ -19,6 +19,6 @@ export async function modelCommand(nameOrAlias) {
|
|
|
19
19
|
console.error(` 没有叫「${nameOrAlias}」的模型,可选:${MODELS.map((x) => x.aliases[0]).join(" / ")}`);
|
|
20
20
|
process.exit(1);
|
|
21
21
|
}
|
|
22
|
-
|
|
22
|
+
persistPreferredModel(cfg, m.id);
|
|
23
23
|
console.log(` ✓ 默认模型已切到 ${m.name}(${m.note})`);
|
|
24
24
|
}
|
package/dist/style.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { basename } from "node:path";
|
|
2
|
+
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
|
+
import { renderBrandHeader } from "./brand.js";
|
|
4
|
+
/**
|
|
5
|
+
* Brand chrome is just the startup hero + window title; everything else
|
|
6
|
+
* (tool rows, thinking blocks, spinner) stays Pi's default UI.
|
|
7
|
+
*/
|
|
8
|
+
export function applyBrandUi(pi, version) {
|
|
9
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
10
|
+
if (ctx.mode !== "tui")
|
|
11
|
+
return;
|
|
12
|
+
ctx.ui.setHeader((_tui, theme) => ({
|
|
13
|
+
render(width) {
|
|
14
|
+
// pi-tui crashes on lines wider than the terminal, so truncate defensively.
|
|
15
|
+
return renderBrandHeader(theme, version, process.cwd(), width).map((line) => truncateToWidth(line, width));
|
|
16
|
+
},
|
|
17
|
+
invalidate() { },
|
|
18
|
+
}));
|
|
19
|
+
ctx.ui.setTitle(`u1s1 — ${basename(process.cwd())}`);
|
|
20
|
+
});
|
|
21
|
+
}
|
package/dist/themes.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { agentDir } from "./config.js";
|
|
4
|
+
/**
|
|
5
|
+
* Claude Code-like: warm terracotta accent, no gray message/tool boxes,
|
|
6
|
+
* text sits on the terminal background instead of Codex-style cards.
|
|
7
|
+
*/
|
|
8
|
+
export const DARK_THEME = {
|
|
9
|
+
name: "u1s1-dark",
|
|
10
|
+
vars: {
|
|
11
|
+
orange: "#d97757",
|
|
12
|
+
orangeSoft: "#e8a87c",
|
|
13
|
+
text: "#e8e4db",
|
|
14
|
+
muted: "#9a9488",
|
|
15
|
+
dim: "#6b665e",
|
|
16
|
+
green: "#8fbc8f",
|
|
17
|
+
red: "#e07a7a",
|
|
18
|
+
yellow: "#d4a574",
|
|
19
|
+
blue: "#89b4c4",
|
|
20
|
+
selectedBg: "#3a342e",
|
|
21
|
+
userMsgBg: "#34312c",
|
|
22
|
+
},
|
|
23
|
+
colors: {
|
|
24
|
+
accent: "orange",
|
|
25
|
+
border: "orangeSoft",
|
|
26
|
+
borderAccent: "orange",
|
|
27
|
+
borderMuted: "dim",
|
|
28
|
+
success: "green",
|
|
29
|
+
error: "red",
|
|
30
|
+
warning: "yellow",
|
|
31
|
+
muted: "muted",
|
|
32
|
+
dim: "dim",
|
|
33
|
+
text: "text",
|
|
34
|
+
thinkingText: "muted",
|
|
35
|
+
selectedBg: "selectedBg",
|
|
36
|
+
scrollbarThumb: "selectedBg",
|
|
37
|
+
searchMatchBg: "selectedBg",
|
|
38
|
+
searchMatchText: "text",
|
|
39
|
+
userMessageBg: "userMsgBg",
|
|
40
|
+
userMessageText: "text",
|
|
41
|
+
customMessageBg: "userMsgBg",
|
|
42
|
+
customMessageText: "text",
|
|
43
|
+
customMessageLabel: "orangeSoft",
|
|
44
|
+
toolPendingBg: "",
|
|
45
|
+
toolSuccessBg: "",
|
|
46
|
+
toolErrorBg: "",
|
|
47
|
+
toolTitle: "text",
|
|
48
|
+
toolOutput: "muted",
|
|
49
|
+
mdHeading: "orangeSoft",
|
|
50
|
+
mdLink: "blue",
|
|
51
|
+
mdLinkUrl: "dim",
|
|
52
|
+
mdCode: "orangeSoft",
|
|
53
|
+
mdCodeBlock: "text",
|
|
54
|
+
mdCodeBlockBorder: "dim",
|
|
55
|
+
mdQuote: "muted",
|
|
56
|
+
mdQuoteBorder: "orange",
|
|
57
|
+
mdHr: "dim",
|
|
58
|
+
mdListBullet: "orange",
|
|
59
|
+
toolDiffAdded: "green",
|
|
60
|
+
toolDiffRemoved: "red",
|
|
61
|
+
toolDiffContext: "muted",
|
|
62
|
+
syntaxComment: "muted",
|
|
63
|
+
syntaxKeyword: "orangeSoft",
|
|
64
|
+
syntaxFunction: "orange",
|
|
65
|
+
syntaxVariable: "text",
|
|
66
|
+
syntaxString: "green",
|
|
67
|
+
syntaxNumber: "yellow",
|
|
68
|
+
syntaxType: "blue",
|
|
69
|
+
syntaxOperator: "muted",
|
|
70
|
+
syntaxPunctuation: "dim",
|
|
71
|
+
thinkingOff: "dim",
|
|
72
|
+
thinkingMinimal: "muted",
|
|
73
|
+
thinkingLow: "blue",
|
|
74
|
+
thinkingMedium: "orangeSoft",
|
|
75
|
+
thinkingHigh: "orange",
|
|
76
|
+
thinkingXhigh: "yellow",
|
|
77
|
+
thinkingMax: "red",
|
|
78
|
+
bashMode: "green",
|
|
79
|
+
},
|
|
80
|
+
export: {
|
|
81
|
+
pageBg: "#1c1917",
|
|
82
|
+
cardBg: "#292524",
|
|
83
|
+
infoBg: "#3d3429",
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
export const LIGHT_THEME = {
|
|
87
|
+
name: "u1s1-light",
|
|
88
|
+
vars: {
|
|
89
|
+
orange: "#c45c38",
|
|
90
|
+
orangeSoft: "#b86a45",
|
|
91
|
+
text: "#2c2825",
|
|
92
|
+
muted: "#6f6a63",
|
|
93
|
+
dim: "#9a9488",
|
|
94
|
+
green: "#3d7a4a",
|
|
95
|
+
red: "#c45c5c",
|
|
96
|
+
yellow: "#9a6b2f",
|
|
97
|
+
blue: "#3d6f8a",
|
|
98
|
+
selectedBg: "#efe8e0",
|
|
99
|
+
userMsgBg: "#e8e4dc",
|
|
100
|
+
},
|
|
101
|
+
colors: {
|
|
102
|
+
accent: "orange",
|
|
103
|
+
border: "orangeSoft",
|
|
104
|
+
borderAccent: "orange",
|
|
105
|
+
borderMuted: "dim",
|
|
106
|
+
success: "green",
|
|
107
|
+
error: "red",
|
|
108
|
+
warning: "yellow",
|
|
109
|
+
muted: "muted",
|
|
110
|
+
dim: "dim",
|
|
111
|
+
text: "text",
|
|
112
|
+
thinkingText: "muted",
|
|
113
|
+
selectedBg: "selectedBg",
|
|
114
|
+
scrollbarThumb: "selectedBg",
|
|
115
|
+
searchMatchBg: "selectedBg",
|
|
116
|
+
searchMatchText: "text",
|
|
117
|
+
userMessageBg: "userMsgBg",
|
|
118
|
+
userMessageText: "text",
|
|
119
|
+
customMessageBg: "userMsgBg",
|
|
120
|
+
customMessageText: "text",
|
|
121
|
+
customMessageLabel: "orangeSoft",
|
|
122
|
+
toolPendingBg: "",
|
|
123
|
+
toolSuccessBg: "",
|
|
124
|
+
toolErrorBg: "",
|
|
125
|
+
toolTitle: "text",
|
|
126
|
+
toolOutput: "muted",
|
|
127
|
+
mdHeading: "orange",
|
|
128
|
+
mdLink: "blue",
|
|
129
|
+
mdLinkUrl: "dim",
|
|
130
|
+
mdCode: "orangeSoft",
|
|
131
|
+
mdCodeBlock: "text",
|
|
132
|
+
mdCodeBlockBorder: "dim",
|
|
133
|
+
mdQuote: "muted",
|
|
134
|
+
mdQuoteBorder: "orange",
|
|
135
|
+
mdHr: "dim",
|
|
136
|
+
mdListBullet: "orange",
|
|
137
|
+
toolDiffAdded: "green",
|
|
138
|
+
toolDiffRemoved: "red",
|
|
139
|
+
toolDiffContext: "muted",
|
|
140
|
+
syntaxComment: "muted",
|
|
141
|
+
syntaxKeyword: "orange",
|
|
142
|
+
syntaxFunction: "orangeSoft",
|
|
143
|
+
syntaxVariable: "text",
|
|
144
|
+
syntaxString: "green",
|
|
145
|
+
syntaxNumber: "yellow",
|
|
146
|
+
syntaxType: "blue",
|
|
147
|
+
syntaxOperator: "muted",
|
|
148
|
+
syntaxPunctuation: "dim",
|
|
149
|
+
thinkingOff: "dim",
|
|
150
|
+
thinkingMinimal: "muted",
|
|
151
|
+
thinkingLow: "blue",
|
|
152
|
+
thinkingMedium: "orangeSoft",
|
|
153
|
+
thinkingHigh: "orange",
|
|
154
|
+
thinkingXhigh: "yellow",
|
|
155
|
+
thinkingMax: "red",
|
|
156
|
+
bashMode: "green",
|
|
157
|
+
},
|
|
158
|
+
export: {
|
|
159
|
+
pageBg: "#faf7f2",
|
|
160
|
+
cardBg: "#ffffff",
|
|
161
|
+
infoBg: "#fff4e8",
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
const THEMES = [DARK_THEME, LIGHT_THEME];
|
|
165
|
+
export const DEFAULT_THEME_SETTING = "u1s1-light/u1s1-dark";
|
|
166
|
+
/** Keep ~/.u1s1/agent/themes in sync so Pi picks them up as normal theme files. */
|
|
167
|
+
export function ensureBrandThemes() {
|
|
168
|
+
const dir = join(agentDir, "themes");
|
|
169
|
+
mkdirSync(dir, { recursive: true });
|
|
170
|
+
for (const theme of THEMES) {
|
|
171
|
+
const path = join(dir, `${theme.name}.json`);
|
|
172
|
+
const body = `${JSON.stringify(theme, null, 2)}\n`;
|
|
173
|
+
if (!existsSync(path) || readFileSync(path, "utf8") !== body) {
|
|
174
|
+
writeFileSync(path, body);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
package/dist/update.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
const require = createRequire(import.meta.url);
|
|
4
|
+
const VERSION = require("../package.json").version;
|
|
5
|
+
const PACKAGE_NAME = "u1s1-cli";
|
|
6
|
+
/** Detect the package manager that installed u1s1. */
|
|
7
|
+
function detectPackageManager() {
|
|
8
|
+
// Check common global install locations for clues
|
|
9
|
+
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
10
|
+
if (userAgent.startsWith("pnpm"))
|
|
11
|
+
return "pnpm";
|
|
12
|
+
if (userAgent.startsWith("yarn"))
|
|
13
|
+
return "yarn";
|
|
14
|
+
if (userAgent.startsWith("bun"))
|
|
15
|
+
return "bun";
|
|
16
|
+
return "npm";
|
|
17
|
+
}
|
|
18
|
+
/** Fetch the latest published version from npm registry. */
|
|
19
|
+
async function getLatestVersion() {
|
|
20
|
+
const url = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
21
|
+
try {
|
|
22
|
+
const res = await fetch(url, {
|
|
23
|
+
headers: { Accept: "application/json" },
|
|
24
|
+
signal: AbortSignal.timeout(10_000),
|
|
25
|
+
});
|
|
26
|
+
if (!res.ok)
|
|
27
|
+
return undefined;
|
|
28
|
+
const body = (await res.json());
|
|
29
|
+
return body.version;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function compareVersions(a, b) {
|
|
36
|
+
const pa = a.split(".").map(Number);
|
|
37
|
+
const pb = b.split(".").map(Number);
|
|
38
|
+
for (let i = 0; i < 3; i++) {
|
|
39
|
+
const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
40
|
+
if (diff !== 0)
|
|
41
|
+
return diff;
|
|
42
|
+
}
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
export async function update() {
|
|
46
|
+
console.log("正在检查更新…");
|
|
47
|
+
const latest = await getLatestVersion();
|
|
48
|
+
if (!latest) {
|
|
49
|
+
console.error("无法获取最新版本。请检查网络连接后重试。");
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
if (compareVersions(latest, VERSION) <= 0) {
|
|
53
|
+
console.log(`当前 v${VERSION} 已是最新版本 ✓`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const pm = detectPackageManager();
|
|
57
|
+
console.log(`发现新版本 v${latest} (当前 v${VERSION})`);
|
|
58
|
+
console.log(`正在用 ${pm} 更新 ${PACKAGE_NAME}…`);
|
|
59
|
+
try {
|
|
60
|
+
const installCmd = pm === "npm" ? `npm install -g ${PACKAGE_NAME}@latest` : `${pm} add -g ${PACKAGE_NAME}@latest`;
|
|
61
|
+
execSync(installCmd, { stdio: "inherit" });
|
|
62
|
+
console.log(`\n✅ 已更新到 v${latest},重启 u1s1 后生效。`);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
console.error("\n自动更新失败。请手动运行:");
|
|
66
|
+
console.error(` ${pm === "npm" ? "npm install -g" : `${pm} add -g`} ${PACKAGE_NAME}@latest`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
}
|
package/dist/usage.js
CHANGED
|
@@ -14,14 +14,14 @@ export async function usage() {
|
|
|
14
14
|
console.error(e.message);
|
|
15
15
|
process.exit(1);
|
|
16
16
|
});
|
|
17
|
-
const
|
|
18
|
-
const
|
|
17
|
+
const remain = me.remaining_usd;
|
|
18
|
+
const spent = me.mtd_usd;
|
|
19
|
+
const denom = remain + spent;
|
|
20
|
+
const remainRatio = denom > 0 ? remain / denom : 0;
|
|
19
21
|
console.log("");
|
|
20
22
|
console.log(` 账号 ${me.email ?? "(未绑定邮箱)"}`);
|
|
21
|
-
console.log(` 本月已用 $${
|
|
22
|
-
console.log(` 剩余额度 $${
|
|
23
|
-
if (me.bonus_balance_usd > 0)
|
|
24
|
-
console.log(` 其中加量 $${me.bonus_balance_usd}(邀请所得,不过期)`);
|
|
23
|
+
console.log(` 本月已用 $${spent}`);
|
|
24
|
+
console.log(` 剩余额度 $${remain} ${bar(remainRatio)}`);
|
|
25
25
|
console.log("");
|
|
26
26
|
console.log(" 邀请朋友双方各得加量 → https://u1s1.io/dashboard");
|
|
27
27
|
console.log("");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "u1s1-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "u1s1 — 有一说一,最省心的 AI 编程搭子。终端里用中文说需求,AI 帮你读文件、改代码、跑命令。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"scripts": {
|
|
16
16
|
"build": "tsc",
|
|
17
17
|
"dev": "tsx src/index.ts",
|
|
18
|
-
"typecheck": "tsc --noEmit"
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
19
20
|
},
|
|
20
21
|
"keywords": [
|
|
21
22
|
"ai",
|
|
@@ -27,11 +28,12 @@
|
|
|
27
28
|
"license": "MIT",
|
|
28
29
|
"homepage": "https://u1s1.io",
|
|
29
30
|
"dependencies": {
|
|
30
|
-
"@earendil-works/pi-coding-agent": "0.84.
|
|
31
|
+
"@earendil-works/pi-coding-agent": "0.84.2",
|
|
32
|
+
"@earendil-works/pi-tui": "0.84.2"
|
|
31
33
|
},
|
|
32
34
|
"devDependencies": {
|
|
33
|
-
"
|
|
35
|
+
"@types/node": "^22.15.0",
|
|
34
36
|
"tsx": "^4.20.0",
|
|
35
|
-
"
|
|
37
|
+
"typescript": "^5.9.2"
|
|
36
38
|
}
|
|
37
39
|
}
|