@helixlife-ai/xiantao 0.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 (47) hide show
  1. package/README.md +91 -0
  2. package/bin/dev.js +5 -0
  3. package/bin/run.js +5 -0
  4. package/dist/base-command.js +40 -0
  5. package/dist/commands/auth/login.js +35 -0
  6. package/dist/commands/auth/logout.js +20 -0
  7. package/dist/commands/auth/status.js +22 -0
  8. package/dist/commands/login.js +3 -0
  9. package/dist/commands/logout.js +3 -0
  10. package/dist/commands/plot/download.js +31 -0
  11. package/dist/commands/plot/fetch-all.js +47 -0
  12. package/dist/commands/plot/login.js +27 -0
  13. package/dist/commands/plot/menu.js +37 -0
  14. package/dist/commands/plot/menus.js +31 -0
  15. package/dist/commands/plot/resolve.js +48 -0
  16. package/dist/commands/plot/run.js +385 -0
  17. package/dist/commands/status.js +3 -0
  18. package/dist/commands/tool/download.js +3 -0
  19. package/dist/commands/tool/exec.js +102 -0
  20. package/dist/commands/tool/fetch-all.js +3 -0
  21. package/dist/commands/tool/inspect.js +149 -0
  22. package/dist/commands/tool/login.js +3 -0
  23. package/dist/commands/tool/menu.js +3 -0
  24. package/dist/commands/tool/menus.js +3 -0
  25. package/dist/commands/tool/resolve.js +3 -0
  26. package/dist/commands/tool/run.js +3 -0
  27. package/dist/commands/tool/search.js +53 -0
  28. package/dist/index.js +1 -0
  29. package/dist/lib/auth.js +23 -0
  30. package/dist/lib/constants.js +11 -0
  31. package/dist/lib/download.js +29 -0
  32. package/dist/lib/envelope.js +70 -0
  33. package/dist/lib/errors.js +42 -0
  34. package/dist/lib/flags.js +11 -0
  35. package/dist/lib/http.js +24 -0
  36. package/dist/lib/json.js +20 -0
  37. package/dist/lib/open-url.js +25 -0
  38. package/dist/lib/plot-interactive.js +633 -0
  39. package/dist/lib/plot-options.js +84 -0
  40. package/dist/lib/plot-schema.js +17 -0
  41. package/dist/lib/plot.js +142 -0
  42. package/dist/lib/storage.js +186 -0
  43. package/dist/lib/xiantao-auth.js +85 -0
  44. package/dist/lib/xiantao-plot.js +144 -0
  45. package/dist/lib/xiantao.js +148 -0
  46. package/dist/xtz-help.js +78 -0
  47. package/package.json +60 -0
