@leeguoo/zentao-mcp 0.5.0 → 0.5.4

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
@@ -4,10 +4,17 @@ ZenTao CLI for products + bugs.
4
4
 
5
5
  ## Installation
6
6
 
7
- Global install:
7
+ Global install (recommended):
8
8
 
9
9
  ```bash
10
- npm i -g @leeguoo/zentao-mcp
10
+ pnpm i -g @leeguoo/zentao-mcp
11
+ ```
12
+
13
+ If you don't have pnpm:
14
+
15
+ ```bash
16
+ npm i -g pnpm
17
+ pnpm i -g @leeguoo/zentao-mcp
11
18
  ```
12
19
 
13
20
  Or use without installing:
@@ -38,6 +45,12 @@ Environment variables:
38
45
 
39
46
  Tip: `ZENTAO_URL` should include the ZenTao base path (often `/zentao`).
40
47
 
48
+ Example:
49
+
50
+ - `https://zentao.example.com/zentao` (common)
51
+
52
+ If you see `404 Not Found` when logging in, your base path is likely missing `/zentao`.
53
+
41
54
  ## Commands
42
55
 
43
56
  List products:
@@ -46,24 +59,48 @@ List products:
46
59
  zentao products list
47
60
  ```
48
61
 
62
+ Full JSON output:
63
+
64
+ ```bash
65
+ zentao products list --json
66
+ ```
67
+
49
68
  List bugs for a product:
50
69
 
51
70
  ```bash
52
71
  zentao bugs list --product 1
53
72
  ```
54
73
 
74
+ Full JSON output:
75
+
76
+ ```bash
77
+ zentao bugs list --product 1 --json
78
+ ```
79
+
55
80
  Get bug details:
56
81
 
57
82
  ```bash
58
83
  zentao bug get --id 123
59
84
  ```
60
85
 
86
+ Full JSON output:
87
+
88
+ ```bash
89
+ zentao bug get --id 123 --json
90
+ ```
91
+
61
92
  List my bugs:
62
93
 
63
94
  ```bash
64
95
  zentao bugs mine --scope assigned --status active
65
96
  ```
66
97
 
98
+ Full JSON output:
99
+
100
+ ```bash
101
+ zentao bugs mine --scope assigned --status active --json
102
+ ```
103
+
67
104
  Self test:
68
105
 
69
106
  ```bash
@@ -89,14 +126,42 @@ zentao whoami
89
126
  zentao products list
90
127
  ```
91
128
 
129
+ Troubleshooting login:
130
+
131
+ - If `Token response parse failed: <html>...404 Not Found...`, try:
132
+ - `https://your-host/zentao` instead of `https://your-host/`
133
+
92
134
  ## Release (maintainers)
93
135
 
136
+ ### GitHub Actions (recommended)
137
+
138
+ This repo supports npm Trusted Publisher (OIDC) via GitHub Actions.
139
+
140
+ 1. Create a tag matching `package.json` version:
141
+
142
+ ```bash
143
+ git tag v0.5.1
144
+ git push origin v0.5.1
145
+ ```
146
+
147
+ 2. The workflow `.github/workflows/publish-npm.yml` will run tests and publish to npm.
148
+
149
+ In npm Trusted Publisher settings, set the workflow filename to `publish-npm.yml`.
150
+
151
+ ### Local release (fallback)
152
+
94
153
  Requires `git`, `npm`, and `gh`.
95
154
 
96
155
  ```bash
97
156
  zentao release patch --dry-run
98
157
  ```
99
158
 
159
+ If you are using GitHub Actions publishing, prefer tagging + pushing, or run local release with:
160
+
161
+ ```bash
162
+ zentao release patch --skip-publish
163
+ ```
164
+
100
165
  ## Local Development
101
166
 
102
167
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leeguoo/zentao-mcp",
3
- "version": "0.5.0",
3
+ "version": "0.5.4",
4
4
  "description": "ZenTao CLI for products + bugs",
5
5
  "keywords": [
6
6
  "zentao",
@@ -27,6 +27,14 @@
27
27
  "publishConfig": {
28
28
  "access": "public"
29
29
  },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/leeguooooo/zentao-mcp"
33
+ },
34
+ "bugs": {
35
+ "url": "https://github.com/leeguooooo/zentao-mcp/issues"
36
+ },
37
+ "homepage": "https://github.com/leeguooooo/zentao-mcp#readme",
30
38
  "scripts": {
31
39
  "start": "node src/index.js",
32
40
  "self-test": "node src/index.js self-test",
@@ -2,12 +2,25 @@
2
2
 
3
3
  This package provides a CLI for ZenTao REST API (products + bugs).
4
4
 
5
+ When instructing users, assume the primary workflow is:
6
+
7
+ 1) install with pnpm globally
8
+ 2) `zentao login` once
9
+ 3) run `zentao ...` commands without passing credentials each time
10
+
5
11
  ## Installation
