ultrastiching-cli 0.2.0 → 0.2.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
@@ -16,22 +16,22 @@ ultrastiching auth configure
16
16
  # 主动替换 Key,需确认:ultrastiching auth configure --replace
17
17
  ```
18
18
 
19
- `auth status` 只输出是否配置及来源。`auth sync` 由安装器自动调用:环境变量没有值时,从已有凭据文件同步到当前进程和持久环境。`auth configure` 已有 Key 时不重复输入;缺失时在本机终端隐藏输入一次,同时保存私有文件和持久环境。`--replace` 确认一次后更换两处的 Key。macOS 更新 Shell、launchd 环境和登录恢复任务;Windows 更新用户环境;Linux 更新 Shell 和用户 environment.d,有会话总线时同步服务启动环境。含 Key 的 POSIX 配置使用 0600,Windows 凭据文件使用私有 ACL。保存后完全退出并重启客户端。不要在 Agent 对话中填写 Key。`ultrastiching mcp headers` 只供 MCP 客户端调用,stdout 含鉴权头,Agent 不应执行或打印该命令的输出。
19
+ `auth status` 只输出是否配置及来源。`auth sync` 由安装器自动调用:环境变量没有值时,从已有凭据文件同步到当前进程和持久环境。`auth configure` 已有 Key 时不重复输入;缺失时在本机终端隐藏输入一次,同时保存私有文件和持久环境。`--replace` 确认一次后更换两处的 Key。macOS 确认 zsh 后只写 `.zshrc`(遵循用户已有的 `ZDOTDIR`),确认 bash 后只写 `.bashrc`,并同步当前用户会话环境,不创建登录启动项;无法确定 Shell 或配置目录时提示用户自行确认,不猜测文件位置。Windows 更新用户环境;Linux 使用相同的 Shell 默认规则并更新用户 environment.d,有会话总线时同步服务启动环境。含 Key 的 POSIX 配置使用 0600,Windows 凭据文件使用私有 ACL。保存后完全退出并重启客户端。macOS 注销或重启电脑后,若桌面启动的客户端缺少变量,在登录终端运行 `ultrastiching auth sync --from-file` 再重新打开客户端即可,无需重新输入 Key。不要在 Agent 对话中填写 Key。`ultrastiching mcp headers` 只供 MCP 客户端调用,stdout 含鉴权头,Agent 不应执行或打印该命令的输出。
20
20
 
21
- 本版为 0.2.0,支持共享凭据配置和安装器自动同步。
21
+ 本版为 0.2.1,支持共享凭据配置和安装器自动同步;输入 API Key 时回显星号,退格同步擦除,实际 Key 不回显。
22
22
 
23
23
  ## 安装与离线发现
24
24
 
25
25
  从 npm 安装指定版本:
26
26
 
27
27
  ```bash
28
- npm install -g ultrastiching-cli@0.2.0
28
+ npm install -g ultrastiching-cli@0.2.1
29
29
  ultrastiching --help
30
30
  ultrastiching list --search 交易日历
31
31
  ultrastiching info plasma_trade_cal
32
32
  ```
33
33
 
34
- `list` 和 `info` 使用随包携带的目录,不需要 API Key,不发送业务请求。也可安装维护者提供的包:`npm install -g ./ultrastiching-cli-0.2.0.tgz`。源码环境使用下面的安装方式:
34
+ `list` 和 `info` 使用随包携带的目录,不需要 API Key,不发送业务请求。也可安装维护者提供的包:`npm install -g ./ultrastiching-cli-0.2.1.tgz`。源码环境使用下面的安装方式:
35
35
 
36
36
  ```bash
37
37
  cd meridian-cli
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultrastiching-cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "UltraStiching data API command line client",
5
5
  "type": "module",
