@helixlife-ai/xiantao 0.1.11 → 0.1.12

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 CHANGED
@@ -34,10 +34,13 @@ 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 prepare violin_flat --file ./data.xlsx --profile opencode --json
37
38
  xt tool exec violin_flat --file ./data.xlsx --profile opencode --json
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.
41
+ For machine callers, use `xt tool prepare` to fetch the upload-dependent final schema, then `xt tool exec` to submit. Keep `--json`, pass an explicit profile with `--profile`, and avoid interactive-only flows.
42
+
43
+ `xt login --json` now returns the authorization link immediately and defaults to `authorized: false` until the browser step completes. Use `xt login --json --wait` when you explicitly want the command to block until authorization is confirmed. `xt status` is a probe-style check and returns `authorized: true` or `authorized: false` together with a reason such as `missing_token` or `expired_token`.
41
44
 
42
45
  ## Command Groups
43
46
 
@@ -52,6 +55,7 @@ For machine callers, use `xt tool exec`, keep `--json`, pass an explicit profile
52
55
 
53
56
  - `xt tool search`
54
57
  - `xt tool inspect`
58
+ - `xt tool prepare`
55
59
  - `xt tool run`
56
60
  - `xt tool exec`
57
61
  - `xt tool download`
@@ -1,4 +1,5 @@
1
1
  import { Command } from '@oclif/core';
2
+ import { createErrorEnvelope, createSuccessEnvelope } from './lib/envelope.js';
2
3
  import { XtzError } from './lib/errors.js';
3
4
  import { resolveAgent } from './lib/storage.js';
4
5
  import { getUpdateReminder } from './lib/update.js';
