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 +4 -4
- package/package.json +1 -1
- package/src/auth.js +5 -5
- package/src/environment.js +27 -20
- package/src/main.js +14 -6
- package/src/prompt.js +13 -4
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
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(
|
|
37
|
-
|
|
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(
|
|
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
|
|
package/src/environment.js
CHANGED
|
@@ -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
|
-
|
|
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)
|
|
53
|
-
if (!['bash', 'zsh'
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
65
|
-
|
|
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
|
-
/**
|
|
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('.
|
|
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
|
-
|
|
88
|
-
if (
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
/**
|
|
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")
|
|
19
|
-
|
|
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("
|
|
31
|
+
output.write("请粘贴 Ultrastiching 的 API Key(Enter 确认,Ctrl+C 取消)");
|
|
23
32
|
});
|
|
24
33
|
}
|
|
25
34
|
|