@yachiyo-5i/xlyra-agent 0.2.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/cli.d.cts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,287 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ configExists,
4
+ defaultConfigPath,
5
+ loadConfig,
6
+ removeEndpoint,
7
+ saveConfig,
8
+ serveFromConfig,
9
+ upsertEndpoint
10
+ } from "./chunk-QH6SEOO6.js";
11
+
12
+ // src/cli.ts
13
+ import fs from "fs";
14
+ import readline2 from "readline";
15
+
16
+ // src/cli-select.ts
17
+ import readline from "readline";
18
+ function moveSelection(index, count, direction) {
19
+ if (count <= 0) throw new Error("\u9009\u62E9\u5217\u8868\u4E0D\u80FD\u4E3A\u7A7A");
20
+ return (index + direction + count) % count;
21
+ }
22
+ async function selectOption(input, output, question, options, initialValue) {
23
+ if (options.length === 0) throw new Error("\u9009\u62E9\u5217\u8868\u4E0D\u80FD\u4E3A\u7A7A");
24
+ let selected = Math.max(0, options.findIndex((option) => option.value === initialValue));
25
+ let firstRender = true;
26
+ const wasRaw = input.isRaw === true;
27
+ readline.emitKeypressEvents(input);
28
+ if (!wasRaw) input.setRawMode?.(true);
29
+ input.resume();
30
+ output.write(`${question}\uFF08\u2191/\u2193 \u9009\u62E9\uFF0C\u56DE\u8F66\u786E\u8BA4\uFF09
31
+ `);
32
+ output.write("\x1B[?25l");
33
+ const render = () => {
34
+ if (!firstRender) readline.moveCursor(output, 0, -options.length);
35
+ firstRender = false;
36
+ for (const [index, option] of options.entries()) {
37
+ readline.clearLine(output, 0);
38
+ readline.cursorTo(output, 0);
39
+ const marker = index === selected ? "\u276F" : " ";
40
+ const description = option.description ? ` ${option.description}` : "";
41
+ output.write(`${marker} ${option.label}${description}
42
+ `);
43
+ }
44
+ readline.cursorTo(output, 0);
45
+ };
46
+ render();
47
+ return new Promise((resolve, reject) => {
48
+ const cleanup = () => {
49
+ input.removeListener("keypress", onKeypress);
50
+ if (!wasRaw) input.setRawMode?.(false);
51
+ output.write("\x1B[?25h");
52
+ };
53
+ const onKeypress = (_text, key) => {
54
+ if (key.ctrl && key.name === "c") {
55
+ cleanup();
56
+ output.write("\n");
57
+ reject(new Error("\u5DF2\u53D6\u6D88\u914D\u7F6E"));
58
+ return;
59
+ }
60
+ if (key.name === "up" || key.name === "down") {
61
+ selected = moveSelection(selected, options.length, key.name === "up" ? -1 : 1);
62
+ render();
63
+ return;
64
+ }
65
+ if (key.name === "return" || key.name === "enter") {
66
+ const value = options[selected].value;
67
+ cleanup();
68
+ resolve(value);
69
+ }
70
+ };
71
+ input.on("keypress", onKeypress);
72
+ });
73
+ }
74
+
75
+ // src/cli.ts
76
+ var args = process.argv.slice(2);
77
+ var [command, sub] = args;
78
+ function takeFlag(name) {
79
+ const i = args.indexOf(name);
80
+ if (i === -1) return void 0;
81
+ return args[i + 1];
82
+ }
83
+ var configPath = takeFlag("--config");
84
+ function requireTty() {
85
+ if (!process.stdin.isTTY) {
86
+ console.error("\u8FD9\u662F\u4EA4\u4E92\u5F0F\u547D\u4EE4\uFF0C\u9700\u8981\u7EC8\u7AEF\u73AF\u5883\uFF08stdin \u4E0D\u662F TTY\uFF09\u3002");
87
+ console.error("\u975E\u4EA4\u4E92\u573A\u666F\u8BF7\u76F4\u63A5\u7F16\u8F91\u914D\u7F6E\u6587\u4EF6\uFF08xlyra config path \u67E5\u770B\u8DEF\u5F84\uFF09\uFF0C\u683C\u5F0F\u89C1 README\u3002");
88
+ process.exit(2);
89
+ }
90
+ }
91
+ async function ask(question, fallback) {
92
+ const hint = fallback ? `\uFF08\u9ED8\u8BA4 ${fallback}\uFF09` : "";
93
+ const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
94
+ try {
95
+ const answer = await new Promise(
96
+ (resolve) => rl.question(`${question}${hint}\uFF1A`, (a) => resolve(a.trim()))
97
+ );
98
+ return answer || fallback || "";
99
+ } finally {
100
+ rl.close();
101
+ }
102
+ }
103
+ async function askEndpoint(existing) {
104
+ console.log(existing ? `
105
+ \u66F4\u65B0\u7AEF\u70B9\u300C${existing.name}\u300D\uFF08\u56DE\u8F66\u4FDD\u7559\u539F\u503C\uFF09\uFF1A` : "\n\u914D\u7F6E\u6A21\u578B\u7AEF\u70B9\uFF1A");
106
+ const name = await ask(" \u7AEF\u70B9\u540D\uFF08\u5982 anthropic / openai\uFF09", existing?.name);
107
+ const protocol = await selectOption(
108
+ process.stdin,
109
+ process.stdout,
110
+ " \u534F\u8BAE\u7C7B\u578B",
111
+ [
112
+ {
113
+ label: "Anthropic Messages",
114
+ value: "anthropic-messages",
115
+ description: "Claude \u53CA\u517C\u5BB9\u7AEF\u70B9"
116
+ },
117
+ {
118
+ label: "OpenAI Responses",
119
+ value: "openai-responses",
120
+ description: "OpenAI \u53CA\u517C\u5BB9\u7AEF\u70B9"
121
+ }
122
+ ],
123
+ existing?.protocol ?? "anthropic-messages"
124
+ );
125
+ const base_url = await ask(
126
+ " Base URL",
127
+ existing?.base_url ?? (protocol === "anthropic-messages" ? "https://api.anthropic.com" : "https://api.openai.com/v1")
128
+ );
129
+ const api_key = await ask(
130
+ " API Key\uFF08\u53EF\u7528 ${ENV_VAR} \u5F15\u7528\u73AF\u5883\u53D8\u91CF\uFF09",
131
+ existing?.api_key
132
+ );
133
+ const default_model = await ask(" \u9ED8\u8BA4\u6A21\u578B id", existing?.default_model);
134
+ const modelsRaw = await ask(
135
+ " \u53EF\u7528\u6A21\u578B\uFF08\u9017\u53F7\u5206\u9694\u7684\u6A21\u578B id\uFF1B\u53EF\u7559\u7A7A\uFF09",
136
+ existing?.models ? Object.keys(existing.models).join(",") : default_model || void 0
137
+ );
138
+ const models = {};
139
+ for (const id of modelsRaw.split(",").map((s) => s.trim()).filter(Boolean)) {
140
+ models[id] = {};
141
+ }
142
+ if (default_model && !(default_model in models)) models[default_model] = {};
143
+ return {
144
+ name,
145
+ protocol,
146
+ base_url,
147
+ api_key,
148
+ ...Object.keys(models).length ? { models } : {},
149
+ ...default_model ? { default_model } : {}
150
+ };
151
+ }
152
+ async function configInit() {
153
+ requireTty();
154
+ const file = configPath ?? defaultConfigPath();
155
+ if (configExists(configPath)) {
156
+ console.error(`\u914D\u7F6E\u5DF2\u5B58\u5728\uFF1A${file}
157
+ \u5982\u9700\u4FEE\u6539\u7AEF\u70B9\uFF1Axlyra config add\uFF1B\u5220\u9664\uFF1Aconfig remove <name>`);
158
+ process.exit(1);
159
+ }
160
+ console.log(`\u521B\u5EFA xlyra-agent \u914D\u7F6E \u2192 ${file}`);
161
+ const endpoint = await askEndpoint();
162
+ const token = await ask("\n\u670D\u52A1\u8BBF\u95EE token\uFF08Bearer \u9274\u6743\uFF1B\u7559\u7A7A = \u4E0D\u9274\u6743\uFF0C\u4EC5\u9650\u672C\u673A\uFF09");
163
+ const portRaw = await ask("\u670D\u52A1\u7AEF\u53E3", "3210");
164
+ saveConfig(
165
+ {
166
+ endpoints: [endpoint],
167
+ server: {
168
+ port: Number(portRaw) || 3210,
169
+ ...token ? { token } : {}
170
+ }
171
+ },
172
+ file
173
+ );
174
+ console.log(`
175
+ \u2713 \u914D\u7F6E\u5DF2\u5199\u5165 ${file}`);
176
+ console.log("\u542F\u52A8\u670D\u52A1\uFF1Axlyra serve");
177
+ }
178
+ async function configAdd(name) {
179
+ requireTty();
180
+ const file = configPath ?? defaultConfigPath();
181
+ const existing = name && configExists(configPath) ? loadConfig(configPath).endpoints.find((e) => e.name === name) : void 0;
182
+ const endpoint = await askEndpoint(existing);
183
+ upsertEndpoint(endpoint, file);
184
+ console.log(`\u2713 \u7AEF\u70B9\u300C${endpoint.name}\u300D\u5DF2\u5199\u5165 ${file}`);
185
+ }
186
+ function configList() {
187
+ const file = configPath ?? defaultConfigPath();
188
+ if (!configExists(configPath)) {
189
+ console.error(`\u914D\u7F6E\u4E0D\u5B58\u5728\uFF1A${file}
190
+ \u5148\u8FD0\u884C xlyra config init`);
191
+ process.exit(1);
192
+ }
193
+ let raw;
194
+ try {
195
+ raw = JSON.parse(fs.readFileSync(file, "utf-8"));
196
+ } catch {
197
+ console.error(`\u914D\u7F6E\u6587\u4EF6\u4E0D\u662F\u5408\u6CD5 JSON\uFF1A${file}`);
198
+ process.exit(1);
199
+ }
200
+ const endpoints = raw.endpoints ?? [];
201
+ if (endpoints.length === 0) {
202
+ console.log("\uFF08\u914D\u7F6E\u4E2D\u6CA1\u6709\u7AEF\u70B9\uFF09");
203
+ return;
204
+ }
205
+ for (const e of endpoints) {
206
+ const key = e.api_key.length > 8 ? `${e.api_key.slice(0, 4)}\u2026${e.api_key.slice(-4)}` : "****";
207
+ const models = e.models ? Object.keys(e.models).join(", ") : "(\u672A\u58F0\u660E\uFF0C\u4EFB\u610F\u6A21\u578B id)";
208
+ console.log(`\u2022 ${e.name} [${e.protocol}]`);
209
+ console.log(` base_url ${e.base_url}`);
210
+ console.log(` api_key ${key}`);
211
+ console.log(` default ${e.default_model ?? "(\u672A\u8BBE\u7F6E)"}`);
212
+ console.log(` models ${models}`);
213
+ }
214
+ console.log(`
215
+ \u914D\u7F6E\u6587\u4EF6\uFF1A${file}`);
216
+ }
217
+ function configRemove(name) {
218
+ if (!name) {
219
+ console.error("\u7528\u6CD5\uFF1Axlyra config remove <\u7AEF\u70B9\u540D>");
220
+ process.exit(2);
221
+ }
222
+ try {
223
+ if (removeEndpoint(name, configPath ?? defaultConfigPath())) {
224
+ console.log(`\u2713 \u7AEF\u70B9\u300C${name}\u300D\u5DF2\u5220\u9664`);
225
+ } else {
226
+ console.error(`\u7AEF\u70B9\u4E0D\u5B58\u5728\uFF1A${name}`);
227
+ process.exit(1);
228
+ }
229
+ } catch (err) {
230
+ console.error(err instanceof Error ? err.message : String(err));
231
+ process.exit(1);
232
+ }
233
+ }
234
+ var USAGE = `\u7528\u6CD5\uFF1Axlyra <\u547D\u4EE4> [\u9009\u9879]
235
+
236
+ \u547D\u4EE4\uFF1A
237
+ serve [--port N] [--config \u8DEF\u5F84] \u542F\u52A8 Agent HTTP \u670D\u52A1\uFF08REST + SSE\uFF09
238
+ config init \u4EA4\u4E92\u5F0F\u521B\u5EFA\u914D\u7F6E\uFF08\u5F15\u5BFC\u8F93\u5165\u7B2C\u4E00\u4E2A\u7AEF\u70B9\uFF09
239
+ config list \u5217\u51FA\u5DF2\u914D\u7F6E\u7AEF\u70B9\uFF08api_key \u6253\u7801\uFF09
240
+ config add [name] \u65B0\u589E/\u66F4\u65B0\u6A21\u578B\u7AEF\u70B9\uFF08\u4EA4\u4E92\u5F0F\uFF1B\u540C\u540D\u8986\u76D6\uFF09
241
+ config remove <name> \u5220\u9664\u7AEF\u70B9
242
+ config path \u6253\u5370\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84
243
+
244
+ \u914D\u7F6E\u6587\u4EF6\u9ED8\u8BA4 ~/.xlyra-agent/config.json\u3002`;
245
+ async function main() {
246
+ if (command === "--help" || command === "-h" || command === "help") {
247
+ console.log(USAGE);
248
+ return;
249
+ }
250
+ if (command === "serve") {
251
+ const port = takeFlag("--port");
252
+ serveFromConfig({
253
+ ...configPath ? { configPath } : {},
254
+ ...port ? { port: Number(port) } : {}
255
+ });
256
+ return;
257
+ }
258
+ if (command === "config") {
259
+ switch (sub) {
260
+ case "init":
261
+ await configInit();
262
+ return;
263
+ case "list":
264
+ configList();
265
+ return;
266
+ case "add":
267
+ await configAdd(args[2] && !args[2].startsWith("-") ? args[2] : void 0);
268
+ return;
269
+ case "remove":
270
+ configRemove(args[2]);
271
+ return;
272
+ case "path":
273
+ console.log(configPath ?? defaultConfigPath());
274
+ return;
275
+ default:
276
+ console.error(USAGE);
277
+ process.exit(2);
278
+ }
279
+ }
280
+ console.error(USAGE);
281
+ process.exit(command ? 2 : 0);
282
+ }
283
+ main().catch((err) => {
284
+ console.error(err instanceof Error ? err.message : String(err));
285
+ process.exit(1);
286
+ });
287
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts","../src/cli-select.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * xlyra CLI(安装为系统命令 `xlyra`)。\n *\n * 子命令:\n * serve [--port N] [--config 路径] 启动 Agent HTTP 服务(REST + SSE)\n * config init 交互式创建配置(引导输入第一个端点)\n * config list 列出已配置端点(api_key 打码显示)\n * config add 新增或更新一个模型端点(按 name 覆盖)\n * config remove <name> 删除端点\n * config path 打印配置文件路径\n */\nimport fs from \"node:fs\";\nimport readline from \"node:readline\";\nimport {\n configExists,\n defaultConfigPath,\n loadConfig,\n removeEndpoint,\n saveConfig,\n upsertEndpoint,\n} from \"./server/config.js\";\nimport { serveFromConfig } from \"./server/index.js\";\nimport type { EndpointConfig } from \"./llm/router.js\";\nimport { selectOption } from \"./cli-select.js\";\n\nconst args = process.argv.slice(2);\nconst [command, sub] = args;\n\nfunction takeFlag(name: string): string | undefined {\n const i = args.indexOf(name);\n if (i === -1) return undefined;\n return args[i + 1];\n}\n\nconst configPath = takeFlag(\"--config\");\n\n// readline 在非 TTY(管道/CI)环境下会吞掉紧随其后的输入行:question 显示后\n// 剩余输入被直接消费而不触发回调(上面 unsettled top-level await 的来源)。\n// 交互式命令要求 TTY,提前给出明确指引而不是挂死。\nfunction requireTty(): void {\n if (!process.stdin.isTTY) {\n console.error(\"这是交互式命令,需要终端环境(stdin 不是 TTY)。\");\n console.error(\"非交互场景请直接编辑配置文件(xlyra config path 查看路径),格式见 README。\");\n process.exit(2);\n }\n}\n\n// ---------------------------------------------------------------------------\n// config 子命令\n// ---------------------------------------------------------------------------\n\n/** 交互式问答(readline 单行输入;支持默认值)。每题独立创建 Interface,\n * 避免它与方向键菜单同时消费 stdin。 */\nasync function ask(question: string, fallback?: string): Promise<string> {\n const hint = fallback ? `(默认 ${fallback})` : \"\";\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n try {\n const answer = await new Promise<string>((resolve) =>\n rl.question(`${question}${hint}:`, (a) => resolve(a.trim())),\n );\n return answer || fallback || \"\";\n } finally {\n rl.close();\n }\n}\n\nasync function askEndpoint(existing?: EndpointConfig): Promise<EndpointConfig> {\n console.log(existing ? `\\n更新端点「${existing.name}」(回车保留原值):` : \"\\n配置模型端点:\");\n const name = await ask(\" 端点名(如 anthropic / openai)\", existing?.name);\n const protocol = await selectOption(\n process.stdin,\n process.stdout,\n \" 协议类型\",\n [\n {\n label: \"Anthropic Messages\",\n value: \"anthropic-messages\" as const,\n description: \"Claude 及兼容端点\",\n },\n {\n label: \"OpenAI Responses\",\n value: \"openai-responses\" as const,\n description: \"OpenAI 及兼容端点\",\n },\n ],\n existing?.protocol ?? \"anthropic-messages\",\n );\n const base_url = await ask(\n \" Base URL\",\n existing?.base_url ??\n (protocol === \"anthropic-messages\" ? \"https://api.anthropic.com\" : \"https://api.openai.com/v1\"),\n );\n const api_key = await ask(\n \" API Key(可用 ${ENV_VAR} 引用环境变量)\",\n existing?.api_key,\n );\n const default_model = await ask(\" 默认模型 id\", existing?.default_model);\n const modelsRaw = await ask(\n \" 可用模型(逗号分隔的模型 id;可留空)\",\n existing?.models ? Object.keys(existing.models).join(\",\") : (default_model || undefined),\n );\n const models: Record<string, { context_window?: number }> = {};\n for (const id of modelsRaw.split(\",\").map((s) => s.trim()).filter(Boolean)) {\n models[id] = {};\n }\n if (default_model && !(default_model in models)) models[default_model] = {};\n return {\n name,\n protocol,\n base_url,\n api_key,\n ...(Object.keys(models).length ? { models } : {}),\n ...(default_model ? { default_model } : {}),\n };\n}\n\nasync function configInit(): Promise<void> {\n requireTty();\n const file = configPath ?? defaultConfigPath();\n if (configExists(configPath)) {\n console.error(`配置已存在:${file}\\n如需修改端点:xlyra config add;删除:config remove <name>`);\n process.exit(1);\n }\n console.log(`创建 xlyra-agent 配置 → ${file}`);\n const endpoint = await askEndpoint();\n const token = await ask(\"\\n服务访问 token(Bearer 鉴权;留空 = 不鉴权,仅限本机)\");\n const portRaw = await ask(\"服务端口\", \"3210\");\n saveConfig(\n {\n endpoints: [endpoint],\n server: {\n port: Number(portRaw) || 3210,\n ...(token ? { token } : {}),\n },\n },\n file,\n );\n console.log(`\\n✓ 配置已写入 ${file}`);\n console.log(\"启动服务:xlyra serve\");\n}\n\nasync function configAdd(name?: string): Promise<void> {\n requireTty();\n const file = configPath ?? defaultConfigPath();\n const existing = name && configExists(configPath)\n ? loadConfig(configPath).endpoints.find((e) => e.name === name)\n : undefined;\n const endpoint = await askEndpoint(existing);\n upsertEndpoint(endpoint, file);\n console.log(`✓ 端点「${endpoint.name}」已写入 ${file}`);\n}\n\nfunction configList(): void {\n const file = configPath ?? defaultConfigPath();\n if (!configExists(configPath)) {\n console.error(`配置不存在:${file}\\n先运行 xlyra config init`);\n process.exit(1);\n }\n // 列表展示用原始文件(不插值 api_key),避免把真实凭证打到终端\n let raw: { endpoints?: EndpointConfig[] };\n try {\n raw = JSON.parse(fs.readFileSync(file, \"utf-8\")) as typeof raw;\n } catch {\n console.error(`配置文件不是合法 JSON:${file}`);\n process.exit(1);\n }\n const endpoints = raw.endpoints ?? [];\n if (endpoints.length === 0) {\n console.log(\"(配置中没有端点)\");\n return;\n }\n for (const e of endpoints) {\n const key = e.api_key.length > 8 ? `${e.api_key.slice(0, 4)}…${e.api_key.slice(-4)}` : \"****\";\n const models = e.models ? Object.keys(e.models).join(\", \") : \"(未声明,任意模型 id)\";\n console.log(`• ${e.name} [${e.protocol}]`);\n console.log(` base_url ${e.base_url}`);\n console.log(` api_key ${key}`);\n console.log(` default ${e.default_model ?? \"(未设置)\"}`);\n console.log(` models ${models}`);\n }\n console.log(`\\n配置文件:${file}`);\n}\n\nfunction configRemove(name?: string): void {\n if (!name) {\n console.error(\"用法:xlyra config remove <端点名>\");\n process.exit(2);\n }\n try {\n if (removeEndpoint(name, configPath ?? defaultConfigPath())) {\n console.log(`✓ 端点「${name}」已删除`);\n } else {\n console.error(`端点不存在:${name}`);\n process.exit(1);\n }\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n}\n\n// ---------------------------------------------------------------------------\n\nconst USAGE = `用法:xlyra <命令> [选项]\n\n命令:\n serve [--port N] [--config 路径] 启动 Agent HTTP 服务(REST + SSE)\n config init 交互式创建配置(引导输入第一个端点)\n config list 列出已配置端点(api_key 打码)\n config add [name] 新增/更新模型端点(交互式;同名覆盖)\n config remove <name> 删除端点\n config path 打印配置文件路径\n\n配置文件默认 ~/.xlyra-agent/config.json。`;\n\nasync function main(): Promise<void> {\n if (command === \"--help\" || command === \"-h\" || command === \"help\") {\n console.log(USAGE);\n return;\n }\n if (command === \"serve\") {\n const port = takeFlag(\"--port\");\n serveFromConfig({\n ...(configPath ? { configPath } : {}),\n ...(port ? { port: Number(port) } : {}),\n });\n return;\n }\n if (command === \"config\") {\n switch (sub) {\n case \"init\":\n await configInit();\n return;\n case \"list\":\n configList();\n return;\n case \"add\":\n await configAdd(args[2] && !args[2].startsWith(\"-\") ? args[2] : undefined);\n return;\n case \"remove\":\n configRemove(args[2]);\n return;\n case \"path\":\n console.log(configPath ?? defaultConfigPath());\n return;\n default:\n console.error(USAGE);\n process.exit(2);\n }\n }\n console.error(USAGE);\n process.exit(command ? 2 : 0);\n}\n\nmain().catch((err) => {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n});\n","import readline from \"node:readline\";\n\nexport interface SelectOption<T> {\n label: string;\n value: T;\n description?: string;\n}\n\ninterface Keypress {\n name?: string;\n ctrl?: boolean;\n}\n\ninterface SelectInput extends NodeJS.ReadableStream {\n isRaw?: boolean;\n setRawMode?: (mode: boolean) => void;\n resume(): this;\n}\n\n/** 上下键使用循环选择,避免在列表首尾卡住。 */\nexport function moveSelection(index: number, count: number, direction: -1 | 1): number {\n if (count <= 0) throw new Error(\"选择列表不能为空\");\n return (index + direction + count) % count;\n}\n\n/** TTY 单选菜单:上下方向键移动,回车确认。 */\nexport async function selectOption<T>(\n input: SelectInput,\n output: NodeJS.WritableStream,\n question: string,\n options: SelectOption<T>[],\n initialValue?: T,\n): Promise<T> {\n if (options.length === 0) throw new Error(\"选择列表不能为空\");\n\n let selected = Math.max(0, options.findIndex((option) => option.value === initialValue));\n let firstRender = true;\n const wasRaw = input.isRaw === true;\n\n readline.emitKeypressEvents(input);\n if (!wasRaw) input.setRawMode?.(true);\n input.resume();\n output.write(`${question}(↑/↓ 选择,回车确认)\\n`);\n output.write(\"\\x1b[?25l\");\n\n const render = (): void => {\n if (!firstRender) readline.moveCursor(output, 0, -options.length);\n firstRender = false;\n for (const [index, option] of options.entries()) {\n readline.clearLine(output, 0);\n readline.cursorTo(output, 0);\n const marker = index === selected ? \"❯\" : \" \";\n const description = option.description ? ` ${option.description}` : \"\";\n output.write(`${marker} ${option.label}${description}\\n`);\n }\n readline.cursorTo(output, 0);\n };\n render();\n\n return new Promise<T>((resolve, reject) => {\n const cleanup = (): void => {\n input.removeListener(\"keypress\", onKeypress);\n if (!wasRaw) input.setRawMode?.(false);\n output.write(\"\\x1b[?25h\");\n };\n\n const onKeypress = (_text: string, key: Keypress): void => {\n if (key.ctrl && key.name === \"c\") {\n cleanup();\n output.write(\"\\n\");\n reject(new Error(\"已取消配置\"));\n return;\n }\n if (key.name === \"up\" || key.name === \"down\") {\n selected = moveSelection(selected, options.length, key.name === \"up\" ? -1 : 1);\n render();\n return;\n }\n if (key.name === \"return\" || key.name === \"enter\") {\n const value = options[selected]!.value;\n cleanup();\n resolve(value);\n }\n };\n\n input.on(\"keypress\", onKeypress);\n });\n}\n"],"mappings":"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","names":["readline","readline"]}