@helixlife-ai/xiantao 0.1.9 → 0.1.11
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 +2 -4
- package/dist/base-command.js +15 -0
- package/dist/commands/auth/login.js +7 -27
- package/dist/commands/plot/fetch-all.js +3 -3
- package/dist/commands/plot/login.js +7 -27
- package/dist/commands/plot/menu.js +3 -3
- package/dist/commands/plot/menus.js +3 -3
- package/dist/commands/plot/resolve.js +3 -3
- package/dist/commands/plot/run.js +6 -7
- package/dist/commands/tool/exec.js +1 -1
- package/dist/commands/tool/inspect.js +3 -3
- package/dist/commands/tool/prepare.js +1 -1
- package/dist/commands/tool/search.js +3 -3
- package/dist/lib/auth.js +25 -11
- package/dist/lib/constants.js +4 -6
- package/dist/lib/errors.js +6 -12
- package/dist/lib/login-flow.js +1 -10
- package/dist/lib/storage.js +0 -70
- package/dist/lib/update.js +151 -0
- package/dist/lib/xiantao-plot.js +3 -3
- package/dist/lib/xiantao.js +38 -18
- package/dist/xtz-help.js +3 -3
- package/package.json +1 -1
- package/dist/commands/auth/bind.js +0 -53
- package/dist/commands/bind.js +0 -3
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ xt tool inspect violin_flat
|
|
|
21
21
|
xt tool run violin_flat ./data.xlsx
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
`xt login` is the recommended first-use entrypoint. It
|
|
24
|
+
`xt login` is the recommended first-use entrypoint. It authorizes and stores the profile token in one step. `xt tool login` is kept as an equivalent entrypoint under the `tool` topic.
|
|
25
25
|
|
|
26
26
|
### Interactive Tool Run
|
|
27
27
|
|
|
@@ -67,12 +67,10 @@ For machine callers, use `xt tool exec`, keep `--json`, pass an explicit profile
|
|
|
67
67
|
|
|
68
68
|
For the common Xiantao tool product, `tool search`, `tool inspect`, `tool run`, `tool resolve`, and `tool menu` default `toolProductUuid` to `c0b6febb-52dd-4525-970a-61bbe9e263ff`. You can override it with `--toolProductUuid`, `XIANTAO_TOOL_PRODUCT_UUID`, or `~/.config/xtz/config.json` under `xiantao.toolProductUuid`.
|
|
69
69
|
|
|
70
|
-
`xt tool search <keyword>` searches
|
|
70
|
+
`xt tool search <keyword>` searches remote tool menus by tool id, title, path, and route. `xt tool inspect <tool_id>` prints resolved metadata together with the dynamic `args_main` schema used by the tool. `xt tool run <tool_id>` runs a Xiantao bioinformatics tool. `xt tool resolve <tool_id>` remains available as the low-level UUID and route lookup. `--demo` reuses the demo file metadata returned by `tool fetch-all`; otherwise `tool run` uploads the local file first and therefore also needs token auth. Run `xt login` first to authorize the stored token. When a tool exposes multiple downloadable results, `--download <path>` uses the output file extension to select the matching artifact.
|
|
71
71
|
|
|
72
72
|
`xt tool run <tool_id> ... --interactive` prompts for dynamic `args_main` values before the final submit, prints reusable `--set` flags, and supports `<` for the previous item plus `/skip` for the current section.
|
|
73
73
|
|
|
74
|
-
When resolving tools from `--toolProductUuid`, the CLI caches the `/tool/menus?is_all=1` result locally for 12 hours per `profile + toolProductUuid` to avoid repeated full-menu fetches.
|
|
75
|
-
|
|
76
74
|
## Development
|
|
77
75
|
|
|
78
76
|
```bash
|
package/dist/base-command.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
import { XtzError } from './lib/errors.js';
|
|
3
3
|
import { resolveAgent } from './lib/storage.js';
|
|
4
|
+
import { getUpdateReminder } from './lib/update.js';
|
|
4
5
|
export class XtzCommand extends Command {
|
|
6
|
+
async init() {
|
|
7
|
+
await super.init();
|
|
8
|
+
await this.warnIfUpdateAvailable();
|
|
9
|
+
}
|
|
5
10
|
async getProfile(profileFlag) {
|
|
6
11
|
return resolveAgent(profileFlag);
|
|
7
12
|
}
|
|
@@ -37,4 +42,14 @@ export class XtzCommand extends Command {
|
|
|
37
42
|
const replacement = command.replace(/^auth /, '');
|
|
38
43
|
this.warn(`\`xt ${command}\` 已兼容保留,建议改用 \`xt ${replacement}\``);
|
|
39
44
|
}
|
|
45
|
+
async warnIfUpdateAvailable() {
|
|
46
|
+
if (this.argv.includes('--json') || !process.stdout.isTTY) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const reminder = await getUpdateReminder(this.config.version);
|
|
50
|
+
if (!reminder) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
this.warn(reminder);
|
|
54
|
+
}
|
|
40
55
|
}
|
|
@@ -2,9 +2,9 @@ import { Flags } from '@oclif/core';
|
|
|
2
2
|
import { XtzCommand } from '../../base-command.js';
|
|
3
3
|
import { globalFlags } from '../../lib/flags.js';
|
|
4
4
|
import { loginAll } from '../../lib/login-flow.js';
|
|
5
|
-
import { getTokenPath
|
|
5
|
+
import { getTokenPath } from '../../lib/storage.js';
|
|
6
6
|
export default class AuthLogin extends XtzCommand {
|
|
7
|
-
static description = '
|
|
7
|
+
static description = 'Authorize the current profile token';
|
|
8
8
|
static flags = {
|
|
9
9
|
...globalFlags,
|
|
10
10
|
open: Flags.boolean({
|
|
@@ -22,42 +22,22 @@ export default class AuthLogin extends XtzCommand {
|
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
24
|
if (phase === 'auth_started') {
|
|
25
|
-
|
|
26
|
-
this.log('已使用现有仙桃网页登录自动完成上传授权,等待确认...');
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
this.log(auth.browserOpened ? '浏览器已打开上传授权链接,等待授权完成...' : `请先在浏览器中打开上传授权链接: ${auth.linkUrl}`);
|
|
25
|
+
this.log(auth.browserOpened ? '浏览器已打开授权链接,等待授权完成...' : `请先在浏览器中打开授权链接: ${auth.linkUrl}`);
|
|
30
26
|
return;
|
|
31
27
|
}
|
|
32
|
-
this.log(
|
|
28
|
+
this.log('授权已确认。');
|
|
33
29
|
});
|
|
34
30
|
this.print(flags, {
|
|
35
31
|
agent,
|
|
36
|
-
auto_authorized: result.auth.authorizedWithStoredXiantao,
|
|
37
32
|
browser_opened: result.auth.browserOpened,
|
|
38
33
|
link_url: result.auth.linkUrl,
|
|
39
34
|
token_path: result.auth.tokenPath,
|
|
40
|
-
xiantao: {
|
|
41
|
-
browser_opened: result.xiantao.browserOpened,
|
|
42
|
-
callback_url: result.xiantao.callbackUrl,
|
|
43
|
-
jump_url: result.xiantao.jumpUrl,
|
|
44
|
-
reused: result.xiantao.reused,
|
|
45
|
-
token_path: result.xiantao.tokenPath,
|
|
46
|
-
},
|
|
47
35
|
}, [
|
|
48
36
|
`profile: ${agent}`,
|
|
49
37
|
`token: ${getTokenPath(agent)}`,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
: result.xiantao.browserOpened
|
|
54
|
-
? '仙桃网页登录成功,token 已刷新。'
|
|
55
|
-
: `请在浏览器中打开: ${result.xiantao.jumpUrl}`,
|
|
56
|
-
result.auth.authorizedWithStoredXiantao
|
|
57
|
-
? '已使用现有仙桃网页登录自动完成授权。'
|
|
58
|
-
: result.auth.browserOpened
|
|
59
|
-
? '浏览器已打开授权链接,请完成授权后运行 `xt status`。'
|
|
60
|
-
: `请在浏览器中打开: ${result.auth.linkUrl}`,
|
|
38
|
+
result.auth.browserOpened
|
|
39
|
+
? '浏览器已打开授权链接,请完成授权后运行 `xt status`。'
|
|
40
|
+
: `请在浏览器中打开: ${result.auth.linkUrl}`,
|
|
61
41
|
].join('\n'));
|
|
62
42
|
}
|
|
63
43
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import { XtzCommand } from '../../base-command.js';
|
|
3
|
+
import { getAuthContext } from '../../lib/auth.js';
|
|
3
4
|
import { globalFlags } from '../../lib/flags.js';
|
|
4
5
|
import { buildArgsMainFromSchema } from '../../lib/plot-schema.js';
|
|
5
|
-
import { getXiantaoAuthContext } from '../../lib/xiantao-auth.js';
|
|
6
6
|
import { fetchAllPlotArgs } from '../../lib/xiantao.js';
|
|
7
7
|
export default class PlotFetchAll extends XtzCommand {
|
|
8
8
|
static description = 'Fetch raw dynamic args for a Xiantao tool via public.args.fetch-all';
|
|
@@ -15,7 +15,7 @@ export default class PlotFetchAll extends XtzCommand {
|
|
|
15
15
|
static flags = {
|
|
16
16
|
...globalFlags,
|
|
17
17
|
token: Flags.string({
|
|
18
|
-
description: 'Bearer token for
|
|
18
|
+
description: 'Bearer token for agent.helixlife.net xiantao APIs; falls back to the stored agent token',
|
|
19
19
|
}),
|
|
20
20
|
uuid: Flags.string({
|
|
21
21
|
description: 'Tool UUID used in the public.args.fetch-all payload',
|
|
@@ -26,7 +26,7 @@ export default class PlotFetchAll extends XtzCommand {
|
|
|
26
26
|
this.warnIfLegacyPlotCommand();
|
|
27
27
|
const { args, flags } = await this.parse(PlotFetchAll);
|
|
28
28
|
const agent = await this.getProfile(flags.profile);
|
|
29
|
-
const auth = await
|
|
29
|
+
const auth = await getAuthContext(agent, flags.token);
|
|
30
30
|
const result = await fetchAllPlotArgs(auth, {
|
|
31
31
|
moduleId: args.moduleId,
|
|
32
32
|
uuid: flags.uuid,
|
|
@@ -2,9 +2,9 @@ import { Flags } from '@oclif/core';
|
|
|
2
2
|
import { XtzCommand } from '../../base-command.js';
|
|
3
3
|
import { globalFlags } from '../../lib/flags.js';
|
|
4
4
|
import { loginAll } from '../../lib/login-flow.js';
|
|
5
|
-
import { getTokenPath
|
|
5
|
+
import { getTokenPath } from '../../lib/storage.js';
|
|
6
6
|
export default class PlotLogin extends XtzCommand {
|
|
7
|
-
static description = '
|
|
7
|
+
static description = 'Authorize the current profile token';
|
|
8
8
|
static flags = {
|
|
9
9
|
...globalFlags,
|
|
10
10
|
open: Flags.boolean({
|
|
@@ -22,42 +22,22 @@ export default class PlotLogin extends XtzCommand {
|
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
24
|
if (phase === 'auth_started') {
|
|
25
|
-
|
|
26
|
-
this.log('已使用现有仙桃网页登录自动完成上传授权,等待确认...');
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
this.log(auth.browserOpened ? '浏览器已打开上传授权链接,等待授权完成...' : `请先在浏览器中打开上传授权链接: ${auth.linkUrl}`);
|
|
25
|
+
this.log(auth.browserOpened ? '浏览器已打开授权链接,等待授权完成...' : `请先在浏览器中打开授权链接: ${auth.linkUrl}`);
|
|
30
26
|
return;
|
|
31
27
|
}
|
|
32
|
-
this.log(
|
|
28
|
+
this.log('授权已确认。');
|
|
33
29
|
});
|
|
34
30
|
this.print(flags, {
|
|
35
31
|
agent,
|
|
36
|
-
auto_authorized: result.auth.authorizedWithStoredXiantao,
|
|
37
32
|
browser_opened: result.auth.browserOpened,
|
|
38
33
|
link_url: result.auth.linkUrl,
|
|
39
34
|
token_path: result.auth.tokenPath,
|
|
40
|
-
xiantao: {
|
|
41
|
-
browser_opened: result.xiantao.browserOpened,
|
|
42
|
-
callback_url: result.xiantao.callbackUrl,
|
|
43
|
-
jump_url: result.xiantao.jumpUrl,
|
|
44
|
-
reused: result.xiantao.reused,
|
|
45
|
-
token_path: result.xiantao.tokenPath,
|
|
46
|
-
},
|
|
47
35
|
}, [
|
|
48
36
|
`profile: ${agent}`,
|
|
49
37
|
`token: ${getTokenPath(agent)}`,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
: result.xiantao.browserOpened
|
|
54
|
-
? '仙桃网页登录成功,token 已刷新。'
|
|
55
|
-
: `请在浏览器中打开: ${result.xiantao.jumpUrl}`,
|
|
56
|
-
result.auth.authorizedWithStoredXiantao
|
|
57
|
-
? '已使用现有仙桃网页登录自动完成授权。'
|
|
58
|
-
: result.auth.browserOpened
|
|
59
|
-
? '浏览器已打开授权链接,请完成授权后运行 `xt status`。'
|
|
60
|
-
: `请在浏览器中打开: ${result.auth.linkUrl}`,
|
|
38
|
+
result.auth.browserOpened
|
|
39
|
+
? '浏览器已打开授权链接,请完成授权后运行 `xt status`。'
|
|
40
|
+
: `请在浏览器中打开: ${result.auth.linkUrl}`,
|
|
61
41
|
].join('\n'));
|
|
62
42
|
}
|
|
63
43
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import { XtzCommand } from '../../base-command.js';
|
|
3
|
+
import { getAuthContext } from '../../lib/auth.js';
|
|
3
4
|
import { globalFlags } from '../../lib/flags.js';
|
|
4
5
|
import { resolveXiantaoToolProductUuid } from '../../lib/storage.js';
|
|
5
|
-
import { getXiantaoAuthContext } from '../../lib/xiantao-auth.js';
|
|
6
6
|
import { fetchToolMenu } from '../../lib/xiantao.js';
|
|
7
7
|
export default class PlotMenu extends XtzCommand {
|
|
8
8
|
static description = 'Fetch Xiantao menu metadata for a tool menu UUID';
|
|
@@ -15,7 +15,7 @@ export default class PlotMenu extends XtzCommand {
|
|
|
15
15
|
static flags = {
|
|
16
16
|
...globalFlags,
|
|
17
17
|
token: Flags.string({
|
|
18
|
-
description: 'Bearer token for
|
|
18
|
+
description: 'Bearer token for agent.helixlife.net xiantao APIs; falls back to the stored agent token',
|
|
19
19
|
}),
|
|
20
20
|
toolProductUuid: Flags.string({
|
|
21
21
|
description: 'tool_product_uuid used in the menu filter payload; defaults to the main Xiantao product UUID',
|
|
@@ -25,7 +25,7 @@ export default class PlotMenu extends XtzCommand {
|
|
|
25
25
|
this.warnIfLegacyPlotCommand();
|
|
26
26
|
const { args, flags } = await this.parse(PlotMenu);
|
|
27
27
|
const agent = await this.getProfile(flags.profile);
|
|
28
|
-
const auth = await
|
|
28
|
+
const auth = await getAuthContext(agent, flags.token);
|
|
29
29
|
const toolProductUuid = await resolveXiantaoToolProductUuid(flags.toolProductUuid);
|
|
30
30
|
const menu = await fetchToolMenu(auth, args.menuUuid, toolProductUuid);
|
|
31
31
|
this.print(flags, {
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import { XtzCommand } from '../../base-command.js';
|
|
3
|
+
import { getAuthContext } from '../../lib/auth.js';
|
|
3
4
|
import { globalFlags } from '../../lib/flags.js';
|
|
4
5
|
import { resolveXiantaoToolProductUuid } from '../../lib/storage.js';
|
|
5
|
-
import { getXiantaoAuthContext } from '../../lib/xiantao-auth.js';
|
|
6
6
|
import { fetchToolMenus } from '../../lib/xiantao.js';
|
|
7
7
|
export default class PlotMenus extends XtzCommand {
|
|
8
8
|
static description = 'Fetch all Xiantao menu metadata for a tool product';
|
|
9
9
|
static flags = {
|
|
10
10
|
...globalFlags,
|
|
11
11
|
token: Flags.string({
|
|
12
|
-
description: 'Bearer token for
|
|
12
|
+
description: 'Bearer token for agent.helixlife.net xiantao APIs; falls back to the stored agent token',
|
|
13
13
|
}),
|
|
14
14
|
toolProductUuid: Flags.string({
|
|
15
15
|
description: 'tool_product_uuid used in the menu filter payload; defaults to the main Xiantao product UUID',
|
|
@@ -19,7 +19,7 @@ export default class PlotMenus extends XtzCommand {
|
|
|
19
19
|
this.warnIfLegacyPlotCommand();
|
|
20
20
|
const { flags } = await this.parse(PlotMenus);
|
|
21
21
|
const agent = await this.getProfile(flags.profile);
|
|
22
|
-
const auth = await
|
|
22
|
+
const auth = await getAuthContext(agent, flags.token);
|
|
23
23
|
const toolProductUuid = await resolveXiantaoToolProductUuid(flags.toolProductUuid);
|
|
24
24
|
const menus = await fetchToolMenus(auth, toolProductUuid);
|
|
25
25
|
this.print(flags, {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import { XtzCommand } from '../../base-command.js';
|
|
3
|
+
import { getAuthContext } from '../../lib/auth.js';
|
|
3
4
|
import { globalFlags } from '../../lib/flags.js';
|
|
4
5
|
import { resolveXiantaoToolProductUuid } from '../../lib/storage.js';
|
|
5
|
-
import { getXiantaoAuthContext } from '../../lib/xiantao-auth.js';
|
|
6
6
|
import { resolveToolMenuByCode } from '../../lib/xiantao.js';
|
|
7
7
|
export default class PlotResolve extends XtzCommand {
|
|
8
8
|
static description = 'Resolve a Xiantao tool id to its UUID and route';
|
|
@@ -15,7 +15,7 @@ export default class PlotResolve extends XtzCommand {
|
|
|
15
15
|
static flags = {
|
|
16
16
|
...globalFlags,
|
|
17
17
|
token: Flags.string({
|
|
18
|
-
description: 'Bearer token for
|
|
18
|
+
description: 'Bearer token for agent.helixlife.net xiantao APIs; falls back to the stored agent token',
|
|
19
19
|
}),
|
|
20
20
|
toolProductUuid: Flags.string({
|
|
21
21
|
description: 'tool_product_uuid from the /products/apply/:uuid URL; defaults to the main Xiantao product UUID',
|
|
@@ -25,7 +25,7 @@ export default class PlotResolve extends XtzCommand {
|
|
|
25
25
|
this.warnIfLegacyPlotCommand();
|
|
26
26
|
const { args, flags } = await this.parse(PlotResolve);
|
|
27
27
|
const agent = await this.getProfile(flags.profile);
|
|
28
|
-
const auth = await
|
|
28
|
+
const auth = await getAuthContext(agent, flags.token);
|
|
29
29
|
const toolProductUuid = await resolveXiantaoToolProductUuid(flags.toolProductUuid);
|
|
30
30
|
const resolved = await resolveToolMenuByCode(auth, toolProductUuid, args.moduleId);
|
|
31
31
|
this.print(flags, {
|
|
@@ -11,15 +11,14 @@ import { parseSetOverrides, plotOverrideFlags } from '../../lib/plot-options.js'
|
|
|
11
11
|
import { buildArgsMainFromSchema } from '../../lib/plot-schema.js';
|
|
12
12
|
import { loadPlotArgsMainConfig, resolveXiantaoToolProductUuid } from '../../lib/storage.js';
|
|
13
13
|
import { resolveXiantaoDownload, submitXiantaoPlot, verifyXiantaoPlotUpload } from '../../lib/xiantao-plot.js';
|
|
14
|
-
import { getXiantaoAuthContext } from '../../lib/xiantao-auth.js';
|
|
15
14
|
import { fetchAllPlotArgs, fetchToolMenu, fetchToolMenus, resolveToolMenuByCode } from '../../lib/xiantao.js';
|
|
16
15
|
export class ToolRunCommandBase extends XtzCommand {
|
|
17
16
|
async prepareToolRun(requestedModuleId, filePath, flags) {
|
|
18
17
|
const agent = await this.getProfile(flags.profile);
|
|
19
|
-
const
|
|
20
|
-
const resolved = await this.resolveModule(requestedModuleId, flags,
|
|
18
|
+
const auth = await getAuthContext(agent, flags.token);
|
|
19
|
+
const resolved = await this.resolveModule(requestedModuleId, flags, auth);
|
|
21
20
|
const { moduleId, route, toolProductUuid, uuid } = resolved;
|
|
22
|
-
const fetchAllResult = await fetchAllPlotArgs(
|
|
21
|
+
const fetchAllResult = await fetchAllPlotArgs(auth, { moduleId, uuid });
|
|
23
22
|
const inputMode = await this.resolveInputMode(fetchAllResult, {
|
|
24
23
|
demo: flags.demo,
|
|
25
24
|
filePath,
|
|
@@ -35,7 +34,7 @@ export class ToolRunCommandBase extends XtzCommand {
|
|
|
35
34
|
argsMain,
|
|
36
35
|
moduleId,
|
|
37
36
|
uuid,
|
|
38
|
-
},
|
|
37
|
+
}, auth);
|
|
39
38
|
if (verifiedSchema.length) {
|
|
40
39
|
schema = verifiedSchema;
|
|
41
40
|
argsMain = deepMerge(buildArgsMainFromSchema(schema), overrides);
|
|
@@ -43,7 +42,7 @@ export class ToolRunCommandBase extends XtzCommand {
|
|
|
43
42
|
return {
|
|
44
43
|
agent,
|
|
45
44
|
argsMain,
|
|
46
|
-
auth
|
|
45
|
+
auth,
|
|
47
46
|
duid,
|
|
48
47
|
inputMode: inputMode.mode,
|
|
49
48
|
moduleId,
|
|
@@ -409,7 +408,7 @@ export default class PlotRun extends ToolRunCommandBase {
|
|
|
409
408
|
description: 'Tool route like app1.violin_flat in generic mode',
|
|
410
409
|
}),
|
|
411
410
|
token: Flags.string({
|
|
412
|
-
description: 'Bearer token for
|
|
411
|
+
description: 'Bearer token for agent.helixlife.net xiantao APIs; falls back to the stored agent token',
|
|
413
412
|
}),
|
|
414
413
|
toolProductUuid: Flags.string({
|
|
415
414
|
description: 'tool_product_uuid used to auto-resolve module UUID and route in generic mode; defaults to the main Xiantao product UUID',
|
|
@@ -40,7 +40,7 @@ export default class ToolExec extends ToolRunCommandBase {
|
|
|
40
40
|
description: 'Tool route like app1.violin_flat in generic mode',
|
|
41
41
|
}),
|
|
42
42
|
token: Flags.string({
|
|
43
|
-
description: 'Bearer token for
|
|
43
|
+
description: 'Bearer token for agent.helixlife.net xiantao APIs; falls back to the stored agent token',
|
|
44
44
|
}),
|
|
45
45
|
toolProductUuid: Flags.string({
|
|
46
46
|
description: 'tool_product_uuid used to auto-resolve module UUID and route in generic mode; defaults to the main Xiantao product UUID',
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import { XtzCommand } from '../../base-command.js';
|
|
3
|
+
import { getAuthContext } from '../../lib/auth.js';
|
|
3
4
|
import { globalFlags } from '../../lib/flags.js';
|
|
4
5
|
import { buildArgsMainFromSchema } from '../../lib/plot-schema.js';
|
|
5
6
|
import { isJsonObject } from '../../lib/json.js';
|
|
6
7
|
import { resolveXiantaoToolProductUuid } from '../../lib/storage.js';
|
|
7
|
-
import { getXiantaoAuthContext } from '../../lib/xiantao-auth.js';
|
|
8
8
|
import { fetchAllPlotArgs, resolveToolMenuByCode } from '../../lib/xiantao.js';
|
|
9
9
|
export default class ToolInspect extends XtzCommand {
|
|
10
10
|
static description = 'Inspect a Xiantao tool and its dynamic inputs';
|
|
@@ -17,7 +17,7 @@ export default class ToolInspect extends XtzCommand {
|
|
|
17
17
|
static flags = {
|
|
18
18
|
...globalFlags,
|
|
19
19
|
token: Flags.string({
|
|
20
|
-
description: 'Bearer token for
|
|
20
|
+
description: 'Bearer token for agent.helixlife.net xiantao APIs; falls back to the stored agent token',
|
|
21
21
|
}),
|
|
22
22
|
toolProductUuid: Flags.string({
|
|
23
23
|
description: 'tool_product_uuid used in the menu filter payload; defaults to the main Xiantao product UUID',
|
|
@@ -26,7 +26,7 @@ export default class ToolInspect extends XtzCommand {
|
|
|
26
26
|
async run() {
|
|
27
27
|
const { args, flags } = await this.parse(ToolInspect);
|
|
28
28
|
const agent = await this.getProfile(flags.profile);
|
|
29
|
-
const auth = await
|
|
29
|
+
const auth = await getAuthContext(agent, flags.token);
|
|
30
30
|
const toolProductUuid = await resolveXiantaoToolProductUuid(flags.toolProductUuid);
|
|
31
31
|
const resolved = await resolveToolMenuByCode(auth, toolProductUuid, args.tool);
|
|
32
32
|
const fetchAllResult = await fetchAllPlotArgs(auth, {
|
|
@@ -38,7 +38,7 @@ export default class ToolPrepare extends ToolRunCommandBase {
|
|
|
38
38
|
description: 'Tool route like app1.violin_flat in generic mode',
|
|
39
39
|
}),
|
|
40
40
|
token: Flags.string({
|
|
41
|
-
description: 'Bearer token for
|
|
41
|
+
description: 'Bearer token for agent.helixlife.net xiantao APIs; falls back to the stored agent token',
|
|
42
42
|
}),
|
|
43
43
|
toolProductUuid: Flags.string({
|
|
44
44
|
description: 'tool_product_uuid used to auto-resolve module UUID and route in generic mode; defaults to the main Xiantao product UUID',
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import { XtzCommand } from '../../base-command.js';
|
|
3
|
+
import { getAuthContext } from '../../lib/auth.js';
|
|
3
4
|
import { globalFlags } from '../../lib/flags.js';
|
|
4
5
|
import { resolveXiantaoToolProductUuid } from '../../lib/storage.js';
|
|
5
|
-
import { getXiantaoAuthContext } from '../../lib/xiantao-auth.js';
|
|
6
6
|
import { fetchToolMenus, searchResolvedToolMenus } from '../../lib/xiantao.js';
|
|
7
7
|
export default class ToolSearch extends XtzCommand {
|
|
8
8
|
static description = 'Search Xiantao tools by keyword';
|
|
@@ -15,7 +15,7 @@ export default class ToolSearch extends XtzCommand {
|
|
|
15
15
|
static flags = {
|
|
16
16
|
...globalFlags,
|
|
17
17
|
token: Flags.string({
|
|
18
|
-
description: 'Bearer token for
|
|
18
|
+
description: 'Bearer token for agent.helixlife.net xiantao APIs; falls back to the stored agent token',
|
|
19
19
|
}),
|
|
20
20
|
toolProductUuid: Flags.string({
|
|
21
21
|
description: 'tool_product_uuid used in the menu filter payload; defaults to the main Xiantao product UUID',
|
|
@@ -24,7 +24,7 @@ export default class ToolSearch extends XtzCommand {
|
|
|
24
24
|
async run() {
|
|
25
25
|
const { args, flags } = await this.parse(ToolSearch);
|
|
26
26
|
const agent = await this.getProfile(flags.profile);
|
|
27
|
-
const auth = await
|
|
27
|
+
const auth = await getAuthContext(agent, flags.token);
|
|
28
28
|
const toolProductUuid = await resolveXiantaoToolProductUuid(flags.toolProductUuid);
|
|
29
29
|
const tools = searchResolvedToolMenus(await fetchToolMenus(auth, toolProductUuid), args.keyword);
|
|
30
30
|
const output = {
|
package/dist/lib/auth.js
CHANGED
|
@@ -2,39 +2,41 @@ import { AUTH_URL_ENDPOINT, CHECK_TOKEN_ENDPOINT } from './constants.js';
|
|
|
2
2
|
import { XtzError } from './errors.js';
|
|
3
3
|
import { openUrl } from './open-url.js';
|
|
4
4
|
import { requestJson } from './http.js';
|
|
5
|
-
import { requireToken, saveToken } from './storage.js';
|
|
5
|
+
import { loadToken, requireToken, saveToken } from './storage.js';
|
|
6
6
|
export async function fetchAuthLink(agent) {
|
|
7
|
-
const
|
|
7
|
+
const token = await loadToken(agent);
|
|
8
|
+
const response = await requestJson(AUTH_URL_ENDPOINT, {
|
|
9
|
+
headers: buildAgentHeaders(token),
|
|
10
|
+
}, { agent });
|
|
8
11
|
const link = new URL(response.data.link_url);
|
|
9
12
|
link.searchParams.set('source', agent);
|
|
10
13
|
return {
|
|
11
|
-
authKey: response.data.auth_key,
|
|
12
14
|
linkUrl: link.toString(),
|
|
13
15
|
tokenKey: response.data.token_key,
|
|
14
16
|
};
|
|
15
17
|
}
|
|
16
18
|
export async function loginAuth(agent, openBrowser = true) {
|
|
17
|
-
const {
|
|
19
|
+
const { linkUrl, tokenKey } = await fetchAuthLink(agent);
|
|
18
20
|
const tokenPath = await saveToken(agent, tokenKey);
|
|
19
21
|
const browserOpened = openBrowser ? await openUrl(linkUrl) : false;
|
|
20
22
|
return {
|
|
21
|
-
authKey,
|
|
22
|
-
authorizedWithStoredXiantao: false,
|
|
23
23
|
browserOpened,
|
|
24
24
|
linkUrl,
|
|
25
25
|
token: tokenKey,
|
|
26
26
|
tokenPath,
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
-
export async function getAuthContext(agent) {
|
|
30
|
-
const token = await requireToken(agent);
|
|
29
|
+
export async function getAuthContext(agent, tokenOverride) {
|
|
30
|
+
const token = tokenOverride?.trim() || await requireToken(agent);
|
|
31
31
|
return { agent, token };
|
|
32
32
|
}
|
|
33
33
|
export async function checkToken(agent, token) {
|
|
34
34
|
const targetToken = token ?? await requireToken(agent);
|
|
35
35
|
const url = new URL(CHECK_TOKEN_ENDPOINT);
|
|
36
36
|
url.searchParams.set('token_key', targetToken);
|
|
37
|
-
const response = await requestJson(url.toString(), {
|
|
37
|
+
const response = await requestJson(url.toString(), {
|
|
38
|
+
headers: buildAgentHeaders(targetToken),
|
|
39
|
+
}, { agent });
|
|
38
40
|
return response.data;
|
|
39
41
|
}
|
|
40
42
|
export async function waitForAuthorizedToken(token, options = {}) {
|
|
@@ -50,14 +52,16 @@ export async function waitForAuthorizedToken(token, options = {}) {
|
|
|
50
52
|
lastMessage = result.message?.trim() || lastMessage;
|
|
51
53
|
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
52
54
|
}
|
|
53
|
-
throw new XtzError(lastMessage ?
|
|
55
|
+
throw new XtzError(lastMessage ? `等待授权完成超时: ${lastMessage}` : '等待授权完成超时,请重新运行 `xt login`。');
|
|
54
56
|
}
|
|
55
57
|
async function pollAuthorizedToken(token) {
|
|
56
58
|
const url = new URL(CHECK_TOKEN_ENDPOINT);
|
|
57
59
|
url.searchParams.set('token_key', token);
|
|
58
60
|
let response;
|
|
59
61
|
try {
|
|
60
|
-
response = await fetch(url.toString()
|
|
62
|
+
response = await fetch(url.toString(), {
|
|
63
|
+
headers: buildAgentHeaders(token),
|
|
64
|
+
});
|
|
61
65
|
}
|
|
62
66
|
catch {
|
|
63
67
|
throw new XtzError('网络请求失败,请稍后重试。');
|
|
@@ -74,3 +78,13 @@ async function pollAuthorizedToken(token) {
|
|
|
74
78
|
throw new XtzError('服务返回了无法解析的响应。');
|
|
75
79
|
}
|
|
76
80
|
}
|
|
81
|
+
function buildAgentHeaders(token) {
|
|
82
|
+
const headers = {
|
|
83
|
+
platform: 'pc',
|
|
84
|
+
source: 'PC',
|
|
85
|
+
};
|
|
86
|
+
if (token?.trim()) {
|
|
87
|
+
headers.Authorization = `Bearer ${token.trim()}`;
|
|
88
|
+
}
|
|
89
|
+
return headers;
|
|
90
|
+
}
|
package/dist/lib/constants.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
export const DEFAULT_AGENT = 'opencode';
|
|
2
2
|
export const XTZ_CONFIG_PATH = '.config/xtz/config.json';
|
|
3
|
-
export const
|
|
4
|
-
export const
|
|
5
|
-
export const
|
|
6
|
-
export const
|
|
7
|
-
export const XIANTAO_TOOL_MENUS_ENDPOINT = 'https://api.helixlife.net/api/v1/xiantaozi/tool/menus';
|
|
8
|
-
export const XIANTAO_MENU_CACHE_MAX_AGE_MS = 12 * 60 * 60 * 1000;
|
|
3
|
+
export const XTZ_UPDATE_CHECK_PATH = '.config/xtz/update-check.json';
|
|
4
|
+
export const AUTH_URL_ENDPOINT = 'https://agent.helixlife.net/api/v1/agent/skills/auth-url';
|
|
5
|
+
export const XIANTAO_BIO_API_ENDPOINT = 'https://agent.helixlife.net/api/v1/agent/bio-api';
|
|
6
|
+
export const XIANTAO_TOOL_MENUS_ENDPOINT = 'https://agent.helixlife.net/api/v1/agent/tool/menus';
|
|
9
7
|
export const DEFAULT_XIANTAO_TOOL_PRODUCT_UUID = 'c0b6febb-52dd-4525-970a-61bbe9e263ff';
|
|
10
8
|
export const CHECK_TOKEN_ENDPOINT = 'https://agent.helixlife.net/api/v1/agent/skills/check-token';
|
|
11
9
|
export const UPLOAD_CONFIG_ENDPOINT = 'https://agent.helixlife.net/api/v1/agent/upload/config?type=bio&oss_type=bio';
|
package/dist/lib/errors.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { deleteToken
|
|
1
|
+
import { deleteToken } from './storage.js';
|
|
2
2
|
export class XtzError extends Error {
|
|
3
3
|
constructor(message) {
|
|
4
4
|
super(message);
|
|
@@ -14,20 +14,11 @@ function isAuthExpiredMessage(message) {
|
|
|
14
14
|
export async function raiseApiError(payload, options = {}) {
|
|
15
15
|
const message = payload.message ?? '请求失败';
|
|
16
16
|
const code = String(payload.code ?? '');
|
|
17
|
-
const tokenKind = options.tokenKind ?? 'agent';
|
|
18
17
|
if (isAuthExpiredMessage(message)) {
|
|
19
18
|
if (options.agent) {
|
|
20
|
-
|
|
21
|
-
await deleteXiantaoToken(options.agent);
|
|
22
|
-
}
|
|
23
|
-
else {
|
|
24
|
-
await deleteToken(options.agent);
|
|
25
|
-
}
|
|
19
|
+
await deleteToken(options.agent);
|
|
26
20
|
}
|
|
27
|
-
|
|
28
|
-
throw new XtzError('您的仙桃网页授权已过期或无效,请重新运行 `xt login`。');
|
|
29
|
-
}
|
|
30
|
-
throw new XtzError('您的授权已过期或无效,请重新运行 `xt login`。');
|
|
21
|
+
throw new XtzError('您的授权已过期或无效,请重新完成授权。');
|
|
31
22
|
}
|
|
32
23
|
if (message.includes('无权限') || message.includes('权限')) {
|
|
33
24
|
throw new XtzError('需购买仙桃高级版 https://www.xiantaozi.com/');
|
|
@@ -38,6 +29,9 @@ export async function raiseApiError(payload, options = {}) {
|
|
|
38
29
|
if (code === '4000020101' || code === '400008') {
|
|
39
30
|
throw new XtzError('您的账号状态异常,请联系 https://www.helixlife.cn');
|
|
40
31
|
}
|
|
32
|
+
if (code === '400004') {
|
|
33
|
+
throw new XtzError('该菜单或商品已下架,请更换 tool_product_uuid 或 menu uuid。');
|
|
34
|
+
}
|
|
41
35
|
if (code === '50000' || code === '40000' || code === '40004') {
|
|
42
36
|
throw new XtzError('请前往 https://www.helixlife.cn 联系客服');
|
|
43
37
|
}
|
package/dist/lib/login-flow.js
CHANGED
|
@@ -1,19 +1,10 @@
|
|
|
1
1
|
import { loginAuth, waitForAuthorizedToken } from './auth.js';
|
|
2
|
-
import { openUrl } from './open-url.js';
|
|
3
|
-
import { bindUserWithStoredXiantaoToken, loginXiantao } from './xiantao-auth.js';
|
|
4
2
|
export async function loginAll(agent, openAuthBrowser = true, onProgress) {
|
|
5
|
-
const auth = await loginAuth(agent,
|
|
6
|
-
const reusedXiantao = await bindUserWithStoredXiantaoToken(agent, auth.authKey);
|
|
7
|
-
auth.authorizedWithStoredXiantao = Boolean(reusedXiantao);
|
|
8
|
-
if (!reusedXiantao && openAuthBrowser) {
|
|
9
|
-
auth.browserOpened = await openUrl(auth.linkUrl);
|
|
10
|
-
}
|
|
3
|
+
const auth = await loginAuth(agent, openAuthBrowser);
|
|
11
4
|
onProgress?.({ auth, phase: 'auth_started' });
|
|
12
5
|
await waitForAuthorizedToken(auth.token);
|
|
13
6
|
onProgress?.({ auth, phase: 'auth_confirmed' });
|
|
14
|
-
const xiantao = reusedXiantao ?? await loginXiantao(agent);
|
|
15
7
|
return {
|
|
16
8
|
auth,
|
|
17
|
-
xiantao,
|
|
18
9
|
};
|
|
19
10
|
}
|
package/dist/lib/storage.js
CHANGED
|
@@ -8,24 +8,12 @@ import { isJsonObject } from './json.js';
|
|
|
8
8
|
export function getTokenPath(agent) {
|
|
9
9
|
return path.join(os.homedir(), '.config', 'helix', agent, 'token-key.txt');
|
|
10
10
|
}
|
|
11
|
-
export function getXiantaoTokenPath(agent) {
|
|
12
|
-
return path.join(os.homedir(), '.config', 'helix', agent, 'xiantao-token.txt');
|
|
13
|
-
}
|
|
14
|
-
function getXiantaoMenuCachePath(agent, toolProductUuid) {
|
|
15
|
-
return path.join(os.homedir(), '.config', 'xtz', 'menus', agent, `${toolProductUuid}.json`);
|
|
16
|
-
}
|
|
17
11
|
export async function saveToken(agent, token) {
|
|
18
12
|
const tokenPath = getTokenPath(agent);
|
|
19
13
|
await mkdir(path.dirname(tokenPath), { recursive: true });
|
|
20
14
|
await writeFile(tokenPath, `${token.trim()}\n`, 'utf8');
|
|
21
15
|
return tokenPath;
|
|
22
16
|
}
|
|
23
|
-
export async function saveXiantaoToken(agent, token) {
|
|
24
|
-
const tokenPath = getXiantaoTokenPath(agent);
|
|
25
|
-
await mkdir(path.dirname(tokenPath), { recursive: true });
|
|
26
|
-
await writeFile(tokenPath, `${token.trim()}\n`, 'utf8');
|
|
27
|
-
return tokenPath;
|
|
28
|
-
}
|
|
29
17
|
export async function loadToken(agent) {
|
|
30
18
|
const tokenPath = getTokenPath(agent);
|
|
31
19
|
try {
|
|
@@ -39,19 +27,6 @@ export async function loadToken(agent) {
|
|
|
39
27
|
throw error;
|
|
40
28
|
}
|
|
41
29
|
}
|
|
42
|
-
export async function loadXiantaoToken(agent) {
|
|
43
|
-
const tokenPath = getXiantaoTokenPath(agent);
|
|
44
|
-
try {
|
|
45
|
-
const token = await readFile(tokenPath, 'utf8');
|
|
46
|
-
return token.trim() || undefined;
|
|
47
|
-
}
|
|
48
|
-
catch (error) {
|
|
49
|
-
if (isNodeError(error) && error.code === 'ENOENT') {
|
|
50
|
-
return undefined;
|
|
51
|
-
}
|
|
52
|
-
throw error;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
30
|
export async function requireToken(agent) {
|
|
56
31
|
const token = await loadToken(agent);
|
|
57
32
|
if (token) {
|
|
@@ -59,13 +34,6 @@ export async function requireToken(agent) {
|
|
|
59
34
|
}
|
|
60
35
|
throw new XtzError(`未找到 ${agent} 的 token,请先运行 \`xt login --profile ${agent}\``);
|
|
61
36
|
}
|
|
62
|
-
export async function requireXiantaoToken(agent) {
|
|
63
|
-
const token = await loadXiantaoToken(agent);
|
|
64
|
-
if (token) {
|
|
65
|
-
return token;
|
|
66
|
-
}
|
|
67
|
-
throw new XtzError(`未找到 ${agent} 的仙桃网页 token,请先运行 \`xt login --profile ${agent}\``);
|
|
68
|
-
}
|
|
69
37
|
export async function deleteToken(agent) {
|
|
70
38
|
const tokenPath = getTokenPath(agent);
|
|
71
39
|
try {
|
|
@@ -79,44 +47,6 @@ export async function deleteToken(agent) {
|
|
|
79
47
|
throw error;
|
|
80
48
|
}
|
|
81
49
|
}
|
|
82
|
-
export async function deleteXiantaoToken(agent) {
|
|
83
|
-
const tokenPath = getXiantaoTokenPath(agent);
|
|
84
|
-
try {
|
|
85
|
-
await rm(tokenPath);
|
|
86
|
-
return true;
|
|
87
|
-
}
|
|
88
|
-
catch (error) {
|
|
89
|
-
if (isNodeError(error) && error.code === 'ENOENT') {
|
|
90
|
-
return false;
|
|
91
|
-
}
|
|
92
|
-
throw error;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
export async function loadXiantaoMenuCache(agent, toolProductUuid, maxAgeMs) {
|
|
96
|
-
const cachePath = getXiantaoMenuCachePath(agent, toolProductUuid);
|
|
97
|
-
try {
|
|
98
|
-
const content = await readFile(cachePath, 'utf8');
|
|
99
|
-
const parsed = JSON.parse(content);
|
|
100
|
-
if (typeof parsed.cachedAt !== 'number' || Date.now() - parsed.cachedAt > maxAgeMs) {
|
|
101
|
-
return undefined;
|
|
102
|
-
}
|
|
103
|
-
return parsed.data;
|
|
104
|
-
}
|
|
105
|
-
catch (error) {
|
|
106
|
-
if (isNodeError(error) && error.code === 'ENOENT') {
|
|
107
|
-
return undefined;
|
|
108
|
-
}
|
|
109
|
-
throw error;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
export async function saveXiantaoMenuCache(agent, toolProductUuid, data) {
|
|
113
|
-
const cachePath = getXiantaoMenuCachePath(agent, toolProductUuid);
|
|
114
|
-
await mkdir(path.dirname(cachePath), { recursive: true });
|
|
115
|
-
await writeFile(cachePath, JSON.stringify({
|
|
116
|
-
cachedAt: Date.now(),
|
|
117
|
-
data,
|
|
118
|
-
}), 'utf8');
|
|
119
|
-
}
|
|
120
50
|
export async function readXtzConfig() {
|
|
121
51
|
const configPath = path.join(os.homedir(), XTZ_CONFIG_PATH);
|
|
122
52
|
try {
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { XTZ_UPDATE_CHECK_PATH } from './constants.js';
|
|
5
|
+
import { isJsonObject } from './json.js';
|
|
6
|
+
const NPM_PACKAGE_NAME = '@helixlife-ai/xiantao';
|
|
7
|
+
const NPM_LATEST_VERSION_URL = `https://registry.npmjs.org/${encodeURIComponent(NPM_PACKAGE_NAME)}/latest`;
|
|
8
|
+
const UPDATE_CHECK_INTERVAL_MS = 12 * 60 * 60 * 1000;
|
|
9
|
+
const UPDATE_CHECK_TIMEOUT_MS = 1200;
|
|
10
|
+
export async function getUpdateReminder(currentVersion) {
|
|
11
|
+
try {
|
|
12
|
+
const state = await readUpdateCheckState();
|
|
13
|
+
const latestVersion = await resolveLatestVersion(state);
|
|
14
|
+
if (!latestVersion || compareVersions(latestVersion, currentVersion) <= 0) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
return `检测到新版本 ${latestVersion}(当前 ${currentVersion}),可运行 \`npm install -g ${NPM_PACKAGE_NAME}@latest\` 更新。`;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function resolveLatestVersion(state) {
|
|
24
|
+
const now = Date.now();
|
|
25
|
+
const lastCheckedAt = state.lastCheckedAt ? Date.parse(state.lastCheckedAt) : Number.NaN;
|
|
26
|
+
if (Number.isFinite(lastCheckedAt) && now - lastCheckedAt < UPDATE_CHECK_INTERVAL_MS) {
|
|
27
|
+
return state.latestVersion?.trim() || undefined;
|
|
28
|
+
}
|
|
29
|
+
const latestVersion = await fetchLatestVersion();
|
|
30
|
+
await writeUpdateCheckState({
|
|
31
|
+
lastCheckedAt: new Date(now).toISOString(),
|
|
32
|
+
latestVersion: latestVersion ?? state.latestVersion,
|
|
33
|
+
});
|
|
34
|
+
return latestVersion ?? (state.latestVersion?.trim() || undefined);
|
|
35
|
+
}
|
|
36
|
+
async function fetchLatestVersion() {
|
|
37
|
+
const response = await fetch(NPM_LATEST_VERSION_URL, {
|
|
38
|
+
headers: {
|
|
39
|
+
accept: 'application/json',
|
|
40
|
+
},
|
|
41
|
+
signal: AbortSignal.timeout(UPDATE_CHECK_TIMEOUT_MS),
|
|
42
|
+
});
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const payload = JSON.parse(await response.text());
|
|
47
|
+
if (!isJsonObject(payload) || typeof payload.version !== 'string') {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
return payload.version.trim() || undefined;
|
|
51
|
+
}
|
|
52
|
+
async function readUpdateCheckState() {
|
|
53
|
+
const filePath = getUpdateCheckPath();
|
|
54
|
+
try {
|
|
55
|
+
const content = await readFile(filePath, 'utf8');
|
|
56
|
+
const parsed = JSON.parse(content);
|
|
57
|
+
if (!isJsonObject(parsed)) {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
lastCheckedAt: typeof parsed.lastCheckedAt === 'string' ? parsed.lastCheckedAt : undefined,
|
|
62
|
+
latestVersion: typeof parsed.latestVersion === 'string' ? parsed.latestVersion : undefined,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (isNodeError(error) && error.code === 'ENOENT') {
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function writeUpdateCheckState(state) {
|
|
73
|
+
const filePath = getUpdateCheckPath();
|
|
74
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
75
|
+
await writeFile(filePath, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
|
|
76
|
+
}
|
|
77
|
+
function compareVersions(left, right) {
|
|
78
|
+
const parsedLeft = parseVersion(left);
|
|
79
|
+
const parsedRight = parseVersion(right);
|
|
80
|
+
if (!parsedLeft || !parsedRight) {
|
|
81
|
+
return left.localeCompare(right);
|
|
82
|
+
}
|
|
83
|
+
const coreLength = Math.max(parsedLeft.core.length, parsedRight.core.length);
|
|
84
|
+
for (let index = 0; index < coreLength; index += 1) {
|
|
85
|
+
const delta = (parsedLeft.core[index] ?? 0) - (parsedRight.core[index] ?? 0);
|
|
86
|
+
if (delta !== 0) {
|
|
87
|
+
return delta;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (parsedLeft.prerelease.length === 0 && parsedRight.prerelease.length === 0) {
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
if (parsedLeft.prerelease.length === 0) {
|
|
94
|
+
return 1;
|
|
95
|
+
}
|
|
96
|
+
if (parsedRight.prerelease.length === 0) {
|
|
97
|
+
return -1;
|
|
98
|
+
}
|
|
99
|
+
const prereleaseLength = Math.max(parsedLeft.prerelease.length, parsedRight.prerelease.length);
|
|
100
|
+
for (let index = 0; index < prereleaseLength; index += 1) {
|
|
101
|
+
const leftPart = parsedLeft.prerelease[index];
|
|
102
|
+
const rightPart = parsedRight.prerelease[index];
|
|
103
|
+
if (leftPart === undefined) {
|
|
104
|
+
return -1;
|
|
105
|
+
}
|
|
106
|
+
if (rightPart === undefined) {
|
|
107
|
+
return 1;
|
|
108
|
+
}
|
|
109
|
+
const leftIsNumber = /^[0-9]+$/.test(leftPart);
|
|
110
|
+
const rightIsNumber = /^[0-9]+$/.test(rightPart);
|
|
111
|
+
if (leftIsNumber && rightIsNumber) {
|
|
112
|
+
const delta = Number(leftPart) - Number(rightPart);
|
|
113
|
+
if (delta !== 0) {
|
|
114
|
+
return delta;
|
|
115
|
+
}
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (leftIsNumber) {
|
|
119
|
+
return -1;
|
|
120
|
+
}
|
|
121
|
+
if (rightIsNumber) {
|
|
122
|
+
return 1;
|
|
123
|
+
}
|
|
124
|
+
const delta = leftPart.localeCompare(rightPart);
|
|
125
|
+
if (delta !== 0) {
|
|
126
|
+
return delta;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
function parseVersion(version) {
|
|
132
|
+
const normalized = version.trim().replace(/^v/, '');
|
|
133
|
+
if (!normalized) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const [corePart, prereleasePart] = normalized.split('-', 2);
|
|
137
|
+
const core = corePart.split('.').map((part) => Number.parseInt(part, 10));
|
|
138
|
+
if (core.some((part) => Number.isNaN(part))) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
core,
|
|
143
|
+
prerelease: prereleasePart ? prereleasePart.split('.').filter(Boolean) : [],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function getUpdateCheckPath() {
|
|
147
|
+
return path.join(os.homedir(), XTZ_UPDATE_CHECK_PATH);
|
|
148
|
+
}
|
|
149
|
+
function isNodeError(error) {
|
|
150
|
+
return error instanceof Error && 'code' in error;
|
|
151
|
+
}
|
package/dist/lib/xiantao-plot.js
CHANGED
|
@@ -28,7 +28,7 @@ export async function verifyXiantaoPlotUpload(auth, input) {
|
|
|
28
28
|
}),
|
|
29
29
|
headers: buildXiantaoHeaders(auth, true),
|
|
30
30
|
method: 'POST',
|
|
31
|
-
}, { agent: auth.agent
|
|
31
|
+
}, { agent: auth.agent });
|
|
32
32
|
const duid = response.data.id?.duid;
|
|
33
33
|
if (!duid) {
|
|
34
34
|
throw new XtzError(response.message ?? '未获取到 duid');
|
|
@@ -68,7 +68,7 @@ export async function submitXiantaoPlot(auth, input) {
|
|
|
68
68
|
}),
|
|
69
69
|
headers: buildXiantaoHeaders(auth, true),
|
|
70
70
|
method: 'POST',
|
|
71
|
-
}, { agent: auth.agent
|
|
71
|
+
}, { agent: auth.agent });
|
|
72
72
|
const downloads = collectDownloadItems(response.data.data);
|
|
73
73
|
const file = typeof response.data.file === 'string' && response.data.file.trim()
|
|
74
74
|
? response.data.file.trim()
|
|
@@ -105,7 +105,7 @@ export async function resolveXiantaoDownload(auth, input) {
|
|
|
105
105
|
}),
|
|
106
106
|
headers: buildXiantaoHeaders(auth, true),
|
|
107
107
|
method: 'POST',
|
|
108
|
-
}, { agent: auth.agent
|
|
108
|
+
}, { agent: auth.agent });
|
|
109
109
|
const url = response.data.url?.trim();
|
|
110
110
|
if (!url) {
|
|
111
111
|
throw new XtzError(response.message ?? '未获取到下载链接');
|
package/dist/lib/xiantao.js
CHANGED
|
@@ -1,21 +1,28 @@
|
|
|
1
|
-
import { XIANTAO_BIO_API_ENDPOINT,
|
|
1
|
+
import { XIANTAO_BIO_API_ENDPOINT, XIANTAO_TOOL_MENUS_ENDPOINT } from './constants.js';
|
|
2
2
|
import { XtzError } from './errors.js';
|
|
3
3
|
import { requestJson } from './http.js';
|
|
4
|
-
import { loadXiantaoMenuCache, saveXiantaoMenuCache } from './storage.js';
|
|
5
4
|
export async function fetchToolMenu(auth, menuUuid, toolProductUuid) {
|
|
6
5
|
const url = new URL(`${XIANTAO_TOOL_MENUS_ENDPOINT}/${menuUuid}`);
|
|
7
6
|
url.searchParams.set('f', encodeMenuFilter({ tool_product_uuid: toolProductUuid }));
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
try {
|
|
8
|
+
const response = await requestJson(url.toString(), {
|
|
9
|
+
headers: buildXiantaoHeaders(auth),
|
|
10
|
+
method: 'GET',
|
|
11
|
+
}, { agent: auth.agent });
|
|
12
|
+
return response.data;
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
if (!(error instanceof XtzError) || !error.message.includes('已下架')) {
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
const fallback = findToolMenu(await fetchToolMenus(auth, toolProductUuid), menuUuid);
|
|
19
|
+
if (fallback) {
|
|
20
|
+
return fallback;
|
|
21
|
+
}
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
13
24
|
}
|
|
14
25
|
export async function fetchToolMenus(auth, toolProductUuid) {
|
|
15
|
-
const cached = await loadXiantaoMenuCache(auth.agent, toolProductUuid, XIANTAO_MENU_CACHE_MAX_AGE_MS);
|
|
16
|
-
if (cached) {
|
|
17
|
-
return cached;
|
|
18
|
-
}
|
|
19
26
|
const url = new URL(XIANTAO_TOOL_MENUS_ENDPOINT);
|
|
20
27
|
url.searchParams.set('f', encodeMenuFilter({
|
|
21
28
|
is_all: 1,
|
|
@@ -24,8 +31,7 @@ export async function fetchToolMenus(auth, toolProductUuid) {
|
|
|
24
31
|
const response = await requestJson(url.toString(), {
|
|
25
32
|
headers: buildXiantaoHeaders(auth),
|
|
26
33
|
method: 'GET',
|
|
27
|
-
}, { agent: auth.agent
|
|
28
|
-
await saveXiantaoMenuCache(auth.agent, toolProductUuid, response.data);
|
|
34
|
+
}, { agent: auth.agent });
|
|
29
35
|
return response.data;
|
|
30
36
|
}
|
|
31
37
|
export async function resolveToolMenuByCode(auth, toolProductUuid, code) {
|
|
@@ -70,7 +76,7 @@ export async function fetchAllPlotArgs(auth, input) {
|
|
|
70
76
|
}),
|
|
71
77
|
headers: buildXiantaoHeaders(auth, true),
|
|
72
78
|
method: 'POST',
|
|
73
|
-
}, { agent: auth.agent
|
|
79
|
+
}, { agent: auth.agent });
|
|
74
80
|
return response.data;
|
|
75
81
|
}
|
|
76
82
|
function collectToolMenuMatches(menus, code) {
|
|
@@ -97,6 +103,21 @@ function collectToolMenuMatches(menus, code) {
|
|
|
97
103
|
}
|
|
98
104
|
return matches;
|
|
99
105
|
}
|
|
106
|
+
function findToolMenu(menus, target) {
|
|
107
|
+
const normalizedTarget = target.trim();
|
|
108
|
+
for (const menu of menus) {
|
|
109
|
+
if (menu.uuid?.trim() === normalizedTarget || String(menu.id ?? '').trim() === normalizedTarget) {
|
|
110
|
+
return menu;
|
|
111
|
+
}
|
|
112
|
+
if (menu.children?.length) {
|
|
113
|
+
const nested = findToolMenu(menu.children, normalizedTarget);
|
|
114
|
+
if (nested) {
|
|
115
|
+
return nested;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
100
121
|
function scoreToolMenuMatch(menu, keyword) {
|
|
101
122
|
let score = 0;
|
|
102
123
|
const code = menu.code.toLowerCase();
|
|
@@ -130,15 +151,14 @@ function scoreToolMenuMatch(menu, keyword) {
|
|
|
130
151
|
return score;
|
|
131
152
|
}
|
|
132
153
|
function encodeMenuFilter(filter) {
|
|
133
|
-
|
|
134
|
-
return Buffer.from(encoded, 'utf8').toString('base64');
|
|
154
|
+
return encodeURIComponent(JSON.stringify(filter));
|
|
135
155
|
}
|
|
136
156
|
export function buildXiantaoHeaders(auth, hasJsonBody = false) {
|
|
137
157
|
const headers = {
|
|
138
158
|
Authorization: `Bearer ${auth.token}`,
|
|
139
|
-
origin: 'https://
|
|
159
|
+
origin: 'https://agent.helixlife.net',
|
|
140
160
|
platform: 'pc',
|
|
141
|
-
referer: 'https://
|
|
161
|
+
referer: 'https://agent.helixlife.net',
|
|
142
162
|
source: 'PC',
|
|
143
163
|
};
|
|
144
164
|
if (hasJsonBody) {
|
package/dist/xtz-help.js
CHANGED
|
@@ -11,15 +11,15 @@ export default class XtzHelp extends Help {
|
|
|
11
11
|
this.log('');
|
|
12
12
|
this.log(this.section('COMMON COMMANDS', this.renderCommandItems([
|
|
13
13
|
{
|
|
14
|
-
description: '
|
|
14
|
+
description: 'Authorize the current profile token',
|
|
15
15
|
id: 'login',
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
|
-
description: 'Show
|
|
18
|
+
description: 'Show token auth status for the current profile',
|
|
19
19
|
id: 'status',
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
|
-
description: 'Delete the stored
|
|
22
|
+
description: 'Delete the stored token for the current profile',
|
|
23
23
|
id: 'logout',
|
|
24
24
|
},
|
|
25
25
|
{
|
package/package.json
CHANGED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { Flags } from '@oclif/core';
|
|
2
|
-
import { XtzCommand } from '../../base-command.js';
|
|
3
|
-
import { loginAuth, waitForAuthorizedToken } from '../../lib/auth.js';
|
|
4
|
-
import { globalFlags } from '../../lib/flags.js';
|
|
5
|
-
import { bindUserWithXiantaoToken } from '../../lib/xiantao-auth.js';
|
|
6
|
-
import { getTokenPath, getXiantaoTokenPath, loadXiantaoToken, saveToken } from '../../lib/storage.js';
|
|
7
|
-
export default class AuthBind extends XtzCommand {
|
|
8
|
-
static description = 'Bind login remotely with a Xiantao web token without the localhost callback flow';
|
|
9
|
-
static flags = {
|
|
10
|
-
...globalFlags,
|
|
11
|
-
'auth-key': Flags.string({
|
|
12
|
-
char: 'k',
|
|
13
|
-
description: 'Existing auth key returned by the upload authorization flow',
|
|
14
|
-
}),
|
|
15
|
-
'token-key': Flags.string({
|
|
16
|
-
description: 'Existing upload token key paired with --auth-key',
|
|
17
|
-
}),
|
|
18
|
-
'xiantao-token': Flags.string({
|
|
19
|
-
description: 'Bearer token for api.helixlife.net xiantao APIs; falls back to XIANTAO_TOKEN or the stored Xiantao web token',
|
|
20
|
-
}),
|
|
21
|
-
};
|
|
22
|
-
async run() {
|
|
23
|
-
this.warnIfLegacyAuthCommand();
|
|
24
|
-
const { flags } = await this.parse(AuthBind);
|
|
25
|
-
const agent = await this.getProfile(flags.profile);
|
|
26
|
-
const token = flags['xiantao-token']?.trim() || process.env.XIANTAO_TOKEN?.trim() || await loadXiantaoToken(agent);
|
|
27
|
-
if (!token) {
|
|
28
|
-
this.error(`未找到 ${agent} 的仙桃网页 token,请传 --xiantao-token、设置 XIANTAO_TOKEN,或先准备 ${getXiantaoTokenPath(agent)}`, { exit: 1 });
|
|
29
|
-
}
|
|
30
|
-
const auth = flags['auth-key']
|
|
31
|
-
? {
|
|
32
|
-
authKey: flags['auth-key'],
|
|
33
|
-
token: flags['token-key']?.trim(),
|
|
34
|
-
tokenPath: getTokenPath(agent),
|
|
35
|
-
}
|
|
36
|
-
: await loginAuth(agent, false);
|
|
37
|
-
if (!auth.token) {
|
|
38
|
-
this.error('传 --auth-key 时必须同时传 --token-key', { exit: 1 });
|
|
39
|
-
}
|
|
40
|
-
if (flags['auth-key']) {
|
|
41
|
-
await saveToken(agent, auth.token);
|
|
42
|
-
}
|
|
43
|
-
const result = await bindUserWithXiantaoToken(agent, auth.authKey, token);
|
|
44
|
-
await waitForAuthorizedToken(auth.token);
|
|
45
|
-
this.print(flags, {
|
|
46
|
-
agent,
|
|
47
|
-
auth_key: auth.authKey,
|
|
48
|
-
bound: true,
|
|
49
|
-
token_path: auth.tokenPath,
|
|
50
|
-
xiantao_token_path: result.tokenPath,
|
|
51
|
-
}, `profile: ${agent}\nbound: true\nauth_key: ${auth.authKey}\ntoken: ${auth.tokenPath}\nxiantao_token: ${result.tokenPath}`);
|
|
52
|
-
}
|
|
53
|
-
}
|
package/dist/commands/bind.js
DELETED