6
6
  "bin": {
package/src/auth.js CHANGED
@@ -33,14 +33,14 @@ export async function configureCredential({
33
33
  } = {}) {
34
34
  const current = configuredCredential(env, home);
35
35
  if (current.key && !replace) {
36
- log('已配置 API Key,直接复用,无需再次输入。');
37
- return syncCredentialEnvironment({ env, home, log, environmentOptions });
36
+ const result = syncCredentialEnvironment({ env, home, log: () => {}, environmentOptions });
37
+ log('已成功设置 Ultrastiching 的 API Key');
38
+ return result;
38
39
  }
39
40
  const file = readCredentialFile(home);
40
41
  const target = credentialsPath(home);
41
42
  const expected = inspectEnvironment({ ...environmentOptions, env, home });
42
- if (current.key && !await confirm(`将替换现有 API Key,同时更新 ${target} 和用户持久环境中的 ${ENV_KEY},是否继续?`)) return { configured: true, source: current.source, cancelled: true };
43
- log(`API Key 将明文保存在本机私有文件 ${target} 和用户持久环境配置;不要粘贴到 Agent 对话中。`);
43
+ if (current.key && !await confirm('将替换现有的 Ultrastiching API Key,是否继续?')) return { configured: true, source: current.source, cancelled: true };
44
44
  const key = await readKey();
45
45
  saveCredential(key, { home, expectedDigest: file?.digest });
46
46
  let result;
@@ -51,7 +51,7 @@ export async function configureCredential({
51
51
  error.message = `凭据文件已保存,但环境同步未完成;修复下述错误后重新运行 ultrastiching auth sync --from-file,无需重新输入 Key。${error.message}`;
52
52
  throw error;
53
53
  }
54
- log('API Key 已同步保存到凭据文件和持久环境;请完全退出并重启客户端。');
54
+ log('已成功设置 Ultrastiching 的 API Key');
55
55
  return { ...credentialStatus({ env, home }), ...result };
56
56
  }
57
57
 
@@ -9,7 +9,20 @@ const START = '# >>> UltraStiching environment >>>';
9
9
  const END = '# <<< UltraStiching environment <<<';
10
10
  const digest = content => createHash('sha256').update(content).digest('hex');
11
11
  const quote = value => "'" + value.replaceAll("'", "'\\''") + "'";
12
- const xml = value => value.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;').replaceAll("'", '&apos;');
12
+
13
+ /** 需要用户确认配置入口;命令显示简短提示,调用方仍能取得判断失败的原因。 */
14
+ export class ShellConfigurationError extends Error {
15
+ constructor(code, details) {
16
+ super('无法确定 Shell 配置文件,请自行确认后设置环境变量。');
17
+ this.name = 'ShellConfigurationError';
18
+ this.code = code;
19
+ this.details = details;
20
+ }
21
+
22
+ toDiagnostic() {
23
+ return { type: this.name, code: this.code, message: this.message, details: this.details };
24
+ }
25
+ }
13
26
 
14
27
  /** 只接受当前用户拥有的普通配置文件;读取用于保留原文和复核并发修改,不执行内容。 */
15
28
  function snapshot(path) {
@@ -49,20 +62,17 @@ function shellContent(content, key) {
49
62
  export function inspectEnvironment({ home = homedir(), platform = process.platform, env = process.env } = {}) {
50
63
  if (platform === 'win32') return [];
51
64
  if (!['darwin', 'linux'].includes(platform)) throw new Error(`暂不支持 ${platform} 的环境变量持久化。`);
52
- const shell = (env.SHELL ?? '').split('/').at(-1) || (platform === 'darwin' ? 'zsh' : 'bash');
53
- if (!['bash', 'zsh', 'sh', 'dash'].includes(shell)) throw new Error(`暂不支持 ${shell} 的启动文件,请先确认该 Shell 的持久化方式。`);
54
- const paths = [join(home, '.profile')];
55
- if (shell === 'zsh') paths.push(join(env.ZDOTDIR && isAbsolute(env.ZDOTDIR) ? env.ZDOTDIR : home, '.zshrc'), join(env.ZDOTDIR && isAbsolute(env.ZDOTDIR) ? env.ZDOTDIR : home, '.zprofile'));
56
- if (shell === 'bash') {
57
- paths.push(join(home, '.bashrc'));
58
- // Bash 只加载第一个存在的登录文件;不创建优先级更高的新文件来遮住用户原配置。
59
- for (const name of ['.bash_profile', '.bash_login']) {
60
- try { lstatSync(join(home, name)); paths.push(join(home, name)); break; }
61
- catch (error) { if (error.code !== 'ENOENT') throw error; }
62
- }
65
+ const shell = (env.SHELL ?? '').split('/').at(-1);
66
+ if (!['bash', 'zsh'].includes(shell)) {
67
+ throw new ShellConfigurationError(shell ? 'shell_unsupported' : 'shell_unknown', { shell: env.SHELL ?? null });
68
+ }
69
+ let directory = home;
70
+ if (shell === 'zsh' && env.ZDOTDIR !== undefined) {
71
+ if (!isAbsolute(env.ZDOTDIR)) throw new ShellConfigurationError('shell_directory_unknown', { shell: env.SHELL, directory: env.ZDOTDIR });
72
+ directory = env.ZDOTDIR;
63
73
  }
64
- if (platform === 'darwin') paths.push(join(home, 'Library', 'LaunchAgents', 'com.ultrastiching.environment.plist'));
65
- else paths.push(join(env.XDG_CONFIG_HOME && isAbsolute(env.XDG_CONFIG_HOME) ? env.XDG_CONFIG_HOME : join(home, '.config'), 'environment.d', '90-ultrastiching.conf'));
74
+ const paths = [join(directory, shell === 'zsh' ? '.zshrc' : '.bashrc')];
75
+ if (platform === 'linux') paths.push(join(env.XDG_CONFIG_HOME && isAbsolute(env.XDG_CONFIG_HOME) ? env.XDG_CONFIG_HOME : join(home, '.config'), 'environment.d', '90-ultrastiching.conf'));
66
76
  return [...new Set(paths)].map(snapshot);
67
77
  }
68
78
 
@@ -79,7 +89,7 @@ function nativeCommand(command, args, key, options, execute) {
79
89
  }
80
90
  }
81
91
 
82
- /** 更新系统启动环境与本进程;已运行的客户端需重启才能继承新值。 */
92
+ /** 更新当前用户会话与本进程的环境;已运行的客户端需重启才能继承新值。 */
83
93
  export function updateSessionEnvironment(key, { platform = process.platform, env = process.env, execute = execFileSync } = {}) {
84
94
  requireKey(key);
85
95
  const next = { ...env, [ENV_KEY]: key };
@@ -94,7 +104,7 @@ export function updateSessionEnvironment(key, { platform = process.platform, env
94
104
  env[ENV_KEY] = key;
95
105
  }
96
106
 
97
- /** 保存 Shell 与系统持久环境,macOS 登录任务只调用原生 setenv,不转接 MCP。 */
107
+ /** 保存一个 Shell 交互配置;macOS 只同步当前会话,不创建登录任务。 */
98
108
  export function persistEnvironment(key, {
99
109
  home = homedir(), platform = process.platform, env = process.env, execute = execFileSync,
100
110
  expected = inspectEnvironment({ home, platform, env }),
@@ -102,10 +112,7 @@ export function persistEnvironment(key, {
102
112
  requireKey(key);
103
113
  const prepared = expected.map(entry => {
104
114
  let content;
105
- if (entry.path.endsWith('.plist')) {
106
- const args = ['/bin/launchctl', 'setenv', ENV_KEY, key].map(value => ` <string>${xml(value)}</string>`).join('\n');
107
- content = `<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0"><dict>\n <key>Label</key><string>com.ultrastiching.environment</string>\n <key>ProgramArguments</key><array>\n${args}\n </array>\n <key>RunAtLoad</key><true/>\n</dict></plist>\n`;
108
- } else if (entry.path.endsWith('.conf')) {
115
+ if (entry.path.endsWith('.conf')) {
109
116
  // systemd environment.d 用 $$ 表示字面美元符号;其他字符按双引号规则转义。
110
117
  const value = key.replaceAll('\\', '\\\\').replaceAll('"', '\\"').replaceAll('`', '\\`').replaceAll('$', () => '$$');
111
118
  content = `${ENV_KEY}="${value}"\n`;
package/src/main.js CHANGED
@@ -6,6 +6,7 @@ import { callByName, redactKey } from './http.js';
6
6
  import { buildPayload, endpoint, loadRegistry, parseParameter, searchEndpoints } from './registry.js';
7
7
  import { streamNewsflash } from './websocket.js';
8
8
  import { credentialStatus, configureCredential, syncCredentialEnvironment, mcpHeaders } from './auth.js';
9
+ import { ShellConfigurationError } from './environment.js';
9
10
  import { readFileSync } from 'node:fs';
10
11
 
11
12
  const VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
@@ -84,12 +85,19 @@ export async function main(argv, { write = (line) => process.stdout.write(line),
84
85
  if (command === 'auth') {
85
86
  if (positional.length !== 2) throw new Error('Use ultrastiching auth status|sync|configure');
86
87
  assertFlags(flags, new Set(name === 'configure' ? ['replace', 'compact'] : name === 'sync' ? ['from_file', 'compact'] : ['compact']));
87
- if (name === 'configure') {
88
- if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error('请在交互式终端运行 ultrastiching auth configure;Key 不能从参数或管道传入。');
89
- emit(write, await configureCredential({ replace: !!flags.replace }), flags.compact);
90
- } else if (name === 'status') emit(write, credentialStatus(), flags.compact);
91
- else if (name === 'sync') emit(write, syncCredentialEnvironment({ fromFile: !!flags.from_file }), flags.compact);
92
- else throw new Error('Use ultrastiching auth status|sync|configure');
88
+ try {
89
+ if (name === 'configure') {
90
+ if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error('请在交互式终端运行 ultrastiching auth configure;Key 不能从参数或管道传入。');
91
+ const result = await configureCredential({ replace: !!flags.replace, log: line => write(`${line}\n`) });
92
+ if (result.cancelled) write('已取消,未修改 API Key。\n');
93
+ } else if (name === 'status') emit(write, credentialStatus(), flags.compact);
94
+ else if (name === 'sync') emit(write, syncCredentialEnvironment({ fromFile: !!flags.from_file }), flags.compact);
95
+ else throw new Error('Use ultrastiching auth status|sync|configure');
96
+ } catch (error) {
97
+ if (!(error instanceof ShellConfigurationError)) throw error;
98
+ write(`${error.message}\n`);
99
+ return { exitCode: 1 };
100
+ }
93
101
  return;
94
102
  }
95
103
  if (command === 'mcp') {
package/src/prompt.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import { emitKeypressEvents } from "node:readline";
2
2
 
3
- /** 本机输入隐藏 Key;取消时恢复终端状态,凭据不进入参数或 Shell 历史。 */
3
+ /**
4
+ * 本机输入 Key 时仅回显星号,让输入和粘贴都有可见反馈,退格同步擦除一个星号。
5
+ * 确认或取消时移除输入监听并恢复终端状态;真实凭据不回显,也不进入参数或 Shell 历史。
6
+ */
4
7
  export function hiddenKey(input = process.stdin, output = process.stdout) {
5
8
  emitKeypressEvents(input);
6
9
  const previousRaw = input.isRaw;
@@ -15,11 +18,17 @@ export function hiddenKey(input = process.stdin, output = process.stdout) {
15
18
  const onKey = (text, key = {}) => {
16
19
  if (key.ctrl && key.name === "c") return finish(new Error("已取消,未保存。"));
17
20
  if (key.name === "return" || key.name === "enter") return finish();
18
- if (key.name === "backspace") value = Array.from(value).slice(0, -1).join("");
19
- else if (text && !key.ctrl && !key.meta && !/[\x00-\x1f\x7f]/u.test(text)) value += text;
21
+ if (key.name === "backspace") {
22
+ if (!value) return;
23
+ value = Array.from(value).slice(0, -1).join("");
24
+ output.write("\b \b");
25
+ } else if (text && !key.ctrl && !key.meta && !/[\x00-\x1f\x7f]/u.test(text)) {
26
+ value += text;
27
+ output.write("*".repeat(Array.from(text).length));
28
+ }
20
29
  };
21
30
  input.on("keypress", onKey);
22
- output.write("粘贴 ultrastiching 数据 API Key(输入隐藏,Enter 确认,Ctrl+C 取消):");
31
+ output.write("请粘贴 Ultrastiching 的 API Key(Enter 确认,Ctrl+C 取消)");
23
32
  });
24
33
  }
25
34