u1s1-cli 0.3.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/dist/index.js CHANGED
@@ -49,6 +49,15 @@ function ensureDefaultSettings() {
49
49
  settings.theme = DEFAULT_THEME_SETTING;
50
50
  changed = true;
51
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
+ }
52
61
  if (changed)
53
62
  writeFileSync(p, JSON.stringify(settings, null, 2) + "\n");
54
63
  }
@@ -206,6 +215,11 @@ async function run() {
206
215
  await update();
207
216
  return;
208
217
  }
218
+ if (cmd === "import") {
219
+ const { importCommand } = await import("./import/index.js");
220
+ await importCommand(args.slice(1));
221
+ return;
222
+ }
209
223
  const { ensureAuth } = await import("./login.js");
210
224
  const cfg = await ensureAuth();
211
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 DASHBOARD_URL = "https://u1s1.io/dashboard";
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,8 +17,8 @@ export async function login(keyArg) {
14
17
  const cfg = loadConfig();
15
18
  let key = keyArg?.trim();
16
19
  if (!key) {
17
- console.log("");
18
- console.log(" u1s1 需要一把 API Key(免费):");
20
+ printConsoleBanner(VERSION);
21
+ console.log(" 需要一把 API Key(免费):");
19
22
  console.log(` 1. 打开 ${DASHBOARD_URL} 注册/登录(30 秒,送 $10 额度)`);
20
23
  console.log(" 2. 复制你的 API Key,粘贴到下面");
21
24
  console.log("");
package/dist/style.js CHANGED
@@ -1,299 +1,21 @@
1
- import { homedir } from "node:os";
2
1
  import { basename } from "node:path";
3
- import { createBashTool, createEditTool, createFindTool, createGrepTool, createLsTool, createReadTool, createWriteTool, } from "@earendil-works/pi-coding-agent";
2
+ import { truncateToWidth } from "@earendil-works/pi-tui";
4
3
  import { renderBrandHeader } from "./brand.js";
5
- function row(text) {
6
- return {
7
- render() {
8
- return text.split("\n");
9
- },
10
- invalidate() { },
11
- };
12
- }
13
- function shortPath(path) {
14
- if (typeof path !== "string" || !path)
15
- return "";
16
- const home = homedir();
17
- if (path === home)
18
- return "~";
19
- if (path.startsWith(`${home}/`) || path.startsWith(`${home}\\`)) {
20
- return `~${path.slice(home.length)}`;
21
- }
22
- const cwd = process.cwd();
23
- if (path === cwd)
24
- return ".";
25
- if (path.startsWith(`${cwd}/`) || path.startsWith(`${cwd}\\`)) {
26
- return path.slice(cwd.length + 1);
27
- }
28
- return path;
29
- }
30
- function asString(value) {
31
- return typeof value === "string" ? value : "";
32
- }
33
- function clip(text, max = 72) {
34
- if (text.length <= max)
35
- return text;
36
- return `${text.slice(0, max - 1)}…`;
37
- }
38
- function callLine(theme, name, detail) {
39
- let text = `${theme.fg("accent", "⏺")} ${theme.bold(theme.fg("toolTitle", name))}`;
40
- if (detail)
41
- text += ` ${theme.fg("muted", clip(detail))}`;
42
- return text;
43
- }
44
- function resultLine(theme, text, color = "dim") {
45
- return ` ${theme.fg("dim", "⎿")} ${theme.fg(color, text)}`;
46
- }
47
- function indentOutput(theme, output, limit = 20) {
48
- const all = output.split("\n");
49
- const shown = all.slice(0, limit);
50
- let text = shown.map((line) => ` ${theme.fg("dim", line)}`).join("\n");
51
- if (all.length > limit) {
52
- text += `\n ${theme.fg("muted", `… ${all.length - limit} 行`)}`;
53
- }
54
- return text;
55
- }
56
- const toolCache = new Map();
57
- function createBuiltInTools(cwd) {
58
- return {
59
- read: createReadTool(cwd),
60
- bash: createBashTool(cwd),
61
- edit: createEditTool(cwd),
62
- write: createWriteTool(cwd),
63
- find: createFindTool(cwd),
64
- grep: createGrepTool(cwd),
65
- ls: createLsTool(cwd),
66
- };
67
- }
68
- function toolsFor(cwd) {
69
- let cached = toolCache.get(cwd);
70
- if (!cached) {
71
- cached = createBuiltInTools(cwd);
72
- toolCache.set(cwd, cached);
73
- }
74
- return cached;
75
- }
76
4
  /**
77
- * Claude Code-like chrome: brand header, pulsing dot, one-line tool rows.
78
- * All of this is Pi's public extension API — no node_modules patch.
5
+ * Brand chrome is just the startup hero + window title; everything else
6
+ * (tool rows, thinking blocks, spinner) stays Pi's default UI.
79
7
  */
80
8
  export function applyBrandUi(pi, version) {
81
9
  pi.on("session_start", async (_event, ctx) => {
82
10
  if (ctx.mode !== "tui")
83
11
  return;
84
12
  ctx.ui.setHeader((_tui, theme) => ({
85
- render() {
86
- return renderBrandHeader(theme, version, process.cwd());
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));
87
16
  },
88
17
  invalidate() { },
89
18
  }));
90
- const theme = ctx.ui.theme;
91
- ctx.ui.setWorkingMessage("思考中");
92
- ctx.ui.setWorkingIndicator({
93
- frames: [
94
- theme.fg("dim", "·"),
95
- theme.fg("muted", "•"),
96
- theme.fg("accent", "●"),
97
- theme.fg("muted", "•"),
98
- ],
99
- intervalMs: 140,
100
- });
101
- ctx.ui.setHiddenThinkingLabel("思考中");
102
19
  ctx.ui.setTitle(`u1s1 — ${basename(process.cwd())}`);
103
20
  });
104
- const cwd = process.cwd();
105
- const originals = toolsFor(cwd);
106
- pi.registerTool({
107
- name: "read",
108
- label: "read",
109
- description: originals.read.description,
110
- parameters: originals.read.parameters,
111
- renderShell: "self",
112
- async execute(toolCallId, params, signal, onUpdate, ctx) {
113
- return toolsFor(ctx.cwd).read.execute(toolCallId, params, signal, onUpdate);
114
- },
115
- renderCall(args, theme) {
116
- return row(callLine(theme, "Read", shortPath(args.path)));
117
- },
118
- renderResult(result, { expanded, isPartial }, theme) {
119
- if (isPartial)
120
- return row(resultLine(theme, "读取中…", "warning"));
121
- const content = result.content[0];
122
- if (content?.type === "image")
123
- return row(resultLine(theme, "图片", "success"));
124
- if (content?.type !== "text")
125
- return row(resultLine(theme, "无内容", "error"));
126
- const details = result.details;
127
- const count = content.text.split("\n").length;
128
- let text = resultLine(theme, `${count} 行`, "success");
129
- if (details?.truncation?.truncated)
130
- text += theme.fg("warning", " · 已截断");
131
- if (expanded)
132
- text += `\n${indentOutput(theme, content.text)}`;
133
- return row(text);
134
- },
135
- });
136
- pi.registerTool({
137
- name: "bash",
138
- label: "bash",
139
- description: originals.bash.description,
140
- parameters: originals.bash.parameters,
141
- renderShell: "self",
142
- async execute(toolCallId, params, signal, onUpdate, ctx) {
143
- return toolsFor(ctx.cwd).bash.execute(toolCallId, params, signal, onUpdate);
144
- },
145
- renderCall(args, theme) {
146
- return row(callLine(theme, "Bash", asString(args.command)));
147
- },
148
- renderResult(result, { expanded, isPartial }, theme) {
149
- if (isPartial)
150
- return row(resultLine(theme, "执行中…", "warning"));
151
- const content = result.content[0];
152
- const output = content?.type === "text" ? content.text : "";
153
- const exitMatch = output.match(/exit code: (\d+)/);
154
- const exitCode = exitMatch ? Number(exitMatch[1]) : null;
155
- const details = result.details;
156
- const ok = exitCode === 0 || exitCode === null;
157
- let text = resultLine(theme, ok ? "完成" : `退出码 ${exitCode}`, ok ? "success" : "error");
158
- if (details?.truncation?.truncated)
159
- text += theme.fg("warning", " · 已截断");
160
- if (expanded && output)
161
- text += `\n${indentOutput(theme, output)}`;
162
- return row(text);
163
- },
164
- });
165
- pi.registerTool({
166
- name: "edit",
167
- label: "edit",
168
- description: originals.edit.description,
169
- parameters: originals.edit.parameters,
170
- renderShell: "self",
171
- async execute(toolCallId, params, signal, onUpdate, ctx) {
172
- return toolsFor(ctx.cwd).edit.execute(toolCallId, params, signal, onUpdate);
173
- },
174
- renderCall(args, theme) {
175
- return row(callLine(theme, "Edit", shortPath(args.path)));
176
- },
177
- renderResult(result, { expanded, isPartial }, theme) {
178
- if (isPartial)
179
- return row(resultLine(theme, "修改中…", "warning"));
180
- const content = result.content[0];
181
- if (content?.type === "text" && content.text.startsWith("Error")) {
182
- return row(resultLine(theme, content.text.split("\n")[0] ?? "失败", "error"));
183
- }
184
- const details = result.details;
185
- if (!details?.diff)
186
- return row(resultLine(theme, "已写入", "success"));
187
- let additions = 0;
188
- let removals = 0;
189
- for (const line of details.diff.split("\n")) {
190
- if (line.startsWith("+") && !line.startsWith("+++"))
191
- additions++;
192
- if (line.startsWith("-") && !line.startsWith("---"))
193
- removals++;
194
- }
195
- let text = resultLine(theme, `+${additions} / -${removals}`, "success");
196
- if (expanded)
197
- text += `\n${indentOutput(theme, details.diff, 30)}`;
198
- return row(text);
199
- },
200
- });
201
- pi.registerTool({
202
- name: "write",
203
- label: "write",
204
- description: originals.write.description,
205
- parameters: originals.write.parameters,
206
- renderShell: "self",
207
- async execute(toolCallId, params, signal, onUpdate, ctx) {
208
- return toolsFor(ctx.cwd).write.execute(toolCallId, params, signal, onUpdate);
209
- },
210
- renderCall(args, theme) {
211
- const count = asString(args.content).split("\n").length;
212
- const detail = `${shortPath(args.path)}${count ? ` (${count} 行)` : ""}`;
213
- return row(callLine(theme, "Write", detail));
214
- },
215
- renderResult(result, { isPartial }, theme) {
216
- if (isPartial)
217
- return row(resultLine(theme, "写入中…", "warning"));
218
- const content = result.content[0];
219
- if (content?.type === "text" && content.text.startsWith("Error")) {
220
- return row(resultLine(theme, content.text.split("\n")[0] ?? "失败", "error"));
221
- }
222
- return row(resultLine(theme, "已写入", "success"));
223
- },
224
- });
225
- pi.registerTool({
226
- name: "grep",
227
- label: "grep",
228
- description: originals.grep.description,
229
- parameters: originals.grep.parameters,
230
- renderShell: "self",
231
- async execute(toolCallId, params, signal, onUpdate, ctx) {
232
- return toolsFor(ctx.cwd).grep.execute(toolCallId, params, signal, onUpdate);
233
- },
234
- renderCall(args, theme) {
235
- const where = shortPath(args.path);
236
- const detail = where ? `${asString(args.pattern)} ${where}` : asString(args.pattern);
237
- return row(callLine(theme, "Grep", detail));
238
- },
239
- renderResult(result, { expanded, isPartial }, theme) {
240
- if (isPartial)
241
- return row(resultLine(theme, "搜索中…", "warning"));
242
- const content = result.content[0];
243
- const output = content?.type === "text" ? content.text : "";
244
- const hits = output ? output.split("\n").filter(Boolean).length : 0;
245
- let text = resultLine(theme, hits ? `${hits} 处` : "无匹配", hits ? "success" : "dim");
246
- if (expanded && output)
247
- text += `\n${indentOutput(theme, output)}`;
248
- return row(text);
249
- },
250
- });
251
- pi.registerTool({
252
- name: "find",
253
- label: "find",
254
- description: originals.find.description,
255
- parameters: originals.find.parameters,
256
- renderShell: "self",
257
- async execute(toolCallId, params, signal, onUpdate, ctx) {
258
- return toolsFor(ctx.cwd).find.execute(toolCallId, params, signal, onUpdate);
259
- },
260
- renderCall(args, theme) {
261
- return row(callLine(theme, "Find", asString(args.pattern)));
262
- },
263
- renderResult(result, { expanded, isPartial }, theme) {
264
- if (isPartial)
265
- return row(resultLine(theme, "查找中…", "warning"));
266
- const content = result.content[0];
267
- const output = content?.type === "text" ? content.text : "";
268
- const hits = output ? output.split("\n").filter(Boolean).length : 0;
269
- let text = resultLine(theme, hits ? `${hits} 个文件` : "没找到", hits ? "success" : "dim");
270
- if (expanded && output)
271
- text += `\n${indentOutput(theme, output)}`;
272
- return row(text);
273
- },
274
- });
275
- pi.registerTool({
276
- name: "ls",
277
- label: "ls",
278
- description: originals.ls.description,
279
- parameters: originals.ls.parameters,
280
- renderShell: "self",
281
- async execute(toolCallId, params, signal, onUpdate, ctx) {
282
- return toolsFor(ctx.cwd).ls.execute(toolCallId, params, signal, onUpdate);
283
- },
284
- renderCall(args, theme) {
285
- return row(callLine(theme, "List", shortPath(args.path) || "."));
286
- },
287
- renderResult(result, { expanded, isPartial }, theme) {
288
- if (isPartial)
289
- return row(resultLine(theme, "列出中…", "warning"));
290
- const content = result.content[0];
291
- const output = content?.type === "text" ? content.text : "";
292
- const hits = output ? output.split("\n").filter(Boolean).length : 0;
293
- let text = resultLine(theme, `${hits} 项`, "success");
294
- if (expanded && output)
295
- text += `\n${indentOutput(theme, output)}`;
296
- return row(text);
297
- },
298
- });
299
21
  }
