@helixlife-ai/xiantao 0.1.6 → 0.1.7
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 +6 -2
- package/dist/commands/auth/bind.js +53 -0
- package/dist/commands/bind.js +3 -0
- package/dist/lib/constants.js +1 -1
- package/dist/lib/storage.js +37 -3
- package/dist/lib/xiantao-auth.js +23 -19
- package/dist/xtz-help.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Install with `npm i -g @helixlife-ai/xiantao` and run it as `xt`.
|
|
|
6
6
|
|
|
7
7
|
`tool` is the primary product topic for bioinformatics tools. `plot` is kept as a compatibility entrypoint for the current command set and prints a migration warning when executed.
|
|
8
8
|
|
|
9
|
-
`--profile` is the primary profile selector for token lookup and config isolation,
|
|
9
|
+
`--profile` is the primary profile selector for token lookup and config isolation. If it is omitted, the CLI resolves the profile from `AGENT_NAME`, then the host runtime (`cursor`, `opencode`, `openclaw`), then `~/.config/xtz/config.json`, and finally defaults to `opencode`. `--agent` is kept as a compatibility alias.
|
|
10
10
|
|
|
11
11
|
`login`, `status`, and `logout` are the primary auth entrypoints. `auth ...` is kept as a compatibility topic and prints a migration warning when executed.
|
|
12
12
|
|
|
@@ -34,7 +34,7 @@ This starts a menu-driven path for choosing a tool, selecting demo data or a loc
|
|
|
34
34
|
### Agent Automation
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
xt tool exec violin_flat --file ./data.xlsx --profile
|
|
37
|
+
xt tool exec violin_flat --file ./data.xlsx --profile opencode --json
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
For machine callers, use `xt tool exec`, keep `--json`, pass an explicit profile with `--profile`, and avoid interactive-only flows.
|
|
@@ -44,8 +44,10 @@ For machine callers, use `xt tool exec`, keep `--json`, pass an explicit profile
|
|
|
44
44
|
### Auth
|
|
45
45
|
|
|
46
46
|
- `xt login`
|
|
47
|
+
- `xt bind`
|
|
47
48
|
- `xt status`
|
|
48
49
|
- `xt logout`
|
|
50
|
+
- `xt auth bind`
|
|
49
51
|
- `xt tool login`
|
|
50
52
|
|
|
51
53
|
### Tool Run
|
|
@@ -73,6 +75,8 @@ For the common Xiantao tool product, `tool search`, `tool inspect`, `tool run`,
|
|
|
73
75
|
|
|
74
76
|
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
77
|
|
|
78
|
+
For remote-agent flows where the Xiantao web login callback cannot hit the machine running the CLI, use `xt bind --xiantao-token <token> --profile openclaw`. The bind command creates the upload auth session, calls the same `/user/skills/bind_user` API directly, waits for confirmation, and persists both the upload token and the Xiantao token locally. If you already created the auth session elsewhere, you can reuse it with `xt bind --auth-key <auth_key> --token-key <token_key> --xiantao-token <token>`.
|
|
79
|
+
|
|
76
80
|
## Development
|
|
77
81
|
|
|
78
82
|
```bash
|
|
@@ -0,0 +1,53 @@
|
|
|
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/lib/constants.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const DEFAULT_AGENT = '
|
|
1
|
+
export const DEFAULT_AGENT = 'opencode';
|
|
2
2
|
export const XTZ_CONFIG_PATH = '.config/xtz/config.json';
|
|
3
3
|
export const AUTH_URL_ENDPOINT = 'https://api.helixlife.net/api/v1/user/skills/auth_url';
|
|
4
4
|
export const AUTH_BIND_ENDPOINT = 'https://api.helixlife.net/api/v1/user/skills/bind_user';
|
package/dist/lib/storage.js
CHANGED
|
@@ -135,11 +135,16 @@ export async function readXtzConfig() {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
export async function resolveAgent(agentFlag) {
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
const cliAgent = agentFlag?.trim();
|
|
139
|
+
if (cliAgent) {
|
|
140
|
+
return cliAgent;
|
|
141
|
+
}
|
|
142
|
+
const environmentAgent = resolveAgentFromEnvironment();
|
|
143
|
+
if (environmentAgent) {
|
|
144
|
+
return environmentAgent;
|
|
140
145
|
}
|
|
141
146
|
const config = await readXtzConfig();
|
|
142
|
-
return config.agent
|
|
147
|
+
return config.agent?.trim() || DEFAULT_AGENT;
|
|
143
148
|
}
|
|
144
149
|
export async function resolveXiantaoToolProductUuid(toolProductUuidFlag) {
|
|
145
150
|
if (toolProductUuidFlag?.trim()) {
|
|
@@ -184,3 +189,32 @@ async function readJsonFile(filePath) {
|
|
|
184
189
|
function isNodeError(error) {
|
|
185
190
|
return error instanceof Error && 'code' in error;
|
|
186
191
|
}
|
|
192
|
+
function resolveAgentFromEnvironment() {
|
|
193
|
+
const agentName = process.env.AGENT_NAME?.trim();
|
|
194
|
+
if (agentName) {
|
|
195
|
+
return agentName;
|
|
196
|
+
}
|
|
197
|
+
if (isCursorEnvironment()) {
|
|
198
|
+
return 'cursor';
|
|
199
|
+
}
|
|
200
|
+
if (isNamedEnvironment('OPENCODE')) {
|
|
201
|
+
return 'opencode';
|
|
202
|
+
}
|
|
203
|
+
if (isNamedEnvironment('OPENCLAW')) {
|
|
204
|
+
return 'openclaw';
|
|
205
|
+
}
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
function isCursorEnvironment() {
|
|
209
|
+
return Boolean(process.env.CURSOR_TRACE_ID?.trim()
|
|
210
|
+
|| process.env.CURSOR_SESSION_ID?.trim()
|
|
211
|
+
|| process.env.CURSOR_AGENT?.trim()
|
|
212
|
+
|| process.env.VSCODE_GIT_ASKPASS_NODE?.includes('Cursor')
|
|
213
|
+
|| process.env.TERM_PROGRAM?.toLowerCase() === 'cursor');
|
|
214
|
+
}
|
|
215
|
+
function isNamedEnvironment(name) {
|
|
216
|
+
return Boolean(process.env[name]?.trim()
|
|
217
|
+
|| process.env[`${name}_AGENT`]?.trim()
|
|
218
|
+
|| process.env[`${name}_SESSION_ID`]?.trim()
|
|
219
|
+
|| process.env.TERM_PROGRAM?.toLowerCase() === name.toLowerCase());
|
|
220
|
+
}
|
package/dist/lib/xiantao-auth.js
CHANGED
|
@@ -3,7 +3,7 @@ import { AUTH_BIND_ENDPOINT, PASSPORT_JUMP_ENDPOINT } from './constants.js';
|
|
|
3
3
|
import { openUrl } from './open-url.js';
|
|
4
4
|
import { XtzError } from './errors.js';
|
|
5
5
|
import { requestJson } from './http.js';
|
|
6
|
-
import {
|
|
6
|
+
import { loadXiantaoToken, requireXiantaoToken, saveXiantaoToken } from './storage.js';
|
|
7
7
|
export async function getXiantaoAuthContext(agent, tokenOverride) {
|
|
8
8
|
const token = tokenOverride?.trim() || process.env.XIANTAO_TOKEN?.trim() || await requireXiantaoToken(agent);
|
|
9
9
|
return { agent, token };
|
|
@@ -29,24 +29,34 @@ export async function loginXiantao(agent) {
|
|
|
29
29
|
tokenPath,
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
|
+
export async function bindUserWithXiantaoToken(agent, authKey, token) {
|
|
33
|
+
await requestJson(AUTH_BIND_ENDPOINT, {
|
|
34
|
+
body: JSON.stringify({ auth_key: authKey }),
|
|
35
|
+
headers: {
|
|
36
|
+
Authorization: `Bearer ${token}`,
|
|
37
|
+
'Content-Type': 'application/json',
|
|
38
|
+
origin: 'https://www.helixlife.net',
|
|
39
|
+
platform: 'pc',
|
|
40
|
+
referer: 'https://www.helixlife.net/',
|
|
41
|
+
source: 'PC',
|
|
42
|
+
},
|
|
43
|
+
method: 'POST',
|
|
44
|
+
}, { agent, tokenKind: 'xiantao' });
|
|
45
|
+
const tokenPath = await saveXiantaoToken(agent, token);
|
|
46
|
+
return {
|
|
47
|
+
browserOpened: false,
|
|
48
|
+
reused: true,
|
|
49
|
+
token,
|
|
50
|
+
tokenPath,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
32
53
|
export async function bindUserWithStoredXiantaoToken(agent, authKey) {
|
|
33
54
|
const auth = await loadXiantaoAuthContext(agent);
|
|
34
55
|
if (!auth) {
|
|
35
56
|
return undefined;
|
|
36
57
|
}
|
|
37
58
|
try {
|
|
38
|
-
await
|
|
39
|
-
body: JSON.stringify({ auth_key: authKey }),
|
|
40
|
-
headers: {
|
|
41
|
-
Authorization: `Bearer ${auth.token}`,
|
|
42
|
-
'Content-Type': 'application/json',
|
|
43
|
-
origin: 'https://www.helixlife.net',
|
|
44
|
-
platform: 'pc',
|
|
45
|
-
referer: 'https://www.helixlife.net/',
|
|
46
|
-
source: 'PC',
|
|
47
|
-
},
|
|
48
|
-
method: 'POST',
|
|
49
|
-
}, { agent, tokenKind: 'xiantao' });
|
|
59
|
+
return await bindUserWithXiantaoToken(agent, authKey, auth.token);
|
|
50
60
|
}
|
|
51
61
|
catch (error) {
|
|
52
62
|
if (error instanceof XtzError && error.message.includes('仙桃网页授权已过期或无效')) {
|
|
@@ -54,12 +64,6 @@ export async function bindUserWithStoredXiantaoToken(agent, authKey) {
|
|
|
54
64
|
}
|
|
55
65
|
throw error;
|
|
56
66
|
}
|
|
57
|
-
return {
|
|
58
|
-
browserOpened: false,
|
|
59
|
-
reused: true,
|
|
60
|
-
token: auth.token,
|
|
61
|
-
tokenPath: getXiantaoTokenPath(agent),
|
|
62
|
-
};
|
|
63
67
|
}
|
|
64
68
|
async function createLoginSession() {
|
|
65
69
|
let resolveToken;
|
package/dist/xtz-help.js
CHANGED
|
@@ -6,7 +6,7 @@ export default class XtzHelp extends Help {
|
|
|
6
6
|
this.log(this.section('MAIN FLOWS', [
|
|
7
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
|
-
['Agent automation', '$ xt tool:prepare violin_flat --file ./data.xlsx --profile
|
|
9
|
+
['Agent automation', '$ xt tool:prepare violin_flat --file ./data.xlsx --profile opencode --json\n$ xt tool exec violin_flat --file ./data.xlsx --profile opencode --json'],
|
|
10
10
|
]));
|
|
11
11
|
this.log('');
|
|
12
12
|
this.log(this.section('COMMON COMMANDS', this.renderCommandItems([
|