6
12
 
7
- Global install:
13
+ Global install (recommended):
8
14
 
9
15
  ```bash
10
- npm i -g @leeguoo/zentao-mcp
16
+ pnpm i -g @leeguoo/zentao-mcp
17
+ ```
18
+
19
+ If pnpm is not installed:
20
+
21
+ ```bash
22
+ npm i -g pnpm
23
+ pnpm i -g @leeguoo/zentao-mcp
11
24
  ```
12
25
 
13
26
  Or use without installing:
@@ -20,6 +33,19 @@ This installs the `zentao` command (and keeps `zentao-mcp` as a compatibility al
20
33
 
21
34
  ## Authentication
22
35
 
36
+ Recommended: login once (stored locally):
37
+
38
+ ```bash
39
+ zentao login --zentao-url=https://zentao.example.com/zentao --zentao-account=leo --zentao-password=***
40
+ ```
41
+
42
+ This writes:
43
+
44
+ - `~/.config/zentao/config.toml` (or `$XDG_CONFIG_HOME/zentao/config.toml`)
45
+
46
+ IMPORTANT: `zentaoUrl` should usually include `/zentao`.
47
+ If login returns 404 HTML, your base path is likely missing `/zentao`.
48
+
23
49
  You can pass credentials via environment variables:
24
50
 
25
51
  ```bash
@@ -42,24 +68,60 @@ Or via CLI flags:
42
68
  zentao products list
43
69
  ```
44
70
 
71
+ By default, this prints a simple TSV table (key fields). To get full JSON:
72
+
73
+ Full JSON output:
74
+
75
+ ```bash
76
+ zentao products list --json
77
+ ```
78
+
45
79
  ### List bugs for a product
46
80
 
47
81
  ```bash
48
82
  zentao bugs list --product 1
49
83
  ```
50
84
 
85
+ By default, this prints a simple TSV table. To get full JSON:
86
+
87
+ Full JSON output:
88
+
89
+ ```bash
90
+ zentao bugs list --product 1 --json
91
+ ```
92
+
51
93
  ### Get bug details
52
94
 
53
95
  ```bash
54
96
  zentao bug get --id 123
55
97
  ```
56
98
 
99
+ By default, this prints a simple TSV (single row). To get full JSON:
100
+
101
+ Full JSON output:
102
+
103
+ ```bash
104
+ zentao bug get --id 123 --json
105
+ ```
106
+
57
107
  ### List my bugs
58
108
 
59
109
  ```bash
60
110
  zentao bugs mine --scope assigned --status active
61
111
  ```
62
112
 
113
+ By default, this prints a simple summary table. To include bug details:
114
+
115
+ ```bash
116
+ zentao bugs mine --status active --include-details
117
+ ```
118
+
119
+ Full JSON output:
120
+
121
+ ```bash
122
+ zentao bugs mine --scope assigned --status active --json
123
+ ```
124
+
63
125
  Common options:
64
126
 
65
127
  - `--scope`: `assigned|opened|resolved|all`
@@ -79,19 +141,9 @@ Use `--expected N` to make it CI-friendly (exit code 2 when mismatch):
79
141
  zentao self-test --expected 0
80
142
  ```
81
143
 
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`)
144
+ ## Whoami
93
145
 
94
- Then:
146
+ After login:
95
147
 
96
148
  ```bash
97
149
  zentao whoami
@@ -104,3 +156,12 @@ Requires `git`, `npm`, and `gh`.
104
156
  ```bash
105
157
  zentao release patch --dry-run
