@share-crm/sharecrm-cli 1.1.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.
Files changed (49) hide show
  1. package/README.md +293 -0
  2. package/dist/cli/parser.js +79 -0
  3. package/dist/cli/root.js +63 -0
  4. package/dist/cli/router.js +32 -0
  5. package/dist/commands/auth/login.js +34 -0
  6. package/dist/commands/auth/logout.js +12 -0
  7. package/dist/commands/auth/status.js +41 -0
  8. package/dist/commands/auth/token.js +70 -0
  9. package/dist/commands/config/init.js +28 -0
  10. package/dist/commands/help/help.js +174 -0
  11. package/dist/commands/remote/execute.js +131 -0
  12. package/dist/core/auth/authTypes.js +2 -0
  13. package/dist/core/auth/deviceFlow.js +77 -0
  14. package/dist/core/auth/tokenManager.js +45 -0
  15. package/dist/core/cache/cacheTypes.js +2 -0
  16. package/dist/core/cache/commandCache.js +24 -0
  17. package/dist/core/config/authBaseUrl.js +11 -0
  18. package/dist/core/config/envPersistence.js +59 -0
  19. package/dist/core/config/interactive.js +60 -0
  20. package/dist/core/config/locale.js +9 -0
  21. package/dist/core/debug/debugOutput.js +18 -0
  22. package/dist/core/debug/runtimeDebug.js +19 -0
  23. package/dist/core/http/apiClient.js +320 -0
  24. package/dist/core/http/requestTypes.js +2 -0
  25. package/dist/core/output/errors.js +44 -0
  26. package/dist/core/output/stderr.js +6 -0
  27. package/dist/core/output/stdout.js +6 -0
  28. package/dist/core/state/authSessionStore.js +129 -0
  29. package/dist/core/state/authSessionTypes.js +2 -0
  30. package/dist/core/state/configStore.js +65 -0
  31. package/dist/core/state/fileLock.js +66 -0
  32. package/dist/core/state/legacySessionMigration.js +109 -0
  33. package/dist/core/state/paths.js +40 -0
  34. package/dist/core/state/secretStore/commonFileCrypto.js +61 -0
  35. package/dist/core/state/secretStore/index.js +28 -0
  36. package/dist/core/state/secretStore/secretStore.darwin.js +139 -0
  37. package/dist/core/state/secretStore/secretStore.linux.js +90 -0
  38. package/dist/core/state/secretStore/secretStore.unsupported.js +17 -0
  39. package/dist/core/state/secretStore/secretStore.win32.js +162 -0
  40. package/dist/core/state/secretStore/types.js +2 -0
  41. package/dist/core/state/sessionMetaStore.js +24 -0
  42. package/dist/core/state/sessionStore.js +23 -0
  43. package/dist/index.js +49 -0
  44. package/dist/shared/constants.js +13 -0
  45. package/dist/shared/env.js +69 -0
  46. package/dist/shared/generatedConfig.js +9 -0
  47. package/dist/shared/utils.js +14 -0
  48. package/dist/types/command.js +2 -0
  49. package/package.json +40 -0
