@wax0629/pi-manager 0.1.0 → 0.1.2

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 CHANGED
@@ -37,6 +37,8 @@ pi-manager
37
37
 
38
38
  会起 `http://127.0.0.1:8670` 并打开浏览器。`pi-manager --help` 看 `--port` / `--host` / `--no-open`。端口已被占用时,会打开已有实例,不报崩。
39
39
 
40
+ 界面语言跟浏览器走(`zh*` 中文,否则英文),侧栏可切换,选择会记住。CLI 文案跟 `PI_MANAGER_LANG` / `LANG` 走,默认英文。
41
+
40
42
  从源码跑(开发):
41
43
 
42
44
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wax0629/pi-manager",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Local control plane for Pi: providers, models, and live import into ~/.pi/agent.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.mjs CHANGED
@@ -4,7 +4,9 @@ import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
 
7
- const HELP = `Pi Manager — 本地控制面
7
+ const CLI_MESSAGES = {
8
+ zh: {
9
+ help: `Pi Manager — 本地控制面
8
10
 
9
11
  用法:
10
12
  pi-manager [选项]
@@ -16,9 +18,65 @@ const HELP = `Pi Manager — 本地控制面
16
18
  -h, --help 显示帮助
17
19
 
18
20
  缺 web/dist 时先运行: npm --prefix web run build
19
- `;
21
+ `,
22
+ unknownArg: "未知参数: {token}",
23
+ needValue: "{flag} 需要一个值",
24
+ invalidPort: "无效端口: {value}",
25
+ missingDist: "缺少 web/dist/index.html。先运行: npm --prefix web run build",
26
+ alreadyRunning: "Pi Manager 已在运行: {url}",
27
+ startFailed: "Pi Manager 启动失败: {error}",
28
+ cannotOpen: "无法打开浏览器: {error}",
29
+ openManual: "请手动打开 {url}"
30
+ },
31
+ en: {
32
+ help: `Pi Manager — local control plane
33
+
34
+ Usage:
35
+ pi-manager [options]
36
+
37
+ Options:
38
+ --host <address> listen address (default 127.0.0.1, or PI_MANAGER_HOST)
39
+ --port <port> listen port (default 8670, or PI_MANAGER_PORT)
40
+ --no-open do not open a browser
41
+ -h, --help show help
42
+
43
+ If web/dist is missing, run: npm --prefix web run build
44
+ `,
45
+ unknownArg: "Unknown argument: {token}",
46
+ needValue: "{flag} requires a value",
47
+ invalidPort: "Invalid port: {value}",
48
+ missingDist: "Missing web/dist/index.html. Run: npm --prefix web run build",
49
+ alreadyRunning: "Pi Manager is already running at {url}",
50
+ startFailed: "Pi Manager failed to start: {error}",
51
+ cannotOpen: "Could not open the browser: {error}",
52
+ openManual: "Open {url} manually"
53
+ }
54
+ };
55
+
56
+ export function cliLocale(env = process.env) {
57
+ const explicit = String(env.PI_MANAGER_LANG || "").trim().toLowerCase();
58
+ if (explicit === "zh" || explicit.startsWith("zh")) return "zh";
59
+ if (explicit === "en" || explicit.startsWith("en")) return "en";
60
+ const lang = String(env.LANG || env.LC_ALL || env.LC_MESSAGES || "").toLowerCase();
61
+ return lang.startsWith("zh") ? "zh" : "en";
62
+ }
63
+
64
+ function formatCliMessage(template, params) {
65
+ if (!params) return template;
66
+ return template.replace(/\{(\w+)\}/g, (match, name) => (
67
+ Object.prototype.hasOwnProperty.call(params, name) ? String(params[name]) : match
68
+ ));
69
+ }
70
+
71
+ export function cliMessage(key, params, env = process.env) {
72
+ const locale = cliLocale(env);
73
+ const table = CLI_MESSAGES[locale] || CLI_MESSAGES.en;
74
+ return formatCliMessage(table[key] || CLI_MESSAGES.en[key] || key, params);
75
+ }
20
76
 
21
- export function parseCliArgs(argv) {
77
+ const HELP = CLI_MESSAGES.en.help;
78
+
79
+ export function parseCliArgs(argv, env = process.env) {
22
80
  const args = { host: undefined, port: undefined, open: true, help: false };
23
81
  for (let i = 0; i < argv.length; i += 1) {
24
82
  const token = argv[i];
@@ -31,7 +89,7 @@ export function parseCliArgs(argv) {
31
89
  continue;
32
90
  }
33
91
  if (token === "--host") {
34
- args.host = requireValue(argv, ++i, "--host");
92
+ args.host = requireValue(argv, ++i, "--host", env);
35
93
  continue;
36
94
  }
37
95
  if (token.startsWith("--host=")) {
@@ -39,28 +97,28 @@ export function parseCliArgs(argv) {
39
97
  continue;
40
98
  }
41
99
  if (token === "--port" || token === "-p") {
42
- args.port = parsePort(requireValue(argv, ++i, token));
100
+ args.port = parsePort(requireValue(argv, ++i, token, env), env);
43
101
  continue;
44
102
  }
45
103
  if (token.startsWith("--port=")) {
46
- args.port = parsePort(token.slice("--port=".length));
104
+ args.port = parsePort(token.slice("--port=".length), env);
47
105
  continue;
48
106
  }
49
- throw new Error(`未知参数: ${token}`);
107
+ throw new Error(cliMessage("unknownArg", { token }, env));
50
108
  }
51
109
  return args;
52
110
  }
53
111
 
54
- function requireValue(argv, index, flag) {
112
+ function requireValue(argv, index, flag, env = process.env) {
55
113
  const value = argv[index];
56
- if (!value || value.startsWith("-")) throw new Error(`${flag} 需要一个值`);
114
+ if (!value || value.startsWith("-")) throw new Error(cliMessage("needValue", { flag }, env));
57
115
  return value;
58
116
  }
59
117
 
60
- function parsePort(value) {
118
+ function parsePort(value, env = process.env) {
61
119
  const port = Number(value);
62
120
  if (!Number.isInteger(port) || port < 1 || port > 65535) {
63
- throw new Error(`无效端口: ${value}`);
121
+ throw new Error(cliMessage("invalidPort", { value }, env));
64
122
  }
65
123
  return port;
66
124
  }
@@ -97,7 +155,7 @@ async function main(argv = process.argv.slice(2)) {
97
155
  return 1;
98
156
  }
99
157
  if (args.help) {
100
- process.stdout.write(HELP);
158
+ process.stdout.write(cliMessage("help"));
101
159
  return 0;
102
160
  }
103
161
  if (args.host) process.env.PI_MANAGER_HOST = args.host;
@@ -105,7 +163,7 @@ async function main(argv = process.argv.slice(2)) {
105
163
 
106
164
  const { startManagerServer, publicDir, store } = await import("./server.mjs");
107
165
  if (!hasWebUi(publicDir)) {
108
- console.error("缺少 web/dist/index.html。先运行: npm --prefix web run build");
166
+ console.error(cliMessage("missingDist"));
109
167
  process.exitCode = 1;
110
168
  return 1;
111
169
  }
@@ -122,9 +180,9 @@ async function main(argv = process.argv.slice(2)) {
122
180
  console.log(`Pi gateway: http://${gateway.host}:${gateway.port}/v1`);