106
158
  ```
159
+
160
+ Recommended publishing flow is via GitHub Actions npm Trusted Publisher (OIDC):
161
+
162
+ 1) bump `package.json` version
163
+ 2) create a matching tag `vX.Y.Z`
164
+ 3) push the tag to GitHub
165
+ 4) workflow `publish-npm.yml` publishes to npm
166
+
167
+ If you use Actions publishing, prefer `zentao release patch --skip-publish` locally.
package/src/cli/help.js CHANGED
@@ -4,10 +4,10 @@ export function printRootHelp() {
4
4
  process.stdout.write(`Usage:\n`);
5
5
  process.stdout.write(` zentao login [--zentao-url ... --zentao-account ... --zentao-password ...] [--yes]\n`);
6
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`);
7
+ process.stdout.write(` zentao products list [--page N] [--limit N] [--json]\n`);
8
+ process.stdout.write(` zentao bugs list --product <id> [--page N] [--limit N] [--json]\n`);
9
+ process.stdout.write(` zentao bug get --id <bugId> [--json]\n`);
10
+ process.stdout.write(` zentao bugs mine [--scope ...] [--status ...] [--include-details] [--json]\n`);
11
11
  process.stdout.write(` zentao self-test [--expected N]\n`);
12
12
  process.stdout.write(` zentao release [patch|minor|major] [--dry-run] [--yes]\n\n`);
13
13
  process.stdout.write(`Auth options:\n`);
@@ -3,9 +3,40 @@ import { extractCommand, hasHelpFlag, parseCliArgs } from "../cli/args.js";
3
3
  import { createClientFromCli } from "../zentao/client.js";
4
4
 
5
5
  function printHelp() {
6
- process.stdout.write(`zentao-mcp bug get\n\n`);
6
+ process.stdout.write(`zentao bug get\n\n`);
7
7
  process.stdout.write(`Usage:\n`);
8
- process.stdout.write(` zentao-mcp bug get --id <bugId>\n`);
8
+ process.stdout.write(` zentao bug get --id <bugId> [--json]\n`);
9
+ }
10
+
11
+ function formatAccount(value) {
12
+ if (!value) return "";
13
+ if (typeof value === "string" || typeof value === "number") return String(value);
14
+ if (typeof value === "object") return String(value.account || value.name || value.realname || "");
15
+ return "";
16
+ }
17
+
18
+ export function formatBugSimple(bug) {
19
+ const header = [
20
+ "id",
21
+ "title",
22
+ "status",
23
+ "pri",
24
+ "severity",
25
+ "assignedTo",
26
+ "openedBy",
27
+ "resolvedBy",
28
+ ].join("\t");
29
+ const row = [
30
+ String(bug?.id ?? ""),
31
+ String(bug?.title ?? ""),
32
+ String(bug?.status ?? ""),
33
+ String(bug?.pri ?? ""),
34
+ String(bug?.severity ?? ""),
35
+ formatAccount(bug?.assignedTo),
36
+ formatAccount(bug?.openedBy),
37
+ formatAccount(bug?.resolvedBy),
38
+ ].join("\t");
39
+ return `${header}\n${row}\n`;
9
40
  }
10
41
 