@@ -13,14 +14,23 @@ export class XtzCommand extends Command {
13
14
  async getAgent(agentFlag) {
14
15
  return this.getProfile(agentFlag);
15
16
  }
16
- print(flags, data, text) {
17
+ print(flags, data, text, context) {
17
18
  if (flags.json) {
19
+ if (context) {
20
+ this.log(JSON.stringify(createSuccessEnvelope(context.profile, context.command ?? this.getCommandName(), data), null, 2));
21
+ return;
22
+ }
18
23
  this.log(JSON.stringify(data, null, 2));
19
24
  return;
20
25
  }
21
26
  this.log(text);
22
27
  }
23
28
  async catch(error) {
29
+ if (this.isJsonOutputRequested()) {
30
+ const profile = await this.getProfile(this.readProfileFlagFromArgv());
31
+ this.log(JSON.stringify(createErrorEnvelope(profile, this.getCommandName(), error), null, 2));
32
+ this.exit(1);
33
+ }
24
34
  if (error instanceof XtzError) {
25
35
  this.error(error.message, { exit: 1 });
26
36
  }
@@ -43,7 +53,7 @@ export class XtzCommand extends Command {
43
53
  this.warn(`\`xt ${command}\` 已兼容保留,建议改用 \`xt ${replacement}\``);
44
54
  }
45
55
  async warnIfUpdateAvailable() {
46
- if (this.argv.includes('--json') || !process.stdout.isTTY) {
56
+ if (this.argv.includes('--json') || !process.stdout.isTTY || this.shouldSkipUpdateReminder()) {
47
57
  return;
48
58
  }
49
59
  const reminder = await getUpdateReminder(this.config.version);
@@ -52,4 +62,42 @@ export class XtzCommand extends Command {
52
62
  }
53
63
  this.warn(reminder);
54
64
  }
65
+ getCommandName() {
66
+ return this.id?.replaceAll(':', ' ') ?? 'unknown';
67
+ }
68
+ isJsonOutputRequested() {
69
+ return this.argv.includes('--json');
70
+ }
71
+ readProfileFlagFromArgv() {
72
+ for (let index = 0; index < this.argv.length; index += 1) {
73
+ const token = this.argv[index];
74
+ if ((token === '--profile' || token === '--agent') && this.argv[index + 1]) {
75
+ return this.argv[index + 1];
76
+ }
77
+ if (token.startsWith('--profile=')) {
78
+ return token.slice('--profile='.length);
79
+ }
80
+ if (token.startsWith('--agent=')) {
81
+ return token.slice('--agent='.length);
82
+ }
83
+ }
84
+ return undefined;
85
+ }
86
+ shouldSkipUpdateReminder() {
87
+ const commandId = this.id ?? '';
88
+ return [
89
+ 'login',
90
+ 'logout',
91
+ 'status',
92
+ 'auth:login',
93
+ 'auth:logout',
94
+ 'auth:status',
95
+ 'plot:login',
96
+ 'plot:run',
97
+ 'tool:exec',
98
+ 'tool:login',
99
+ 'tool:prepare',
100
+ 'tool:run',
101
+ ].includes(commandId);
102
+ }
55
103
  }
@@ -1,6 +1,7 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import { XtzCommand } from '../../base-command.js';
3
3
  import { globalFlags } from '../../lib/flags.js';
4
+ import { loginAuth } from '../../lib/auth.js';
4
5
  import { loginAll } from '../../lib/login-flow.js';
5
6
  import { getTokenPath } from '../../lib/storage.js';
6
7
  export default class AuthLogin extends XtzCommand {
@@ -12,32 +13,64 @@ export default class AuthLogin extends XtzCommand {
12
13
  default: true,
13
14
  description: 'Open the authorization link in the browser',
14
15
  }),
16
+ wait: Flags.boolean({
17
+ allowNo: true,
18
+ default: true,
19
+ description: 'Wait until the browser authorization is confirmed before exiting',
20
+ }),
15
21
  };
16
22
  async run() {
17
23
  this.warnIfLegacyAuthCommand();
18
24
  const { flags } = await this.parse(AuthLogin);
19
25
  const agent = await this.getProfile(flags.profile);
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
- });
26
+ const waitForAuthorization = this.shouldWait(flags);
27
+ const result = waitForAuthorization
28
+ ? await loginAll(agent, flags.open, ({ auth, phase }) => {
29
+ if (flags.json) {
30
+ return;
31
+ }
32
+ if (phase === 'auth_started') {
33
+ this.log(auth.browserOpened ? '浏览器已打开授权链接,等待授权完成...' : `请先在浏览器中打开授权链接: ${auth.linkUrl}`);
34
+ return;
35
+ }
36
+ this.log('授权已确认。');
37
+ })
38
+ : { auth: await loginAuth(agent, flags.open) };
39
+ const authorized = waitForAuthorization;
30
40
  this.print(flags, {
31
41
  agent,
42
+ authorization_pending: !authorized,
43
+ authorized,
32
44
  browser_opened: result.auth.browserOpened,
33
45
  link_url: result.auth.linkUrl,
34
46
  token_path: result.auth.tokenPath,
35
- }, [
36
- `profile: ${agent}`,
37
- `token: ${getTokenPath(agent)}`,
38
- result.auth.browserOpened
39
- ? '浏览器已打开授权链接,请完成授权后运行 `xt status`。'
40
- : `请在浏览器中打开: ${result.auth.linkUrl}`,
41
- ].join('\n'));
47
+ wait: waitForAuthorization,
48
+ }, this.formatLoginText({
49
+ agent,
50
+ authorized,
51
+ browserOpened: result.auth.browserOpened,
52
+ linkUrl: result.auth.linkUrl,
53
+ }), { profile: agent });
54
+ }
55
+ shouldWait(flags) {
56
+ if (!this.hasExplicitWaitFlag()) {
57
+ return !flags.json;
58
+ }
59
+ return flags.wait;
60
+ }
61
+ hasExplicitWaitFlag() {
62
+ return this.argv.includes('--wait') || this.argv.includes('--no-wait');
63
+ }
64
+ formatLoginText(input) {
65
+ const lines = [
66
+ `profile: ${input.agent}`,
67
+ `token: ${getTokenPath(input.agent)}`,
68
+ `authorized: ${input.authorized}`,
69
+ ];
70
+ if (input.authorized) {
71
+ return lines.join('\n');
72
+ }
73
+ lines.push(input.browserOpened ? '浏览器已打开授权链接,请完成授权后运行 `xt status`。' : `请在浏览器中打开: ${input.linkUrl}`);
74
+ return lines.join('\n');
42
75
  }
