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,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"products": {
|
|
3
|
+
"cawplan": {
|
|
4
|
+
"displayName": "CawPlan",
|
|
5
|
+
"cliName": "cawplan",
|
|
6
|
+
"oauthEndpoint": "cawplan-cli",
|
|
7
|
+
"credentialsDir": ".cawplan",
|
|
8
|
+
"defaultEnv": "prd",
|
|
9
|
+
"env": {
|
|
10
|
+
"prd": {
|
|
11
|
+
"portalBase": "https://www.cawplan.com",
|
|
12
|
+
"apiBase": "https://api.cawplan.com/core-product"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { cawplanRequest } from "../lib/http.js";
|
|
2
|
+
import { buildQueryFromFlags } from "../lib/cache.js";
|
|
3
|
+
import { resolveApiPath } from "../lib/products.js";
|
|
4
|
+
export function registerActivitiesCommand(program) {
|
|
5
|
+
const activities = program.command("activities").description("Query activity logs");
|
|
6
|
+
activities
|
|
7
|
+
.command("query")
|
|
8
|
+
.description("Query activities")
|
|
9
|
+
.option("--time_range <range>", "Time range (e.g. 1m)")
|
|
10
|
+
.option("--page_size <n>", "Page size")
|
|
11
|
+
.option("--page_num <n>", "Page number")
|
|
12
|
+
.option("--user_id <id>", "Filter by user ID")
|
|
13
|
+
.option("--product_id <id>", "Filter by product ID")
|
|
14
|
+
.option("--activity_types <csv>", "Activity types (CSV)")
|
|
15
|
+
.action(async (opts) => {
|
|
16
|
+
const flags = {};
|
|
17
|
+
if (opts.time_range)
|
|
18
|
+
flags.time_range = opts.time_range;
|
|
19
|
+
if (opts.page_size)
|
|
20
|
+
flags.page_size = opts.page_size;
|
|
21
|
+
if (opts.page_num)
|
|
22
|
+
flags.page_num = opts.page_num;
|
|
23
|
+
const query = buildQueryFromFlags(flags, ["time_range", "page_size", "page_num"]);
|
|
24
|
+
const body = {};
|
|
25
|
+
if (opts.user_id)
|
|
26
|
+
body.user_id = opts.user_id;
|
|
27
|
+
if (opts.product_id)
|
|
28
|
+
body.product_id = opts.product_id;
|
|
29
|
+
if (opts.activity_types) {
|
|
30
|
+
body.activity_types = opts.activity_types
|
|
31
|
+
.split(",")
|
|
32
|
+
.map((t) => t.trim())
|
|
33
|
+
.filter(Boolean);
|
|
34
|
+
}
|
|
35
|
+
const result = await cawplanRequest({
|
|
36
|
+
method: "POST",
|
|
37
|
+
path: resolveApiPath("/api/v1/public/openapi/activities/query"),
|
|
38
|
+
query,
|
|
39
|
+
body,
|
|
40
|
+
});
|
|
41
|
+
console.log(JSON.stringify(result, null, 2));
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { cawplanRequest } from "../lib/http.js";
|
|
2
|
+
import { buildQueryFromFlags } from "../lib/cache.js";
|
|
3
|
+
export function registerAnalyticsCommand(program) {
|
|
4
|
+
const analytics = program.command("analytics").description("Product analytics (AI-categorized feedback)");
|
|
5
|
+
analytics
|
|
6
|
+
.command("get <product_id>")
|
|
7
|
+
.description("Get AI-categorized feedback analytics for a product")
|
|
8
|
+
.option("--time_range <range>", "Time range (e.g. 1w, 1m, 3m, 1y)")
|
|
9
|
+
.option("--start <date>", "Start date YYYY-MM-DD")
|
|
10
|
+
.option("--end <date>", "End date YYYY-MM-DD")
|
|
11
|
+
.option("--version <v>", "Filter by version (major.minor, e.g. 3.4)")
|
|
12
|
+
.action(async (productId, opts) => {
|
|
13
|
+
const flags = {};
|
|
14
|
+
if (opts.time_range)
|
|
15
|
+
flags.time_range = opts.time_range;
|
|
16
|
+
if (opts.start)
|
|
17
|
+
flags.start = opts.start;
|
|
18
|
+
if (opts.end)
|
|
19
|
+
flags.end = opts.end;
|
|
20
|
+
if (opts.version)
|
|
21
|
+
flags.version = opts.version;
|
|
22
|
+
const query = buildQueryFromFlags(flags, ["time_range", "start", "end", "version"]);
|
|
23
|
+
const result = await cawplanRequest({
|
|
24
|
+
method: "GET",
|
|
25
|
+
path: `/api/v1/public/openapi/product/${productId}/analytics`,
|
|
26
|
+
query,
|
|
27
|
+
});
|
|
28
|
+
console.log(JSON.stringify(result, null, 2));
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { createInterface } from "node:readline";
|
|
2
|
+
import { readCredentials, writeCredentials, deleteCredentials, isAccessTokenExpired, } from "../lib/credentials.js";
|
|
3
|
+
import { getAuthState } from "../lib/auth-state.js";
|
|
4
|
+
import { runOAuthLogin } from "../lib/oauth.js";
|
|
5
|
+
import { success, error } from "../lib/output.js";
|
|
6
|
+
import { getConfigPath, readUserConfig, writeUserConfig } from "../lib/user-config.js";
|
|
7
|
+
import { getDefaultEnvName, getEnvNames, getProductEnvConfig } from "../lib/products.js";
|
|
8
|
+
function maskKey(key) {
|
|
9
|
+
if (key.length <= 8)
|
|
10
|
+
return "****";
|
|
11
|
+
return `${key.slice(0, 4)}****${key.slice(-4)}`;
|
|
12
|
+
}
|
|
13
|
+
async function prompt(question) {
|
|
14
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
rl.question(question, (answer) => {
|
|
17
|
+
rl.close();
|
|
18
|
+
resolve(answer.trim());
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
async function promptWithDefault(question, defaultValue) {
|
|
23
|
+
const answer = await prompt(`${question} [${defaultValue}]: `);
|
|
24
|
+
return answer || defaultValue;
|
|
25
|
+
}
|
|
26
|
+
export function registerAuthCommand(program) {
|
|
27
|
+
const auth = program.command("auth").description("Manage authentication");
|
|
28
|
+
auth
|
|
29
|
+
.command("login")
|
|
30
|
+
.description("Log in via browser (OAuth2, recommended)")
|
|
31
|
+
.action(async () => {
|
|
32
|
+
try {
|
|
33
|
+
const creds = await runOAuthLogin();
|
|
34
|
+
const existing = await readCredentials();
|
|
35
|
+
await writeCredentials({ ...existing, ...creds });
|
|
36
|
+
const who = creds.email ? ` as ${creds.email}` : "";
|
|
37
|
+
success(`Logged in${who}. Token expires in 24h (auto-refresh enabled).`);
|
|
38
|
+
}
|
|
39
|
+
catch (err_) {
|
|
40
|
+
error(String(err_ instanceof Error ? err_.message : err_));
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
auth
|
|
45
|
+
.command("configure")
|
|
46
|
+
.description("Configure environment and API Key for authentication")
|
|
47
|
+
.action(async () => {
|
|
48
|
+
const existingConfig = await readUserConfig();
|
|
49
|
+
const envNames = getEnvNames();
|
|
50
|
+
const defaultEnv = existingConfig?.env ?? getDefaultEnvName();
|
|
51
|
+
console.log("Open https://www.cawplan.com/account/api to generate an API Key");
|
|
52
|
+
const env = await promptWithDefault(`Environment (${envNames.join(" / ")})`, defaultEnv);
|
|
53
|
+
if (!envNames.includes(env)) {
|
|
54
|
+
error(`Unknown environment '${env}'. Expected one of: ${envNames.join(", ")}`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
const envConfig = getProductEnvConfig(env);
|
|
58
|
+
const useExistingUrls = existingConfig?.env === env;
|
|
59
|
+
const baseUrlDefault = useExistingUrls && existingConfig?.baseUrl ? existingConfig.baseUrl : envConfig.apiBase;
|
|
60
|
+
const portalUrlDefault = useExistingUrls && existingConfig?.portalUrl
|
|
61
|
+
? existingConfig.portalUrl
|
|
62
|
+
: envConfig.portalBase;
|
|
63
|
+
const baseUrl = await promptWithDefault("API base URL", baseUrlDefault);
|
|
64
|
+
const portalUrl = await promptWithDefault("Portal URL", portalUrlDefault);
|
|
65
|
+
const apiKey = await prompt("Paste your API Key: ");
|
|
66
|
+
if (!apiKey) {
|
|
67
|
+
error("API Key cannot be empty");
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
const existing = await readCredentials();
|
|
71
|
+
await writeUserConfig({ env, baseUrl, portalUrl });
|
|
72
|
+
await writeCredentials({ ...existing, apiKey });
|
|
73
|
+
success(`Configuration saved to ${getConfigPath()}.`);
|
|
74
|
+
success("API Key saved.");
|
|
75
|
+
});
|
|
76
|
+
auth
|
|
77
|
+
.command("status")
|
|
78
|
+
.description("Show current authentication status")
|
|
79
|
+
.action(async () => {
|
|
80
|
+
const state = await getAuthState();
|
|
81
|
+
const creds = state.credentials;
|
|
82
|
+
let oauthLine;
|
|
83
|
+
let activeLine;
|
|
84
|
+
if (creds?.accessToken) {
|
|
85
|
+
const expired = isAccessTokenExpired(creds);
|
|
86
|
+
if (expired && creds.refreshToken) {
|
|
87
|
+
const expiry = new Date((creds.expire ?? 0) * 1000).toISOString();
|
|
88
|
+
oauthLine = `OAuth: token expired at ${expiry}, auto-refresh enabled`;
|
|
89
|
+
}
|
|
90
|
+
else if (!expired) {
|
|
91
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
92
|
+
const remaining = (creds.expire ?? 0) - nowSec;
|
|
93
|
+
const hours = Math.floor(remaining / 3600);
|
|
94
|
+
const email = creds.email ? ` as ${creds.email}` : "";
|
|
95
|
+
const autoRefresh = creds.refreshToken ? ", auto-refresh enabled" : "";
|
|
96
|
+
oauthLine = `OAuth: logged in${email} (expires in ${hours}h${autoRefresh})`;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
oauthLine = `OAuth: token expired (no refresh token)`;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
oauthLine = "OAuth: not configured";
|
|
104
|
+
}
|
|
105
|
+
if (state.active === "oauth") {
|
|
106
|
+
activeLine = "Active: OAuth (preferred)";
|
|
107
|
+
}
|
|
108
|
+
else if (state.active === "apiKey") {
|
|
109
|
+
activeLine = "Active: API Key";
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
activeLine = "Active: none";
|
|
113
|
+
}
|
|
114
|
+
let apiKeyLine = "API Key: not configured";
|
|
115
|
+
if (creds?.apiKey && state.envApiKey) {
|
|
116
|
+
apiKeyLine = `API Key: configured (${maskKey(creds.apiKey)}, env ${maskKey(state.envApiKey)})`;
|
|
117
|
+
}
|
|
118
|
+
else if (creds?.apiKey) {
|
|
119
|
+
apiKeyLine = `API Key: configured (${maskKey(creds.apiKey)})`;
|
|
120
|
+
}
|
|
121
|
+
else if (state.envApiKey) {
|
|
122
|
+
apiKeyLine = `API Key: configured via CAWPLAN_API_KEY (${maskKey(state.envApiKey)})`;
|
|
123
|
+
}
|
|
124
|
+
console.log(oauthLine);
|
|
125
|
+
console.log(apiKeyLine);
|
|
126
|
+
const config = await readUserConfig();
|
|
127
|
+
if (config) {
|
|
128
|
+
console.log(`Config: ${config.env ?? "default"} (${getConfigPath()})`);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
console.log("Config: default");
|
|
132
|
+
}
|
|
133
|
+
console.log(activeLine);
|
|
134
|
+
if (state.active === "none") {
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
auth
|
|
139
|
+
.command("logout")
|
|
140
|
+
.description("Remove stored credentials")
|
|
141
|
+
.action(async () => {
|
|
142
|
+
await deleteCredentials();
|
|
143
|
+
success("Logged out.");
|
|
144
|
+
});
|
|
145
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { cawplanRequest } from "../lib/http.js";
|
|
2
|
+
import { buildQueryFromFlags } from "../lib/cache.js";
|
|
3
|
+
export function registerCommunityCommand(program) {
|
|
4
|
+
const community = program.command("community").description("Community data");
|
|
5
|
+
community
|
|
6
|
+
.command("timeline")
|
|
7
|
+
.description("Get community release timeline across all products")
|
|
8
|
+
.option("--time_range <range>", "Time range (e.g. 1w, 1m, 3m, 1y)")
|
|
9
|
+
.option("--start <date>", "Start date YYYY-MM-DD")
|
|
10
|
+
.option("--end <date>", "End date YYYY-MM-DD")
|
|
11
|
+
.option("--channels <csv>", "Release channels: GA,EA,Alpha")
|
|
12
|
+
.action(async (opts) => {
|
|
13
|
+
const flags = {};
|
|
14
|
+
if (opts.time_range)
|
|
15
|
+
flags.time_range = opts.time_range;
|
|
16
|
+
if (opts.start)
|
|
17
|
+
flags.start = opts.start;
|
|
18
|
+
if (opts.end)
|
|
19
|
+
flags.end = opts.end;
|
|
20
|
+
if (opts.channels)
|
|
21
|
+
flags.channels = opts.channels;
|
|
22
|
+
const query = buildQueryFromFlags(flags, ["time_range", "start", "end", "channels"]);
|
|
23
|
+
const result = await cawplanRequest({
|
|
24
|
+
method: "GET",
|
|
25
|
+
path: "/api/v1/public/openapi/community/timeline",
|
|
26
|
+
query,
|
|
27
|
+
});
|
|
28
|
+
console.log(JSON.stringify(result, null, 2));
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
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
|
+
function parseJsonBody(value) {
|
|
5
|
+
if (!value)
|
|
6
|
+
return undefined;
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(value);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
console.error("Error: --body must be valid JSON");
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function registerCriticalCommand(program) {
|
|
16
|
+
const critical = program.command("critical").description("Manage critical issues");
|
|
17
|
+
critical
|
|
18
|
+
.command("list <product_id>")
|
|
19
|
+
.description("List critical issues for a product")
|
|
20
|
+
.option("--time_range <range>", "Time range (e.g. 1m)")
|
|
21
|
+
.option("--start <date>", "Start date YYYY-MM-DD")
|
|
22
|
+
.option("--end <date>", "End date YYYY-MM-DD")
|
|
23
|
+
.option("--status <csv>", "Status filter (CSV)")
|
|
24
|
+
.option("--search <q>", "Search query")
|
|
25
|
+
.option("--page_size <n>", "Page size")
|
|
26
|
+
.option("--page_num <n>", "Page number")
|
|
27
|
+
.action(async (productId, opts) => {
|
|
28
|
+
const flags = {};
|
|
29
|
+
if (opts.time_range)
|
|
30
|
+
flags.time_range = opts.time_range;
|
|
31
|
+
if (opts.start)
|
|
32
|
+
flags.start = opts.start;
|
|
33
|
+
if (opts.end)
|
|
34
|
+
flags.end = opts.end;
|
|
35
|
+
if (opts.status)
|
|
36
|
+
flags.status = opts.status;
|
|
37
|
+
if (opts.search)
|
|
38
|
+
flags.search = opts.search;
|
|
39
|
+
if (opts.page_size)
|
|
40
|
+
flags.page_size = opts.page_size;
|
|
41
|
+
if (opts.page_num)
|
|
42
|
+
flags.page_num = opts.page_num;
|
|
43
|
+
const query = buildQueryFromFlags(flags, ["time_range", "start", "end", "status", "search", "page_size", "page_num"]);
|
|
44
|
+
const result = await cawplanRequest({
|
|
45
|
+
method: "GET",
|
|
46
|
+
path: `/api/v1/public/openapi/product/${productId}/critical_issues`,
|
|
47
|
+
query,
|
|
48
|
+
});
|
|
49
|
+
console.log(JSON.stringify(result, null, 2));
|
|
50
|
+
});
|
|
51
|
+
critical
|
|
52
|
+
.command("line <product_line_id>")
|
|
53
|
+
.alias("product-line")
|
|
54
|
+
.description("List critical issues for a product line")
|
|
55
|
+
.option("--time_range <range>", "Time range")
|
|
56
|
+
.option("--start <date>", "Start date YYYY-MM-DD")
|
|
57
|
+
.option("--end <date>", "End date YYYY-MM-DD")
|
|
58
|
+
.option("--status <csv>", "Status filter (CSV)")
|
|
59
|
+
.option("--search <q>", "Search query")
|
|
60
|
+
.option("--page_size <n>", "Page size")
|
|
61
|
+
.option("--page_num <n>", "Page number")
|
|
62
|
+
.action(async (productLineId, opts) => {
|
|
63
|
+
const flags = {};
|
|
64
|
+
if (opts.time_range)
|
|
65
|
+
flags.time_range = opts.time_range;
|
|
66
|
+
if (opts.start)
|
|
67
|
+
flags.start = opts.start;
|
|
68
|
+
if (opts.end)
|
|
69
|
+
flags.end = opts.end;
|
|
70
|
+
if (opts.status)
|
|
71
|
+
flags.status = opts.status;
|
|
72
|
+
if (opts.search)
|
|
73
|
+
flags.search = opts.search;
|
|
74
|
+
if (opts.page_size)
|
|
75
|
+
flags.page_size = opts.page_size;
|
|
76
|
+
if (opts.page_num)
|
|
77
|
+
flags.page_num = opts.page_num;
|
|
78
|
+
const query = buildQueryFromFlags(flags, ["time_range", "start", "end", "status", "search", "page_size", "page_num"]);
|
|
79
|
+
const result = await cawplanRequest({
|
|
80
|
+
method: "GET",
|
|
81
|
+
path: `/api/v1/public/openapi/product_line/${productLineId}/critical_issues`,
|
|
82
|
+
query,
|
|
83
|
+
});
|
|
84
|
+
console.log(JSON.stringify(result, null, 2));
|
|
85
|
+
});
|
|
86
|
+
critical
|
|
87
|
+
.command("search")
|
|
88
|
+
.description("Search critical issues across products")
|
|
89
|
+
.option("--time_range <range>", "Time range")
|
|
90
|
+
.option("--days <n>", "Days (deprecated, use --time_range)")
|
|
91
|
+
.option("--start_date <date>", "Start date YYYY-MM-DD")
|
|
92
|
+
.option("--end_date <date>", "End date YYYY-MM-DD")
|
|
93
|
+
.option("--status <csv>", "Status filter (CSV)")
|
|
94
|
+
.option("--issue_types <csv>", "Issue types (CSV)")
|
|
95
|
+
.option("--product_line_ids <csv>", "Product line IDs (CSV)")
|
|
96
|
+
.option("--product_type_ids <csv>", "Product type IDs (CSV)")
|
|
97
|
+
.option("--product_ids <csv>", "Product IDs (CSV)")
|
|
98
|
+
.option("--tech_owners <csv>", "Tech owner IDs (CSV)")
|
|
99
|
+
.option("--search <q>", "Search query")
|
|
100
|
+
.option("--page_size <n>", "Page size")
|
|
101
|
+
.option("--page_num <n>", "Page number")
|
|
102
|
+
.option("--refresh", "Bypass cache")
|
|
103
|
+
.action(async (opts) => {
|
|
104
|
+
if (!opts.time_range && !opts.days && !(opts.start_date && opts.end_date)) {
|
|
105
|
+
console.error("Error: critical search requires --time_range, --days, or --start_date + --end_date");
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
const refresh = Boolean(opts.refresh);
|
|
109
|
+
const flagsMap = {};
|
|
110
|
+
if (opts.time_range)
|
|
111
|
+
flagsMap.time_range = opts.time_range;
|
|
112
|
+
if (opts.days)
|
|
113
|
+
flagsMap.days = opts.days;
|
|
114
|
+
if (opts.start_date)
|
|
115
|
+
flagsMap.start_date = opts.start_date;
|
|
116
|
+
if (opts.end_date)
|
|
117
|
+
flagsMap.end_date = opts.end_date;
|
|
118
|
+
if (opts.page_size)
|
|
119
|
+
flagsMap.page_size = opts.page_size;
|
|
120
|
+
if (opts.page_num)
|
|
121
|
+
flagsMap.page_num = opts.page_num;
|
|
122
|
+
const query = buildQueryFromFlags(flagsMap, ["time_range", "days", "start_date", "end_date", "page_size", "page_num"]);
|
|
123
|
+
const body = {};
|
|
124
|
+
const status = csvToArray(opts.status);
|
|
125
|
+
const issueTypes = csvToArray(opts.issue_types);
|
|
126
|
+
const productLineIds = csvToArray(opts.product_line_ids);
|
|
127
|
+
const productTypeIds = csvToArray(opts.product_type_ids);
|
|
128
|
+
const productIds = csvToArray(opts.product_ids);
|
|
129
|
+
const techOwners = csvToArray(opts.tech_owners);
|
|
130
|
+
if (status)
|
|
131
|
+
body.status = status;
|
|
132
|
+
if (issueTypes)
|
|
133
|
+
body.issue_types = issueTypes;
|
|
134
|
+
if (productLineIds)
|
|
135
|
+
body.product_line_ids = productLineIds;
|
|
136
|
+
if (productTypeIds)
|
|
137
|
+
body.product_type_ids = productTypeIds;
|
|
138
|
+
if (productIds)
|
|
139
|
+
body.product_ids = productIds;
|
|
140
|
+
if (techOwners)
|
|
141
|
+
body.tech_owners = techOwners;
|
|
142
|
+
if (opts.search)
|
|
143
|
+
body.search = opts.search;
|
|
144
|
+
const key = await buildScopedCacheKey(`critical:search:${buildCacheKey("query", query)}|body=${stableStringify(body)}`, undefined);
|
|
145
|
+
const cached = getCache(key, refresh);
|
|
146
|
+
if (cached) {
|
|
147
|
+
console.log(JSON.stringify(cached, null, 2));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const result = await cawplanRequest({
|
|
151
|
+
method: "POST",
|
|
152
|
+
path: resolveApiPath("/api/v1/public/openapi/critical_issues/search"),
|
|
153
|
+
query,
|
|
154
|
+
body,
|
|
155
|
+
});
|
|
156
|
+
setCache(key, result);
|
|
157
|
+
console.log(JSON.stringify(result, null, 2));
|
|
158
|
+
});
|
|
159
|
+
critical
|
|
160
|
+
.command("get <product_id> <critical_issue_id>")
|
|
161
|
+
.description("Get critical issue details")
|
|
162
|
+
.action(async (productId, criticalId) => {
|
|
163
|
+
const result = await cawplanRequest({
|
|
164
|
+
method: "GET",
|
|
165
|
+
path: `/api/v1/public/openapi/product/${productId}/critical_issues/${criticalId}`,
|
|
166
|
+
});
|
|
167
|
+
console.log(JSON.stringify(result, null, 2));
|
|
168
|
+
});
|
|
169
|
+
critical
|
|
170
|
+
.command("create <product_id>")
|
|
171
|
+
.description("Create a critical issue")
|
|
172
|
+
.requiredOption("--body <json>", "JSON body")
|
|
173
|
+
.action(async (productId, opts) => {
|
|
174
|
+
const body = parseJsonBody(opts.body);
|
|
175
|
+
const result = await cawplanRequest({
|
|
176
|
+
method: "POST",
|
|
177
|
+
path: `/api/v1/public/openapi/product/${productId}/critical_issues`,
|
|
178
|
+
body,
|
|
179
|
+
});
|
|
180
|
+
console.log(JSON.stringify(result, null, 2));
|
|
181
|
+
});
|
|
182
|
+
critical
|
|
183
|
+
.command("update <product_id> <critical_issue_id>")
|
|
184
|
+
.description("Update a critical issue")
|
|
185
|
+
.requiredOption("--body <json>", "JSON body")
|
|
186
|
+
.action(async (productId, criticalId, opts) => {
|
|
187
|
+
const body = parseJsonBody(opts.body);
|
|
188
|
+
const result = await cawplanRequest({
|
|
189
|
+
method: "PUT",
|
|
190
|
+
path: `/api/v1/public/openapi/product/${productId}/critical_issues/${criticalId}`,
|
|
191
|
+
body,
|
|
192
|
+
});
|
|
193
|
+
console.log(JSON.stringify(result, null, 2));
|
|
194
|
+
});
|
|
195
|
+
critical
|
|
196
|
+
.command("delete <product_id> <critical_issue_id>")
|
|
197
|
+
.description("Delete a critical issue")
|
|
198
|
+
.action(async (productId, criticalId) => {
|
|
199
|
+
const result = await cawplanRequest({
|
|
200
|
+
method: "DELETE",
|
|
201
|
+
path: `/api/v1/public/openapi/product/${productId}/critical_issues/${criticalId}`,
|
|
202
|
+
});
|
|
203
|
+
console.log(JSON.stringify(result, null, 2));
|
|
204
|
+
});
|
|
205
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { cawplanRequest } from "../lib/http.js";
|
|
2
|
+
export function registerKnowledgeCommand(program) {
|
|
3
|
+
const knowledge = program.command("knowledge").description("Knowledge base operations");
|
|
4
|
+
knowledge
|
|
5
|
+
.command("search")
|
|
6
|
+
.description("Search the knowledge base")
|
|
7
|
+
.requiredOption("--query <text>", "Search query text")
|
|
8
|
+
.option("--product_id <id>", "Optional product ID to scope the search")
|
|
9
|
+
.option("--limit <n>", "Result limit (default 10)", "10")
|
|
10
|
+
.action(async (opts) => {
|
|
11
|
+
const body = {
|
|
12
|
+
query: opts.query,
|
|
13
|
+
limit: Number(opts.limit),
|
|
14
|
+
};
|
|
15
|
+
if (opts.product_id)
|
|
16
|
+
body.product_id = opts.product_id;
|
|
17
|
+
const result = await cawplanRequest({
|
|
18
|
+
method: "POST",
|
|
19
|
+
path: "/api/v1/public/openapi/knowledge/search",
|
|
20
|
+
body,
|
|
21
|
+
});
|
|
22
|
+
console.log(JSON.stringify(result, null, 2));
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { cawplanRequest } from "../lib/http.js";
|
|
2
|
+
import { buildQueryFromFlags } from "../lib/cache.js";
|
|
3
|
+
export function registerMetricsCommand(program) {
|
|
4
|
+
const metrics = program.command("metrics").description("Product metrics");
|
|
5
|
+
metrics
|
|
6
|
+
.command("get <product_id>")
|
|
7
|
+
.description("Get metrics for a product")
|
|
8
|
+
.option("--time_range <range>", "Time range (e.g. 1m)")
|
|
9
|
+
.option("--start <date>", "Start date YYYY-MM-DD")
|
|
10
|
+
.option("--end <date>", "End date YYYY-MM-DD")
|
|
11
|
+
.action(async (productId, opts) => {
|
|
12
|
+
const flags = {};
|
|
13
|
+
if (opts.time_range)
|
|
14
|
+
flags.time_range = opts.time_range;
|
|
15
|
+
if (opts.start)
|
|
16
|
+
flags.start = opts.start;
|
|
17
|
+
if (opts.end)
|
|
18
|
+
flags.end = opts.end;
|
|
19
|
+
const query = buildQueryFromFlags(flags, ["time_range", "start", "end"]);
|
|
20
|
+
const result = await cawplanRequest({
|
|
21
|
+
method: "GET",
|
|
22
|
+
path: `/api/v1/public/openapi/product/${productId}/metrics`,
|
|
23
|
+
query,
|
|
24
|
+
});
|
|
25
|
+
console.log(JSON.stringify(result, null, 2));
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { cawplanRequest } from "../lib/http.js";
|
|
2
|
+
export function registerProductActivityCommand(program) {
|
|
3
|
+
const productActivity = program.command("product-activity").description("Product activity reports");
|
|
4
|
+
productActivity
|
|
5
|
+
.command("get")
|
|
6
|
+
.description("Get a product activity report")
|
|
7
|
+
.requiredOption("--product_id <id>", "Product ID")
|
|
8
|
+
.requiredOption("--start <date>", "Start date YYYY-MM-DD")
|
|
9
|
+
.requiredOption("--end <date>", "End date YYYY-MM-DD")
|
|
10
|
+
.option("--version_id <id>", "Optional version ID")
|
|
11
|
+
.action(async (opts) => {
|
|
12
|
+
const query = {
|
|
13
|
+
product_id: opts.product_id,
|
|
14
|
+
start_date: opts.start,
|
|
15
|
+
end_date: opts.end,
|
|
16
|
+
};
|
|
17
|
+
if (opts.version_id)
|
|
18
|
+
query.version_id = opts.version_id;
|
|
19
|
+
const result = await cawplanRequest({
|
|
20
|
+
method: "GET",
|
|
21
|
+
path: "/api/v1/public/openapi/product-report",
|
|
22
|
+
query,
|
|
23
|
+
});
|
|
24
|
+
console.log(JSON.stringify(result, null, 2));
|
|
25
|
+
});
|
|
26
|
+
}
|