u1s1-cli 0.3.0 → 0.5.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
@@ -1,12 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync } from "node:child_process";
3
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { createRequire } from "node:module";
6
6
  import { printConsoleBanner } from "./brand.js";
7
7
  import { agentDir, loadConfig, MODELS, persistPreferredModel, PROVIDER_ID, resolvePreferredModel, } from "./config.js";
8
8
  import { applyBrandUi } from "./style.js";
9
- import { DEFAULT_THEME_SETTING, ensureBrandThemes } from "./themes.js";
10
9
  const require = createRequire(import.meta.url);
11
10
  const VERSION = require("../package.json").version;
12
11
  const BRAND_APPEND = `## u1s1
@@ -39,19 +38,31 @@ function ensureDefaultSettings() {
39
38
  }
40
39
  }
41
40
  let changed = false;
42
- if (!("hideThinkingBlock" in settings)) {
43
- settings.hideThinkingBlock = true;
44
- changed = true;
41
+ // Pi default is one-at-a-time (one queued message per turn). u1s1 delivers
42
+ // all queued messages together so burst typing isn't split across turns.
43
+ // Only fill in missing keys so an explicit /settings choice still sticks.
44
+ for (const key of ["steeringMode", "followUpMode"]) {
45
+ if (!(key in settings)) {
46
+ settings[key] = "all";
47
+ changed = true;
48
+ }
45
49
  }
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
+ // ≤0.4.0 shipped branded themes and forced them as default; the files are
51
+ // gone now, so a settings.json still pointing at them must fall back to
52
+ // pi's default theme.
53
+ if (typeof settings.theme === "string" && settings.theme.includes("u1s1-")) {
54
+ delete settings.theme;
50
55
  changed = true;
51
56
  }
52
57
  if (changed)
53
58
  writeFileSync(p, JSON.stringify(settings, null, 2) + "\n");
54
59
  }
60
+ /** ≤0.4.0 wrote u1s1-dark/u1s1-light into the pi themes dir; remove them. */
61
+ function cleanupBrandThemes() {
62
+ for (const name of ["u1s1-dark", "u1s1-light"]) {
63
+ rmSync(join(agentDir, "themes", `${name}.json`), { force: true });
64
+ }
65
+ }
55
66
  /**
56
67
  * tmux 默认不转发键盘修饰键,会把 Shift+Enter 当成普通回车发出去,消息没写完就被发送。
57
68
  * 只开 extended-keys 不够:默认 terminal-features 里 xterm* 没有 extkeys,tmux 根本不会
@@ -93,7 +104,7 @@ function ensureTmuxKeyboardProtocol() {
93
104
  run(["bind-key", "-n", "C-Enter", "send-keys", "-l", "\x1b[13;5u"]);
94
105
  }
95
106
  async function runAgent(cfg, args) {
96
- ensureBrandThemes();
107
+ cleanupBrandThemes();
97
108
  ensureBrandPrompt();
98
109
  ensureDefaultSettings();
99
110
  ensureTmuxKeyboardProtocol();
@@ -206,6 +217,11 @@ async function run() {
206
217
  await update();
207
218
  return;
208
219
  }
220
+ if (cmd === "import") {
221
+ const { importCommand } = await import("./import/index.js");
222
+ await importCommand(args.slice(1));
223
+ return;
224
+ }
209
225
  const { ensureAuth } = await import("./login.js");
210
226
  const cfg = await ensureAuth();
211
227
  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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u1s1-cli",
3
- "version": "0.3.0",
3
+ "version": "0.5.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
  }
package/dist/themes.js DELETED
@@ -1,175 +0,0 @@
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
- },
22
- colors: {
23
- accent: "orange",
24
- border: "orangeSoft",
25
- borderAccent: "orange",
26
- borderMuted: "dim",
27
- success: "green",
28
- error: "red",
29
- warning: "yellow",
30
- muted: "muted",
31
- dim: "dim",
32
- text: "text",
33
- thinkingText: "muted",
34
- selectedBg: "selectedBg",
35
- scrollbarThumb: "selectedBg",
36
- searchMatchBg: "selectedBg",
37
- searchMatchText: "text",
38
- userMessageBg: "",
39
- userMessageText: "text",
40
- customMessageBg: "",
41
- customMessageText: "text",
42
- customMessageLabel: "orangeSoft",
43
- toolPendingBg: "",
44
- toolSuccessBg: "",
45
- toolErrorBg: "",
46
- toolTitle: "text",
47
- toolOutput: "muted",
48
- mdHeading: "orangeSoft",
49
- mdLink: "blue",
50
- mdLinkUrl: "dim",
51
- mdCode: "orangeSoft",
52
- mdCodeBlock: "text",
53
- mdCodeBlockBorder: "dim",
54
- mdQuote: "muted",
55
- mdQuoteBorder: "orange",
56
- mdHr: "dim",
57
- mdListBullet: "orange",
58
- toolDiffAdded: "green",
59
- toolDiffRemoved: "red",
60
- toolDiffContext: "muted",
61
- syntaxComment: "muted",
62
- syntaxKeyword: "orangeSoft",
63
- syntaxFunction: "orange",
64
- syntaxVariable: "text",
65
- syntaxString: "green",
66
- syntaxNumber: "yellow",
67
- syntaxType: "blue",
68
- syntaxOperator: "muted",
69
- syntaxPunctuation: "dim",
70
- thinkingOff: "dim",
71
- thinkingMinimal: "muted",
72
- thinkingLow: "blue",
73
- thinkingMedium: "orangeSoft",
74
- thinkingHigh: "orange",
75
- thinkingXhigh: "yellow",
76
- thinkingMax: "red",
77
- bashMode: "green",
78
- },
79
- export: {
80
- pageBg: "#1c1917",
81
- cardBg: "#292524",
82
- infoBg: "#3d3429",
83
- },
84
- };
85
- export const LIGHT_THEME = {
86
- name: "u1s1-light",
87
- vars: {
88
- orange: "#c45c38",
89
- orangeSoft: "#b86a45",
90
- text: "#2c2825",
91
- muted: "#6f6a63",
92
- dim: "#9a9488",
93
- green: "#3d7a4a",
94
- red: "#c45c5c",
95
- yellow: "#9a6b2f",
96
- blue: "#3d6f8a",
97
- selectedBg: "#efe8e0",
98
- },
99
- colors: {
100
- accent: "orange",
101
- border: "orangeSoft",
102
- borderAccent: "orange",
103
- borderMuted: "dim",
104
- success: "green",
105
- error: "red",
106
- warning: "yellow",
107
- muted: "muted",
108
- dim: "dim",
109
- text: "text",
110
- thinkingText: "muted",
111
- selectedBg: "selectedBg",
112
- scrollbarThumb: "selectedBg",
113
- searchMatchBg: "selectedBg",
114
- searchMatchText: "text",
115
- userMessageBg: "",
116
- userMessageText: "text",
117
- customMessageBg: "",
118
- customMessageText: "text",
119
- customMessageLabel: "orangeSoft",
120
- toolPendingBg: "",
121
- toolSuccessBg: "",
122
- toolErrorBg: "",
123
- toolTitle: "text",
124
- toolOutput: "muted",
125
- mdHeading: "orange",
126
- mdLink: "blue",
127
- mdLinkUrl: "dim",
128
- mdCode: "orangeSoft",
129
- mdCodeBlock: "text",
130
- mdCodeBlockBorder: "dim",
131
- mdQuote: "muted",
132
- mdQuoteBorder: "orange",
133
- mdHr: "dim",
134
- mdListBullet: "orange",
135
- toolDiffAdded: "green",
136
- toolDiffRemoved: "red",
137
- toolDiffContext: "muted",
138
- syntaxComment: "muted",
139
- syntaxKeyword: "orange",
140
- syntaxFunction: "orangeSoft",
141
- syntaxVariable: "text",
142
- syntaxString: "green",
143
- syntaxNumber: "yellow",
144
- syntaxType: "blue",
145
- syntaxOperator: "muted",
146
- syntaxPunctuation: "dim",
147
- thinkingOff: "dim",
148
- thinkingMinimal: "muted",
149
- thinkingLow: "blue",
150
- thinkingMedium: "orangeSoft",
151
- thinkingHigh: "orange",
152
- thinkingXhigh: "yellow",
153
- thinkingMax: "red",
154
- bashMode: "green",
155
- },
156
- export: {
157
- pageBg: "#faf7f2",
158
- cardBg: "#ffffff",
159
- infoBg: "#fff4e8",
160
- },
161
- };
162
- const THEMES = [DARK_THEME, LIGHT_THEME];
163
- export const DEFAULT_THEME_SETTING = "u1s1-light/u1s1-dark";
164
- /** Keep ~/.u1s1/agent/themes in sync so Pi picks them up as normal theme files. */
165
- export function ensureBrandThemes() {
166
- const dir = join(agentDir, "themes");
167
- mkdirSync(dir, { recursive: true });
168
- for (const theme of THEMES) {
169
- const path = join(dir, `${theme.name}.json`);
170
- const body = `${JSON.stringify(theme, null, 2)}\n`;
171
- if (!existsSync(path) || readFileSync(path, "utf8") !== body) {
172
- writeFileSync(path, body);
173
- }
174
- }
175
- }