package/README.md ADDED
@@ -0,0 +1,293 @@
1
+ # sharecrm CLI Client
2
+
3
+ `fs-cli-client` 是 `sharecrm` 命令行客户端的源码目录,用于访问远程命令能力,支持 OAuth 设备码登录、远程命令执行、命令帮助查询和请求级调试输出。
4
+
5
+ - Node.js: `>=18.17`
6
+ - npm package: `sharecrm`
7
+ - CLI 命令名: `sharecrm`
8
+
9
+ ## 功能概览
10
+
11
+ - OAuth 设备码登录
12
+ - 本地登录状态查询与退出登录
13
+ - 远程命令执行
14
+ - 本地帮助和远程命令帮助
15
+ - `--debug` 调试输出
16
+ - 会话元数据与密钥分离存储
17
+
18
+ ## 平台支持
19
+
20
+ 当前代码按平台选择不同的本地 secret store 后端:
21
+
22
+ | 平台 | 默认配置目录 | secret store |
23
+ | --- | --- | --- |
24
+ | macOS | `~/.sharecrm-cli` | Keychain 保存主密钥,本地文件保存加密后的 secret |
25
+ | Linux | `~/.sharecrm-cli` | 本地 `keychain/` 目录保存主密钥和加密后的 secret |
26
+ | Windows | `%USERPROFILE%\\.sharecrm-cli` | PowerShell + DPAPI + `HKCU:\\Software\\sharecrm-cli\\keychain` |
27
+
28
+ 说明:
29
+
30
+ - 所有平台都会在配置目录下保存 `config.json`、会话锁文件和命令缓存。
31
+ - Windows 运行依赖 `powershell.exe`。
32
+ - `FS_CLI_CONFIG_DIR` 可覆盖默认配置目录。
33
+
34
+ ## 安装
35
+
36
+ ### 本地开发安装
37
+
38
+ ```bash
39
+ npm install
40
+ npm run build
41
+ npm link
42
+ ```
43
+
44
+ ### npm命令安装
45
+ ```bash
46
+ npm install -g sharecrm@1.1.6
47
+ ```
48
+ 安装完成后可直接执行:
49
+
50
+ ```bash
51
+ sharecrm --help
52
+ ```
53
+
54
+ ### 不全局链接的运行方式
55
+
56
+ 构建后直接运行:
57
+
58
+ ```bash
59
+ node dist/index.js --help
60
+ ```
61
+
62
+ 或直接从源码调试启动:
63
+
64
+ ```bash
65
+ npm run build:debug -- --help
66
+ ```
67
+
68
+ ## 配置
69
+
70
+ 通过环境变量配置认证地址、业务接口地址和本地状态目录:
71
+
72
+ | 变量名 | 是否必需 | 默认值 | 说明 |
73
+ | --- |-----------| --- | --- |
74
+ | `FS_CLI_AUTH_BASE_URL` | 认证相关命令必需 | 构建产物中的运行时配置 | OAuth 设备码登录相关接口地址,必须为 `https://` |
75
+ | `FS_CLI_API_BASE_URL` | 否 | 构建产物中的运行时配置 | 远程命令接口地址,必须为 `https://`。登录成功后如服务端返回 `apiUrl`,优先使用登录态中的地址 |
76
+ | `FS_CLI_CONFIG_DIR` | 否 | `<homeDir>/.sharecrm-cli` | 本地配置、会话与缓存目录 |
77
+ | `FS_CLI_DEFAULT_CLIENT_ID` | 否 | 构建产物中的默认值 | OAuth 设备码登录使用的客户端 ID |
78
+
79
+ `homeDir` 的解析规则:
80
+
81
+ - macOS / Linux: 优先使用 `os.homedir()`,为空时回退到 `HOME`
82
+ - Windows: 优先使用 `os.homedir()`,为空时回退到 `USERPROFILE`,再回退到 `HOMEDRIVE + HOMEPATH`
83
+
84
+ ## 快速开始
85
+
86
+ ### 1. 设置环境变量
87
+
88
+ macOS / Linux:
89
+
90
+ ```bash
91
+ export FS_CLI_AUTH_BASE_URL="https://your-auth.example.com"
92
+ export FS_CLI_API_BASE_URL="https://your-api.example.com/cli/"
93
+ ```
94
+
95
+ Windows PowerShell:
96
+
97
+ ```powershell
98
+ $env:FS_CLI_AUTH_BASE_URL = "https://your-auth.example.com"
99
+ $env:FS_CLI_API_BASE_URL = "https://your-api.example.com/cli/"
100
+ ```
101
+
102
+ ### 2. 登录授权
103
+
104
+ ```bash
105
+ sharecrm auth login
106
+ ```
107
+
108
+ 执行后 CLI 会输出授权地址、设备验证码,并在授权完成后输出登录结果 JSON。例如:
109
+
110
+ ```text
111
+ 在浏览器中打开以下链接进行认证:
112
+ https://your-auth.example.com/device
113
+ 设备验证码: ABCD-EFGH
114
+ 等待用户授权中...
115
+ {
116
+ "success": true,
117
+ "userId": "user-1",
118
+ "appId": "FSAID_xxx",
119
+ "scope": [
120
+ "crm"
121
+ ]
122
+ }
123
+ ```
124
+
125
+ ### 3. 查看帮助
126
+
127
+ ```bash
128
+ sharecrm --help
129
+ sharecrm help
130
+ sharecrm help crm
131
+ sharecrm crm QueryRecords --help
132
+ ```
133
+
134
+ 说明:
135
+
136
+ - 无登录态时,`sharecrm` 和 `sharecrm help` 仍会输出本地帮助
137
+ - 已登录时,`sharecrm help <command...>` 会继续请求远程命令清单
138
+
139
+ ### 4. 执行远程命令
140
+
141
+ ```bash
142
+ sharecrm knowledge search WebSearchAction -d '{"query":"北京有什么好玩的"}'
143
+ ```
144
+
145
+ 也可以把 JSON 放在命令最后一个参数:
146
+
147
+ ```bash
148
+ sharecrm knowledge search WebSearchAction '{"query":"北京有什么好玩的"}'
149
+ ```
150
+
151
+ 如果未传 `-d` / `--data` 且最后一个参数也不是 JSON,CLI 会默认发送空对象 `{}`。
152
+
153
+ ## 命令说明
154
+
155
+ ### 本地命令
156
+
157
+ ```bash
158
+ sharecrm auth login
159
+ sharecrm auth status
160
+ sharecrm auth logout
161
+ sharecrm help [command...]
162
+ sharecrm --help
163
+ ```
164
+
165
+ - `auth login`: 发起 OAuth 设备码登录
166
+ - `auth status`: 输出当前登录状态 JSON
167
+ - `auth logout`: 清理本地登录态
168
+ - `help [command...]`: 显示本地帮助或远程命令帮助
169
+
170
+ ### `auth status` 输出
171
+
172
+ `sharecrm auth status` 输出 JSON,当前包含以下字段:
173
+
174
+ - `appId`
175
+ - `expiresAt`
176
+ - `grantedAt`
177
+ - `identity`
178
+ - `scope`
179
+ - `tokenStatus`
180
+ - `userName`
181
+ - `userId`
182
+ - `cliVersion`
183
+
184
+ 其中:
185
+
186
+ - `tokenStatus = normal` 表示本地 access token 存在且未过期
187
+ - `tokenStatus = needs_refresh` 表示 access token 缺失、过期,或本地仅剩会话元数据
188
+
189
+ ### 远程命令
190
+
191
+ ```bash
192
+ sharecrm <command> -d '{...}'
193
+ sharecrm <command> --data '{...}'
194
+ sharecrm <command> '{...}'
195
+ sharecrm <command>
196
+ ```
197
+
198
+ 参数规则:
199
+
200
+ - `-d` 与 `--data` 等价
201
+ - 如果最后一个参数看起来像 JSON(`{...}` 或 `[...]`),会被识别为请求参数
202
+ - 如果都没有提供,则默认使用 `{}` 作为请求参数
203
+ - CLI 会要求 `-d/--data` 对应的值必须是合法 JSON 对象
204
+
205
+ ## 调试
206
+
207
+ 全局 `--debug` 参数会把远程接口请求、响应和异常信息输出到 `stderr`:
208
+
209
+ ```bash
210
+ sharecrm --debug knowledge search WebSearchAction -d '{"query":"test"}'
211
+ ```
212
+
213
+ 说明:
214
+
215
+ - `--debug` 是全局参数,建议放在命令前面
216
+ - 正常结果输出到 `stdout`
217
+ - 调试信息输出到 `stderr`
218
+ - 遇到 `CliError` 时会附带结构化异常信息
219
+
220
+ ## 认证与本地状态
221
+
222
+ - `config.json` 仅保存 CLI 元数据和会话元数据,不保存 `accessToken` / `refreshToken`
223
+ - `accessToken` / `refreshToken` 通过平台对应的 secret store 保存
224
+ - 旧版明文 `session.json` 会在读取时尝试迁移
225
+ - 当远程接口返回登录失效时,CLI 会尝试使用 `refreshToken` 刷新登录态
226
+ - 刷新成功后会自动重试一次原请求
227
+ - 刷新失败或缺少刷新所需信息时,会提示重新登录
228
+
229
+ 重新登录提示示例:
230
+
231
+ ```text
232
+ 提示:登录状态已失效,请重新登录授权。
233
+ 执行命令:sharecrm auth login
234
+ ```
235
+
236
+ ## 常见问题
237
+
238
+ ### 1. 未登录无法执行远程命令
239
+
240
+ 错误信息:
241
+
242
+ ```text
243
+ You must login before executing remote commands.
244
+ sharecrm auth login
245
+ ```
246
+
247
+ 处理方式:
248
+
249
+ ```bash
250
+ sharecrm auth login
251
+ ```
252
+
253
+ ### 2. 登录状态已失效
254
+
255
+ 错误信息:
256
+
257
+ ```text
258
+ 提示:登录状态已失效,请重新登录授权。
259
+ 执行命令:sharecrm auth login
260
+ ```
261
+
262
+ 处理方式:重新执行登录。
263
+
264
+ ### 3. 设备码过期或授权被拒绝
265
+
266
+ 可能会看到类似错误:
267
+
268
+ - `Device code expired. Please run auth login again.`
269
+ - `Authorization was denied. Please try again after granting access.`
270
+
271
+ 处理方式:重新执行 `sharecrm auth login`。
272
+
273
+ ### 4. 缺少认证地址配置
274
+
275
+ 如果 `FS_CLI_AUTH_BASE_URL` 未配置且构建产物中也没有有效默认值,认证相关命令会失败。
276
+
277
+ ### 5. Windows secret store 异常
278
+
279
+ Windows 下会话 secret 读取依赖 `powershell.exe` 与 DPAPI。若本地 secret 不可读,`sharecrm` 和 `sharecrm help` 会按未登录处理;远程命令执行仍需要重新登录或修复本地环境。
280
+
281
+ ## 开发与测试
282
+
283
+ ```bash
284
+ npm run build
285
+ npm run typecheck
286
+ npm run lint
287
+ npm test
288
+ ```
289
+
290
+ 说明:
291
+
292
+ - `npm test` 会先执行构建,再运行 `vitest`
293
+ - `npm run build:debug` 可直接从源码启动 CLI
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseCliInput = parseCliInput;
4
+ const utils_1 = require("../shared/utils");
5
+ const LOCAL_COMMANDS = new Set(['auth login', 'auth logout', 'auth status', 'auth token', 'config init', 'help']);
6
+ function looksLikeJsonArgument(value) {
7
+ if (!value) {
8
+ return false;
9
+ }
10
+ const trimmed = value.trim();
11
+ return (trimmed.startsWith('{') && trimmed.endsWith('}'))
12
+ || (trimmed.startsWith('[') && trimmed.endsWith(']'));
13
+ }
14
+ function parseCliInput(argv) {
15
+ const tokens = [...argv];
16
+ if (tokens.length === 0 || tokens[0] === 'help' || (0, utils_1.isHelpFlag)(tokens[0])) {
17
+ return {
18
+ kind: 'local',
19
+ commandPath: ['help'],
20
+ helpTarget: (0, utils_1.isHelpFlag)(tokens[0]) ? [] : tokens.slice(1),
21
+ rawArgs: tokens,
22
+ mode: 'help',
23
+ };
24
+ }
25
+ const firstTwo = (0, utils_1.joinCommandSegments)(tokens.slice(0, 2));
26
+ if (LOCAL_COMMANDS.has(firstTwo)) {
27
+ return {
28
+ kind: 'local',
29
+ commandPath: tokens.slice(0, 2),
30
+ rawArgs: tokens.slice(2),
31
+ mode: 'execute',
32
+ };
33
+ }
34
+ const helpIndex = tokens.findIndex((token) => (0, utils_1.isHelpFlag)(token));
35
+ if (helpIndex >= 0) {
36
+ const helpTarget = tokens.slice(0, helpIndex);
37
+ return {
38
+ kind: 'local',
39
+ commandPath: ['help'],
40
+ helpTarget,
41
+ rawArgs: tokens,
42
+ commandKey: (0, utils_1.joinCommandSegments)(helpTarget),
43
+ mode: 'help',
44
+ };
45
+ }
46
+ const dataFlagIndex = tokens.findIndex((token) => token === '-d' || token === '--data');
47
+ if (dataFlagIndex >= 0) {
48
+ const commandSegments = tokens.slice(0, dataFlagIndex);
49
+ const arg = tokens[dataFlagIndex + 1];
50
+ return {
51
+ kind: 'remote',
52
+ commandPath: commandSegments,
53
+ rawArgs: tokens,
54
+ commandKey: (0, utils_1.joinCommandSegments)(commandSegments),
55
+ arg,
56
+ mode: 'execute',
57
+ };
58
+ }
59
+ if (looksLikeJsonArgument(tokens.at(-1))) {
60
+ const commandSegments = tokens.slice(0, -1);
61
+ const arg = tokens.at(-1);
62
+ return {
63
+ kind: 'remote',
64
+ commandPath: commandSegments,
65
+ rawArgs: tokens,
66
+ commandKey: (0, utils_1.joinCommandSegments)(commandSegments),
67
+ arg,
68
+ mode: 'execute',
69
+ };
70
+ }
71
+ return {
72
+ kind: 'remote',
73
+ commandPath: tokens,
74
+ rawArgs: tokens,
75
+ commandKey: (0, utils_1.joinCommandSegments)(tokens),
76
+ arg: '{}',
77
+ mode: 'execute',
78
+ };
79
+ }
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createRootCommand = createRootCommand;
4
+ const commander_1 = require("commander");
5
+ const runtimeDebug_1 = require("../core/debug/runtimeDebug");
6
+ const login_1 = require("../commands/auth/login");
7
+ const token_1 = require("../commands/auth/token");
8
+ const logout_1 = require("../commands/auth/logout");
9
+ const status_1 = require("../commands/auth/status");
10
+ const help_1 = require("../commands/help/help");
11
+ const execute_1 = require("../commands/remote/execute");
12
+ const init_1 = require("../commands/config/init");
13
+ const constants_1 = require("../shared/constants");
14
+ const parser_1 = require("./parser");
15
+ function createRootCommand() {
16
+ const program = new commander_1.Command();
17
+ program
18
+ .name(constants_1.CLI_NAME)
19
+ .version(constants_1.CLI_VERSION)
20
+ .description('ShareCRM CLI client Tools')
21
+ .helpOption(false)
22
+ .allowUnknownOption(true)
23
+ .allowExcessArguments(true)
24
+ .option('--d, --data', 'URL/query parameters JSON')
25
+ .option('--help', 'Help about any command\n')
26
+ .option('--debug', 'Print remote API debug logs to stderr')
27
+ .action(async () => {
28
+ (0, runtimeDebug_1.setDebugEnabled)(Boolean(program.opts().debug));
29
+ const parsed = (0, parser_1.parseCliInput)(program.args);
30
+ if (parsed.mode === 'help') {
31
+ await (0, help_1.runHelpCommand)(program, parsed.helpTarget ?? []);
32
+ return;
33
+ }
34
+ if (parsed.kind === 'remote') {
35
+ await (0, execute_1.runRemoteCommand)(parsed.commandKey ?? '', parsed.arg, parsed.kind);
36
+ return;
37
+ }
38
+ await (0, help_1.runHelpCommand)(program, []);
39
+ });
40
+ const auth = program.command('auth').description('Authentication commands');
41
+ auth.command('login')
42
+ .description('Login with OAuth device flow')
43
+ .action(async () => {
44
+ await (0, login_1.runLoginCommand)();
45
+ });
46
+ auth.command('logout').description('Clear local session').action(logout_1.runLogoutCommand);
47
+ auth.command('status').description('Show current session status').action(status_1.runStatusCommand);
48
+ auth.command('token')
49
+ .description('Import token JSON after refresh validation')
50
+ .option('-d, --data <json>', 'Token JSON payload')
51
+ .action(async (options) => {
52
+ await (0, token_1.runAuthTokenCommand)(options.data);
53
+ });
54
+ const config = program.command('config').description('Configuration commands');
55
+ config.command('init').description('Initialize cli config').action(async () => {
56
+ await (0, init_1.runConfigInitCommand)();
57
+ });
58
+ program.command('help [command...]').description('Show help').action(async (...args) => {
59
+ const commandArgs = Array.isArray(args[0]) ? args[0] : [];
60
+ await (0, help_1.runHelpCommand)(program, commandArgs);
61
+ });
62
+ return program;
63
+ }
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.routeCliInput = routeCliInput;
4
+ const help_1 = require("../commands/help/help");
5
+ const login_1 = require("../commands/auth/login");
6
+ const logout_1 = require("../commands/auth/logout");
7
+ const status_1 = require("../commands/auth/status");
8
+ const execute_1 = require("../commands/remote/execute");
9
+ async function routeCliInput(command, input) {
10
+ if (input.mode === 'help') {
11
+ await (0, help_1.runHelpCommand)(command, input.helpTarget ?? []);
12
+ return;
13
+ }
14
+ if (input.kind === 'local') {
15
+ const commandKey = input.commandPath.join(' ');
16
+ switch (commandKey) {
17
+ case 'auth login':
18
+ await (0, login_1.runLoginCommand)();
19
+ return;
20
+ case 'auth logout':
21
+ await (0, logout_1.runLogoutCommand)();
22
+ return;
23
+ case 'auth status':
24
+ await (0, status_1.runStatusCommand)();
25
+ return;
26
+ default:
27
+ await (0, help_1.runHelpCommand)(command, []);
28
+ return;
29
+ }
30
+ }
31
+ await (0, execute_1.runRemoteCommand)(input.commandKey ?? '', input.arg);
32
+ }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runLoginCommand = runLoginCommand;
4
+ const stdout_1 = require("../../core/output/stdout");
5
+ const constants_1 = require("../../shared/constants");
6
+ const deviceFlow_1 = require("../../core/auth/deviceFlow");
7
+ const configStore_1 = require("../../core/state/configStore");
8
+ const sessionStore_1 = require("../../core/state/sessionStore");
9
+ async function runLoginCommand() {
10
+ const configStore = new configStore_1.ConfigStore();
11
+ await configStore.initialize();
12
+ const deviceCode = await (0, deviceFlow_1.requestDeviceCode)((0, constants_1.getDefaultDeviceClientId)());
13
+ (0, stdout_1.writeStdout)('在浏览器中打开以下链接进行认证:');
14
+ (0, stdout_1.writeStdout)(`${deviceCode.verificationUrl}`);
15
+ (0, stdout_1.writeStdout)(`设备验证码: ${deviceCode.userCode}`);
16
+ (0, stdout_1.writeStdout)('等待用户授权中...');
17
+ //writeStdout(`Local state directory: ${resolveCliPaths().rootDir}`);
18
+ const session = await (0, deviceFlow_1.pollDeviceAuthorization)(deviceCode.deviceCode, deviceCode.interval);
19
+ if (session.apiUrl) {
20
+ process.env.FS_CLI_API_BASE_URL = session.apiUrl;
21
+ }
22
+ await new sessionStore_1.SessionStore().save({
23
+ ...session,
24
+ grantedAt: new Date().toISOString(),
25
+ identity: session.identity ?? 'user',
26
+ userName: session.userName ?? '',
27
+ });
28
+ (0, stdout_1.writeStdout)(JSON.stringify({
29
+ success: true,
30
+ userId: session.userId,
31
+ appId: session.appId,
32
+ scope: session.scope,
33
+ }, null, 2));
34
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runLogoutCommand = runLogoutCommand;
4
+ const stdout_1 = require("../../core/output/stdout");
5
+ const sessionStore_1 = require("../../core/state/sessionStore");
6
+ const legacySessionMigration_1 = require("../../core/state/legacySessionMigration");
7
+ async function runLogoutCommand() {
8
+ const sessionStore = new sessionStore_1.SessionStore();
9
+ await sessionStore.clear();
10
+ await (0, legacySessionMigration_1.removeLegacySessionFile)();
11
+ (0, stdout_1.writeStdout)('Logged out locally.');
12
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runStatusCommand = runStatusCommand;
4
+ const stdout_1 = require("../../core/output/stdout");
5
+ const constants_1 = require("../../shared/constants");
6
+ const errors_1 = require("../../core/output/errors");
7
+ const sessionStore_1 = require("../../core/state/sessionStore");
8
+ const sessionMetaStore_1 = require("../../core/state/sessionMetaStore");
9
+ function formatExpiresAt(tokenExpireAt) {
10
+ return tokenExpireAt ? new Date(tokenExpireAt).toISOString() : '';
11
+ }
12
+ function resolveTokenStatus(tokenExpireAt, hasAccessToken) {
13
+ if (!hasAccessToken || !tokenExpireAt || tokenExpireAt <= Date.now()) {
14
+ return 'needs_refresh';
15
+ }
16
+ return 'normal';
17
+ }
18
+ async function runStatusCommand() {
19
+ let session = null;
20
+ try {
21
+ session = await new sessionStore_1.SessionStore().load();
22
+ }
23
+ catch (error) {
24
+ if (!(error instanceof errors_1.CliError) || error.message !== 'Failed to read session file.') {
25
+ throw error;
26
+ }
27
+ }
28
+ const sessionMeta = session ?? await new sessionMetaStore_1.SessionMetaStore().loadSession();
29
+ const hasAccessToken = Boolean(session?.accessToken);
30
+ (0, stdout_1.writeStdout)(JSON.stringify({
31
+ appId: sessionMeta?.appId ?? '',
32
+ expiresAt: formatExpiresAt(sessionMeta?.tokenExpireAt),
33
+ grantedAt: sessionMeta?.grantedAt ?? '',
34
+ identity: sessionMeta?.identity ?? 'user',
35
+ scope: sessionMeta?.scope.join(' ') ?? '',
36
+ tokenStatus: resolveTokenStatus(sessionMeta?.tokenExpireAt, hasAccessToken),
37
+ userName: sessionMeta?.userName ?? '',
38
+ userId: sessionMeta?.userId ?? '',
39
+ cliVersion: constants_1.CLI_VERSION,
40
+ }, null, 2));
41
+ }
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runAuthTokenCommand = runAuthTokenCommand;
4
+ const errors_1 = require("../../core/output/errors");
5
+ const deviceFlow_1 = require("../../core/auth/deviceFlow");
6
+ const sessionStore_1 = require("../../core/state/sessionStore");
7
+ const interactive_1 = require("../../core/config/interactive");
8
+ const stdout_1 = require("../../core/output/stdout");
9
+ const REQUIRED_FIELDS = [
10
+ 'accessToken',
11
+ 'refreshToken',
12
+ 'expiresIn',
13
+ 'scope',
14
+ 'userId',
15
+ 'userName',
16
+ 'appId',
17
+ 'apiUrl',
18
+ ];
19
+ function parseImportedTokenPayload(raw) {
20
+ let parsed;
21
+ try {
22
+ parsed = JSON.parse(raw);
23
+ }
24
+ catch {
25
+ throw new errors_1.CliError('Invalid JSON format. Please provide a valid JSON string.', 1);
26
+ }
27
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
28
+ throw new errors_1.CliError('Token data must be a JSON object.', 1);
29
+ }
30
+ const obj = parsed;
31
+ const missing = REQUIRED_FIELDS.filter((field) => {
32
+ const val = obj[field];
33
+ if (field === 'scope') {
34
+ return val === undefined || val === null;
35
+ }
36
+ return val === undefined || val === null || (typeof val === 'string' && val.trim() === '');
37
+ });
38
+ if (missing.length > 0) {
39
+ throw new errors_1.CliError(`Missing or empty required fields: ${missing.join(', ')}.`, 1);
40
+ }
41
+ return obj;
42
+ }
43
+ async function runAuthTokenCommand(dataArg) {
44
+ const raw = dataArg ?? await (0, interactive_1.readStdinText)('请输入 token JSON: ');
45
+ if (!raw.trim()) {
46
+ throw new errors_1.CliError('未读取到 token JSON,请使用 --data 传参或通过标准输入传入。', 1);
47
+ }
48
+ const input = parseImportedTokenPayload(raw);
49
+ const existing = {
50
+ accessToken: input.accessToken,
51
+ refreshToken: input.refreshToken,
52
+ tokenExpireAt: Date.now() + input.expiresIn * 1000,
53
+ scope: (0, deviceFlow_1.toScope)(input.scope),
54
+ userId: input.userId,
55
+ appId: input.appId,
56
+ apiUrl: input.apiUrl,
57
+ grantedAt: new Date().toISOString(),
58
+ identity: 'user',
59
+ userName: input.userName,
60
+ };
61
+ const refreshed = await (0, deviceFlow_1.refreshAccessToken)(input.refreshToken, input.appId, undefined, existing);
62
+ await new sessionStore_1.SessionStore().save({
63
+ ...refreshed,
64
+ grantedAt: existing.grantedAt,
65
+ identity: refreshed.identity ?? 'user',
66
+ userName: refreshed.userName ?? input.userName,
67
+ apiUrl: refreshed.apiUrl ?? input.apiUrl,
68
+ });
69
+ (0, stdout_1.writeStdout)(JSON.stringify({ success: true, userId: input.userId, appId: input.appId }, null, 2));
70
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runConfigInitCommand = runConfigInitCommand;
4
+ const configStore_1 = require("../../core/state/configStore");
5
+ const envPersistence_1 = require("../../core/config/envPersistence");
6
+ const authBaseUrl_1 = require("../../core/config/authBaseUrl");
7
+ const interactive_1 = require("../../core/config/interactive");
8
+ const stdout_1 = require("../../core/output/stdout");
9
+ const DEFAULT_AUTH_BASE_URL = 'https://open.fxiaoke.com/';
10
+ async function runConfigInitCommand() {
11
+ const configStore = new configStore_1.ConfigStore();
12
+ const current = await configStore.initialize();
13
+ const locale = await (0, interactive_1.promptSelect)('请选择语言', [
14
+ { label: '中文', value: 'zh-CN' },
15
+ { label: 'English', value: 'en' },
16
+ { label: '繁體中文', value: 'zh-TW' },
17
+ ]);
18
+ const input = await (0, interactive_1.promptText)(`请输入 FS_CLI_AUTH_BASE_URL(回车使用默认值 ${DEFAULT_AUTH_BASE_URL}): `);
19
+ const authBaseUrl = (0, authBaseUrl_1.normalizeAuthBaseUrl)(input || DEFAULT_AUTH_BASE_URL);
20
+ await configStore.save({
21
+ ...current,
22
+ locale,
23
+ authBaseUrl,
24
+ });
25
+ process.env.FS_CLI_AUTH_BASE_URL = authBaseUrl;
26
+ await (0, envPersistence_1.persistAuthBaseUrl)(authBaseUrl);
27
+ (0, stdout_1.writeStdout)(JSON.stringify({ success: true, locale, authBaseUrl }, null, 2));
28
+ }