43
76
  }
@@ -15,6 +15,6 @@ export default class AuthLogout extends XtzCommand {
15
15
  agent,
16
16
  removed,
17
17
  token_path: getTokenPath(agent),
18
- }, removed ? `已删除 token: ${getTokenPath(agent)}` : `未找到 token: ${getTokenPath(agent)}`);
18
+ }, removed ? `已删除 token: ${getTokenPath(agent)}` : `未找到 token: ${getTokenPath(agent)}`, { profile: agent });
19
19
  }
20
20
  }
@@ -1,9 +1,10 @@
1
1
  import { XtzCommand } from '../../base-command.js';
2
+ import { XtzError } from '../../lib/errors.js';
2
3
  import { checkToken } from '../../lib/auth.js';
3
4
  import { globalFlags } from '../../lib/flags.js';
4
- import { getTokenPath, requireToken } from '../../lib/storage.js';
5
+ import { getTokenPath, loadToken } from '../../lib/storage.js';
5
6
  export default class AuthStatus extends XtzCommand {
6
- static description = 'Check whether the current profile token is authorized';
7
+ static description = 'Show token auth status for the current profile';
7
8
  static flags = {
8
9
  ...globalFlags,
9
10
  };
@@ -11,12 +12,49 @@ export default class AuthStatus extends XtzCommand {
11
12
  this.warnIfLegacyAuthCommand();
12
13
  const { flags } = await this.parse(AuthStatus);
13
14
  const agent = await this.getProfile(flags.profile);
14
- const token = await requireToken(agent);
15
- await checkToken(agent, token);
15
+ const tokenPath = getTokenPath(agent);
16
+ const token = await loadToken(agent);
17
+ const status = !token
18
+ ? {
19
+ authorized: false,
20
+ reason: 'missing_token',
21
+ }
22
+ : await this.resolveAuthorization(agent, token);
16
23
  this.print(flags, {
17
24
  agent,
18
- authorized: true,
19
- token_path: getTokenPath(agent),
20
- }, `profile: ${agent}\nauthorized: true\ntoken: ${getTokenPath(agent)}`);
25
+ authorized: status.authorized,
26
+ reason: status.reason,
27
+ token_path: tokenPath,
28
+ }, this.formatStatusText(agent, tokenPath, status), { profile: agent });
29
+ }
30
+ async resolveAuthorization(agent, token) {
31
+ try {
32
+ await checkToken(agent, token);
33
+ return {
34
+ authorized: true,
35
+ reason: null,
36
+ };
37
+ }
38
+ catch (error) {
39
+ if (!(error instanceof XtzError) || !error.message.includes('授权已过期')) {
40
+ throw error;
41
+ }
42
+ return {
43
+ authorized: false,
44
+ reason: 'expired_token',
45
+ };
46
+ }
47
+ }
48
+ formatStatusText(agent, tokenPath, status) {
49
+ const lines = [
50
+ `profile: ${agent}`,
51
+ `authorized: ${status.authorized}`,
52
+ `token: ${tokenPath}`,
53
+ ];
54
+ if (!status.authorized && status.reason) {
55
+ lines.push(`reason: ${status.reason}`);
56
+ lines.push(`run: xt login --profile ${agent}`);
57
+ }
58
+ return lines.join('\n');
21
59
  }
22
60
  }
@@ -42,6 +42,6 @@ export default class PlotFetchAll extends XtzCommand {
42
42
  notice: result.notice ?? '',
43
43
  uuid: flags.uuid,
44
44
  };