package/dist/themes.js CHANGED
@@ -18,6 +18,7 @@ export const DARK_THEME = {
18
18
  yellow: "#d4a574",
19
19
  blue: "#89b4c4",
20
20
  selectedBg: "#3a342e",
21
+ userMsgBg: "#34312c",
21
22
  },
22
23
  colors: {
23
24
  accent: "orange",
@@ -35,9 +36,9 @@ export const DARK_THEME = {
35
36
  scrollbarThumb: "selectedBg",
36
37
  searchMatchBg: "selectedBg",
37
38
  searchMatchText: "text",
38
- userMessageBg: "",
39
+ userMessageBg: "userMsgBg",
39
40
  userMessageText: "text",
40
- customMessageBg: "",
41
+ customMessageBg: "userMsgBg",
41
42
  customMessageText: "text",
42
43
  customMessageLabel: "orangeSoft",
43
44
  toolPendingBg: "",
@@ -95,6 +96,7 @@ export const LIGHT_THEME = {
95
96
  yellow: "#9a6b2f",
96
97
  blue: "#3d6f8a",
97
98
  selectedBg: "#efe8e0",
99
+ userMsgBg: "#e8e4dc",
98
100
  },
99
101
  colors: {
100
102
  accent: "orange",
@@ -112,9 +114,9 @@ export const LIGHT_THEME = {
112
114
  scrollbarThumb: "selectedBg",
113
115
  searchMatchBg: "selectedBg",
114
116
  searchMatchText: "text",
115
- userMessageBg: "",
117
+ userMessageBg: "userMsgBg",
116
118
  userMessageText: "text",
117
- customMessageBg: "",
119
+ customMessageBg: "userMsgBg",
118
120
  customMessageText: "text",
119
121
  customMessageLabel: "orangeSoft",
120
122
  toolPendingBg: "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u1s1-cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "u1s1 — 有一说一,最省心的 AI 编程搭子。终端里用中文说需求,AI 帮你读文件、改代码、跑命令。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -28,11 +28,12 @@
28
28
  "license": "MIT",
29
29
  "homepage": "https://u1s1.io",
30
30
  "dependencies": {
31
- "@earendil-works/pi-coding-agent": "0.84.2"
31
+ "@earendil-works/pi-coding-agent": "0.84.2",
32
+ "@earendil-works/pi-tui": "0.84.2"
32
33
  },
33
34
  "devDependencies": {
34
- "typescript": "^5.9.2",
35
+ "@types/node": "^22.15.0",
35
36
  "tsx": "^4.20.0",
36
- "@types/node": "^22.15.0"
37
+ "typescript": "^5.9.2"
37
38
  }
38
39
  }