@leeguoo/zentao-mcp 0.4.1 → 0.5.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.
- package/README.md +69 -139
- package/package.json +13 -13
- package/skills/zentao-cli.md +106 -0
- package/src/cli/args.js +99 -0
- package/src/cli/help.js +49 -0
- package/src/commands/bug.js +27 -0
- package/src/commands/bugs.js +68 -0
- package/src/commands/login.js +60 -0
- package/src/commands/products.js +27 -0
- package/src/commands/release.js +198 -0
- package/src/commands/selftest.js +52 -0
- package/src/commands/whoami.js +32 -0
- package/src/config/store.js +81 -0
- package/src/index.js +49 -453
- package/src/zentao/client.js +324 -0
package/README.md
CHANGED
|
@@ -1,177 +1,107 @@
|
|
|
1
1
|
# zentao-mcp
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Quick Start
|
|
6
|
-
|
|
7
|
-
### Cursor IDE
|
|
8
|
-
|
|
9
|
-
1. Open Cursor Settings (⌘, on Mac or Ctrl+, on Windows/Linux)
|
|
10
|
-
2. Navigate to **Features** → **Model Context Protocol**
|
|
11
|
-
3. Click **Edit Config** to open `~/.cursor/mcp.json` (or create it)
|
|
12
|
-
4. Add the following configuration:
|
|
13
|
-
|
|
14
|
-
```json
|
|
15
|
-
{
|
|
16
|
-
"mcpServers": {
|
|
17
|
-
"zentao-mcp": {
|
|
18
|
-
"command": "npx",
|
|
19
|
-
"args": [
|
|
20
|
-
"-y",
|
|
21
|
-
"@leeguoo/zentao-mcp",
|
|
22
|
-
"--zentao-url=https://zentao.example.com/zentao",
|
|
23
|
-
"--zentao-account=leo",
|
|
24
|
-
"--zentao-password=***",
|
|
25
|
-
"--stdio"
|
|
26
|
-
]
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
```
|
|
3
|
+
ZenTao CLI for products + bugs.
|
|
31
4
|
|
|
32
|
-
|
|
5
|
+
## Installation
|
|
33
6
|
|
|
34
|
-
|
|
7
|
+
Global install:
|
|
35
8
|
|
|
36
|
-
|
|
9
|
+
```bash
|
|
10
|
+
npm i -g @leeguoo/zentao-mcp
|
|
11
|
+
```
|
|
37
12
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"-y",
|
|
43
|
-
"@leeguoo/zentao-mcp",
|
|
44
|
-
"--zentao-url=https://zentao.example.com/zentao",
|
|
45
|
-
"--zentao-account=leo",
|
|
46
|
-
"--zentao-password=***",
|
|
47
|
-
"--stdio"
|
|
48
|
-
]
|
|
13
|
+
Or use without installing:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx -y @leeguoo/zentao-mcp --help
|
|
49
17
|
```
|
|
50
18
|
|
|
51
|
-
|
|
52
|
-
- Claude Desktop: `~/Library/Application Support/Claude/claude_desktop_config.toml` (Mac) or `%APPDATA%\Claude\claude_desktop_config.toml` (Windows)
|
|
53
|
-
- Cursor: `~/.cursor/mcp.json` (JSON format)
|
|
19
|
+
This installs the `zentao` command (and keeps `zentao-mcp` as a compatibility alias).
|
|
54
20
|
|
|
55
21
|
## Configuration
|
|
56
22
|
|
|
57
23
|
### Required Parameters
|
|
58
24
|
|
|
59
|
-
You can configure the
|
|
25
|
+
You can configure the CLI using CLI arguments or environment variables:
|
|
26
|
+
|
|
27
|
+
CLI arguments:
|
|
60
28
|
|
|
61
|
-
**CLI Arguments:**
|
|
62
29
|
- `--zentao-url` (e.g. `https://zentao.example.com/zentao`)
|
|
63
30
|
- `--zentao-account`
|
|
64
31
|
- `--zentao-password`
|
|
65
32
|
|
|
66
|
-
|
|
67
|
-
|
|
33
|
+
Environment variables:
|
|
34
|
+
|
|
35
|
+
- `ZENTAO_URL`
|
|
68
36
|
- `ZENTAO_ACCOUNT`
|
|
69
37
|
- `ZENTAO_PASSWORD`
|
|
70
38
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
"command": "npx",
|
|
80
|
-
"args": ["-y", "@leeguoo/zentao-mcp", "--stdio"],
|
|
81
|
-
"env": {
|
|
82
|
-
"ZENTAO_URL": "https://zentao.example.com/zentao",
|
|
83
|
-
"ZENTAO_ACCOUNT": "leo",
|
|
84
|
-
"ZENTAO_PASSWORD": "***"
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
39
|
+
Tip: `ZENTAO_URL` should include the ZenTao base path (often `/zentao`).
|
|
40
|
+
|
|
41
|
+
## Commands
|
|
42
|
+
|
|
43
|
+
List products:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
zentao products list
|
|
89
47
|
```
|
|
90
48
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
The MCP server provides four tools that can be triggered by natural language in Cursor:
|
|
96
|
-
|
|
97
|
-
- **`zentao_products_list`** - List all products
|
|
98
|
-
- **`zentao_bugs_list`** - List bugs for a specific product
|
|
99
|
-
- **`zentao_bug_get`** - Get bug details by ID
|
|
100
|
-
- **`zentao_bugs_mine`** - List my bugs by assignment or creator (status filter supported)
|
|
101
|
-
|
|
102
|
-
### Usage Examples
|
|
103
|
-
|
|
104
|
-
After configuring the MCP server in Cursor, you can use natural language to interact with ZenTao:
|
|
105
|
-
|
|
106
|
-
**English:**
|
|
107
|
-
- "Show me all products"
|
|
108
|
-
- "List bugs for product 1"
|
|
109
|
-
- "Show bug 123"
|
|
110
|
-
- "Show me bugs"
|
|
111
|
-
- "Show my bugs"
|
|
112
|
-
- "List bugs assigned to me"
|
|
113
|
-
- "View bugs in product 2"
|
|
114
|
-
|
|
115
|
-
**Chinese (中文):**
|
|
116
|
-
- "看bug" / "查看bug" / "显示bug"
|
|
117
|
-
- "产品1的bug列表"
|
|
118
|
-
- "查看bug 123"
|
|
119
|
-
- "显示所有产品"
|
|
120
|
-
- "查看产品2的问题"
|
|
121
|
-
- "我的bug"
|
|
122
|
-
- "分配给我的bug"
|
|
123
|
-
|
|
124
|
-
The AI will automatically:
|
|
125
|
-
1. Use `zentao_products_list` to get product IDs when needed
|
|
126
|
-
2. Use `zentao_bugs_list` when you ask to see bugs
|
|
127
|
-
3. Use `zentao_bug_get` when you ask for bug details
|
|
128
|
-
4. Use `zentao_bugs_mine` when you ask for your own bugs
|
|
129
|
-
|
|
130
|
-
### Tool Parameters
|
|
131
|
-
|
|
132
|
-
**zentao_products_list:**
|
|
133
|
-
```json
|
|
134
|
-
{
|
|
135
|
-
"page": 1,
|
|
136
|
-
"limit": 1000
|
|
137
|
-
}
|
|
49
|
+
List bugs for a product:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
zentao bugs list --product 1
|
|
138
53
|
```
|
|
139
54
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
"page": 1,
|
|
145
|
-
"limit": 20
|
|
146
|
-
}
|
|
55
|
+
Get bug details:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
zentao bug get --id 123
|
|
147
59
|
```
|
|
148
60
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
61
|
+
List my bugs:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
zentao bugs mine --scope assigned --status active
|
|
154
65
|
```
|
|
155
66
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
67
|
+
Self test:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
zentao self-test
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Login
|
|
74
|
+
|
|
75
|
+
Save credentials locally (stored as plaintext TOML under your user config directory):
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
zentao login --zentao-url=https://zentao.example.com/zentao --zentao-account=leo --zentao-password=***
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Config file:
|
|
82
|
+
|
|
83
|
+
- `~/.config/zentao/config.toml` (or `$XDG_CONFIG_HOME/zentao/config.toml`)
|
|
84
|
+
|
|
85
|
+
Then commands can omit auth flags:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
zentao whoami
|
|
89
|
+
zentao products list
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Release (maintainers)
|
|
93
|
+
|
|
94
|
+
Requires `git`, `npm`, and `gh`.
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
zentao release patch --dry-run
|
|
165
98
|
```
|
|
166
99
|
|
|
167
100
|
## Local Development
|
|
168
101
|
|
|
169
102
|
```bash
|
|
170
103
|
pnpm install
|
|
171
|
-
|
|
172
|
-
ZENTAO_ACCOUNT=leo \\
|
|
173
|
-
ZENTAO_PASSWORD=*** \\
|
|
174
|
-
pnpm start
|
|
104
|
+
pnpm test
|
|
175
105
|
```
|
|
176
106
|
|
|
177
107
|
## Security
|
package/package.json
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leeguoo/zentao-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "ZenTao CLI for products + bugs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"zentao",
|
|
7
7
|
"chandao",
|
|
8
|
-
"mcp",
|
|
9
|
-
"modelcontextprotocol",
|
|
10
|
-
"llm",
|
|
11
|
-
"ai",
|
|
12
8
|
"api",
|
|
13
9
|
"rest",
|
|
14
10
|
"bug-tracker",
|
|
@@ -20,10 +16,12 @@
|
|
|
20
16
|
],
|
|
21
17
|
"type": "module",
|
|
22
18
|
"bin": {
|
|
19
|
+
"zentao": "src/index.js",
|
|
23
20
|
"zentao-mcp": "src/index.js"
|
|
24
21
|
},
|
|
25
22
|
"files": [
|
|
26
23
|
"src",
|
|
24
|
+
"skills",
|
|
27
25
|
"README.md"
|
|
28
26
|
],
|
|
29
27
|
"publishConfig": {
|
|
@@ -31,13 +29,15 @@
|
|
|
31
29
|
},
|
|
32
30
|
"scripts": {
|
|
33
31
|
"start": "node src/index.js",
|
|
34
|
-
"self-test": "node
|
|
35
|
-
"release": "
|
|
36
|
-
"release:patch": "
|
|
37
|
-
"release:minor": "
|
|
38
|
-
"release:major": "
|
|
32
|
+
"self-test": "node src/index.js self-test",
|
|
33
|
+
"release": "node src/index.js release",
|
|
34
|
+
"release:patch": "node src/index.js release patch",
|
|
35
|
+
"release:minor": "node src/index.js release minor",
|
|
36
|
+
"release:major": "node src/index.js release major",
|
|
37
|
+
"test": "node --test"
|
|
39
38
|
},
|
|
40
|
-
"dependencies": {
|
|
41
|
-
|
|
39
|
+
"dependencies": {},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18"
|
|
42
42
|
}
|
|
43
43
|
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# zentao (ZenTao CLI)
|
|
2
|
+
|
|
3
|
+
This package provides a CLI for ZenTao REST API (products + bugs).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Global install:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i -g @leeguoo/zentao-mcp
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or use without installing:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx -y @leeguoo/zentao-mcp --help
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This installs the `zentao` command (and keeps `zentao-mcp` as a compatibility alias).
|
|
20
|
+
|
|
21
|
+
## Authentication
|
|
22
|
+
|
|
23
|
+
You can pass credentials via environment variables:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
export ZENTAO_URL="https://zentao.example.com/zentao"
|
|
27
|
+
export ZENTAO_ACCOUNT="leo"
|
|
28
|
+
export ZENTAO_PASSWORD="***"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or via CLI flags:
|
|
32
|
+
|
|
33
|
+
- `--zentao-url`
|
|
34
|
+
- `--zentao-account`
|
|
35
|
+
- `--zentao-password`
|
|
36
|
+
|
|
37
|
+
## Commands
|
|
38
|
+
|
|
39
|
+
### List products
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
zentao products list
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### List bugs for a product
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
zentao bugs list --product 1
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Get bug details
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
zentao bug get --id 123
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### List my bugs
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
zentao bugs mine --scope assigned --status active
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Common options:
|
|
64
|
+
|
|
65
|
+
- `--scope`: `assigned|opened|resolved|all`
|
|
66
|
+
- `--status`: `active|resolved|closed|all` (supports `,` or `|` separated)
|
|
67
|
+
- `--include-details`: include bug list in response
|
|
68
|
+
- `--product-ids`: limit scan to specific products, e.g. `--product-ids=1,2,3`
|
|
69
|
+
|
|
70
|
+
### Self test
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
zentao self-test
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Use `--expected N` to make it CI-friendly (exit code 2 when mismatch):
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
zentao self-test --expected 0
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Login / Whoami
|
|
83
|
+
|
|
84
|
+
Save credentials locally:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
zentao login --zentao-url=https://zentao.example.com/zentao --zentao-account=leo --zentao-password=***
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Config file:
|
|
91
|
+
|
|
92
|
+
- `~/.config/zentao/config.toml` (or `$XDG_CONFIG_HOME/zentao/config.toml`)
|
|
93
|
+
|
|
94
|
+
Then:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
zentao whoami
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Release (maintainers)
|
|
101
|
+
|
|
102
|
+
Requires `git`, `npm`, and `gh`.
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
zentao release patch --dry-run
|
|
106
|
+
```
|
package/src/cli/args.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export function parseCliArgs(argv) {
|
|
2
|
+
const args = {};
|
|
3
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
4
|
+
const raw = argv[i];
|
|
5
|
+
if (!raw.startsWith("--")) continue;
|
|
6
|
+
const [flag, inlineValue] = raw.split("=", 2);
|
|
7
|
+
const key = flag.replace(/^--/, "");
|
|
8
|
+
if (inlineValue !== undefined) {
|
|
9
|
+
args[key] = inlineValue;
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
const next = argv[i + 1];
|
|
13
|
+
if (next && !next.startsWith("--")) {
|
|
14
|
+
args[key] = next;
|
|
15
|
+
i += 1;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
args[key] = true;
|
|
19
|
+
}
|
|
20
|
+
return args;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function hasHelpFlag(argv) {
|
|
24
|
+
return argv.includes("--help") || argv.includes("-h");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getOption(cliArgs, env, envName, cliName) {
|
|
28
|
+
if (cliArgs[cliName]) return cliArgs[cliName];
|
|
29
|
+
const envValue = env[envName];
|
|
30
|
+
if (envValue) return envValue;
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isFlagToken(raw) {
|
|
35
|
+
return raw.startsWith("-") && raw !== "-";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function splitSubcommand(argv) {
|
|
39
|
+
// Goal: find the first positional argument that isn't a flag or a flag value.
|
|
40
|
+
// This allows: `zentao-mcp self-test --foo=bar`.
|
|
41
|
+
// It also avoids treating `--zentao-url https://...` value as a subcommand.
|
|
42
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
43
|
+
const raw = argv[i];
|
|
44
|
+
|
|
45
|
+
if (raw === "--") {
|
|
46
|
+
return { command: null, commandArgv: argv };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (raw.startsWith("--")) {
|
|
50
|
+
const hasInlineValue = raw.includes("=");
|
|
51
|
+
if (hasInlineValue) continue;
|
|
52
|
+
const next = argv[i + 1];
|
|
53
|
+
if (next && !isFlagToken(next)) {
|
|
54
|
+
i += 1;
|
|
55
|
+
}
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (raw === "-h") continue;
|
|
60
|
+
if (raw === "-v") continue;
|
|
61
|
+
if (raw.startsWith("-")) continue;
|
|
62
|
+
|
|
63
|
+
return { command: raw, commandArgv: argv.slice(i + 1) };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { command: null, commandArgv: argv };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function findFirstPositional(argv) {
|
|
70
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
71
|
+
const raw = argv[i];
|
|
72
|
+
if (raw === "--") continue;
|
|
73
|
+
|
|
74
|
+
if (raw.startsWith("--")) {
|
|
75
|
+
const hasInlineValue = raw.includes("=");
|
|
76
|
+
if (hasInlineValue) continue;
|
|
77
|
+
const next = argv[i + 1];
|
|
78
|
+
if (next && !isFlagToken(next)) {
|
|
79
|
+
i += 1;
|
|
80
|
+
}
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (raw === "-h") continue;
|
|
85
|
+
if (raw === "-v") continue;
|
|
86
|
+
if (raw.startsWith("-")) continue;
|
|
87
|
+
|
|
88
|
+
return { index: i, value: raw };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { index: -1, value: null };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function extractCommand(argv) {
|
|
95
|
+
const { index, value } = findFirstPositional(argv);
|
|
96
|
+
if (!value) return { command: null, argv };
|
|
97
|
+
const nextArgv = argv.slice(0, index).concat(argv.slice(index + 1));
|
|
98
|
+
return { command: value, argv: nextArgv };
|
|
99
|
+
}
|
package/src/cli/help.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export function printRootHelp() {
|
|
2
|
+
// Keep this plain text: many users run via npx and paste output.
|
|
3
|
+
process.stdout.write(`zentao - ZenTao CLI\n\n`);
|
|
4
|
+
process.stdout.write(`Usage:\n`);
|
|
5
|
+
process.stdout.write(` zentao login [--zentao-url ... --zentao-account ... --zentao-password ...] [--yes]\n`);
|
|
6
|
+
process.stdout.write(` zentao whoami\n`);
|
|
7
|
+
process.stdout.write(` zentao products list [--page N] [--limit N]\n`);
|
|
8
|
+
process.stdout.write(` zentao bugs list --product <id> [--page N] [--limit N]\n`);
|
|
9
|
+
process.stdout.write(` zentao bug get --id <bugId>\n`);
|
|
10
|
+
process.stdout.write(` zentao bugs mine [--scope ...] [--status ...] [--include-details]\n`);
|
|
11
|
+
process.stdout.write(` zentao self-test [--expected N]\n`);
|
|
12
|
+
process.stdout.write(` zentao release [patch|minor|major] [--dry-run] [--yes]\n\n`);
|
|
13
|
+
process.stdout.write(`Auth options:\n`);
|
|
14
|
+
process.stdout.write(` --zentao-url or env ZENTAO_URL\n`);
|
|
15
|
+
process.stdout.write(` --zentao-account or env ZENTAO_ACCOUNT\n`);
|
|
16
|
+
process.stdout.write(` --zentao-password or env ZENTAO_PASSWORD\n`);
|
|
17
|
+
process.stdout.write(` --help, -h show help\n\n`);
|
|
18
|
+
process.stdout.write(`Subcommands:\n`);
|
|
19
|
+
process.stdout.write(` login save credentials locally\n`);
|
|
20
|
+
process.stdout.write(` whoami show current account\n`);
|
|
21
|
+
process.stdout.write(` products ZenTao products\n`);
|
|
22
|
+
process.stdout.write(` bugs ZenTao bugs\n`);
|
|
23
|
+
process.stdout.write(` bug ZenTao bug\n`);
|
|
24
|
+
process.stdout.write(` self-test run a basic API roundtrip\n`);
|
|
25
|
+
process.stdout.write(` release version bump + tag + gh release + npm publish\n`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function printSelfTestHelp() {
|
|
29
|
+
process.stdout.write(`zentao self-test - verify API access\n\n`);
|
|
30
|
+
process.stdout.write(`Usage:\n`);
|
|
31
|
+
process.stdout.write(` zentao self-test --zentao-url=... --zentao-account=... --zentao-password=...\n`);
|
|
32
|
+
process.stdout.write(` ZENTAO_URL=... ZENTAO_ACCOUNT=... ZENTAO_PASSWORD=... zentao self-test\n\n`);
|
|
33
|
+
process.stdout.write(`Options:\n`);
|
|
34
|
+
process.stdout.write(` --expected N exit 2 if total != N\n`);
|
|
35
|
+
process.stdout.write(` --help, -h show help\n`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function printReleaseHelp() {
|
|
39
|
+
process.stdout.write(`zentao release - create a new release\n\n`);
|
|
40
|
+
process.stdout.write(`Usage:\n`);
|
|
41
|
+
process.stdout.write(` zentao release [patch|minor|major] [--dry-run] [--yes]\n\n`);
|
|
42
|
+
process.stdout.write(`Options:\n`);
|
|
43
|
+
process.stdout.write(` --dry-run print planned commands, do not run\n`);
|
|
44
|
+
process.stdout.write(` --yes auto-confirm prompts (dirty git, npm login)\n`);
|
|
45
|
+
process.stdout.write(` --skip-push do not push commits/tags\n`);
|
|
46
|
+
process.stdout.write(` --skip-github-release do not create GitHub release\n`);
|
|
47
|
+
process.stdout.write(` --skip-publish do not npm publish\n`);
|
|
48
|
+
process.stdout.write(` --help, -h show help\n`);
|
|
49
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import { extractCommand, hasHelpFlag, parseCliArgs } from "../cli/args.js";
|
|
3
|
+
import { createClientFromCli } from "../zentao/client.js";
|
|
4
|
+
|
|
5
|
+
function printHelp() {
|
|
6
|
+
process.stdout.write(`zentao-mcp bug get\n\n`);
|
|
7
|
+
process.stdout.write(`Usage:\n`);
|
|
8
|
+
process.stdout.write(` zentao-mcp bug get --id <bugId>\n`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function runBug({ argv = [], env = process.env } = {}) {
|
|
12
|
+
if (hasHelpFlag(argv)) {
|
|
13
|
+
printHelp();
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const { command: sub, argv: argvWithoutSub } = extractCommand(argv);
|
|
18
|
+
if (sub !== "get") throw new Error(`Unknown bug subcommand: ${sub || "(missing)"}`);
|
|
19
|
+
|
|
20
|
+
const cliArgs = parseCliArgs(argvWithoutSub);
|
|
21
|
+
const id = cliArgs.id;
|
|
22
|
+
if (!id) throw new Error("Missing --id");
|
|
23
|
+
|
|
24
|
+
const api = createClientFromCli({ argv: argvWithoutSub, env });
|
|
25
|
+
const result = await api.getBug({ id });
|
|
26
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
27
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import { extractCommand, hasHelpFlag, parseCliArgs } from "../cli/args.js";
|
|
3
|
+
import { createClientFromCli } from "../zentao/client.js";
|
|
4
|
+
|
|
5
|
+
function parseCsvIntegers(value) {
|
|
6
|
+
if (value === undefined || value === null || value === "") return null;
|
|
7
|
+
if (Array.isArray(value)) {
|
|
8
|
+
const nested = value.flatMap((item) => String(item).split(/[,|]/));
|
|
9
|
+
const parsed = nested.map((item) => Number(item)).filter((n) => Number.isFinite(n));
|
|
10
|
+
return parsed.length ? parsed : null;
|
|
11
|
+
}
|
|
12
|
+
const tokens = String(value)
|
|
13
|
+
.split(/[,|]/)
|
|
14
|
+
.map((item) => item.trim())
|
|
15
|
+
.filter(Boolean);
|
|
16
|
+
const parsed = tokens.map((item) => Number(item)).filter((n) => Number.isFinite(n));
|
|
17
|
+
return parsed.length ? parsed : null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function printHelp() {
|
|
21
|
+
process.stdout.write(`zentao-mcp bugs <subcommand>\n\n`);
|
|
22
|
+
process.stdout.write(`Usage:\n`);
|
|
23
|
+
process.stdout.write(` zentao-mcp bugs list --product <id> [--page N] [--limit N]\n`);
|
|
24
|
+
process.stdout.write(` zentao-mcp bugs mine [--scope assigned|opened|resolved|all] [--status active|resolved|closed|all] [--include-details]\n`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function runBugs({ argv = [], env = process.env } = {}) {
|
|
28
|
+
if (hasHelpFlag(argv)) {
|
|
29
|
+
printHelp();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const { command: sub, argv: argvWithoutSub } = extractCommand(argv);
|
|
34
|
+
const cliArgs = parseCliArgs(argvWithoutSub);
|
|
35
|
+
const api = createClientFromCli({ argv: argvWithoutSub, env });
|
|
36
|
+
|
|
37
|
+
if (sub === "list") {
|
|
38
|
+
const product = cliArgs.product;
|
|
39
|
+
if (!product) throw new Error("Missing --product");
|
|
40
|
+
const result = await api.listBugs({
|
|
41
|
+
product,
|
|
42
|
+
page: cliArgs.page,
|
|
43
|
+
limit: cliArgs.limit,
|
|
44
|
+
});
|
|
45
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (sub === "mine") {
|
|
50
|
+
const includeDetails = Boolean(cliArgs["include-details"]);
|
|
51
|
+
const includeZero = Boolean(cliArgs["include-zero"]);
|
|
52
|
+
const productIds = parseCsvIntegers(cliArgs["product-ids"]);
|
|
53
|
+
const result = await api.bugsMine({
|
|
54
|
+
account: cliArgs.account,
|
|
55
|
+
scope: cliArgs.scope,
|
|
56
|
+
status: cliArgs.status,
|
|
57
|
+
productIds,
|
|
58
|
+
includeZero,
|
|
59
|
+
perPage: cliArgs["per-page"],
|
|
60
|
+
maxItems: cliArgs["max-items"],
|
|
61
|
+
includeDetails,
|
|
62
|
+
});
|
|
63
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
throw new Error(`Unknown bugs subcommand: ${sub || "(missing)"}`);
|
|
68
|
+
}
|