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.
Files changed (52) hide show
  1. package/config/products.json +17 -0
  2. package/dist/commands/activities.d.ts +2 -0
  3. package/dist/commands/activities.js +43 -0
  4. package/dist/commands/analytics.d.ts +2 -0
  5. package/dist/commands/analytics.js +30 -0
  6. package/dist/commands/auth.d.ts +2 -0
  7. package/dist/commands/auth.js +145 -0
  8. package/dist/commands/community.d.ts +2 -0
  9. package/dist/commands/community.js +30 -0
  10. package/dist/commands/critical.d.ts +2 -0
  11. package/dist/commands/critical.js +205 -0
  12. package/dist/commands/knowledge.d.ts +2 -0
  13. package/dist/commands/knowledge.js +24 -0
  14. package/dist/commands/metrics.d.ts +2 -0
  15. package/dist/commands/metrics.js +27 -0
  16. package/dist/commands/product-activity.d.ts +2 -0
  17. package/dist/commands/product-activity.js +26 -0
  18. package/dist/commands/products.d.ts +2 -0
  19. package/dist/commands/products.js +100 -0
  20. package/dist/commands/qa-reports.d.ts +2 -0
  21. package/dist/commands/qa-reports.js +71 -0
  22. package/dist/commands/tickets.d.ts +2 -0
  23. package/dist/commands/tickets.js +431 -0
  24. package/dist/commands/todos.d.ts +2 -0
  25. package/dist/commands/todos.js +24 -0
  26. package/dist/commands/user-activity.d.ts +2 -0
  27. package/dist/commands/user-activity.js +31 -0
  28. package/dist/commands/users.d.ts +2 -0
  29. package/dist/commands/users.js +80 -0
  30. package/dist/commands/versions.d.ts +2 -0
  31. package/dist/commands/versions.js +65 -0
  32. package/dist/index.d.ts +2 -0
  33. package/dist/index.js +82 -0
  34. package/dist/lib/auth-state.d.ts +11 -0
  35. package/dist/lib/auth-state.js +27 -0
  36. package/dist/lib/cache.d.ts +20 -0
  37. package/dist/lib/cache.js +135 -0
  38. package/dist/lib/config.d.ts +8 -0
  39. package/dist/lib/config.js +25 -0
  40. package/dist/lib/credentials.d.ts +15 -0
  41. package/dist/lib/credentials.js +50 -0
  42. package/dist/lib/http.d.ts +14 -0
  43. package/dist/lib/http.js +174 -0
  44. package/dist/lib/oauth.d.ts +20 -0
  45. package/dist/lib/oauth.js +155 -0
  46. package/dist/lib/output.d.ts +3 -0
  47. package/dist/lib/output.js +9 -0
  48. package/dist/lib/products.d.ts +24 -0
  49. package/dist/lib/products.js +87 -0
  50. package/dist/lib/user-config.d.ts +10 -0
  51. package/dist/lib/user-config.js +47 -0
  52. 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,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerQAReportsCommand(program: Command): void;
@@ -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,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerTicketsCommand(program: Command): void;
@@ -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,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerTodosCommand(program: Command): void;
@@ -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,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerUserActivityCommand(program: Command): void;
@@ -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
+ }