45
- this.print(flags, output, JSON.stringify(output, null, 2));
45
+ this.print(flags, output, JSON.stringify(output, null, 2), { profile: agent });
46
46
  }
47
47
  }
@@ -1,5 +1,6 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import { XtzCommand } from '../../base-command.js';
3
+ import { loginAuth } from '../../lib/auth.js';
3
4
  import { globalFlags } from '../../lib/flags.js';
4
5
  import { loginAll } from '../../lib/login-flow.js';
5
6
  import { getTokenPath } from '../../lib/storage.js';
@@ -12,32 +13,64 @@ export default class PlotLogin extends XtzCommand {
12
13
  default: true,
13
14
  description: 'Open the authorization link in the browser',
14
15
  }),
16
+ wait: Flags.boolean({
17
+ allowNo: true,
18
+ default: true,
19
+ description: 'Wait until the browser authorization is confirmed before exiting',
20
+ }),
15
21
  };
16
22
  async run() {
17
23
  this.warnIfLegacyPlotCommand();
18
24
  const { flags } = await this.parse(PlotLogin);
19
25
  const agent = await this.getProfile(flags.profile);
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
- });
26
+ const waitForAuthorization = this.shouldWait(flags);
27
+ const result = waitForAuthorization
28
+ ? await loginAll(agent, flags.open, ({ auth, phase }) => {
29
+ if (flags.json) {
30
+ return;
31
+ }
32
+ if (phase === 'auth_started') {
33
+ this.log(auth.browserOpened ? '浏览器已打开授权链接,等待授权完成...' : `请先在浏览器中打开授权链接: ${auth.linkUrl}`);
34
+ return;
35
+ }
36
+ this.log('授权已确认。');
37
+ })
38
+ : { auth: await loginAuth(agent, flags.open) };
39
+ const authorized = waitForAuthorization;
30
40
  this.print(flags, {
31
41
  agent,
42
+ authorization_pending: !authorized,
43
+ authorized,
32
44
  browser_opened: result.auth.browserOpened,
33
45
  link_url: result.auth.linkUrl,
34
46
  token_path: result.auth.tokenPath,
35
- }, [
36
- `profile: ${agent}`,
37
- `token: ${getTokenPath(agent)}`,
38
- result.auth.browserOpened
39
- ? '浏览器已打开授权链接,请完成授权后运行 `xt status`。'
40
- : `请在浏览器中打开: ${result.auth.linkUrl}`,
41
- ].join('\n'));
47
+ wait: waitForAuthorization,
48
+ }, this.formatLoginText({
49
+ agent,
50
+ authorized,
51
+ browserOpened: result.auth.browserOpened,
52
+ linkUrl: result.auth.linkUrl,
53
+ }), { profile: agent });
54
+ }
55
+ shouldWait(flags) {
56
+ if (!this.hasExplicitWaitFlag()) {
57
+ return !flags.json;
58
+ }
59
+ return flags.wait;
60
+ }
61
+ hasExplicitWaitFlag() {
62
+ return this.argv.includes('--wait') || this.argv.includes('--no-wait');
63
+ }
64
+ formatLoginText(input) {
65
+ const lines = [
66
+ `profile: ${input.agent}`,
67
+ `token: ${getTokenPath(input.agent)}`,
68
+ `authorized: ${input.authorized}`,
69
+ ];
70
+ if (input.authorized) {
71
+ return lines.join('\n');
72
+ }
73
+ lines.push(input.browserOpened ? '浏览器已打开授权链接,请完成授权后运行 `xt status`。' : `请在浏览器中打开: ${input.linkUrl}`);
74
+ return lines.join('\n');
42
75
  }
43
76
  }
@@ -32,6 +32,6 @@ export default class PlotMenu extends XtzCommand {
32
32
  agent,
33
33
  menu,
34
34
  tool_product_uuid: toolProductUuid,
35
- }, JSON.stringify(menu, null, 2));
35
+ }, JSON.stringify(menu, null, 2), { profile: agent });
36
36
  }