123
181
  } catch (error) {
124
182
  if (error && error.code === "EADDRINUSE") {
125
- console.log(`Pi Manager 已在运行: ${url}`);
183
+ console.log(cliMessage("alreadyRunning", { url }));
126
184
  } else {
127
- console.error(`Pi Manager 启动失败: ${error instanceof Error ? error.message : String(error)}`);
185
+ console.error(cliMessage("startFailed", { error: error instanceof Error ? error.message : String(error) }));
128
186
  process.exitCode = 1;
129
187
  return 1;
130
188
  }
@@ -134,8 +192,8 @@ async function main(argv = process.argv.slice(2)) {
134
192
  try {
135
193
  await openBrowser(url);
136
194
  } catch (error) {
137
- console.error(`无法打开浏览器: ${error instanceof Error ? error.message : String(error)}`);
138
- console.error(`请手动打开 ${url}`);
195
+ console.error(cliMessage("cannotOpen", { error: error instanceof Error ? error.message : String(error) }));
196
+ console.error(cliMessage("openManual", { url }));
139
197
  }
140
198
  }
141
199
 
@@ -144,12 +202,20 @@ async function main(argv = process.argv.slice(2)) {
144
202
  return 0;
145
203
  }
146
204
 
147
- const isDirectRun = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
148
- if (isDirectRun) {
205
+ export function isCliEntrypoint(argv1 = process.argv[1], moduleUrl = import.meta.url) {
206
+ if (!argv1) return false;
207
+ try {
208
+ return fs.realpathSync(argv1) === fs.realpathSync(fileURLToPath(moduleUrl));
209
+ } catch {
210
+ return path.resolve(argv1) === fileURLToPath(moduleUrl);
211
+ }
212
+ }
213
+
214
+ if (isCliEntrypoint()) {
149
215
  main().catch((error) => {
150
216
  console.error(error instanceof Error ? error.message : String(error));
151
217
  process.exitCode = 1;
152
218
  });
153
219
  }
154
220
 
155
- export { main, HELP };
221
+ export { main, HELP, CLI_MESSAGES };