package/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # xt
2
+
3
+ CLI for Xiantao bioinformatics tools.
4
+
5
+ Install with `npm i -g @helixlife-ai/xiantao` and run it as `xt`.
6
+
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
+
9
+ `--profile` is the primary profile selector for token lookup and config isolation, for example `default` or `openclaw`. `--agent` is kept as a compatibility alias.
10
+
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
+
13
+ ## Main Flows
14
+
15
+ ### First Use
16
+
17
+ ```bash
18
+ xt login
19
+ xt tool search violin
20
+ xt tool inspect violin_flat
21
+ xt tool run violin_flat ./data.xlsx
22
+ ```
23
+
24
+ `xt login` is used for upload-related APIs. If the stored Xiantao web token is missing or expired for `tool search`, `tool inspect`, or `tool run`, run `xt tool login` under the same profile.
25
+
26
+ ### Interactive Tool Run
27
+
28
+ ```bash
29
+ xt tool run --interactive
30
+ ```
31
+
32
+ This starts a menu-driven path for choosing a tool, selecting demo data or a local file, filling dynamic `args_main`, and optionally downloading generated outputs.
33
+
34
+ ### Agent Automation
35
+
36
+ ```bash
37
+ xt tool exec violin_flat --file ./data.xlsx --profile openclaw --json
38
+ ```
39
+
40
+ For machine callers, use `xt tool exec`, keep `--json`, pass an explicit profile with `--profile`, and avoid interactive-only flows.
41
+
42
+ ## Command Groups
43
+
44
+ ### Auth
45
+
46
+ - `xt login`
47
+ - `xt status`
48
+ - `xt logout`
49
+ - `xt tool login`
50
+
51
+ ### Tool Run
52
+
53
+ - `xt tool search`
54
+ - `xt tool inspect`
55
+ - `xt tool run`
56
+ - `xt tool exec`
57
+ - `xt tool download`
58
+
59
+ ### Tool Debug
60
+
61
+ - `xt tool resolve`
62
+ - `xt tool menu`
63
+ - `xt tool menus`
64
+ - `xt tool fetch-all`
65
+
66
+ ## Notes
67
+
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
+
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 `xt login`.
71
+
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
+
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
+ ## Development
77
+
78
+ ```bash
79
+ npm install
80
+ npm run build
81
+ node ./bin/run.js --help
82
+ ```
83
+
84
+ ## Autocomplete
85
+
86
+ oclif supports shell completion through `@oclif/plugin-autocomplete`.
87
+
88
+ ```bash
89
+ xt autocomplete zsh
90
+ xt autocomplete bash
91
+ ```
package/bin/dev.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {execute} from '@oclif/core'
4
+
5
+ await execute({development: true, dir: import.meta.url})
package/bin/run.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {execute} from '@oclif/core'
4
+
5
+ await execute({dir: import.meta.url})
@@ -0,0 +1,40 @@
1
+ import { Command } from '@oclif/core';
2
+ import { XtzError } from './lib/errors.js';
3
+ import { resolveAgent } from './lib/storage.js';
4
+ export class XtzCommand extends Command {
5
+ async getProfile(profileFlag) {
6
+ return resolveAgent(profileFlag);
7
+ }
8
+ async getAgent(agentFlag) {
9
+ return this.getProfile(agentFlag);
10
+ }
11
+ print(flags, data, text) {
12
+ if (flags.json) {
13
+ this.log(JSON.stringify(data, null, 2));
14
+ return;
15
+ }
16
+ this.log(text);
17
+ }
18
+ async catch(error) {
19
+ if (error instanceof XtzError) {
20
+ this.error(error.message, { exit: 1 });
21
+ }
22
+ throw error;
23
+ }
24
+ warnIfLegacyPlotCommand() {
25
+ if (!this.id?.startsWith('plot:') || this.argv.includes('--json')) {
26
+ return;
27
+ }
28
+ const command = this.id.replaceAll(':', ' ');
29
+ const replacement = command.replace(/^plot\b/, 'tool');
30
+ this.warn(`\`xt ${command}\` 已兼容保留,建议改用 \`xt ${replacement}\``);
31
+ }
32
+ warnIfLegacyAuthCommand() {
33
+ if (!this.id?.startsWith('auth:') || this.argv.includes('--json')) {
34
+ return;
35
+ }
36
+ const command = this.id.replaceAll(':', ' ');
37
+ const replacement = command.replace(/^auth /, '');
38
+ this.warn(`\`xt ${command}\` 已兼容保留,建议改用 \`xt ${replacement}\``);
39
+ }
40
+ }
@@ -0,0 +1,35 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { XtzCommand } from '../../base-command.js';
3
+ import { fetchAuthLink } from '../../lib/auth.js';
4
+ import { globalFlags } from '../../lib/flags.js';
5
+ import { openUrl } from '../../lib/open-url.js';
6
+ import { getTokenPath, saveToken } from '../../lib/storage.js';
7
+ export default class AuthLogin extends XtzCommand {
8
+ static description = 'Get an authorization link for the current profile and save the token locally';
9
+ static flags = {
10
+ ...globalFlags,
11
+ open: Flags.boolean({
12
+ allowNo: true,
13
+ default: true,
14
+ description: 'Open the authorization link in the browser',
15
+ }),
16
+ };
17
+ async run() {
18
+ this.warnIfLegacyAuthCommand();
19
+ const { flags } = await this.parse(AuthLogin);
20
+ const agent = await this.getProfile(flags.profile);
21
+ const { linkUrl, tokenKey } = await fetchAuthLink(agent);
22
+ const tokenPath = await saveToken(agent, tokenKey);
23
+ const opened = flags.open ? await openUrl(linkUrl) : false;
24
+ this.print(flags, {
25
+ agent,
26
+ browser_opened: opened,
27
+ link_url: linkUrl,
28
+ token_path: tokenPath,
29
+ }, [
30
+ `profile: ${agent}`,
31
+ `token: ${getTokenPath(agent)}`,
32
+ opened ? '浏览器已打开授权链接,请完成授权后运行 `xt status`。' : `请在浏览器中打开: ${linkUrl}`,
33
+ ].join('\n'));
34
+ }
35
+ }
@@ -0,0 +1,20 @@
1
+ import { XtzCommand } from '../../base-command.js';
2
+ import { globalFlags } from '../../lib/flags.js';
3
+ import { deleteToken, getTokenPath } from '../../lib/storage.js';
4
+ export default class AuthLogout extends XtzCommand {
5
+ static description = 'Delete the stored token for the current profile';
6
+ static flags = {
7
+ ...globalFlags,
8
+ };
9
+ async run() {
10
+ this.warnIfLegacyAuthCommand();
11
+ const { flags } = await this.parse(AuthLogout);
12
+ const agent = await this.getProfile(flags.profile);
13
+ const removed = await deleteToken(agent);
14
+ this.print(flags, {
15
+ agent,
16
+ removed,
17
+ token_path: getTokenPath(agent),
18
+ }, removed ? `已删除 token: ${getTokenPath(agent)}` : `未找到 token: ${getTokenPath(agent)}`);
19
+ }
20
+ }
@@ -0,0 +1,22 @@
1
+ import { XtzCommand } from '../../base-command.js';
2
+ import { checkToken } from '../../lib/auth.js';
3
+ import { globalFlags } from '../../lib/flags.js';
4
+ import { getTokenPath, requireToken } from '../../lib/storage.js';
5
+ export default class AuthStatus extends XtzCommand {
6
+ static description = 'Check whether the current profile token is authorized';
7
+ static flags = {
8
+ ...globalFlags,
9
+ };
10
+ async run() {
11
+ this.warnIfLegacyAuthCommand();
12
+ const { flags } = await this.parse(AuthStatus);
13
+ const agent = await this.getProfile(flags.profile);
14
+ const token = await requireToken(agent);
15
+ await checkToken(agent, token);
16
+ this.print(flags, {
17
+ agent,
18
+ authorized: true,
19
+ token_path: getTokenPath(agent),
20
+ }, `profile: ${agent}\nauthorized: true\ntoken: ${getTokenPath(agent)}`);
21
+ }
22
+ }
@@ -0,0 +1,3 @@
1
+ import AuthLogin from './auth/login.js';
2
+ export default class Login extends AuthLogin {
3
+ }
@@ -0,0 +1,3 @@
1
+ import AuthLogout from './auth/logout.js';
2
+ export default class Logout extends AuthLogout {
3
+ }
@@ -0,0 +1,31 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { XtzCommand } from '../../base-command.js';
3
+ import { downloadToPath } from '../../lib/download.js';
4
+ export default class PlotDownload extends XtzCommand {
5
+ static description = 'Download a generated tool result URL to a local file';
6
+ static args = {
7
+ url: Args.string({
8
+ description: 'Direct file URL returned by a tool command',
9
+ required: true,
10
+ }),
11
+ };
12
+ static flags = {
13
+ json: Flags.boolean({
14
+ default: false,
15
+ description: 'Output JSON',
16
+ }),
17
+ output: Flags.string({
18
+ char: 'o',
19
+ description: 'Destination path; defaults to the file name inferred from the URL',
20
+ }),
21
+ };
22
+ async run() {
23
+ this.warnIfLegacyPlotCommand();
24
+ const { args, flags } = await this.parse(PlotDownload);
25
+ const filePath = await downloadToPath(args.url, flags.output);
26
+ this.print(flags, {
27
+ path: filePath,
28
+ url: args.url,
29
+ }, filePath);
30
+ }
31
+ }
@@ -0,0 +1,47 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { XtzCommand } from '../../base-command.js';
3
+ import { globalFlags } from '../../lib/flags.js';
4
+ import { buildArgsMainFromSchema } from '../../lib/plot-schema.js';
5
+ import { getXiantaoAuthContext } from '../../lib/xiantao-auth.js';
6
+ import { fetchAllPlotArgs } from '../../lib/xiantao.js';
7
+ export default class PlotFetchAll extends XtzCommand {
8
+ static description = 'Fetch raw dynamic args for a Xiantao tool via public.args.fetch-all';
9
+ static args = {
10
+ moduleId: Args.string({
11
+ description: 'Tool id, e.g. violin_flat',
12
+ required: true,
13
+ }),
14
+ };
15
+ static flags = {
16
+ ...globalFlags,
17
+ token: Flags.string({
18
+ description: 'Bearer token for api.helixlife.net xiantao APIs; falls back to XIANTAO_TOKEN or the stored Xiantao web token',
19
+ }),
20
+ uuid: Flags.string({
21
+ description: 'Tool UUID used in the public.args.fetch-all payload',
22
+ required: true,
23
+ }),
24
+ };
25
+ async run() {
26
+ this.warnIfLegacyPlotCommand();
27
+ const { args, flags } = await this.parse(PlotFetchAll);
28
+ const agent = await this.getProfile(flags.profile);
29
+ const auth = await getXiantaoAuthContext(agent, flags.token);
30
+ const result = await fetchAllPlotArgs(auth, {
31
+ moduleId: args.moduleId,
32
+ uuid: flags.uuid,
33
+ });
34
+ const argsMainDefaults = buildArgsMainFromSchema(result.args_main);
35
+ const output = {
36
+ agent,
37
+ args_data: result.args_data ?? [],
38
+ args_main: result.args_main ?? [],
39
+ args_main_defaults: argsMainDefaults,
40
+ duid: result.id?.duid ?? '',
41
+ module_id: args.moduleId,
42
+ notice: result.notice ?? '',
43
+ uuid: flags.uuid,
44
+ };
45
+ this.print(flags, output, JSON.stringify(output, null, 2));
46
+ }
47
+ }
@@ -0,0 +1,27 @@
1
+ import { XtzCommand } from '../../base-command.js';
2
+ import { globalFlags } from '../../lib/flags.js';
3
+ import { getXiantaoTokenPath } from '../../lib/storage.js';
4
+ import { loginXiantao } from '../../lib/xiantao-auth.js';
5
+ export default class PlotLogin extends XtzCommand {
6
+ static description = 'Sign in to Xiantao web APIs for the current profile and save the returned token locally';
7
+ static flags = {
8
+ ...globalFlags,
9
+ };
10
+ async run() {
11
+ this.warnIfLegacyPlotCommand();
12
+ const { flags } = await this.parse(PlotLogin);
13
+ const agent = await this.getProfile(flags.profile);
14
+ const result = await loginXiantao(agent);
15
+ this.print(flags, {
16
+ agent,
17
+ browser_opened: result.browserOpened,
18
+ callback_url: result.callbackUrl,
19
+ jump_url: result.jumpUrl,
20
+ token_path: result.tokenPath,
21
+ }, [
22
+ `profile: ${agent}`,
23
+ `xiantao_token: ${getXiantaoTokenPath(agent)}`,
24
+ result.browserOpened ? '仙桃网页登录成功,token 已保存。' : `请在浏览器中打开: ${result.jumpUrl}`,
25
+ ].join('\n'));
26
+ }
27
+ }
@@ -0,0 +1,37 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { XtzCommand } from '../../base-command.js';
3
+ import { globalFlags } from '../../lib/flags.js';
4
+ import { resolveXiantaoToolProductUuid } from '../../lib/storage.js';
5
+ import { getXiantaoAuthContext } from '../../lib/xiantao-auth.js';
6
+ import { fetchToolMenu } from '../../lib/xiantao.js';
7
+ export default class PlotMenu extends XtzCommand {
8
+ static description = 'Fetch Xiantao menu metadata for a tool menu UUID';
9
+ static args = {
10
+ menuUuid: Args.string({
11
+ description: 'Tool menu UUID from /tool/menus/:uuid',
12
+ required: true,
13
+ }),
14
+ };
15
+ static flags = {
16
+ ...globalFlags,
17
+ token: Flags.string({
18
+ description: 'Bearer token for api.helixlife.net xiantao APIs; falls back to XIANTAO_TOKEN or the stored Xiantao web token',
19
+ }),
20
+ toolProductUuid: Flags.string({
21
+ description: 'tool_product_uuid used in the menu filter payload; defaults to the main Xiantao product UUID',
22
+ }),
23
+ };
24
+ async run() {
25
+ this.warnIfLegacyPlotCommand();
26
+ const { args, flags } = await this.parse(PlotMenu);
27
+ const agent = await this.getProfile(flags.profile);
28
+ const auth = await getXiantaoAuthContext(agent, flags.token);
29
+ const toolProductUuid = await resolveXiantaoToolProductUuid(flags.toolProductUuid);
30
+ const menu = await fetchToolMenu(auth, args.menuUuid, toolProductUuid);
31
+ this.print(flags, {
32
+ agent,
33
+ menu,
34
+ tool_product_uuid: toolProductUuid,
35
+ }, JSON.stringify(menu, null, 2));
36
+ }
37
+ }
@@ -0,0 +1,31 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { XtzCommand } from '../../base-command.js';
3
+ import { globalFlags } from '../../lib/flags.js';
4
+ import { resolveXiantaoToolProductUuid } from '../../lib/storage.js';
5
+ import { getXiantaoAuthContext } from '../../lib/xiantao-auth.js';
6
+ import { fetchToolMenus } from '../../lib/xiantao.js';
7
+ export default class PlotMenus extends XtzCommand {
8
+ static description = 'Fetch all Xiantao menu metadata for a tool product';
9
+ static flags = {
10
+ ...globalFlags,
11
+ token: Flags.string({
12
+ description: 'Bearer token for api.helixlife.net xiantao APIs; falls back to XIANTAO_TOKEN or the stored Xiantao web token',
13
+ }),
14
+ toolProductUuid: Flags.string({
15
+ description: 'tool_product_uuid used in the menu filter payload; defaults to the main Xiantao product UUID',
16
+ }),
17
+ };
18
+ async run() {
19
+ this.warnIfLegacyPlotCommand();
20
+ const { flags } = await this.parse(PlotMenus);
21
+ const agent = await this.getProfile(flags.profile);
22
+ const auth = await getXiantaoAuthContext(agent, flags.token);
23
+ const toolProductUuid = await resolveXiantaoToolProductUuid(flags.toolProductUuid);
24
+ const menus = await fetchToolMenus(auth, toolProductUuid);
25
+ this.print(flags, {
26
+ agent,
27
+ menus,
28
+ tool_product_uuid: toolProductUuid,
29
+ }, JSON.stringify(menus, null, 2));
30
+ }
31
+ }
@@ -0,0 +1,48 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { XtzCommand } from '../../base-command.js';
3
+ import { globalFlags } from '../../lib/flags.js';
4
+ import { resolveXiantaoToolProductUuid } from '../../lib/storage.js';
5
+ import { getXiantaoAuthContext } from '../../lib/xiantao-auth.js';
6
+ import { resolveToolMenuByCode } from '../../lib/xiantao.js';
7
+ export default class PlotResolve extends XtzCommand {
8
+ static description = 'Resolve a Xiantao tool id to its UUID and route';
9
+ static args = {
10
+ moduleId: Args.string({
11
+ description: 'Tool id, e.g. violin_flat',
12
+ required: true,
13
+ }),
14
+ };
15
+ static flags = {
16
+ ...globalFlags,
17
+ token: Flags.string({
18
+ description: 'Bearer token for api.helixlife.net xiantao APIs; falls back to XIANTAO_TOKEN or the stored Xiantao web token',
19
+ }),
20
+ toolProductUuid: Flags.string({
21
+ description: 'tool_product_uuid from the /products/apply/:uuid URL; defaults to the main Xiantao product UUID',
22
+ }),
23
+ };
24
+ async run() {
25
+ this.warnIfLegacyPlotCommand();
26
+ const { args, flags } = await this.parse(PlotResolve);
27
+ const agent = await this.getProfile(flags.profile);
28
+ const auth = await getXiantaoAuthContext(agent, flags.token);
29
+ const toolProductUuid = await resolveXiantaoToolProductUuid(flags.toolProductUuid);
30
+ const resolved = await resolveToolMenuByCode(auth, toolProductUuid, args.moduleId);
31
+ this.print(flags, {
32
+ agent,
33
+ module_id: args.moduleId,
34
+ path: resolved.path,
35
+ route: resolved.route,
36
+ title: resolved.title,
37
+ tool_product_uuid: toolProductUuid,
38
+ uuid: resolved.uuid,
39
+ }, JSON.stringify({
40
+ module_id: args.moduleId,
41
+ path: resolved.path,
42
+ route: resolved.route,
43
+ title: resolved.title,
44
+ tool_product_uuid: toolProductUuid,
45
+ uuid: resolved.uuid,
46
+ }, null, 2));
47
+ }
48
+ }