cawplan 0.0.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/config/products.json +17 -0
- package/dist/commands/activities.d.ts +2 -0
- package/dist/commands/activities.js +43 -0
- package/dist/commands/analytics.d.ts +2 -0
- package/dist/commands/analytics.js +30 -0
- package/dist/commands/auth.d.ts +2 -0
- package/dist/commands/auth.js +145 -0
- package/dist/commands/community.d.ts +2 -0
- package/dist/commands/community.js +30 -0
- package/dist/commands/critical.d.ts +2 -0
- package/dist/commands/critical.js +205 -0
- package/dist/commands/knowledge.d.ts +2 -0
- package/dist/commands/knowledge.js +24 -0
- package/dist/commands/metrics.d.ts +2 -0
- package/dist/commands/metrics.js +27 -0
- package/dist/commands/product-activity.d.ts +2 -0
- package/dist/commands/product-activity.js +26 -0
- package/dist/commands/products.d.ts +2 -0
- package/dist/commands/products.js +100 -0
- package/dist/commands/qa-reports.d.ts +2 -0
- package/dist/commands/qa-reports.js +71 -0
- package/dist/commands/tickets.d.ts +2 -0
- package/dist/commands/tickets.js +431 -0
- package/dist/commands/todos.d.ts +2 -0
- package/dist/commands/todos.js +24 -0
- package/dist/commands/user-activity.d.ts +2 -0
- package/dist/commands/user-activity.js +31 -0
- package/dist/commands/users.d.ts +2 -0
- package/dist/commands/users.js +80 -0
- package/dist/commands/versions.d.ts +2 -0
- package/dist/commands/versions.js +65 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +82 -0
- package/dist/lib/auth-state.d.ts +11 -0
- package/dist/lib/auth-state.js +27 -0
- package/dist/lib/cache.d.ts +20 -0
- package/dist/lib/cache.js +135 -0
- package/dist/lib/config.d.ts +8 -0
- package/dist/lib/config.js +25 -0
- package/dist/lib/credentials.d.ts +15 -0
- package/dist/lib/credentials.js +50 -0
- package/dist/lib/http.d.ts +14 -0
- package/dist/lib/http.js +174 -0
- package/dist/lib/oauth.d.ts +20 -0
- package/dist/lib/oauth.js +155 -0
- package/dist/lib/output.d.ts +3 -0
- package/dist/lib/output.js +9 -0
- package/dist/lib/products.d.ts +24 -0
- package/dist/lib/products.js +87 -0
- package/dist/lib/user-config.d.ts +10 -0
- package/dist/lib/user-config.js +47 -0
- package/package.json +49 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { cawplanRequest } from "../lib/http.js";
|
|
2
|
+
import { getCache, setCache, buildScopedCacheKey, buildQueryFromFlags } from "../lib/cache.js";
|
|
3
|
+
export function registerProductsCommand(program) {
|
|
4
|
+
const products = program.command("products").description("Manage products");
|
|
5
|
+
products
|
|
6
|
+
.command("list")
|
|
7
|
+
.description("List all products")
|
|
8
|
+
.option("--search <q>", "Search query")
|
|
9
|
+
.option("--page_size <n>", "Page size")
|
|
10
|
+
.option("--page_num <n>", "Page number")
|
|
11
|
+
.option("--type_id <id>", "Filter by type ID")
|
|
12
|
+
.option("--product_line_id <id>", "Filter by product line ID")
|
|
13
|
+
.option("--refresh", "Bypass cache")
|
|
14
|
+
.action(async (opts) => {
|
|
15
|
+
const flags = {};
|
|
16
|
+
if (opts.search)
|
|
17
|
+
flags.search = opts.search;
|
|
18
|
+
if (opts.page_size)
|
|
19
|
+
flags.page_size = opts.page_size;
|
|
20
|
+
if (opts.page_num)
|
|
21
|
+
flags.page_num = opts.page_num;
|
|
22
|
+
if (opts.type_id)
|
|
23
|
+
flags.type_id = opts.type_id;
|
|
24
|
+
if (opts.product_line_id)
|
|
25
|
+
flags.product_line_id = opts.product_line_id;
|
|
26
|
+
const refresh = Boolean(opts.refresh);
|
|
27
|
+
const query = buildQueryFromFlags(flags, ["search", "page_size", "page_num", "type_id", "product_line_id"]);
|
|
28
|
+
const key = await buildScopedCacheKey("products:list", query);
|
|
29
|
+
const cached = getCache(key, refresh);
|
|
30
|
+
if (cached) {
|
|
31
|
+
console.log(JSON.stringify(cached, null, 2));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const result = await cawplanRequest({
|
|
35
|
+
method: "GET",
|
|
36
|
+
path: "/api/v1/public/openapi/products",
|
|
37
|
+
query,
|
|
38
|
+
});
|
|
39
|
+
setCache(key, result);
|
|
40
|
+
console.log(JSON.stringify(result, null, 2));
|
|
41
|
+
});
|
|
42
|
+
const productLines = program.command("product-lines").description("Manage product lines");
|
|
43
|
+
productLines
|
|
44
|
+
.command("list")
|
|
45
|
+
.description("List all product lines")
|
|
46
|
+
.option("--page_size <n>", "Page size")
|
|
47
|
+
.option("--page_num <n>", "Page number")
|
|
48
|
+
.option("--refresh", "Bypass cache")
|
|
49
|
+
.action(async (opts) => {
|
|
50
|
+
const flags = {};
|
|
51
|
+
if (opts.page_size)
|
|
52
|
+
flags.page_size = opts.page_size;
|
|
53
|
+
if (opts.page_num)
|
|
54
|
+
flags.page_num = opts.page_num;
|
|
55
|
+
const refresh = Boolean(opts.refresh);
|
|
56
|
+
const query = buildQueryFromFlags(flags, ["page_size", "page_num"]);
|
|
57
|
+
const key = await buildScopedCacheKey("product-lines:list", query);
|
|
58
|
+
const cached = getCache(key, refresh);
|
|
59
|
+
if (cached) {
|
|
60
|
+
console.log(JSON.stringify(cached, null, 2));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const result = await cawplanRequest({
|
|
64
|
+
method: "GET",
|
|
65
|
+
path: "/api/v1/public/openapi/product_lines",
|
|
66
|
+
query,
|
|
67
|
+
});
|
|
68
|
+
setCache(key, result);
|
|
69
|
+
console.log(JSON.stringify(result, null, 2));
|
|
70
|
+
});
|
|
71
|
+
productLines
|
|
72
|
+
.command("get <product_line_id>")
|
|
73
|
+
.description("Get product line details")
|
|
74
|
+
.option("--refresh", "Bypass cache")
|
|
75
|
+
.action(async (productLineId, opts) => {
|
|
76
|
+
const refresh = Boolean(opts.refresh);
|
|
77
|
+
const key = await buildScopedCacheKey(`product-lines:detail:${productLineId}`, undefined);
|
|
78
|
+
const cached = getCache(key, refresh);
|
|
79
|
+
if (cached) {
|
|
80
|
+
console.log(JSON.stringify(cached, null, 2));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const result = await cawplanRequest({
|
|
84
|
+
method: "GET",
|
|
85
|
+
path: `/api/v1/public/openapi/product_lines/${productLineId}`,
|
|
86
|
+
});
|
|
87
|
+
setCache(key, result);
|
|
88
|
+
console.log(JSON.stringify(result, null, 2));
|
|
89
|
+
});
|
|
90
|
+
productLines
|
|
91
|
+
.command("statuses <product_line_id>")
|
|
92
|
+
.description("Get ticket statuses for a product line")
|
|
93
|
+
.action(async (productLineId) => {
|
|
94
|
+
const result = await cawplanRequest({
|
|
95
|
+
method: "GET",
|
|
96
|
+
path: `/api/v1/public/openapi/product_lines/${productLineId}/ticket_statuses`,
|
|
97
|
+
});
|
|
98
|
+
console.log(JSON.stringify(result, null, 2));
|
|
99
|
+
});
|
|
100
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { cawplanRequest } from "../lib/http.js";
|
|
2
|
+
import { buildQueryFromFlags } from "../lib/cache.js";
|
|
3
|
+
export function registerQAReportsCommand(program) {
|
|
4
|
+
const qa = program.command("qa-reports").description("QA reports");
|
|
5
|
+
qa
|
|
6
|
+
.command("list <product_id>")
|
|
7
|
+
.description("List QA reports for a product, grouped by version")
|
|
8
|
+
.option("--type <type>", "Report type: sqa|aqa|stress|performance|smoke")
|
|
9
|
+
.option("--result <r>", "Result: pass|pass_with_issues|failed")
|
|
10
|
+
.option("--status <s>", "Status filter")
|
|
11
|
+
.option("--page_size <n>", "Page size (max 100)")
|
|
12
|
+
.option("--page_num <n>", "Page number")
|
|
13
|
+
.action(async (productId, opts) => {
|
|
14
|
+
const flags = {};
|
|
15
|
+
if (opts.type)
|
|
16
|
+
flags.type = opts.type;
|
|
17
|
+
if (opts.result)
|
|
18
|
+
flags.result = opts.result;
|
|
19
|
+
if (opts.status)
|
|
20
|
+
flags.status = opts.status;
|
|
21
|
+
if (opts.page_size)
|
|
22
|
+
flags.page_size = opts.page_size;
|
|
23
|
+
if (opts.page_num)
|
|
24
|
+
flags.page_num = opts.page_num;
|
|
25
|
+
const query = buildQueryFromFlags(flags, ["type", "result", "status", "page_size", "page_num"]);
|
|
26
|
+
const result = await cawplanRequest({
|
|
27
|
+
method: "GET",
|
|
28
|
+
path: `/api/v1/public/openapi/product/${productId}/qa_report`,
|
|
29
|
+
query,
|
|
30
|
+
});
|
|
31
|
+
console.log(JSON.stringify(result, null, 2));
|
|
32
|
+
});
|
|
33
|
+
qa
|
|
34
|
+
.command("list-version <product_id> <version_id>")
|
|
35
|
+
.description("List QA reports for a specific version")
|
|
36
|
+
.option("--type <type>", "Report type: sqa|aqa|stress|performance|smoke")
|
|
37
|
+
.option("--result <r>", "Result: pass|pass_with_issues|failed")
|
|
38
|
+
.option("--status <s>", "Status filter")
|
|
39
|
+
.option("--page_size <n>", "Page size (max 100)")
|
|
40
|
+
.option("--page_num <n>", "Page number")
|
|
41
|
+
.action(async (productId, versionId, opts) => {
|
|
42
|
+
const flags = {};
|
|
43
|
+
if (opts.type)
|
|
44
|
+
flags.type = opts.type;
|
|
45
|
+
if (opts.result)
|
|
46
|
+
flags.result = opts.result;
|
|
47
|
+
if (opts.status)
|
|
48
|
+
flags.status = opts.status;
|
|
49
|
+
if (opts.page_size)
|
|
50
|
+
flags.page_size = opts.page_size;
|
|
51
|
+
if (opts.page_num)
|
|
52
|
+
flags.page_num = opts.page_num;
|
|
53
|
+
const query = buildQueryFromFlags(flags, ["type", "result", "status", "page_size", "page_num"]);
|
|
54
|
+
const result = await cawplanRequest({
|
|
55
|
+
method: "GET",
|
|
56
|
+
path: `/api/v1/public/openapi/product/${productId}/versions/${versionId}/qa_report`,
|
|
57
|
+
query,
|
|
58
|
+
});
|
|
59
|
+
console.log(JSON.stringify(result, null, 2));
|
|
60
|
+
});
|
|
61
|
+
qa
|
|
62
|
+
.command("get <product_id> <version_id> <qa_report_id>")
|
|
63
|
+
.description("Get a specific QA report by ID")
|
|
64
|
+
.action(async (productId, versionId, qaReportId) => {
|
|
65
|
+
const result = await cawplanRequest({
|
|
66
|
+
method: "GET",
|
|
67
|
+
path: `/api/v1/public/openapi/product/${productId}/versions/${versionId}/qa_report/${qaReportId}`,
|
|
68
|
+
});
|
|
69
|
+
console.log(JSON.stringify(result, null, 2));
|
|
70
|
+
});
|
|
71
|
+
}
|
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import { cawplanRequest } from "../lib/http.js";
|
|
2
|
+
import { getCache, setCache, buildCacheKey, buildScopedCacheKey, buildQueryFromFlags, csvToArray, stableStringify } from "../lib/cache.js";
|
|
3
|
+
import { resolveApiPath } from "../lib/products.js";
|
|
4
|
+
export function registerTicketsCommand(program) {
|
|
5
|
+
const tickets = program.command("tickets").description("Manage tickets");
|
|
6
|
+
tickets
|
|
7
|
+
.command("list <product_id> <version_id>")
|
|
8
|
+
.description("List tickets for a version")
|
|
9
|
+
.requiredOption("--type <type>", "Ticket type: FEATURE|BUGFIX")
|
|
10
|
+
.option("--page_size <n>", "Page size")
|
|
11
|
+
.option("--page_num <n>", "Page number")
|
|
12
|
+
.action(async (productId, versionId, opts) => {
|
|
13
|
+
const flags = { type: opts.type };
|
|
14
|
+
if (opts.page_size)
|
|
15
|
+
flags.page_size = opts.page_size;
|
|
16
|
+
if (opts.page_num)
|
|
17
|
+
flags.page_num = opts.page_num;
|
|
18
|
+
const query = buildQueryFromFlags(flags, ["type", "page_size", "page_num"]);
|
|
19
|
+
const result = await cawplanRequest({
|
|
20
|
+
method: "GET",
|
|
21
|
+
path: `/api/v1/public/openapi/product/${productId}/versions/${versionId}/tickets`,
|
|
22
|
+
query,
|
|
23
|
+
});
|
|
24
|
+
console.log(JSON.stringify(result, null, 2));
|
|
25
|
+
});
|
|
26
|
+
tickets
|
|
27
|
+
.command("poll")
|
|
28
|
+
.description("Poll tickets by status across products")
|
|
29
|
+
.requiredOption("--status <csv>", "Ticket statuses (CSV)")
|
|
30
|
+
.option("--product_ids <csv>", "Product IDs (CSV)")
|
|
31
|
+
.option("--product_line_ids <csv>", "Product line IDs (CSV)")
|
|
32
|
+
.option("--since_updated_at <ts>", "Unix timestamp filter")
|
|
33
|
+
.option("--page_size <n>", "Page size")
|
|
34
|
+
.option("--page_num <n>", "Page number")
|
|
35
|
+
.action(async (opts) => {
|
|
36
|
+
const status = csvToArray(opts.status);
|
|
37
|
+
if (!status) {
|
|
38
|
+
console.error("Error: tickets poll requires --status CSV");
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
const body = { status };
|
|
42
|
+
const productIds = csvToArray(opts.product_ids);
|
|
43
|
+
const productLineIds = csvToArray(opts.product_line_ids);
|
|
44
|
+
if (productIds)
|
|
45
|
+
body.product_ids = productIds;
|
|
46
|
+
if (productLineIds)
|
|
47
|
+
body.product_line_ids = productLineIds;
|
|
48
|
+
if (opts.since_updated_at !== undefined)
|
|
49
|
+
body.since_updated_at = Number(opts.since_updated_at);
|
|
50
|
+
if (opts.page_num !== undefined)
|
|
51
|
+
body.page_num = Number(opts.page_num);
|
|
52
|
+
if (opts.page_size !== undefined)
|
|
53
|
+
body.page_size = Number(opts.page_size);
|
|
54
|
+
const result = await cawplanRequest({
|
|
55
|
+
method: "POST",
|
|
56
|
+
path: resolveApiPath("/api/v1/public/openapi/tickets/poll"),
|
|
57
|
+
body,
|
|
58
|
+
});
|
|
59
|
+
console.log(JSON.stringify(result, null, 2));
|
|
60
|
+
});
|
|
61
|
+
tickets
|
|
62
|
+
.command("search")
|
|
63
|
+
.description("Search tickets")
|
|
64
|
+
.option("--time_range <range>", "Time range (e.g. 1m, 3m)")
|
|
65
|
+
.option("--start_date <date>", "Start date YYYY-MM-DD")
|
|
66
|
+
.option("--end_date <date>", "End date YYYY-MM-DD")
|
|
67
|
+
.option("--product_ids <csv>", "Product IDs")
|
|
68
|
+
.option("--product_line_ids <csv>", "Product line IDs")
|
|
69
|
+
.option("--version_ids <csv>", "Version IDs")
|
|
70
|
+
.option("--unique_ids <csv>", "Ticket unique IDs")
|
|
71
|
+
.option("--display_ids <csv>", "Ticket display IDs")
|
|
72
|
+
.option("--parent_ids <csv>", "Parent ticket IDs")
|
|
73
|
+
.option("--type <csv>", "Ticket types")
|
|
74
|
+
.option("--status <csv>", "Ticket statuses")
|
|
75
|
+
.option("--priority <csv>", "Priorities")
|
|
76
|
+
.option("--platform <csv>", "Platforms")
|
|
77
|
+
.option("--assignees <csv>", "Assignees")
|
|
78
|
+
.option("--search <q>", "Search query")
|
|
79
|
+
.option("--page_size <n>", "Page size")
|
|
80
|
+
.option("--page_num <n>", "Page number")
|
|
81
|
+
.option("--refresh", "Bypass cache")
|
|
82
|
+
.action(async (opts) => {
|
|
83
|
+
const uniqueIds = csvToArray(opts.unique_ids);
|
|
84
|
+
const displayIds = csvToArray(opts.display_ids);
|
|
85
|
+
const parentIds = csvToArray(opts.parent_ids);
|
|
86
|
+
const idLookup = Boolean(uniqueIds || displayIds || parentIds);
|
|
87
|
+
if (!idLookup && !opts.time_range && !(opts.start_date && opts.end_date)) {
|
|
88
|
+
console.error("Error: tickets search requires --time_range or --start_date + --end_date (unless --unique_ids/--display_ids/--parent_ids is set)");
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
const refresh = Boolean(opts.refresh);
|
|
92
|
+
const flags = {};
|
|
93
|
+
if (opts.time_range)
|
|
94
|
+
flags.time_range = opts.time_range;
|
|
95
|
+
if (opts.start_date)
|
|
96
|
+
flags.start_date = opts.start_date;
|
|
97
|
+
if (opts.end_date)
|
|
98
|
+
flags.end_date = opts.end_date;
|
|
99
|
+
if (opts.page_size)
|
|
100
|
+
flags.page_size = opts.page_size;
|
|
101
|
+
if (opts.page_num)
|
|
102
|
+
flags.page_num = opts.page_num;
|
|
103
|
+
const query = buildQueryFromFlags(flags, ["time_range", "start_date", "end_date", "page_size", "page_num"]);
|
|
104
|
+
const body = {};
|
|
105
|
+
const productIds = csvToArray(opts.product_ids);
|
|
106
|
+
const productLineIds = csvToArray(opts.product_line_ids);
|
|
107
|
+
const versionIds = csvToArray(opts.version_ids);
|
|
108
|
+
const type = csvToArray(opts.type);
|
|
109
|
+
const status = csvToArray(opts.status);
|
|
110
|
+
const priority = csvToArray(opts.priority);
|
|
111
|
+
const platform = csvToArray(opts.platform);
|
|
112
|
+
const assignees = csvToArray(opts.assignees);
|
|
113
|
+
if (productIds)
|
|
114
|
+
body.product_ids = productIds;
|
|
115
|
+
if (productLineIds)
|
|
116
|
+
body.product_line_ids = productLineIds;
|
|
117
|
+
if (versionIds)
|
|
118
|
+
body.version_ids = versionIds;
|
|
119
|
+
if (uniqueIds)
|
|
120
|
+
body.unique_ids = uniqueIds;
|
|
121
|
+
if (displayIds)
|
|
122
|
+
body.display_ids = displayIds;
|
|
123
|
+
if (parentIds)
|
|
124
|
+
body.parent_ids = parentIds;
|
|
125
|
+
if (type)
|
|
126
|
+
body.type = type;
|
|
127
|
+
if (status)
|
|
128
|
+
body.status = status;
|
|
129
|
+
if (priority)
|
|
130
|
+
body.priority = priority;
|
|
131
|
+
if (platform)
|
|
132
|
+
body.platform = platform;
|
|
133
|
+
if (assignees)
|
|
134
|
+
body.assignees = assignees;
|
|
135
|
+
if (opts.search)
|
|
136
|
+
body.search = opts.search;
|
|
137
|
+
const key = await buildScopedCacheKey(`tickets:search:${buildCacheKey("query", query)}|body=${stableStringify(body)}`, undefined);
|
|
138
|
+
const cached = getCache(key, refresh);
|
|
139
|
+
if (cached) {
|
|
140
|
+
console.log(JSON.stringify(cached, null, 2));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const result = await cawplanRequest({
|
|
144
|
+
method: "POST",
|
|
145
|
+
path: resolveApiPath("/api/v1/public/openapi/tickets/search"),
|
|
146
|
+
query,
|
|
147
|
+
body,
|
|
148
|
+
});
|
|
149
|
+
setCache(key, result);
|
|
150
|
+
console.log(JSON.stringify(result, null, 2));
|
|
151
|
+
});
|
|
152
|
+
tickets
|
|
153
|
+
.command("batch-create <product_id>")
|
|
154
|
+
.description("Batch-create tickets under a product")
|
|
155
|
+
.requiredOption("--tickets <json>", "JSON array of ticket objects")
|
|
156
|
+
.action(async (productId, opts) => {
|
|
157
|
+
let tickets;
|
|
158
|
+
try {
|
|
159
|
+
tickets = JSON.parse(opts.tickets);
|
|
160
|
+
if (!Array.isArray(tickets))
|
|
161
|
+
throw new Error("must be an array");
|
|
162
|
+
}
|
|
163
|
+
catch (e) {
|
|
164
|
+
console.error(`Error: --tickets must be a valid JSON array: ${e.message}`);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
const result = await cawplanRequest({
|
|
168
|
+
method: "POST",
|
|
169
|
+
path: `/api/v1/public/openapi/product/${productId}/tickets/batch`,
|
|
170
|
+
body: { tickets },
|
|
171
|
+
});
|
|
172
|
+
console.log(JSON.stringify(result, null, 2));
|
|
173
|
+
});
|
|
174
|
+
tickets
|
|
175
|
+
.command("create-version <product_id> <version_id>")
|
|
176
|
+
.description("Create a version ticket")
|
|
177
|
+
.requiredOption("--description <text>", "Ticket description")
|
|
178
|
+
.option("--type <type>", "Ticket type: FEATURE|BUGFIX")
|
|
179
|
+
.option("--priority <p>", "Priority: LOW|MEDIUM|HIGH|CRITICAL")
|
|
180
|
+
.option("--status <key>", "Status key")
|
|
181
|
+
.option("--assignees <csv>", "Assignee IDs (CSV)")
|
|
182
|
+
.option("--parent_id <id>", "Parent ticket ID")
|
|
183
|
+
.option("--label_ids <csv>", "Label IDs (CSV)")
|
|
184
|
+
.option("--reporter_id <id>", "Reporter user ID")
|
|
185
|
+
.option("--due_date <date>", "Due date YYYY-MM-DD")
|
|
186
|
+
.option("--comment <text>", "Comment")
|
|
187
|
+
.action(async (productId, versionId, opts) => {
|
|
188
|
+
const body = { description: opts.description };
|
|
189
|
+
if (opts.type)
|
|
190
|
+
body.type = opts.type;
|
|
191
|
+
if (opts.priority)
|
|
192
|
+
body.priority = opts.priority;
|
|
193
|
+
if (opts.status)
|
|
194
|
+
body.status = opts.status;
|
|
195
|
+
if (opts.parent_id)
|
|
196
|
+
body.parent_id = opts.parent_id;
|
|
197
|
+
if (opts.reporter_id)
|
|
198
|
+
body.reporter_id = opts.reporter_id;
|
|
199
|
+
if (opts.due_date)
|
|
200
|
+
body.due_date = opts.due_date;
|
|
201
|
+
if (opts.comment)
|
|
202
|
+
body.comment = opts.comment;
|
|
203
|
+
const assignees = csvToArray(opts.assignees);
|
|
204
|
+
if (assignees)
|
|
205
|
+
body.assignee_ids = assignees;
|
|
206
|
+
const labelIds = csvToArray(opts.label_ids);
|
|
207
|
+
if (labelIds)
|
|
208
|
+
body.label_ids = labelIds;
|
|
209
|
+
const result = await cawplanRequest({
|
|
210
|
+
method: "POST",
|
|
211
|
+
path: `/api/v1/public/openapi/product/${productId}/versions/${versionId}/tickets`,
|
|
212
|
+
body,
|
|
213
|
+
});
|
|
214
|
+
console.log(JSON.stringify(result, null, 2));
|
|
215
|
+
});
|
|
216
|
+
tickets
|
|
217
|
+
.command("create-backlog <product_id>")
|
|
218
|
+
.description("Create a backlog ticket (not assigned to any version)")
|
|
219
|
+
.requiredOption("--description <text>", "Ticket description")
|
|
220
|
+
.option("--type <type>", "Ticket type: FEATURE|BUGFIX")
|
|
221
|
+
.option("--priority <p>", "Priority: LOW|MEDIUM|HIGH|CRITICAL")
|
|
222
|
+
.option("--status <key>", "Status key")
|
|
223
|
+
.option("--assignees <csv>", "Assignee IDs (CSV)")
|
|
224
|
+
.option("--parent_id <id>", "Parent ticket ID")
|
|
225
|
+
.option("--label_ids <csv>", "Label IDs (CSV)")
|
|
226
|
+
.option("--reporter_id <id>", "Reporter user ID")
|
|
227
|
+
.option("--due_date <date>", "Due date YYYY-MM-DD")
|
|
228
|
+
.option("--comment <text>", "Comment")
|
|
229
|
+
.action(async (productId, opts) => {
|
|
230
|
+
const body = { description: opts.description };
|
|
231
|
+
if (opts.type)
|
|
232
|
+
body.type = opts.type;
|
|
233
|
+
if (opts.priority)
|
|
234
|
+
body.priority = opts.priority;
|
|
235
|
+
if (opts.status)
|
|
236
|
+
body.status = opts.status;
|
|
237
|
+
if (opts.parent_id)
|
|
238
|
+
body.parent_id = opts.parent_id;
|
|
239
|
+
if (opts.reporter_id)
|
|
240
|
+
body.reporter_id = opts.reporter_id;
|
|
241
|
+
if (opts.due_date)
|
|
242
|
+
body.due_date = opts.due_date;
|
|
243
|
+
if (opts.comment)
|
|
244
|
+
body.comment = opts.comment;
|
|
245
|
+
const assignees = csvToArray(opts.assignees);
|
|
246
|
+
if (assignees)
|
|
247
|
+
body.assignee_ids = assignees;
|
|
248
|
+
const labelIds = csvToArray(opts.label_ids);
|
|
249
|
+
if (labelIds)
|
|
250
|
+
body.label_ids = labelIds;
|
|
251
|
+
const result = await cawplanRequest({
|
|
252
|
+
method: "POST",
|
|
253
|
+
path: `/api/v1/public/openapi/product/${productId}/tickets`,
|
|
254
|
+
body,
|
|
255
|
+
});
|
|
256
|
+
console.log(JSON.stringify(result, null, 2));
|
|
257
|
+
});
|
|
258
|
+
tickets
|
|
259
|
+
.command("update <product_id> <version_id> <ticket_id>")
|
|
260
|
+
.description("Update a ticket")
|
|
261
|
+
.option("--status <key>", "Status key")
|
|
262
|
+
.option("--progress_comment <text>", "Progress comment")
|
|
263
|
+
.option("--priority <p>", "Priority")
|
|
264
|
+
.option("--description <text>", "Description")
|
|
265
|
+
.option("--comment <text>", "Comment")
|
|
266
|
+
.option("--parent_id <id>", "Parent ticket ID")
|
|
267
|
+
.option("--due_date <date>", "Due date YYYY-MM-DD")
|
|
268
|
+
.option("--assignees <csv>", "Assignee IDs (CSV)")
|
|
269
|
+
.option("--label_ids <csv>", "Label IDs (CSV)")
|
|
270
|
+
.option("--expected_version <n>", "Optimistic lock version")
|
|
271
|
+
.action(async (productId, versionId, ticketId, opts) => {
|
|
272
|
+
const body = {};
|
|
273
|
+
if (opts.status)
|
|
274
|
+
body.status = opts.status;
|
|
275
|
+
if (opts.progress_comment !== undefined)
|
|
276
|
+
body.progress_comment = opts.progress_comment;
|
|
277
|
+
if (opts.priority)
|
|
278
|
+
body.priority = opts.priority;
|
|
279
|
+
if (opts.description !== undefined)
|
|
280
|
+
body.description = opts.description;
|
|
281
|
+
if (opts.comment !== undefined)
|
|
282
|
+
body.comment = opts.comment;
|
|
283
|
+
if (opts.parent_id)
|
|
284
|
+
body.parent_id = opts.parent_id;
|
|
285
|
+
if (opts.due_date)
|
|
286
|
+
body.due_date = opts.due_date;
|
|
287
|
+
const assignees = csvToArray(opts.assignees);
|
|
288
|
+
if (assignees)
|
|
289
|
+
body.assignee_ids = assignees;
|
|
290
|
+
const labelIds = csvToArray(opts.label_ids);
|
|
291
|
+
if (labelIds)
|
|
292
|
+
body.label_ids = labelIds;
|
|
293
|
+
const hasExpectedVersion = opts.expected_version !== undefined;
|
|
294
|
+
if (hasExpectedVersion)
|
|
295
|
+
body.version = Number(opts.expected_version);
|
|
296
|
+
if (Object.keys(body).length === 0) {
|
|
297
|
+
console.error("Error: tickets update requires at least one updatable flag");
|
|
298
|
+
process.exit(1);
|
|
299
|
+
}
|
|
300
|
+
try {
|
|
301
|
+
const result = await cawplanRequest({
|
|
302
|
+
method: "PUT",
|
|
303
|
+
path: `/api/v1/public/openapi/product/${productId}/versions/${versionId}/tickets/${ticketId}`,
|
|
304
|
+
body,
|
|
305
|
+
});
|
|
306
|
+
console.log(JSON.stringify(result, null, 2));
|
|
307
|
+
}
|
|
308
|
+
catch (err) {
|
|
309
|
+
const apiErr = err;
|
|
310
|
+
if (hasExpectedVersion && apiErr?.status === 409) {
|
|
311
|
+
console.error(`Conflict: ticket was modified since version ${opts.expected_version}. Re-read and retry with the latest --expected_version.`);
|
|
312
|
+
process.exit(1);
|
|
313
|
+
}
|
|
314
|
+
throw err;
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
// Ticket relations sub-commands
|
|
318
|
+
const relate = tickets.command("relate").description("Manage ticket relations");
|
|
319
|
+
relate
|
|
320
|
+
.command("create <product_id> <version_id> <ticket_id>")
|
|
321
|
+
.description("Create a relation")
|
|
322
|
+
.requiredOption("--target <ticket_uid>", "Target ticket UID")
|
|
323
|
+
.requiredOption("--type <type>", "Relation type: RELATED|BLOCKING|BLOCKED_BY|DUPLICATE")
|
|
324
|
+
.action(async (productId, versionId, ticketId, opts) => {
|
|
325
|
+
const result = await cawplanRequest({
|
|
326
|
+
method: "POST",
|
|
327
|
+
path: `/api/v1/public/openapi/product/${productId}/versions/${versionId}/tickets/${ticketId}/relations`,
|
|
328
|
+
body: { target_ticket_id: opts.target, relation_type: opts.type },
|
|
329
|
+
});
|
|
330
|
+
console.log(JSON.stringify(result, null, 2));
|
|
331
|
+
});
|
|
332
|
+
relate
|
|
333
|
+
.command("update <product_id> <version_id> <ticket_id> <relation_id>")
|
|
334
|
+
.description("Update a relation")
|
|
335
|
+
.requiredOption("--type <type>", "Relation type")
|
|
336
|
+
.action(async (productId, versionId, ticketId, relationId, opts) => {
|
|
337
|
+
const result = await cawplanRequest({
|
|
338
|
+
method: "PUT",
|
|
339
|
+
path: `/api/v1/public/openapi/product/${productId}/versions/${versionId}/tickets/${ticketId}/relations/${relationId}`,
|
|
340
|
+
body: { relation_type: opts.type },
|
|
341
|
+
});
|
|
342
|
+
console.log(JSON.stringify(result, null, 2));
|
|
343
|
+
});
|
|
344
|
+
relate
|
|
345
|
+
.command("delete <product_id> <version_id> <ticket_id> <relation_id>")
|
|
346
|
+
.description("Delete a relation")
|
|
347
|
+
.action(async (productId, versionId, ticketId, relationId) => {
|
|
348
|
+
const result = await cawplanRequest({
|
|
349
|
+
method: "DELETE",
|
|
350
|
+
path: `/api/v1/public/openapi/product/${productId}/versions/${versionId}/tickets/${ticketId}/relations/${relationId}`,
|
|
351
|
+
});
|
|
352
|
+
console.log(JSON.stringify(result, null, 2));
|
|
353
|
+
});
|
|
354
|
+
relate
|
|
355
|
+
.command("list <product_id> <version_id> <ticket_id>")
|
|
356
|
+
.description("List relations for a ticket")
|
|
357
|
+
.action(async (productId, versionId, ticketId) => {
|
|
358
|
+
const result = await cawplanRequest({
|
|
359
|
+
method: "GET",
|
|
360
|
+
path: `/api/v1/public/openapi/product/${productId}/versions/${versionId}/tickets/${ticketId}/relations`,
|
|
361
|
+
});
|
|
362
|
+
console.log(JSON.stringify(result, null, 2));
|
|
363
|
+
});
|
|
364
|
+
// Backlog commands
|
|
365
|
+
const backlog = program.command("backlog").description("Manage product backlog");
|
|
366
|
+
backlog
|
|
367
|
+
.command("list <product_id>")
|
|
368
|
+
.description("List backlog tickets")
|
|
369
|
+
.option("--page_size <n>", "Page size")
|
|
370
|
+
.option("--page_num <n>", "Page number")
|
|
371
|
+
.action(async (productId, opts) => {
|
|
372
|
+
const flags = {};
|
|
373
|
+
if (opts.page_size)
|
|
374
|
+
flags.page_size = opts.page_size;
|
|
375
|
+
if (opts.page_num)
|
|
376
|
+
flags.page_num = opts.page_num;
|
|
377
|
+
const query = buildQueryFromFlags(flags, ["page_size", "page_num"]);
|
|
378
|
+
const result = await cawplanRequest({
|
|
379
|
+
method: "GET",
|
|
380
|
+
path: `/api/v1/public/openapi/product/${productId}/tickets`,
|
|
381
|
+
query,
|
|
382
|
+
});
|
|
383
|
+
console.log(JSON.stringify(result, null, 2));
|
|
384
|
+
});
|
|
385
|
+
backlog
|
|
386
|
+
.command("get <product_id> <ticket_id>")
|
|
387
|
+
.description("Get a backlog ticket")
|
|
388
|
+
.action(async (productId, ticketId) => {
|
|
389
|
+
const result = await cawplanRequest({
|
|
390
|
+
method: "GET",
|
|
391
|
+
path: `/api/v1/public/openapi/product/${productId}/tickets/${ticketId}`,
|
|
392
|
+
});
|
|
393
|
+
console.log(JSON.stringify(result, null, 2));
|
|
394
|
+
});
|
|
395
|
+
// Labels
|
|
396
|
+
const labels = program.command("labels").description("Manage labels");
|
|
397
|
+
labels
|
|
398
|
+
.command("list")
|
|
399
|
+
.description("List labels")
|
|
400
|
+
.option("--search <q>", "Search query")
|
|
401
|
+
.option("--product_id <id>", "Filter by product ID")
|
|
402
|
+
.option("--page_size <n>", "Page size")
|
|
403
|
+
.option("--page_num <n>", "Page number")
|
|
404
|
+
.option("--refresh", "Bypass cache")
|
|
405
|
+
.action(async (opts) => {
|
|
406
|
+
const flags = {};
|
|
407
|
+
if (opts.search)
|
|
408
|
+
flags.search = opts.search;
|
|
409
|
+
if (opts.product_id)
|
|
410
|
+
flags.product_id = opts.product_id;
|
|
411
|
+
if (opts.page_size)
|
|
412
|
+
flags.page_size = opts.page_size;
|
|
413
|
+
if (opts.page_num)
|
|
414
|
+
flags.page_num = opts.page_num;
|
|
415
|
+
const refresh = Boolean(opts.refresh);
|
|
416
|
+
const query = buildQueryFromFlags(flags, ["search", "product_id", "page_size", "page_num"]);
|
|
417
|
+
const key = await buildScopedCacheKey("labels:list", query);
|
|
418
|
+
const cached = getCache(key, refresh);
|
|
419
|
+
if (cached) {
|
|
420
|
+
console.log(JSON.stringify(cached, null, 2));
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
const result = await cawplanRequest({
|
|
424
|
+
method: "GET",
|
|
425
|
+
path: "/api/v1/public/openapi/labels",
|
|
426
|
+
query,
|
|
427
|
+
});
|
|
428
|
+
setCache(key, result);
|
|
429
|
+
console.log(JSON.stringify(result, null, 2));
|
|
430
|
+
});
|
|
431
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { cawplanRequest } from "../lib/http.js";
|
|
2
|
+
import { buildQueryFromFlags } from "../lib/cache.js";
|
|
3
|
+
export function registerTodosCommand(program) {
|
|
4
|
+
const todos = program.command("todos").description("Manage todos");
|
|
5
|
+
todos
|
|
6
|
+
.command("user <user_id>")
|
|
7
|
+
.description("Get todos for a user")
|
|
8
|
+
.option("--ticket_status <csv>", "Ticket status filter (CSV)")
|
|
9
|
+
.option("--issue_status <csv>", "Issue status filter (CSV)")
|
|
10
|
+
.action(async (userId, opts) => {
|
|
11
|
+
const flags = {};
|
|
12
|
+
if (opts.ticket_status)
|
|
13
|
+
flags.ticket_status = opts.ticket_status;
|
|
14
|
+
if (opts.issue_status)
|
|
15
|
+
flags.issue_status = opts.issue_status;
|
|
16
|
+
const query = buildQueryFromFlags(flags, ["ticket_status", "issue_status"]);
|
|
17
|
+
const result = await cawplanRequest({
|
|
18
|
+
method: "GET",
|
|
19
|
+
path: `/api/v1/public/openapi/todos/users/${userId}`,
|
|
20
|
+
query,
|
|
21
|
+
});
|
|
22
|
+
console.log(JSON.stringify(result, null, 2));
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { cawplanRequest } from "../lib/http.js";
|
|
2
|
+
export function registerUserActivityCommand(program) {
|
|
3
|
+
const userActivity = program.command("user-activity").description("User activity reports");
|
|
4
|
+
userActivity
|
|
5
|
+
.command("get")
|
|
6
|
+
.description("Get user activity report")
|
|
7
|
+
.option("--user_id <id>", "User ID")
|
|
8
|
+
.option("--email <email>", "User email (exact match)")
|
|
9
|
+
.requiredOption("--start <date>", "Start date YYYY-MM-DD")
|
|
10
|
+
.requiredOption("--end <date>", "End date YYYY-MM-DD")
|
|
11
|
+
.action(async (opts) => {
|
|
12
|
+
if (!opts.user_id && !opts.email) {
|
|
13
|
+
console.error("Error: user-activity get requires --user_id or --email");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
const query = {
|
|
17
|
+
start_date: opts.start,
|
|
18
|
+
end_date: opts.end,
|
|
19
|
+
};
|
|
20
|
+
if (opts.user_id)
|
|
21
|
+
query.user_id = opts.user_id;
|
|
22
|
+
if (opts.email)
|
|
23
|
+
query.email = opts.email;
|
|
24
|
+
const result = await cawplanRequest({
|
|
25
|
+
method: "GET",
|
|
26
|
+
path: "/api/v1/public/openapi/user-report",
|
|
27
|
+
query,
|
|
28
|
+
});
|
|
29
|
+
console.log(JSON.stringify(result, null, 2));
|
|
30
|
+
});
|
|
31
|
+
}
|