@leeguoo/zentao-mcp 0.4.0 → 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 +61 -120
- 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 -426
- package/src/zentao/client.js +324 -0
package/README.md
CHANGED
|
@@ -1,166 +1,107 @@
|
|
|
1
1
|
# zentao-mcp
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
##
|
|
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.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
31
6
|
|
|
32
|
-
|
|
7
|
+
Global install:
|
|
33
8
|
|
|
34
|
-
|
|
9
|
+
```bash
|
|
10
|
+
npm i -g @leeguoo/zentao-mcp
|
|
11
|
+
```
|
|
35
12
|
|
|
36
|
-
|
|
13
|
+
Or use without installing:
|
|
37
14
|
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
command = "npx"
|
|
41
|
-
args = [
|
|
42
|
-
"-y",
|
|
43
|
-
"@leeguoo/zentao-mcp",
|
|
44
|
-
"--zentao-url=https://zentao.example.com/zentao",
|
|
45
|
-
"--zentao-account=leo",
|
|
46
|
-
"--zentao-password=***",
|
|
47
|
-
"--stdio"
|
|
48
|
-
]
|
|
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
|
-
|
|
49
|
+
List bugs for a product:
|
|
92
50
|
|
|
93
|
-
|
|
51
|
+
```bash
|
|
52
|
+
zentao bugs list --product 1
|
|
53
|
+
```
|
|
94
54
|
|
|
95
|
-
|
|
55
|
+
Get bug details:
|
|
96
56
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
57
|
+
```bash
|
|
58
|
+
zentao bug get --id 123
|
|
59
|
+
```
|
|
100
60
|
|
|
101
|
-
|
|
61
|
+
List my bugs:
|
|
102
62
|
|
|
103
|
-
|
|
63
|
+
```bash
|
|
64
|
+
zentao bugs mine --scope assigned --status active
|
|
65
|
+
```
|
|
104
66
|
|
|
105
|
-
|
|
106
|
-
- "Show me all products"
|
|
107
|
-
- "List bugs for product 1"
|
|
108
|
-
- "Show me bugs"
|
|
109
|
-
- "Show my bugs"
|
|
110
|
-
- "List bugs assigned to me"
|
|
111
|
-
- "View bugs in product 2"
|
|
67
|
+
Self test:
|
|
112
68
|
|
|
113
|
-
|
|
114
|
-
-
|
|
115
|
-
|
|
116
|
-
- "显示所有产品"
|
|
117
|
-
- "查看产品2的问题"
|
|
118
|
-
- "我的bug"
|
|
119
|
-
- "分配给我的bug"
|
|
69
|
+
```bash
|
|
70
|
+
zentao self-test
|
|
71
|
+
```
|
|
120
72
|
|
|
121
|
-
|
|
122
|
-
1. Use `zentao_products_list` to get product IDs when needed
|
|
123
|
-
2. Use `zentao_bugs_list` when you ask to see bugs
|
|
124
|
-
3. Use `zentao_bugs_mine` when you ask for your own bugs
|
|
73
|
+
## Login
|
|
125
74
|
|
|
126
|
-
|
|
75
|
+
Save credentials locally (stored as plaintext TOML under your user config directory):
|
|
127
76
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
{
|
|
131
|
-
"page": 1,
|
|
132
|
-
"limit": 1000
|
|
133
|
-
}
|
|
77
|
+
```bash
|
|
78
|
+
zentao login --zentao-url=https://zentao.example.com/zentao --zentao-account=leo --zentao-password=***
|
|
134
79
|
```
|
|
135
80
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
|
143
90
|
```
|
|
144
91
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
"includeDetails": true,
|
|
152
|
-
"maxItems": 50
|
|
153
|
-
}
|
|
92
|
+
## Release (maintainers)
|
|
93
|
+
|
|
94
|
+
Requires `git`, `npm`, and `gh`.
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
zentao release patch --dry-run
|
|
154
98
|
```
|
|
155
99
|
|
|
156
100
|
## Local Development
|
|
157
101
|
|
|
158
102
|
```bash
|
|
159
103
|
pnpm install
|
|
160
|
-
|
|
161
|
-
ZENTAO_ACCOUNT=leo \\
|
|
162
|
-
ZENTAO_PASSWORD=*** \\
|
|
163
|
-
pnpm start
|
|
104
|
+
pnpm test
|
|
164
105
|
```
|
|
165
106
|
|
|
166
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
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import readline from "node:readline";
|
|
3
|
+
import { hasHelpFlag, parseCliArgs, getOption } from "../cli/args.js";
|
|
4
|
+
import { saveConfig } from "../config/store.js";
|
|
5
|
+
import { ZentaoClient } from "../zentao/client.js";
|
|
6
|
+
|
|
7
|
+
function printHelp() {
|
|
8
|
+
process.stdout.write(`zentao login - save credentials locally\n\n`);
|
|
9
|
+
process.stdout.write(`Usage:\n`);
|
|
10
|
+
process.stdout.write(` zentao login --zentao-url=... --zentao-account=... --zentao-password=... [--yes]\n`);
|
|
11
|
+
process.stdout.write(`\n`);
|
|
12
|
+
process.stdout.write(`Notes:\n`);
|
|
13
|
+
process.stdout.write(` Credentials are stored as plaintext TOML in your user config directory.\n`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function prompt(question) {
|
|
17
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
18
|
+
try {
|
|
19
|
+
return await new Promise((resolve) => rl.question(question, resolve));
|
|
20
|
+
} finally {
|
|
21
|
+
rl.close();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function runLogin({ argv = [], env = process.env } = {}) {
|
|
26
|
+
if (hasHelpFlag(argv)) {
|
|
27
|
+
printHelp();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const cliArgs = parseCliArgs(argv);
|
|
32
|
+
const yes = Boolean(cliArgs.yes);
|
|
33
|
+
let baseUrl = getOption(cliArgs, env, "ZENTAO_URL", "zentao-url");
|
|
34
|
+
let account = getOption(cliArgs, env, "ZENTAO_ACCOUNT", "zentao-account");
|
|
35
|
+
let password = getOption(cliArgs, env, "ZENTAO_PASSWORD", "zentao-password");
|
|
36
|
+
|
|
37
|
+
if (!baseUrl && !yes) baseUrl = String(await prompt("ZENTAO_URL: ")).trim();
|
|
38
|
+
if (!account && !yes) account = String(await prompt("ZENTAO_ACCOUNT: ")).trim();
|
|
39
|
+
if (!password && !yes) password = String(await prompt("ZENTAO_PASSWORD (echoed): ")).trim();
|
|
40
|
+
|
|
41
|
+
if (!baseUrl || !account || !password) {
|
|
42
|
+
throw new Error("Missing credentials. Provide flags/env, or run interactively.");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Verify credentials by requesting a token.
|
|
46
|
+
const client = new ZentaoClient({ baseUrl, account, password });
|
|
47
|
+
await client.ensureToken();
|
|
48
|
+
|
|
49
|
+
const filePath = saveConfig(
|
|
50
|
+
{
|
|
51
|
+
zentaoUrl: baseUrl,
|
|
52
|
+
zentaoAccount: account,
|
|
53
|
+
zentaoPassword: password,
|
|
54
|
+
},
|
|
55
|
+
{ env }
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
process.stdout.write(`Logged in as ${account}\n`);
|
|
59
|
+
process.stdout.write(`Saved to ${filePath}\n`);
|
|
60
|
+
}
|