@helixlife-ai/xiantao 0.1.2 → 0.1.3
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 +3 -3
- package/dist/commands/auth/login.js +22 -16
- package/dist/commands/plot/login.js +30 -20
- package/dist/lib/auth.js +39 -17
- package/dist/lib/errors.js +2 -2
- package/dist/lib/login-flow.js +13 -0
- package/dist/lib/storage.js +2 -2
- package/dist/lib/xiantao-auth.js +1 -17
- package/dist/xtz-help.js +3 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -15,13 +15,13 @@ Install with `npm i -g @helixlife-ai/xiantao` and run it as `xt`.
|
|
|
15
15
|
### First Use
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
xt
|
|
18
|
+
xt login
|
|
19
19
|
xt tool search violin
|
|
20
20
|
xt tool inspect violin_flat
|
|
21
21
|
xt tool run violin_flat ./data.xlsx
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
`xt
|
|
24
|
+
`xt login` is the recommended first-use entrypoint. It refreshes both the upload token and the Xiantao web token for the current profile. `xt tool login` is kept as an equivalent entrypoint under the `tool` topic.
|
|
25
25
|
|
|
26
26
|
### Interactive Tool Run
|
|
27
27
|
|
|
@@ -67,7 +67,7 @@ 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 cached or 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 upload auth
|
|
70
|
+
`xt tool search <keyword>` searches cached or 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 upload auth. Run `xt login` first to refresh both tokens in one step.
|
|
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
|
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import { XtzCommand } from '../../base-command.js';
|
|
3
|
-
import { loginAuth } from '../../lib/auth.js';
|
|
4
3
|
import { globalFlags } from '../../lib/flags.js';
|
|
4
|
+
import { loginAll } from '../../lib/login-flow.js';
|
|
5
5
|
import { getTokenPath, getXiantaoTokenPath } from '../../lib/storage.js';
|
|
6
|
-
import { ensureXiantaoLogin } from '../../lib/xiantao-auth.js';
|
|
7
6
|
export default class AuthLogin extends XtzCommand {
|
|
8
|
-
static description = '
|
|
7
|
+
static description = 'Sign in for the current profile and refresh both the upload token and the Xiantao web token';
|
|
9
8
|
static flags = {
|
|
10
9
|
...globalFlags,
|
|
11
10
|
open: Flags.boolean({
|
|
@@ -18,28 +17,35 @@ export default class AuthLogin extends XtzCommand {
|
|
|
18
17
|
this.warnIfLegacyAuthCommand();
|
|
19
18
|
const { flags } = await this.parse(AuthLogin);
|
|
20
19
|
const agent = await this.getProfile(flags.profile);
|
|
21
|
-
const
|
|
22
|
-
|
|
20
|
+
const result = await loginAll(agent, flags.open, ({ auth, phase }) => {
|
|
21
|
+
if (flags.json) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (phase === 'auth_started') {
|
|
25
|
+
this.log(auth.browserOpened ? '浏览器已打开上传授权链接,等待授权完成...' : `请先在浏览器中打开上传授权链接: ${auth.linkUrl}`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
this.log('上传授权已确认,继续仙桃网页登录...');
|
|
29
|
+
});
|
|
23
30
|
this.print(flags, {
|
|
24
31
|
agent,
|
|
25
|
-
browser_opened:
|
|
26
|
-
link_url:
|
|
27
|
-
token_path:
|
|
32
|
+
browser_opened: result.auth.browserOpened,
|
|
33
|
+
link_url: result.auth.linkUrl,
|
|
34
|
+
token_path: result.auth.tokenPath,
|
|
28
35
|
xiantao: {
|
|
29
|
-
browser_opened:
|
|
30
|
-
callback_url:
|
|
31
|
-
jump_url:
|
|
32
|
-
|
|
33
|
-
token_path: xiantaoResult.tokenPath,
|
|
36
|
+
browser_opened: result.xiantao.browserOpened,
|
|
37
|
+
callback_url: result.xiantao.callbackUrl,
|
|
38
|
+
jump_url: result.xiantao.jumpUrl,
|
|
39
|
+
token_path: result.xiantao.tokenPath,
|
|
34
40
|
},
|
|
35
41
|
}, [
|
|
36
42
|
`profile: ${agent}`,
|
|
37
43
|
`token: ${getTokenPath(agent)}`,
|
|
38
44
|
`xiantao_token: ${getXiantaoTokenPath(agent)}`,
|
|
39
|
-
|
|
45
|
+
result.xiantao.browserOpened ? '仙桃网页登录成功,token 已刷新。' : `请在浏览器中打开: ${result.xiantao.jumpUrl}`,
|
|
46
|
+
result.auth.browserOpened
|
|
40
47
|
? '浏览器已打开授权链接,请完成授权后运行 `xt status`。'
|
|
41
|
-
: `请在浏览器中打开: ${
|
|
42
|
-
xiantaoResult.created ? '已自动补齐仙桃网页登录 token。' : '仙桃网页登录 token 已存在,跳过补齐。',
|
|
48
|
+
: `请在浏览器中打开: ${result.auth.linkUrl}`,
|
|
43
49
|
].join('\n'));
|
|
44
50
|
}
|
|
45
51
|
}
|
|
@@ -1,41 +1,51 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
1
2
|
import { XtzCommand } from '../../base-command.js';
|
|
2
|
-
import { ensureAuthLogin } from '../../lib/auth.js';
|
|
3
3
|
import { globalFlags } from '../../lib/flags.js';
|
|
4
|
+
import { loginAll } from '../../lib/login-flow.js';
|
|
4
5
|
import { getTokenPath, getXiantaoTokenPath } from '../../lib/storage.js';
|
|
5
|
-
import { loginXiantao } from '../../lib/xiantao-auth.js';
|
|
6
6
|
export default class PlotLogin extends XtzCommand {
|
|
7
|
-
static description = 'Sign in
|
|
7
|
+
static description = 'Sign in for the current profile and refresh both the upload token and the Xiantao web token';
|
|
8
8
|
static flags = {
|
|
9
9
|
...globalFlags,
|
|
10
|
+
open: Flags.boolean({
|
|
11
|
+
allowNo: true,
|
|
12
|
+
default: true,
|
|
13
|
+
description: 'Open the authorization link in the browser',
|
|
14
|
+
}),
|
|
10
15
|
};
|
|
11
16
|
async run() {
|
|
12
17
|
this.warnIfLegacyPlotCommand();
|
|
13
18
|
const { flags } = await this.parse(PlotLogin);
|
|
14
19
|
const agent = await this.getProfile(flags.profile);
|
|
15
|
-
const result = await
|
|
16
|
-
|
|
20
|
+
const result = await loginAll(agent, flags.open, ({ auth, phase }) => {
|
|
21
|
+
if (flags.json) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (phase === 'auth_started') {
|
|
25
|
+
this.log(auth.browserOpened ? '浏览器已打开上传授权链接,等待授权完成...' : `请先在浏览器中打开上传授权链接: ${auth.linkUrl}`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
this.log('上传授权已确认,继续仙桃网页登录...');
|
|
29
|
+
});
|
|
17
30
|
this.print(flags, {
|
|
18
31
|
agent,
|
|
19
|
-
browser_opened: result.browserOpened,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
token_path: authResult.tokenPath,
|
|
32
|
+
browser_opened: result.auth.browserOpened,
|
|
33
|
+
link_url: result.auth.linkUrl,
|
|
34
|
+
token_path: result.auth.tokenPath,
|
|
35
|
+
xiantao: {
|
|
36
|
+
browser_opened: result.xiantao.browserOpened,
|
|
37
|
+
callback_url: result.xiantao.callbackUrl,
|
|
38
|
+
jump_url: result.xiantao.jumpUrl,
|
|
39
|
+
token_path: result.xiantao.tokenPath,
|
|
28
40
|
},
|
|
29
41
|
}, [
|
|
30
42
|
`profile: ${agent}`,
|
|
31
43
|
`token: ${getTokenPath(agent)}`,
|
|
32
44
|
`xiantao_token: ${getXiantaoTokenPath(agent)}`,
|
|
33
|
-
result.browserOpened ? '仙桃网页登录成功,token
|
|
34
|
-
|
|
35
|
-
?
|
|
36
|
-
|
|
37
|
-
: `已自动补齐上传授权 token,请在浏览器中打开: ${authResult.linkUrl}`
|
|
38
|
-
: '上传授权 token 已存在,跳过补齐。',
|
|
45
|
+
result.xiantao.browserOpened ? '仙桃网页登录成功,token 已刷新。' : `请在浏览器中打开: ${result.xiantao.jumpUrl}`,
|
|
46
|
+
result.auth.browserOpened
|
|
47
|
+
? '浏览器已打开授权链接,请完成授权后运行 `xt status`。'
|
|
48
|
+
: `请在浏览器中打开: ${result.auth.linkUrl}`,
|
|
39
49
|
].join('\n'));
|
|
40
50
|
}
|
|
41
51
|
}
|
package/dist/lib/auth.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { AUTH_URL_ENDPOINT, CHECK_TOKEN_ENDPOINT } from './constants.js';
|
|
2
|
+
import { XtzError } from './errors.js';
|
|
2
3
|
import { openUrl } from './open-url.js';
|
|
3
4
|
import { requestJson } from './http.js';
|
|
4
|
-
import {
|
|
5
|
+
import { requireToken, saveToken } from './storage.js';
|
|
5
6
|
export async function fetchAuthLink(agent) {
|
|
6
7
|
const response = await requestJson(AUTH_URL_ENDPOINT);
|
|
7
8
|
const link = new URL(response.data.link_url);
|
|
@@ -22,22 +23,6 @@ export async function loginAuth(agent, openBrowser = true) {
|
|
|
22
23
|
tokenPath,
|
|
23
24
|
};
|
|
24
25
|
}
|
|
25
|
-
export async function ensureAuthLogin(agent, openBrowser = true) {
|
|
26
|
-
const token = await loadToken(agent);
|
|
27
|
-
if (token) {
|
|
28
|
-
return {
|
|
29
|
-
browserOpened: false,
|
|
30
|
-
created: false,
|
|
31
|
-
token,
|
|
32
|
-
tokenPath: getTokenPath(agent),
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
const result = await loginAuth(agent, openBrowser);
|
|
36
|
-
return {
|
|
37
|
-
...result,
|
|
38
|
-
created: true,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
26
|
export async function getAuthContext(agent) {
|
|
42
27
|
const token = await requireToken(agent);
|
|
43
28
|
return { agent, token };
|
|
@@ -49,3 +34,40 @@ export async function checkToken(agent, token) {
|
|
|
49
34
|
const response = await requestJson(url.toString(), {}, { agent });
|
|
50
35
|
return response.data;
|
|
51
36
|
}
|
|
37
|
+
export async function waitForAuthorizedToken(token, options = {}) {
|
|
38
|
+
const timeoutMs = options.timeoutMs ?? 2 * 60 * 1000;
|
|
39
|
+
const intervalMs = options.intervalMs ?? 1500;
|
|
40
|
+
const deadline = Date.now() + timeoutMs;
|
|
41
|
+
let lastMessage = '';
|
|
42
|
+
while (Date.now() < deadline) {
|
|
43
|
+
const result = await pollAuthorizedToken(token);
|
|
44
|
+
if (result.success) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
lastMessage = result.message?.trim() || lastMessage;
|
|
48
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
49
|
+
}
|
|
50
|
+
throw new XtzError(lastMessage ? `等待上传授权完成超时: ${lastMessage}` : '等待上传授权完成超时,请重新运行 `xt login`。');
|
|
51
|
+
}
|
|
52
|
+
async function pollAuthorizedToken(token) {
|
|
53
|
+
const url = new URL(CHECK_TOKEN_ENDPOINT);
|
|
54
|
+
url.searchParams.set('token_key', token);
|
|
55
|
+
let response;
|
|
56
|
+
try {
|
|
57
|
+
response = await fetch(url.toString());
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
throw new XtzError('网络请求失败,请稍后重试。');
|
|
61
|
+
}
|
|
62
|
+
const body = await response.text();
|
|
63
|
+
try {
|
|
64
|
+
const payload = JSON.parse(body);
|
|
65
|
+
return {
|
|
66
|
+
message: payload.message,
|
|
67
|
+
success: response.ok && payload.success !== false,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
throw new XtzError('服务返回了无法解析的响应。');
|
|
72
|
+
}
|
|
73
|
+
}
|
package/dist/lib/errors.js
CHANGED
|
@@ -19,9 +19,9 @@ export async function raiseApiError(payload, options = {}) {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
if (tokenKind === 'xiantao') {
|
|
22
|
-
throw new XtzError('您的仙桃网页授权已过期或无效,请重新运行 `xt
|
|
22
|
+
throw new XtzError('您的仙桃网页授权已过期或无效,请重新运行 `xt login`。');
|
|
23
23
|
}
|
|
24
|
-
throw new XtzError('您的授权已过期或无效,请重新运行 `xt login
|
|
24
|
+
throw new XtzError('您的授权已过期或无效,请重新运行 `xt login`。');
|
|
25
25
|
}
|
|
26
26
|
if (message.includes('无权限') || message.includes('权限')) {
|
|
27
27
|
throw new XtzError('需购买仙桃高级版 https://www.xiantaozi.com/');
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { loginAuth, waitForAuthorizedToken } from './auth.js';
|
|
2
|
+
import { loginXiantao } from './xiantao-auth.js';
|
|
3
|
+
export async function loginAll(agent, openAuthBrowser = true, onProgress) {
|
|
4
|
+
const auth = await loginAuth(agent, openAuthBrowser);
|
|
5
|
+
onProgress?.({ auth, phase: 'auth_started' });
|
|
6
|
+
await waitForAuthorizedToken(auth.token);
|
|
7
|
+
onProgress?.({ auth, phase: 'auth_confirmed' });
|
|
8
|
+
const xiantao = await loginXiantao(agent);
|
|
9
|
+
return {
|
|
10
|
+
auth,
|
|
11
|
+
xiantao,
|
|
12
|
+
};
|
|
13
|
+
}
|
package/dist/lib/storage.js
CHANGED
|
@@ -57,14 +57,14 @@ export async function requireToken(agent) {
|
|
|
57
57
|
if (token) {
|
|
58
58
|
return token;
|
|
59
59
|
}
|
|
60
|
-
throw new XtzError(`未找到 ${agent} 的 token,请先运行 \`xt login --profile ${agent}
|
|
60
|
+
throw new XtzError(`未找到 ${agent} 的 token,请先运行 \`xt login --profile ${agent}\``);
|
|
61
61
|
}
|
|
62
62
|
export async function requireXiantaoToken(agent) {
|
|
63
63
|
const token = await loadXiantaoToken(agent);
|
|
64
64
|
if (token) {
|
|
65
65
|
return token;
|
|
66
66
|
}
|
|
67
|
-
throw new XtzError(`未找到 ${agent} 的仙桃网页 token,请先运行 \`xt
|
|
67
|
+
throw new XtzError(`未找到 ${agent} 的仙桃网页 token,请先运行 \`xt login --profile ${agent}\``);
|
|
68
68
|
}
|
|
69
69
|
export async function deleteToken(agent) {
|
|
70
70
|
const tokenPath = getTokenPath(agent);
|
package/dist/lib/xiantao-auth.js
CHANGED
|
@@ -2,7 +2,7 @@ import http from 'node:http';
|
|
|
2
2
|
import { PASSPORT_JUMP_ENDPOINT } from './constants.js';
|
|
3
3
|
import { openUrl } from './open-url.js';
|
|
4
4
|
import { XtzError } from './errors.js';
|
|
5
|
-
import {
|
|
5
|
+
import { loadXiantaoToken, requireXiantaoToken, saveXiantaoToken } from './storage.js';
|
|
6
6
|
export async function getXiantaoAuthContext(agent, tokenOverride) {
|
|
7
7
|
const token = tokenOverride?.trim() || process.env.XIANTAO_TOKEN?.trim() || await requireXiantaoToken(agent);
|
|
8
8
|
return { agent, token };
|
|
@@ -27,22 +27,6 @@ export async function loginXiantao(agent) {
|
|
|
27
27
|
tokenPath,
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
-
export async function ensureXiantaoLogin(agent) {
|
|
31
|
-
const token = await loadXiantaoToken(agent);
|
|
32
|
-
if (token) {
|
|
33
|
-
return {
|
|
34
|
-
browserOpened: false,
|
|
35
|
-
created: false,
|
|
36
|
-
token,
|
|
37
|
-
tokenPath: getXiantaoTokenPath(agent),
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
const result = await loginXiantao(agent);
|
|
41
|
-
return {
|
|
42
|
-
...result,
|
|
43
|
-
created: true,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
30
|
async function createLoginSession() {
|
|
47
31
|
let resolveToken;
|
|
48
32
|
let rejectToken;
|
package/dist/xtz-help.js
CHANGED
|
@@ -4,14 +4,14 @@ export default class XtzHelp extends Help {
|
|
|
4
4
|
this.log(this.formatRoot());
|
|
5
5
|
this.log('');
|
|
6
6
|
this.log(this.section('MAIN FLOWS', [
|
|
7
|
-
['First use', '$ xt
|
|
7
|
+
['First use', '$ xt login\n$ xt tool search violin\n$ xt tool inspect violin_flat\n$ xt tool run violin_flat ./data.xlsx'],
|
|
8
8
|
['Interactive tool run', '$ xt tool run --interactive'],
|
|
9
9
|
['Agent automation', '$ xt tool:prepare violin_flat --file ./data.xlsx --profile openclaw --json\n$ xt tool exec violin_flat --file ./data.xlsx --profile openclaw --json'],
|
|
10
10
|
]));
|
|
11
11
|
this.log('');
|
|
12
12
|
this.log(this.section('COMMON COMMANDS', this.renderCommandItems([
|
|
13
13
|
{
|
|
14
|
-
description: 'Sign in for
|
|
14
|
+
description: 'Sign in for the current profile and refresh both auth tokens',
|
|
15
15
|
id: 'login',
|
|
16
16
|
},
|
|
17
17
|
{
|
|
@@ -23,7 +23,7 @@ export default class XtzHelp extends Help {
|
|
|
23
23
|
id: 'logout',
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
|
-
description: '
|
|
26
|
+
description: 'Alias of xt login under the tool topic',
|
|
27
27
|
id: 'tool:login',
|
|
28
28
|
},
|
|
29
29
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@helixlife-ai/xiantao",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "CLI for Xiantao bioinformatics tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
}
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@helixlife-ai/xiantao": "^0.1.
|
|
58
|
+
"@helixlife-ai/xiantao": "^0.1.3",
|
|
59
59
|
"@oclif/core": "^4.9.0",
|
|
60
60
|
"@oclif/plugin-autocomplete": "^3.2.41"
|
|
61
61
|
},
|