11
42
  export async function runBug({ argv = [], env = process.env } = {}) {
@@ -23,5 +54,16 @@ export async function runBug({ argv = [], env = process.env } = {}) {
23
54
 
24
55
  const api = createClientFromCli({ argv: argvWithoutSub, env });
25
56
  const result = await api.getBug({ id });
26
- process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
57
+ if (cliArgs.json) {
58
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
59
+ return;
60
+ }
61
+
62
+ const bug = result?.result?.bug;
63
+ if (!bug || typeof bug !== "object") {
64
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
65
+ return;
66
+ }
67
+
68
+ process.stdout.write(formatBugSimple(bug));
27
69
  }
@@ -18,10 +18,62 @@ function parseCsvIntegers(value) {
18
18
  }
19
19
 
20
20
  function printHelp() {
21
- process.stdout.write(`zentao-mcp bugs <subcommand>\n\n`);
21
+ process.stdout.write(`zentao bugs <subcommand>\n\n`);
22
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`);
23
+ process.stdout.write(` zentao bugs list --product <id> [--page N] [--limit N] [--json]\n`);
24
+ process.stdout.write(` zentao bugs mine [--scope assigned|opened|resolved|all] [--status active|resolved|closed|all] [--include-details] [--json]\n`);
25
+ }
26
+
27
+ function formatAccount(value) {
28
+ if (!value) return "";
29
+ if (typeof value === "string" || typeof value === "number") return String(value);
30
+ if (typeof value === "object") return String(value.account || value.name || value.realname || "");
31
+ return "";
32
+ }
33
+
34
+ export function formatBugsSimple(bugs) {
35
+ const rows = [];
36
+ rows.push(["id", "title", "status", "pri", "severity", "assignedTo"].join("\t"));
37
+ for (const bug of bugs) {
38
+ rows.push(
39
+ [
40
+ String(bug?.id ?? ""),
41
+ String(bug?.title ?? ""),
42
+ String(bug?.status ?? ""),
43
+ String(bug?.pri ?? ""),
44
+ String(bug?.severity ?? ""),
45
+ formatAccount(bug?.assignedTo),
46
+ ].join("\t")
47
+ );
48
+ }
49
+ return `${rows.join("\n")}\n`;
50
+ }
51
+
52
+ export function formatBugsMineSimple(result) {
53
+ const total = result?.total ?? 0;
54
+ const products = Array.isArray(result?.products) ? result.products : [];
55
+ const rows = [];
56
+ rows.push(`total\t${total}`);
57
+ rows.push(["id", "name", "myBugs", "totalBugs"].join("\t"));
58
+ for (const product of products) {
59
+ rows.push(
60
+ [
61
+ String(product?.id ?? ""),
62
+ String(product?.name ?? ""),
63
+ String(product?.myBugs ?? ""),
64
+ String(product?.totalBugs ?? ""),
65
+ ].join("\t")
66
+ );
67
+ }
68
+
69
+ const bugs = Array.isArray(result?.bugs) ? result.bugs : [];
70
+ if (bugs.length) {
71
+ rows.push("");
72
+ rows.push("bugs");
73
+ rows.push(formatBugsSimple(bugs).trimEnd());
74
+ }
75
+
76
+ return `${rows.join("\n")}\n`;
25
77
  }
26
78
 
27
79
  export async function runBugs({ argv = [], env = process.env } = {}) {
@@ -42,7 +94,19 @@ export async function runBugs({ argv = [], env = process.env } = {}) {
42
94
  page: cliArgs.page,
43
95
  limit: cliArgs.limit,
44
96
  });
45
- process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
97
+
98
+ if (cliArgs.json) {
99
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
100
+ return;
101
+ }
102
+
103
+ const bugs = result?.result?.bugs;
104
+ if (!Array.isArray(bugs)) {
105
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
106
+ return;
107
+ }
108
+
109
+ process.stdout.write(formatBugsSimple(bugs));
46
110
  return;
47
111
  }
48
112
 
@@ -60,7 +124,13 @@ export async function runBugs({ argv = [], env = process.env } = {}) {
60
124
  maxItems: cliArgs["max-items"],
61
125
  includeDetails,
62
126
  });
63
- process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
127
+
128
+ if (cliArgs.json) {
129
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
130
+ return;
131
+ }
132
+
133
+ process.stdout.write(formatBugsMineSimple(result?.result));
64
134
  return;
65
135
  }
66
136
 
@@ -3,9 +3,28 @@ import { extractCommand, hasHelpFlag, parseCliArgs } from "../cli/args.js";
3
3
  import { createClientFromCli } from "../zentao/client.js";
4
4
 
5
5
  function printHelp() {
6
- process.stdout.write(`zentao-mcp products list\n\n`);
6
+ process.stdout.write(`zentao products list\n\n`);
7
7
  process.stdout.write(`Usage:\n`);
8
- process.stdout.write(` zentao-mcp products list [--page N] [--limit N] [--json]\n`);
8
+ process.stdout.write(` zentao products list [--page N] [--limit N] [--json]\n`);
9
+ process.stdout.write(`\n`);
10
+ process.stdout.write(`Options:\n`);
11
+ process.stdout.write(` --json print full JSON payload\n`);
12
+ }
13
+
14
+ export function formatProductsSimple(products) {
15
+ const rows = [];
16
+ rows.push(["id", "name", "totalBugs", "status"].join("\t"));
17
+ for (const product of products) {
18
+ rows.push(
19
+ [
20
+ String(product.id ?? ""),
21
+ String(product.name ?? ""),
22
+ String(product.totalBugs ?? product.totalBugsCount ?? ""),
23
+ String(product.status ?? product.productStatus ?? ""),
24
+ ].join("\t")
25
+ );
26
+ }
27
+ return `${rows.join("\n")}\n`;
9
28
  }
10
29
 
11
30
  export async function runProducts({ argv = [], env = process.env } = {}) {
@@ -23,5 +42,17 @@ export async function runProducts({ argv = [], env = process.env } = {}) {
23
42
  page: cliArgs.page,
24
43
  limit: cliArgs.limit,
25
44
  });
26
- process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
45
+
46
+ if (cliArgs.json) {
47
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
48
+ return;
49
+ }
50
+
51
+ const products = result?.result?.products;
52
+ if (!Array.isArray(products)) {
53
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
54
+ return;
55
+ }
56
+
57
+ process.stdout.write(formatProductsSimple(products));
27
58
  }