37
37
  }
@@ -26,6 +26,6 @@ export default class PlotMenus extends XtzCommand {
26
26
  agent,
27
27
  menus,
28
28
  tool_product_uuid: toolProductUuid,
29
- }, JSON.stringify(menus, null, 2));
29
+ }, JSON.stringify(menus, null, 2), { profile: agent });
30
30
  }
31
31
  }
@@ -43,6 +43,6 @@ export default class PlotResolve extends XtzCommand {
43
43
  title: resolved.title,
44
44
  tool_product_uuid: toolProductUuid,
45
45
  uuid: resolved.uuid,
46
- }, null, 2));
46
+ }, null, 2), { profile: agent });
47
47
  }
48
48
  }
@@ -427,6 +427,6 @@ export default class PlotRun extends ToolRunCommandBase {
427
427
  throw new XtzError('FILE 与 `--file` 不能同时使用。');
428
428
  }
429
429
  const result = await this.executeToolRun(args.tool, args.file ?? flags.file, flags);
430
- this.print(flags, result.data, result.text);
430
+ this.print(flags, result.data, result.text, { profile: result.data.agent });
431
431
  }
432
432
  }
@@ -51,7 +51,7 @@ export default class ToolInspect extends XtzCommand {
51
51
  tool_product_uuid: toolProductUuid,
52
52
  uuid: resolved.uuid,
53
53
  };
54
- this.print(flags, output, this.formatInspectText(output.module_id, output.title, output.path, output.route, output.uuid, output.notice, inputs, summary, schema));
54
+ this.print(flags, output, this.formatInspectText(output.module_id, output.title, output.path, output.route, output.uuid, output.notice, inputs, summary, schema), { profile: agent });
55
55
  }
56
56
  formatInspectText(moduleId, title, path, route, uuid, notice, inputs, summary, schema) {
57
57
  const lines = [
@@ -7,7 +7,7 @@ import { buildArgsMainFromSchema } from '../../lib/plot-schema.js';
7
7
  import { ToolRunCommandBase } from '../plot/run.js';
8
8
  export default class ToolPrepare extends ToolRunCommandBase {
9
9
  static description = 'Prepare a Xiantao bioinformatics tool in agent mode and return the final dynamic schema';
10
- static usage = 'tool:prepare <tool> (--file <value> | --demo) --json';
10
+ static usage = 'tool prepare <tool> (--file <value> | --demo) --json';
11
11
  static args = {
12
12
  tool: Args.string({
13
13
  description: 'Tool id, e.g. violin_flat',
@@ -34,7 +34,7 @@ export default class ToolSearch extends XtzCommand {
34
34
  tools,
35
35
  total: tools.length,
36
36
  };
37
- this.print(flags, output, this.formatSearchText(args.keyword, tools));
37
+ this.print(flags, output, this.formatSearchText(args.keyword, tools), { profile: agent });
38
38
  }
39
39
  formatSearchText(keyword, tools) {
40
40
  if (tools.length === 0) {
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 opencode --json\n$ xt tool exec violin_flat --file ./data.xlsx --profile opencode --json'],
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([
@@ -58,7 +58,7 @@ export default class XtzHelp extends Help {
58
58
  ['xt tool menu', 'Fetch one raw tool menu by UUID'],
59
59
  ['xt tool menus', 'Fetch all raw tool menus for a product'],
60
60
  ['xt tool fetch-all', 'Fetch raw dynamic args for a tool'],
61
- ['xt tool:prepare', 'Resolve upload-dependent dynamic schema for agent workflows'],
61
+ ['xt tool prepare', 'Resolve upload-dependent dynamic schema for agent workflows'],
62
62
  ['xt auth ...', 'Legacy auth topic kept for compatibility'],
63
63
  ['xt plot ...', 'Legacy plot topic kept for compatibility'],
64
64
  ]));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helixlife-ai/xiantao",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "CLI for Xiantao bioinformatics tools",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",