casement-lite-cli 0.0.1

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/dist/index.js ADDED
@@ -0,0 +1,1863 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
5
+ import { homedir } from "os";
6
+ import { join } from "path";
7
+ import { spawn } from "child_process";
8
+ import { createInterface } from "readline/promises";
9
+ import { stdin as input, stdout as output } from "process";
10
+ var APP_DIR = join(homedir(), ".casement-lite");
11
+ var SESSION_FILE = join(APP_DIR, "session.json");
12
+ var DEFAULT_BASE_URL = "https://casement.scredit.io";
13
+ function toText(v) {
14
+ if (v === void 0 || v === null) return "";
15
+ return String(v).trim();
16
+ }
17
+ function toBool(v, fallback) {
18
+ const s = toText(v).toLowerCase();
19
+ if (!s) return fallback;
20
+ return ["1", "true", "yes", "y", "on"].includes(s);
21
+ }
22
+ function toPositiveInt(v, fallback) {
23
+ const n = Number(v);
24
+ if (!Number.isFinite(n) || n <= 0) return fallback;
25
+ return Math.floor(n);
26
+ }
27
+ function safeJsonParse(text) {
28
+ try {
29
+ return JSON.parse(text);
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
34
+ function normalizeBaseUrl(url) {
35
+ const raw = toText(url) || DEFAULT_BASE_URL;
36
+ const u = raw.startsWith("http://") || raw.startsWith("https://") ? raw : `https://${raw}`;
37
+ return u.replace(/\/$/, "");
38
+ }
39
+ function parseGlobalArgv(argv) {
40
+ const args = argv.slice(2);
41
+ const filtered = [];
42
+ let json = false;
43
+ for (const arg of args) {
44
+ if (arg === "--json") {
45
+ json = true;
46
+ continue;
47
+ }
48
+ filtered.push(arg);
49
+ }
50
+ return {
51
+ command: filtered[0] || null,
52
+ args: filtered.slice(1),
53
+ options: { json }
54
+ };
55
+ }
56
+ function parseFlags(args) {
57
+ const positionals = [];
58
+ const flags = {};
59
+ for (let i = 0; i < args.length; i += 1) {
60
+ const arg = args[i];
61
+ if (!arg.startsWith("--")) {
62
+ positionals.push(arg);
63
+ continue;
64
+ }
65
+ const body = arg.slice(2);
66
+ if (!body) continue;
67
+ if (body.includes("=")) {
68
+ const [k, ...rest] = body.split("=");
69
+ flags[k] = rest.join("=");
70
+ continue;
71
+ }
72
+ const next = args[i + 1];
73
+ if (next !== void 0 && !next.startsWith("--")) {
74
+ flags[body] = next;
75
+ i += 1;
76
+ } else {
77
+ flags[body] = true;
78
+ }
79
+ }
80
+ return { positionals, flags };
81
+ }
82
+ function getFlag(flags, ...keys) {
83
+ for (const k of keys) {
84
+ if (Object.prototype.hasOwnProperty.call(flags, k)) {
85
+ return toText(flags[k]);
86
+ }
87
+ }
88
+ return "";
89
+ }
90
+ function printResult(data, options) {
91
+ const spacing = options.json ? 0 : 2;
92
+ console.log(JSON.stringify(data, null, spacing));
93
+ }
94
+ function ensureSessionDir() {
95
+ if (!existsSync(APP_DIR)) {
96
+ mkdirSync(APP_DIR, { recursive: true });
97
+ }
98
+ }
99
+ function loadSession() {
100
+ if (!existsSync(SESSION_FILE)) return null;
101
+ try {
102
+ const parsed = JSON.parse(readFileSync(SESSION_FILE, "utf-8"));
103
+ if (!parsed || typeof parsed !== "object") return null;
104
+ const baseUrl = normalizeBaseUrl(parsed.baseUrl || DEFAULT_BASE_URL);
105
+ return {
106
+ baseUrl,
107
+ token: toText(parsed.token) || void 0,
108
+ cookie: toText(parsed.cookie) || void 0,
109
+ updatedAt: parsed.updatedAt || (/* @__PURE__ */ new Date()).toISOString()
110
+ };
111
+ } catch {
112
+ return null;
113
+ }
114
+ }
115
+ function saveSession(session) {
116
+ ensureSessionDir();
117
+ writeFileSync(SESSION_FILE, `${JSON.stringify(session, null, 2)}
118
+ `, "utf-8");
119
+ }
120
+ function removeSession() {
121
+ if (existsSync(SESSION_FILE)) rmSync(SESSION_FILE);
122
+ }
123
+ async function prompt(question, mask = false) {
124
+ const rl = createInterface({ input, output, terminal: true });
125
+ if (!mask) {
126
+ const ans = await rl.question(question);
127
+ rl.close();
128
+ return ans.trim();
129
+ }
130
+ const previousRaw = input.isRaw;
131
+ if (typeof input.setRawMode === "function") {
132
+ input.setRawMode(true);
133
+ }
134
+ output.write(question);
135
+ let value = "";
136
+ await new Promise((resolve) => {
137
+ const onData = (chunk) => {
138
+ const str = chunk.toString("utf8");
139
+ if (str === "\r" || str === "\n") {
140
+ output.write("\n");
141
+ input.off("data", onData);
142
+ resolve();
143
+ return;
144
+ }
145
+ if (str === "") {
146
+ input.off("data", onData);
147
+ rl.close();
148
+ process.exit(1);
149
+ }
150
+ if (str === "\x7F") {
151
+ if (value.length > 0) {
152
+ value = value.slice(0, -1);
153
+ output.write("\b \b");
154
+ }
155
+ return;
156
+ }
157
+ value += str;
158
+ output.write("*");
159
+ };
160
+ input.on("data", onData);
161
+ });
162
+ if (typeof input.setRawMode === "function") {
163
+ input.setRawMode(Boolean(previousRaw));
164
+ }
165
+ rl.close();
166
+ return value.trim();
167
+ }
168
+ function openUrl(url) {
169
+ const platform = process.platform;
170
+ if (platform === "darwin") {
171
+ spawn("open", [url], { stdio: "ignore", detached: true }).unref();
172
+ return;
173
+ }
174
+ if (platform === "win32") {
175
+ spawn("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true }).unref();
176
+ return;
177
+ }
178
+ spawn("xdg-open", [url], { stdio: "ignore", detached: true }).unref();
179
+ }
180
+ function sleep(ms) {
181
+ return new Promise((resolve) => setTimeout(resolve, ms));
182
+ }
183
+ function tokenFromCookieHeader(cookieHeader) {
184
+ const raw = toText(cookieHeader);
185
+ if (!raw) return "";
186
+ const wanted = /* @__PURE__ */ new Set([
187
+ "casement_login_token",
188
+ "CASEMENT_LOGIN_TOKEN",
189
+ "token",
190
+ "access_token",
191
+ "authorization",
192
+ "Authorization"
193
+ ]);
194
+ const chunks = raw.split(";");
195
+ for (const one of chunks) {
196
+ const [k, ...rest] = one.split("=");
197
+ const key = toText(k);
198
+ if (!key || !wanted.has(key)) continue;
199
+ const value = toText(rest.join("="));
200
+ if (!value) continue;
201
+ if (/^bearer\s+/i.test(value)) return value.replace(/^bearer\s+/i, "").trim();
202
+ return value;
203
+ }
204
+ return "";
205
+ }
206
+ function maybeDecodeURIComponent(text) {
207
+ const raw = toText(text);
208
+ if (!raw) return "";
209
+ try {
210
+ return decodeURIComponent(raw);
211
+ } catch {
212
+ return raw;
213
+ }
214
+ }
215
+ async function captureSessionFromBrowser(args) {
216
+ const startedAt = Date.now();
217
+ const timeoutMs = Math.max(30, args.timeoutSec) * 1e3;
218
+ const deadline = startedAt + timeoutMs;
219
+ let attempts = 0;
220
+ let playwrightModule;
221
+ try {
222
+ const dynamicImport = new Function("m", "return import(m)");
223
+ playwrightModule = await dynamicImport("playwright");
224
+ } catch {
225
+ return {
226
+ ok: false,
227
+ token: "",
228
+ cookie: "",
229
+ error: "\u7F3A\u5C11 playwright \u4F9D\u8D56\u3002\u8BF7\u5148\u6267\u884C: corepack pnpm install",
230
+ attempts,
231
+ elapsedMs: Date.now() - startedAt
232
+ };
233
+ }
234
+ const chromium = playwrightModule?.chromium;
235
+ if (!chromium || typeof chromium.launch !== "function") {
236
+ return {
237
+ ok: false,
238
+ token: "",
239
+ cookie: "",
240
+ error: "playwright.chromium \u4E0D\u53EF\u7528",
241
+ attempts,
242
+ elapsedMs: Date.now() - startedAt
243
+ };
244
+ }
245
+ let browser = null;
246
+ try {
247
+ const launchOptions = { headless: args.headless };
248
+ if (args.channel) launchOptions.channel = args.channel;
249
+ try {
250
+ browser = await chromium.launch(launchOptions);
251
+ } catch (launchErr) {
252
+ if (args.channel) {
253
+ browser = await chromium.launch({ headless: args.headless });
254
+ } else {
255
+ throw launchErr;
256
+ }
257
+ }
258
+ const context = await browser.newContext();
259
+ const page = await context.newPage();
260
+ await page.goto(args.loginUrl, { waitUntil: "domcontentloaded", timeout: 12e4 });
261
+ if (!args.json) {
262
+ console.log(`\u5DF2\u6253\u5F00\u6D4F\u89C8\u5668\u767B\u5F55\u9875: ${args.loginUrl}`);
263
+ console.log("\u8BF7\u5728\u8BE5\u7A97\u53E3\u5B8C\u6210\u767B\u5F55\uFF0CCLI \u5C06\u81EA\u52A8\u91C7\u96C6 token/cookie\u3002");
264
+ }
265
+ while (Date.now() < deadline) {
266
+ attempts += 1;
267
+ const cookies = await context.cookies(args.baseUrl);
268
+ const cookieHeader = cookies.map((c) => `${c.name}=${c.value}`).join("; ");
269
+ const storageToken = await page.evaluate(() => {
270
+ const g = globalThis;
271
+ const keyCandidates = [
272
+ "CASEMENT_LOGIN_TOKEN",
273
+ "casement_login_token",
274
+ "caseMent_login_token",
275
+ "login_token",
276
+ "access_token",
277
+ "token",
278
+ "Authorization",
279
+ "authorization"
280
+ ];
281
+ const stores = [g.localStorage, g.sessionStorage];
282
+ for (const store of stores) {
283
+ for (const key of keyCandidates) {
284
+ const v = store.getItem(key);
285
+ if (v) return v;
286
+ }
287
+ }
288
+ return "";
289
+ }).catch(() => "");
290
+ const cookieToken = tokenFromCookieHeader(cookieHeader);
291
+ const token = toText(maybeDecodeURIComponent(storageToken || cookieToken));
292
+ const cookie = toText(cookieHeader);
293
+ if (token || cookie) {
294
+ const probeSession = {
295
+ baseUrl: args.baseUrl,
296
+ token: token || void 0,
297
+ cookie: cookie || void 0,
298
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
299
+ };
300
+ const probe = await new CasementClient(probeSession).post("/casement/project/list", {
301
+ showTotal: true,
302
+ projectStatus: 1
303
+ });
304
+ if (probe.ok) {
305
+ return {
306
+ ok: true,
307
+ token,
308
+ cookie,
309
+ error: null,
310
+ attempts,
311
+ elapsedMs: Date.now() - startedAt
312
+ };
313
+ }
314
+ }
315
+ await sleep(args.pollMs);
316
+ }
317
+ return {
318
+ ok: false,
319
+ token: "",
320
+ cookie: "",
321
+ error: `\u8D85\u65F6\u672A\u68C0\u6D4B\u5230\u6709\u6548\u767B\u5F55\u4F1A\u8BDD\uFF08>${args.timeoutSec}s\uFF09`,
322
+ attempts,
323
+ elapsedMs: Date.now() - startedAt
324
+ };
325
+ } catch (e) {
326
+ const msg = toText(e);
327
+ const hint = /Executable doesn't exist|Please run the following command/i.test(msg) ? "\u6D4F\u89C8\u5668\u8FD0\u884C\u65F6\u7F3A\u5931\uFF0C\u53EF\u6267\u884C: npx playwright install chromium" : "";
328
+ return {
329
+ ok: false,
330
+ token: "",
331
+ cookie: "",
332
+ error: hint ? `${msg}
333
+ ${hint}` : String(e),
334
+ attempts,
335
+ elapsedMs: Date.now() - startedAt
336
+ };
337
+ } finally {
338
+ if (browser) {
339
+ await browser.close().catch(() => {
340
+ });
341
+ }
342
+ }
343
+ }
344
+ var CasementClient = class {
345
+ baseUrl;
346
+ token;
347
+ cookie;
348
+ constructor(session) {
349
+ this.baseUrl = normalizeBaseUrl(session.baseUrl || DEFAULT_BASE_URL);
350
+ this.token = toText(session.token) || void 0;
351
+ this.cookie = toText(session.cookie) || void 0;
352
+ }
353
+ getSessionSummary() {
354
+ return {
355
+ baseUrl: this.baseUrl,
356
+ hasToken: !!this.token,
357
+ hasCookie: !!this.cookie
358
+ };
359
+ }
360
+ async post(path, body) {
361
+ const url = `${this.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
362
+ const headers = {
363
+ accept: "application/json",
364
+ "content-type": "application/json;charset=UTF-8"
365
+ };
366
+ if (this.token) headers.authorization = `Bearer ${this.token}`;
367
+ if (this.cookie) headers.cookie = this.cookie;
368
+ const resp = await fetch(url, {
369
+ method: "POST",
370
+ headers,
371
+ body: JSON.stringify(body || {})
372
+ });
373
+ const text = await resp.text();
374
+ const parsed = safeJsonParse(text);
375
+ const raw = parsed ?? { code: resp.ok ? 0 : resp.status, msg: text };
376
+ const code = raw && typeof raw === "object" && "code" in raw ? Number(raw.code) : null;
377
+ const msg = raw && typeof raw === "object" ? toText(raw.msg || raw.message) || null : null;
378
+ const result = raw && typeof raw === "object" && "result" in raw ? raw.result : null;
379
+ return {
380
+ ok: code === 0,
381
+ code,
382
+ msg,
383
+ httpStatus: resp.status,
384
+ result,
385
+ raw
386
+ };
387
+ }
388
+ };
389
+ function ensureClientOrThrow() {
390
+ const session = loadSession();
391
+ if (!session) {
392
+ throw new Error("\u672A\u627E\u5230\u767B\u5F55\u4F1A\u8BDD\u3002\u8BF7\u5148\u6267\u884C: casement auth login");
393
+ }
394
+ if (!toText(session.token) && !toText(session.cookie)) {
395
+ throw new Error("\u4F1A\u8BDD\u65E0 token/cookie\u3002\u8BF7\u91CD\u65B0\u6267\u884C: casement auth login");
396
+ }
397
+ return new CasementClient(session);
398
+ }
399
+ function phaseNameFromCode(code) {
400
+ const n = Number(code);
401
+ const m = {
402
+ 0: "TODO",
403
+ 10: "DEV",
404
+ 20: "TEST",
405
+ 30: "UAT",
406
+ 40: "LIVE",
407
+ 99: "DONE"
408
+ };
409
+ return m[n] || `PHASE_${String(code)}`;
410
+ }
411
+ function normalizePhaseTimeline(versionPhase) {
412
+ const src = versionPhase && typeof versionPhase === "object" ? versionPhase : {};
413
+ const preferred = ["devTd", "devIntegration", "dev", "test", "uat", "live"];
414
+ const keys = [.../* @__PURE__ */ new Set([...preferred, ...Object.keys(src)])];
415
+ const map = {};
416
+ const timeline = keys.map((k) => {
417
+ const node = src[k] && typeof src[k] === "object" ? src[k] : {};
418
+ const item = {
419
+ phase: k,
420
+ startTime: toText(node.startTime) || null,
421
+ endTime: toText(node.endTime) || null
422
+ };
423
+ map[k] = { startTime: item.startTime, endTime: item.endTime };
424
+ return item;
425
+ });
426
+ return { timeline, map };
427
+ }
428
+ function inferCheckpointStage(currentPhase) {
429
+ const n = Number(currentPhase);
430
+ const m = {
431
+ 0: "TodoToDev",
432
+ 10: "DevToTest",
433
+ 20: "TestToUAT",
434
+ 30: "UATToLive",
435
+ 40: "LiveToComplete",
436
+ 99: "Complete"
437
+ };
438
+ return m[n] || null;
439
+ }
440
+ function checkStatusName(code) {
441
+ const n = Number(code);
442
+ const m = {
443
+ 1: "PASS",
444
+ 2: "FAILURE",
445
+ 3: "WARNING",
446
+ 4: "SKIPPED"
447
+ };
448
+ return m[n] || `STATUS_${String(code)}`;
449
+ }
450
+ function normalizeAssignee(v) {
451
+ const raw = toText(v);
452
+ if (!raw) return "";
453
+ const at = raw.indexOf("@");
454
+ return at > 0 ? raw.slice(0, at) : raw;
455
+ }
456
+ function normalizePriority(v) {
457
+ const raw = toText(v);
458
+ if (!raw) return "";
459
+ const s = raw.toLowerCase();
460
+ const table = {
461
+ p0: "Highest",
462
+ highest: "Highest",
463
+ critical: "Highest",
464
+ blocker: "Highest",
465
+ p1: "High",
466
+ high: "High",
467
+ p2: "Medium",
468
+ medium: "Medium",
469
+ normal: "Medium",
470
+ p3: "Low",
471
+ low: "Low",
472
+ p4: "Lowest",
473
+ lowest: "Lowest",
474
+ trivial: "Lowest"
475
+ };
476
+ return table[s] || raw;
477
+ }
478
+ function normalizeRequirementType(v) {
479
+ const raw = toText(v);
480
+ if (!raw) return "";
481
+ const norm = raw.toLowerCase();
482
+ const rtMap = {
483
+ "on_call_incident_handling": "On_call_Incident_Handling",
484
+ "on-call & incident handling / \u503C\u73ED & \u6545\u969C\u5904\u7406": "On_call_Incident_Handling",
485
+ "business_support_collaboration": "Business_Support_Collaboration",
486
+ "business support & collaboration / \u4E1A\u52A1\u652F\u6301 & \u534F\u4F5C": "Business_Support_Collaboration",
487
+ "leave_vacation": "Leave_Vacation",
488
+ "leave & vacation / \u8BF7\u5047 & \u4F11\u5047": "Leave_Vacation",
489
+ "team_other_tasks": "Team_Other_Tasks",
490
+ "team & other tasks / \u56E2\u961F & \u5176\u4ED6\u5DE5\u4F5C": "Team_Other_Tasks"
491
+ };
492
+ return rtMap[norm] || raw;
493
+ }
494
+ function normalizeFieldName(rawField) {
495
+ const norm = toText(rawField).toLowerCase();
496
+ const table = {
497
+ summary: "summary",
498
+ title: "summary",
499
+ status: "status",
500
+ storypoints: "storyPoints",
501
+ storypoint: "storyPoints",
502
+ story_points: "storyPoints",
503
+ sp: "storyPoints",
504
+ priority: "priority",
505
+ assignee: "assignee",
506
+ owner: "assignee",
507
+ pic: "assignee",
508
+ phase: "phase",
509
+ stage: "phase",
510
+ startdate: "startDate",
511
+ start_date: "startDate",
512
+ duedate: "dueDate",
513
+ due_date: "dueDate",
514
+ description: "description",
515
+ desc: "description",
516
+ requirementtype: "requirementType",
517
+ requirement_type: "requirementType",
518
+ subtasktype: "requirementType",
519
+ subtask_type: "requirementType",
520
+ jirasubtasktype: "requirementType"
521
+ };
522
+ return table[norm] || toText(rawField);
523
+ }
524
+ function buildAssigneeFallbackCandidates(rawAssigneeInput, normalizedAssignee) {
525
+ const raw = toText(rawAssigneeInput);
526
+ const normalized = toText(normalizedAssignee);
527
+ if (!normalized || normalized.includes("@")) return [];
528
+ const out = [];
529
+ if (raw && raw.includes("@")) out.push(raw);
530
+ out.push(`${normalized}@shopee.com`);
531
+ out.push(`${normalized}@sea.com`);
532
+ const uniq = [];
533
+ const seen = /* @__PURE__ */ new Set();
534
+ for (const one of out) {
535
+ const key = toText(one).toLowerCase();
536
+ if (!key || key === normalized.toLowerCase()) continue;
537
+ if (seen.has(key)) continue;
538
+ seen.add(key);
539
+ uniq.push(one);
540
+ }
541
+ return uniq;
542
+ }
543
+ async function authLogin(args, options) {
544
+ const parsed = parseFlags(args);
545
+ const flags = parsed.flags;
546
+ const fromFile = getFlag(flags, "from", "file");
547
+ let token = getFlag(flags, "token") || toText(process.env.CASEMENT_LOGIN_TOKEN);
548
+ let cookie = getFlag(flags, "cookie") || toText(process.env.CASEMENT_COOKIE);
549
+ const baseUrl = normalizeBaseUrl(getFlag(flags, "baseUrl", "base-url") || DEFAULT_BASE_URL);
550
+ const mode = toText(getFlag(flags, "mode")).toLowerCase();
551
+ const autoCapture = mode ? mode === "auto" : toBool(getFlag(flags, "auto"), true);
552
+ const autoOpen = toBool(flags.open, true);
553
+ const headlessFlag = toBool(flags.headless, false);
554
+ const headless = false;
555
+ const timeoutSec = toPositiveInt(getFlag(flags, "timeout", "timeoutSec", "timeout-sec"), 300);
556
+ const pollMs = Math.max(500, toPositiveInt(getFlag(flags, "pollMs", "poll-ms"), 2e3));
557
+ const browserChannel = toText(getFlag(flags, "browser", "channel") || "chrome");
558
+ const loginUrl = toText(getFlag(flags, "loginUrl", "login-url") || `${baseUrl}/workspace`);
559
+ const askInput = toBool(flags.interactive, true);
560
+ let loginMethod = "flags/env";
561
+ let autoCaptureDetail = null;
562
+ if (fromFile) {
563
+ try {
564
+ const obj = JSON.parse(readFileSync(fromFile, "utf-8"));
565
+ token = token || toText(obj.token || obj.CASEMENT_LOGIN_TOKEN);
566
+ cookie = cookie || toText(obj.cookie || obj.COOKIE || obj.cookieHeader);
567
+ loginMethod = "file";
568
+ } catch (e) {
569
+ printResult({ ok: false, error: `\u65E0\u6CD5\u8BFB\u53D6 --from \u6587\u4EF6: ${String(e)}` }, options);
570
+ process.exit(1);
571
+ }
572
+ }
573
+ if (!token && !cookie && autoCapture) {
574
+ const auto = await captureSessionFromBrowser({
575
+ baseUrl,
576
+ loginUrl,
577
+ headless,
578
+ timeoutSec,
579
+ pollMs,
580
+ channel: browserChannel,
581
+ json: options.json
582
+ });
583
+ autoCaptureDetail = {
584
+ ok: auto.ok,
585
+ error: auto.error,
586
+ attempts: auto.attempts,
587
+ elapsedMs: auto.elapsedMs,
588
+ channel: browserChannel,
589
+ headless,
590
+ requestedHeadless: headlessFlag,
591
+ timeoutSec
592
+ };
593
+ if (auto.ok) {
594
+ token = toText(token || auto.token);
595
+ cookie = toText(cookie || auto.cookie);
596
+ loginMethod = "auto-browser";
597
+ } else if (!options.json) {
598
+ console.log(`\u81EA\u52A8\u91C7\u96C6\u5931\u8D25: ${toText(auto.error) || "unknown"}`);
599
+ console.log("\u5C06\u56DE\u9000\u5230\u624B\u52A8\u8F93\u5165\u6A21\u5F0F\u3002");
600
+ }
601
+ }
602
+ if (!token && !cookie && askInput) {
603
+ if (autoOpen) {
604
+ openUrl(loginUrl);
605
+ }
606
+ if (!options.json) {
607
+ console.log("\u8BF7\u5728\u6D4F\u89C8\u5668\u5B8C\u6210\u767B\u5F55\u540E\uFF0C\u7C98\u8D34 token/cookie\uFF08\u53EF\u7A7A\u4E00\u9879\uFF0C\u81F3\u5C11\u8981\u6709\u4E00\u9879\uFF09\u3002");
608
+ }
609
+ token = token || await prompt("CASEMENT_LOGIN_TOKEN (\u53EF\u7559\u7A7A): ", true);
610
+ cookie = cookie || await prompt("Cookie (\u53EF\u7559\u7A7A): ");
611
+ loginMethod = "manual-input";
612
+ }
613
+ token = toText(token);
614
+ cookie = toText(cookie);
615
+ if (cookie.toLowerCase().startsWith("cookie:")) {
616
+ cookie = cookie.slice(7).trim();
617
+ }
618
+ if (!token && !cookie) {
619
+ printResult({
620
+ ok: false,
621
+ error: "\u7F3A\u5C11 token/cookie\uFF0C\u65E0\u6CD5\u521B\u5EFA\u4F1A\u8BDD",
622
+ hint: "\u53EF\u7528: casement auth login --token <TOKEN> --cookie '<k=v; ...>'",
623
+ autoCapture: autoCaptureDetail
624
+ }, options);
625
+ process.exit(1);
626
+ }
627
+ const session = {
628
+ baseUrl,
629
+ token: token || void 0,
630
+ cookie: cookie || void 0,
631
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
632
+ };
633
+ saveSession(session);
634
+ const client = new CasementClient(session);
635
+ const probe = await client.post("/casement/project/list", { showTotal: true, projectStatus: 1 });
636
+ printResult({
637
+ ok: true,
638
+ saved: SESSION_FILE,
639
+ method: loginMethod,
640
+ session: {
641
+ baseUrl,
642
+ hasToken: !!session.token,
643
+ hasCookie: !!session.cookie,
644
+ updatedAt: session.updatedAt
645
+ },
646
+ autoCapture: autoCaptureDetail,
647
+ verify: {
648
+ ok: probe.ok,
649
+ code: probe.code,
650
+ msg: probe.msg,
651
+ httpStatus: probe.httpStatus
652
+ },
653
+ hint: probe.ok ? "\u767B\u5F55\u4F1A\u8BDD\u53EF\u7528" : "\u4F1A\u8BDD\u5DF2\u4FDD\u5B58\uFF0C\u4F46\u6821\u9A8C\u5931\u8D25\uFF0C\u53EF\u7A0D\u540E\u7528 casement auth status \u590D\u67E5"
654
+ }, options);
655
+ }
656
+ async function authStatus(options) {
657
+ const session = loadSession();
658
+ if (!session) {
659
+ printResult({ ok: false, exists: false, sessionFile: SESSION_FILE, hint: "\u5148\u6267\u884C casement auth login" }, options);
660
+ return;
661
+ }
662
+ const client = new CasementClient(session);
663
+ const probe = await client.post("/casement/project/list", { showTotal: true, projectStatus: 1 });
664
+ printResult({
665
+ ok: probe.ok,
666
+ exists: true,
667
+ sessionFile: SESSION_FILE,
668
+ session: {
669
+ baseUrl: session.baseUrl,
670
+ hasToken: !!session.token,
671
+ hasCookie: !!session.cookie,
672
+ updatedAt: session.updatedAt
673
+ },
674
+ verify: {
675
+ ok: probe.ok,
676
+ code: probe.code,
677
+ msg: probe.msg,
678
+ httpStatus: probe.httpStatus
679
+ }
680
+ }, options);
681
+ }
682
+ function authLogout(options) {
683
+ removeSession();
684
+ printResult({ ok: true, removed: SESSION_FILE }, options);
685
+ }
686
+ async function cmdProjects(args, options) {
687
+ const client = ensureClientOrThrow();
688
+ const parsed = parseFlags(args);
689
+ let keyword = getFlag(parsed.flags, "keyword") || toText(parsed.positionals[0]);
690
+ const limit = toPositiveInt(getFlag(parsed.flags, "limit") || parsed.positionals[1], 50);
691
+ let projectStatus = toPositiveInt(getFlag(parsed.flags, "projectStatus"), 1);
692
+ if (!getFlag(parsed.flags, "projectStatus") && keyword && /^\d+$/.test(keyword)) {
693
+ projectStatus = toPositiveInt(keyword, 1);
694
+ keyword = "";
695
+ }
696
+ const groupIdRaw = getFlag(parsed.flags, "groupId");
697
+ const groupId = groupIdRaw ? Number(groupIdRaw) : null;
698
+ const includeRaw = toBool(parsed.flags.raw, false);
699
+ const resp = await client.post("/casement/project/list", {
700
+ showTotal: true,
701
+ projectStatus
702
+ });
703
+ const result = resp.result || {};
704
+ let rows = Array.isArray(result.data) ? result.data : [];
705
+ if (Number.isFinite(groupId)) {
706
+ rows = rows.filter((x) => Number(x.groupId) === Number(groupId));
707
+ }
708
+ if (keyword) {
709
+ const q = keyword.toLowerCase();
710
+ rows = rows.filter((p) => [p.projectName, p.jiraLibraryKey, p.principal, p.groupName, p.relatedWiseskylarkProject].filter(Boolean).join(" ").toLowerCase().includes(q));
711
+ }
712
+ const projects = rows.map((p) => ({
713
+ id: p.id,
714
+ projectName: p.projectName,
715
+ jiraLibraryKey: p.jiraLibraryKey,
716
+ principal: p.principal,
717
+ groupId: p.groupId,
718
+ groupName: p.groupName,
719
+ projectStatus: p.projectStatus,
720
+ projectGrayStatus: p.projectGrayStatus,
721
+ relatedWiseskylarkProject: p.relatedWiseskylarkProject,
722
+ createTime: p.createTime,
723
+ updateTime: p.updateTime,
724
+ raw: includeRaw ? p : void 0
725
+ }));
726
+ printResult({
727
+ ok: resp.ok,
728
+ code: resp.code,
729
+ msg: resp.msg,
730
+ projectStatus,
731
+ keyword: keyword || null,
732
+ groupId,
733
+ total: result.total || rows.length,
734
+ filteredTotal: projects.length,
735
+ count: Math.min(projects.length, limit),
736
+ projects: projects.slice(0, limit)
737
+ }, options);
738
+ }
739
+ async function cmdProjectDetail(args, options) {
740
+ const client = ensureClientOrThrow();
741
+ const parsed = parseFlags(args);
742
+ const projectId = Number(getFlag(parsed.flags, "projectId") || parsed.positionals[0]);
743
+ if (!Number.isFinite(projectId) || projectId <= 0) {
744
+ printResult({ ok: false, error: "Missing or invalid projectId" }, options);
745
+ process.exit(1);
746
+ }
747
+ const includeMembers = toBool(getFlag(parsed.flags, "includeMembers") || parsed.positionals[1], false);
748
+ const includeVersions = toBool(getFlag(parsed.flags, "includeVersions") || parsed.positionals[2], false);
749
+ const includeRaw = toBool(parsed.flags.raw, false);
750
+ const memberPageSize = toPositiveInt(getFlag(parsed.flags, "memberPageSize"), 50);
751
+ const versionPageSize = toPositiveInt(getFlag(parsed.flags, "versionPageSize"), 20);
752
+ const detail = await client.post("/casement/project/detail", { projectId });
753
+ if (!detail.ok) {
754
+ printResult({ ok: false, error: detail.msg || "Failed to fetch project detail", code: detail.code, httpStatus: detail.httpStatus }, options);
755
+ return;
756
+ }
757
+ let members = null;
758
+ let membersTotal = null;
759
+ if (includeMembers) {
760
+ const m = await client.post("/casement/project-member/list", { projectId, current: 1, pageSize: memberPageSize });
761
+ if (m.ok) {
762
+ const rows = Array.isArray(m.result?.data) ? m.result.data : [];
763
+ membersTotal = Number(m.result?.total || rows.length);
764
+ members = rows.map((x) => ({
765
+ userId: x.userId,
766
+ email: x.email,
767
+ team: x.team,
768
+ roles: Array.isArray(x.roles) ? x.roles.map((r) => r.roleName || r.roleId) : [],
769
+ raw: includeRaw ? x : void 0
770
+ }));
771
+ }
772
+ }
773
+ let versions = null;
774
+ let versionsTotal = null;
775
+ if (includeVersions) {
776
+ const v = await client.post("/casement/project_version/get_project_version_list", {
777
+ projectId,
778
+ current: 1,
779
+ pageSize: versionPageSize
780
+ });
781
+ if (v.ok) {
782
+ const rows = Array.isArray(v.result?.data) ? v.result.data : [];
783
+ versionsTotal = Number(v.result?.total || rows.length);
784
+ versions = rows.map((x) => ({
785
+ id: x.id,
786
+ versionName: x.versionName,
787
+ currentPhase: x.currentPhase,
788
+ startTime: x.startTime,
789
+ endTime: x.endTime,
790
+ pic: x.pic,
791
+ publishWindowStatus: x.publishWindowStatus,
792
+ raw: includeRaw ? x : void 0
793
+ }));
794
+ }
795
+ }
796
+ const p = detail.result || {};
797
+ printResult({
798
+ ok: detail.ok,
799
+ code: detail.code,
800
+ msg: detail.msg,
801
+ projectId,
802
+ project: {
803
+ id: p.id,
804
+ projectName: p.projectName,
805
+ projectDesc: p.projectDesc,
806
+ groupId: p.groupId,
807
+ groupName: p.groupName,
808
+ projectStatus: p.projectStatus,
809
+ projectGrayStatus: p.projectGrayStatus,
810
+ jiraLibraryKey: p.jiraLibraryKey,
811
+ principal: p.principal,
812
+ createTime: p.createTime,
813
+ updateTime: p.updateTime,
814
+ relatedWiseskylarkProject: p.relatedWiseskylarkProject,
815
+ raw: includeRaw ? p : void 0
816
+ },
817
+ includeMembers,
818
+ membersTotal,
819
+ members,
820
+ includeVersions,
821
+ versionsTotal,
822
+ versions
823
+ }, options);
824
+ }
825
+ async function cmdProjectMembers(args, options) {
826
+ const client = ensureClientOrThrow();
827
+ const parsed = parseFlags(args);
828
+ const projectId = Number(getFlag(parsed.flags, "projectId") || parsed.positionals[0]);
829
+ if (!Number.isFinite(projectId) || projectId <= 0) {
830
+ printResult({ ok: false, error: "Missing or invalid projectId" }, options);
831
+ process.exit(1);
832
+ }
833
+ const page = toPositiveInt(getFlag(parsed.flags, "page") || parsed.positionals[1], 1);
834
+ const pageSize = toPositiveInt(getFlag(parsed.flags, "pageSize") || parsed.positionals[2], 50);
835
+ const keyword = (getFlag(parsed.flags, "keyword") || parsed.positionals[3] || "").toLowerCase();
836
+ const limit = toPositiveInt(getFlag(parsed.flags, "limit") || parsed.positionals[4], pageSize);
837
+ const includeRaw = toBool(parsed.flags.raw, false);
838
+ const resp = await client.post("/casement/project-member/list", {
839
+ projectId,
840
+ current: page,
841
+ pageSize
842
+ });
843
+ const result = resp.result || {};
844
+ let rows = Array.isArray(result.data) ? result.data : [];
845
+ if (keyword) {
846
+ rows = rows.filter((m) => [m.email, m.team].filter(Boolean).join(" ").toLowerCase().includes(keyword));
847
+ }
848
+ const members = rows.map((m) => ({
849
+ userId: m.userId,
850
+ projectId: m.projectId,
851
+ email: m.email,
852
+ team: m.team,
853
+ roleConstraints: m.roleConstraints,
854
+ roles: Array.isArray(m.roles) ? m.roles.map((r) => ({ roleId: r.roleId, roleName: r.roleName })) : [],
855
+ raw: includeRaw ? m : void 0
856
+ }));
857
+ printResult({
858
+ ok: resp.ok,
859
+ code: resp.code,
860
+ msg: resp.msg,
861
+ projectId,
862
+ page,
863
+ pageSize,
864
+ keyword: keyword || null,
865
+ total: result.total || members.length,
866
+ filteredTotal: members.length,
867
+ count: Math.min(members.length, limit),
868
+ members: members.slice(0, limit)
869
+ }, options);
870
+ }
871
+ async function cmdVersions(args, options) {
872
+ const client = ensureClientOrThrow();
873
+ const parsed = parseFlags(args);
874
+ const projectId = Number(getFlag(parsed.flags, "projectId") || parsed.positionals[0]);
875
+ if (!Number.isFinite(projectId) || projectId <= 0) {
876
+ printResult({ ok: false, error: "Missing or invalid projectId" }, options);
877
+ process.exit(1);
878
+ }
879
+ const page = toPositiveInt(getFlag(parsed.flags, "page") || parsed.positionals[1], 1);
880
+ const pageSize = toPositiveInt(getFlag(parsed.flags, "pageSize") || parsed.positionals[2], 20);
881
+ const keywordRaw = getFlag(parsed.flags, "keyword") || parsed.positionals[3] || "";
882
+ const keyword = keywordRaw.toLowerCase();
883
+ const limitOrOwnerPos = parsed.positionals[4] || "";
884
+ const ownerPos = parsed.positionals[5] || "";
885
+ const owner = (getFlag(parsed.flags, "owner") || ownerPos || (limitOrOwnerPos && !/^\d+$/.test(limitOrOwnerPos) ? limitOrOwnerPos : "")).toLowerCase();
886
+ const limit = toPositiveInt(getFlag(parsed.flags, "limit") || (/^\d+$/.test(limitOrOwnerPos) ? limitOrOwnerPos : ""), pageSize);
887
+ const filterAliases = {
888
+ undone: "undone",
889
+ not_done: "undone",
890
+ notdone: "undone",
891
+ active: "undone",
892
+ doing: "undone",
893
+ inprogress: "undone",
894
+ done: "done",
895
+ completed: "done",
896
+ complete: "done",
897
+ closed: "done"
898
+ };
899
+ const phaseFilter = filterAliases[keyword] || null;
900
+ const resp = await client.post("/casement/project_version/get_project_version_list", {
901
+ projectId,
902
+ current: page,
903
+ pageSize
904
+ });
905
+ const result = resp.result || {};
906
+ let rows = Array.isArray(result.data) ? result.data : [];
907
+ if (phaseFilter === "undone") rows = rows.filter((v) => Number(v.currentPhase) !== 99);
908
+ else if (phaseFilter === "done") rows = rows.filter((v) => Number(v.currentPhase) === 99);
909
+ else if (keyword) rows = rows.filter((v) => toText(v.versionName).toLowerCase().includes(keyword));
910
+ if (owner) rows = rows.filter((v) => toText(v.pic).toLowerCase().includes(owner));
911
+ const includeRaw = toBool(parsed.flags.raw, false);
912
+ const versions = rows.map((v) => {
913
+ const phase = normalizePhaseTimeline(v.versionPhase);
914
+ return {
915
+ id: v.id,
916
+ versionName: v.versionName,
917
+ projectId: v.projectId,
918
+ jiraVersionId: v.jiraVersionId,
919
+ currentPhase: v.currentPhase,
920
+ currentPhaseName: phaseNameFromCode(v.currentPhase),
921
+ isDone: Number(v.currentPhase) === 99,
922
+ status: {
923
+ currentPhase: v.currentPhase,
924
+ currentPhaseName: phaseNameFromCode(v.currentPhase),
925
+ isDone: Number(v.currentPhase) === 99,
926
+ publishWindowStatus: v.publishWindowStatus || null,
927
+ publishWindowName: v.publishWindowName || null
928
+ },
929
+ startTime: v.startTime,
930
+ endTime: v.endTime,
931
+ phaseTimeline: phase.timeline,
932
+ phaseTimelineMap: phase.map,
933
+ pic: v.pic,
934
+ publishWindowId: v.publishWindowId,
935
+ publishWindowStatus: v.publishWindowStatus,
936
+ publishWindowName: v.publishWindowName,
937
+ pfb: v.pfb,
938
+ raw: includeRaw ? v : void 0
939
+ };
940
+ });
941
+ printResult({
942
+ ok: resp.ok,
943
+ code: resp.code,
944
+ msg: resp.msg,
945
+ projectId,
946
+ page,
947
+ pageSize,
948
+ keyword: keyword || null,
949
+ phaseFilter,
950
+ owner: owner || null,
951
+ total: result.total || versions.length,
952
+ filteredTotal: versions.length,
953
+ count: Math.min(versions.length, limit),
954
+ versions: versions.slice(0, limit)
955
+ }, options);
956
+ }
957
+ function normalizeCheckpointRows(rows, detailLimit, includeRaw) {
958
+ const cap = Math.max(1, detailLimit);
959
+ return (Array.isArray(rows) ? rows : []).slice(0, cap).map((r) => ({
960
+ jiraKey: r.jiraKey || null,
961
+ issueType: r.issueType || r.type || null,
962
+ summary: r.summary || null,
963
+ reporter: r.reporter || null,
964
+ assignee: r.assignee || null,
965
+ status: r.status || null,
966
+ financialRisk: r.financialRisk || null,
967
+ nonComplianceContent: r.nonComplianceContent || r.rcSignoffMsg || r.message || null,
968
+ rcSignOff: r.rcSignOff || null,
969
+ message: r.message || null,
970
+ raw: includeRaw ? r : void 0
971
+ }));
972
+ }
973
+ function normalizeCheckpointDetails(item, detailLimit, includeRaw) {
974
+ const details = item && item.result && item.result.details && typeof item.result.details === "object" ? item.result.details : {};
975
+ return Object.entries(details).map(([key, val]) => {
976
+ const one = val && typeof val === "object" ? val : {};
977
+ const more = one.moreDetails && typeof one.moreDetails === "object" ? one.moreDetails : {};
978
+ const sourceRows = Array.isArray(more.dataSource) ? more.dataSource : [];
979
+ return {
980
+ key,
981
+ pass: one.pass,
982
+ actual: one.actual,
983
+ expect: one.expect,
984
+ message: one.message || null,
985
+ total: Number(more.total || sourceRows.length || 0),
986
+ rows: normalizeCheckpointRows(sourceRows, detailLimit, includeRaw),
987
+ hasMoreRows: sourceRows.length > detailLimit
988
+ };
989
+ });
990
+ }
991
+ async function cmdVersionDetail(args, options) {
992
+ const client = ensureClientOrThrow();
993
+ const parsed = parseFlags(args);
994
+ const versionId = Number(getFlag(parsed.flags, "versionId") || parsed.positionals[0]);
995
+ if (!Number.isFinite(versionId) || versionId <= 0) {
996
+ printResult({ ok: false, error: "Missing or invalid versionId" }, options);
997
+ process.exit(1);
998
+ }
999
+ const includeProject = toBool(getFlag(parsed.flags, "includeProject") || parsed.positionals[1], true);
1000
+ const includeTasks = toBool(getFlag(parsed.flags, "includeTasks") || parsed.positionals[2], true);
1001
+ const taskPage = toPositiveInt(getFlag(parsed.flags, "taskPage") || parsed.positionals[3], 1);
1002
+ const taskPageSize = Math.min(200, toPositiveInt(getFlag(parsed.flags, "taskPageSize") || parsed.positionals[4], 50));
1003
+ const includeCheckpoint = toBool(getFlag(parsed.flags, "includeCheckpoint") || parsed.positionals[5], true);
1004
+ const checkpointStageArg = getFlag(parsed.flags, "checkpointStage") || parsed.positionals[6] || "";
1005
+ const checkpointDetailLimit = Math.min(100, toPositiveInt(getFlag(parsed.flags, "checkpointDetailLimit") || parsed.positionals[7], 20));
1006
+ const includeRaw = toBool(parsed.flags.raw, false);
1007
+ const detailResp = await client.post("/casement/project_version/get_project_version_detail", { id: versionId });
1008
+ if (!detailResp.ok) {
1009
+ printResult({ ok: false, error: detailResp.msg || "Failed to fetch version detail", code: detailResp.code, httpStatus: detailResp.httpStatus }, options);
1010
+ return;
1011
+ }
1012
+ const version = detailResp.result || {};
1013
+ const phase = normalizePhaseTimeline(version.versionPhase);
1014
+ let project = null;
1015
+ if (includeProject && Number.isFinite(Number(version.projectId)) && Number(version.projectId) > 0) {
1016
+ const p = await client.post("/casement/project/detail", { projectId: Number(version.projectId) });
1017
+ if (p.ok) {
1018
+ const one = p.result || {};
1019
+ project = {
1020
+ id: one.id,
1021
+ projectName: one.projectName,
1022
+ groupId: one.groupId,
1023
+ groupName: one.groupName,
1024
+ jiraLibraryKey: one.jiraLibraryKey,
1025
+ principal: one.principal,
1026
+ projectStatus: one.projectStatus,
1027
+ raw: includeRaw ? one : void 0
1028
+ };
1029
+ }
1030
+ }
1031
+ let tasks = null;
1032
+ if (includeTasks && Number.isFinite(Number(version.projectId)) && Number(version.projectId) > 0 && version.versionName) {
1033
+ const taskPayload = {
1034
+ projectId: Number(version.projectId),
1035
+ fixVersions: String(version.versionName),
1036
+ current: taskPage,
1037
+ pageSize: taskPageSize
1038
+ };
1039
+ const taskResp = await client.post("/casement/jira-task/v2/default_list", taskPayload);
1040
+ if (taskResp.ok) {
1041
+ const taskResult = taskResp.result || {};
1042
+ const rows = Array.isArray(taskResult.data) ? taskResult.data : [];
1043
+ tasks = {
1044
+ ok: true,
1045
+ page: taskPage,
1046
+ pageSize: taskPageSize,
1047
+ total: Number(taskResult.total || 0),
1048
+ count: rows.length,
1049
+ list: rows.map((t) => ({
1050
+ id: t.id,
1051
+ jiraId: t.jiraId,
1052
+ jiraKey: t.jiraKey,
1053
+ summary: t.summary,
1054
+ status: t.status,
1055
+ currentPhase: t.currentPhase,
1056
+ currentPhaseName: phaseNameFromCode(t.currentPhase),
1057
+ priority: t.priority,
1058
+ assignee: t.assignee,
1059
+ developer: t.developer,
1060
+ qa: t.qa,
1061
+ reporter: t.reporter,
1062
+ creator: t.creator,
1063
+ fixVersions: t.fixVersions,
1064
+ fixVersionId: t.fixVersionId,
1065
+ requirementType: t.requirementType,
1066
+ country: t.country,
1067
+ business: t.business,
1068
+ childrenCount: t.childrenCount,
1069
+ createTime: t.createTime,
1070
+ updateTime: t.updateTime,
1071
+ resolvedDate: t.resolvedDate,
1072
+ raw: includeRaw ? t : void 0
1073
+ }))
1074
+ };
1075
+ } else {
1076
+ tasks = {
1077
+ ok: false,
1078
+ page: taskPage,
1079
+ pageSize: taskPageSize,
1080
+ error: taskResp.msg || "Failed to fetch version tasks",
1081
+ code: taskResp.code,
1082
+ httpStatus: taskResp.httpStatus
1083
+ };
1084
+ }
1085
+ }
1086
+ let checkpoint = null;
1087
+ if (includeCheckpoint && Number.isFinite(Number(version.projectId)) && Number(version.projectId) > 0 && Number.isFinite(Number(version.publishWindowId)) && Number(version.publishWindowId) > 0) {
1088
+ const checkpointStage = checkpointStageArg || inferCheckpointStage(version.currentPhase);
1089
+ if (checkpointStage) {
1090
+ const checkpointPayload = {
1091
+ appId: -1,
1092
+ versionId: Number(version.id || versionId),
1093
+ windowId: Number(version.publishWindowId),
1094
+ projectId: Number(version.projectId),
1095
+ sceneType: "VersionPhaseChange",
1096
+ stage: checkpointStage
1097
+ };
1098
+ const cp = await client.post("/casement/workflow-scene-record/get_precheck_result", checkpointPayload);
1099
+ if (cp.ok) {
1100
+ const checkpointResult = cp.result || {};
1101
+ const rows = Array.isArray(checkpointResult.cicdResults) ? checkpointResult.cicdResults : [];
1102
+ const checks = rows.map((item) => {
1103
+ const detailEntries = normalizeCheckpointDetails(item, checkpointDetailLimit, includeRaw);
1104
+ const hasDetailIssue = detailEntries.some((d) => Number(d.total || 0) > 0 || !!d.message);
1105
+ const hasIssue = Number(item.checkStatus) !== 1 || String(item.runStatus || "").toLowerCase() === "failure" || hasDetailIssue;
1106
+ return {
1107
+ category: item.category || null,
1108
+ displayName: item.displayName || item.taskName || null,
1109
+ taskName: item.taskName || null,
1110
+ taskDesc: item.taskDesc || null,
1111
+ runStatus: item.runStatus || null,
1112
+ taskStatus: item.taskStatus || null,
1113
+ checkStatus: item.checkStatus,
1114
+ checkStatusName: checkStatusName(item.checkStatus),
1115
+ skippable: item.skippable,
1116
+ operator: item.operator || null,
1117
+ updateTime: item.updateTime || null,
1118
+ hasIssue,
1119
+ details: detailEntries,
1120
+ raw: includeRaw ? item : void 0
1121
+ };
1122
+ });
1123
+ const financialRiskChecks = checks.filter((c) => c.category === "FinancialRisk");
1124
+ const financialRiskIssues = financialRiskChecks.filter((c) => c.hasIssue);
1125
+ const allIssues = checks.filter((c) => c.hasIssue);
1126
+ checkpoint = {
1127
+ ok: true,
1128
+ sceneType: "VersionPhaseChange",
1129
+ stage: checkpointStage,
1130
+ isPass: !!checkpointResult.isPass,
1131
+ existing: !!checkpointResult.existing,
1132
+ totalChecks: checks.length,
1133
+ issueCount: allIssues.length,
1134
+ checks,
1135
+ issues: allIssues,
1136
+ financialRisk: {
1137
+ totalChecks: financialRiskChecks.length,
1138
+ issueCount: financialRiskIssues.length,
1139
+ issues: financialRiskIssues
1140
+ }
1141
+ };
1142
+ } else {
1143
+ checkpoint = {
1144
+ ok: false,
1145
+ stage: checkpointStage,
1146
+ error: cp.msg || "Failed to fetch checkpoint result",
1147
+ code: cp.code,
1148
+ httpStatus: cp.httpStatus
1149
+ };
1150
+ }
1151
+ }
1152
+ }
1153
+ printResult({
1154
+ ok: detailResp.ok,
1155
+ code: detailResp.code,
1156
+ msg: detailResp.msg,
1157
+ versionId,
1158
+ version: {
1159
+ id: version.id,
1160
+ versionName: version.versionName,
1161
+ projectId: version.projectId,
1162
+ jiraVersionId: version.jiraVersionId,
1163
+ currentPhase: version.currentPhase,
1164
+ currentPhaseName: phaseNameFromCode(version.currentPhase),
1165
+ isDone: Number(version.currentPhase) === 99,
1166
+ status: {
1167
+ currentPhase: version.currentPhase,
1168
+ currentPhaseName: phaseNameFromCode(version.currentPhase),
1169
+ isDone: Number(version.currentPhase) === 99,
1170
+ publishWindowStatus: version.publishWindowStatus || null,
1171
+ publishWindowName: version.publishWindowName || null
1172
+ },
1173
+ startTime: version.startTime,
1174
+ endTime: version.endTime,
1175
+ phaseTimeline: phase.timeline,
1176
+ phaseTimelineMap: phase.map,
1177
+ pic: version.pic,
1178
+ publishWindowId: version.publishWindowId,
1179
+ publishWindowStatus: version.publishWindowStatus,
1180
+ publishWindowName: version.publishWindowName,
1181
+ raw: includeRaw ? version : void 0
1182
+ },
1183
+ includeProject,
1184
+ includeTasks,
1185
+ includeCheckpoint,
1186
+ taskPage,
1187
+ taskPageSize,
1188
+ checkpointDetailLimit,
1189
+ tasks,
1190
+ checkpoint,
1191
+ project
1192
+ }, options);
1193
+ }
1194
+ async function cmdTasks(args, options) {
1195
+ const client = ensureClientOrThrow();
1196
+ const parsed = parseFlags(args);
1197
+ const type = getFlag(parsed.flags, "type") || parsed.positionals[0] || "assignToMe";
1198
+ const email = getFlag(parsed.flags, "email") || parsed.positionals[1] || "";
1199
+ const page = toPositiveInt(getFlag(parsed.flags, "page") || parsed.positionals[2], 1);
1200
+ const pageSize = toPositiveInt(getFlag(parsed.flags, "pageSize") || parsed.positionals[3], 20);
1201
+ const payload = { type, current: page, pageSize };
1202
+ if (email) payload.email = email;
1203
+ const resp = await client.post("/casement/workspace/task_list", payload);
1204
+ const result = resp.result || {};
1205
+ const rows = Array.isArray(result.data) ? result.data : Array.isArray(result.list) ? result.list : [];
1206
+ const tasks = rows.map((t) => ({
1207
+ jiraKey: t.jiraKey || t.jira_key || t.key,
1208
+ summary: t.summary || t.title,
1209
+ status: t.status || t.taskStatus,
1210
+ fixVersions: t.fixVersions || t.fixVersion || [],
1211
+ projectName: t.projectName,
1212
+ raw: t
1213
+ }));
1214
+ printResult({
1215
+ ok: resp.ok,
1216
+ code: resp.code,
1217
+ msg: resp.msg,
1218
+ page,
1219
+ pageSize,
1220
+ type,
1221
+ total: result.total || tasks.length,
1222
+ count: tasks.length,
1223
+ tasks
1224
+ }, options);
1225
+ }
1226
+ async function cmdSubtasks(args, options) {
1227
+ const client = ensureClientOrThrow();
1228
+ const parsed = parseFlags(args);
1229
+ const typeInput = getFlag(parsed.flags, "type") || parsed.positionals[0] || "undone";
1230
+ const typeMap = {
1231
+ thisWeek: "weekly_done",
1232
+ week: "weekly_done",
1233
+ lastWeek: "lastWeek_done",
1234
+ nextWeek: "nextWeek_done",
1235
+ overdue: "past_due",
1236
+ mine: "createdByMe"
1237
+ };
1238
+ const type = typeMap[typeInput] || typeInput;
1239
+ const email = getFlag(parsed.flags, "email") || parsed.positionals[1] || "";
1240
+ const page = toPositiveInt(getFlag(parsed.flags, "page") || parsed.positionals[2], 1);
1241
+ const pageSize = toPositiveInt(getFlag(parsed.flags, "pageSize") || parsed.positionals[3], 20);
1242
+ const payload = { type, current: page, pageSize };
1243
+ if (email) payload.email = email;
1244
+ const resp = await client.post("/casement/workspace/subTask_list", payload);
1245
+ const result = resp.result || {};
1246
+ const rows = Array.isArray(result.data) ? result.data : Array.isArray(result.list) ? result.list : [];
1247
+ const subtasks = rows.map((t) => ({
1248
+ jiraSubtask: t.jiraSubtask || t.jiraKey || t.jira_key || t.key,
1249
+ parentJiraKey: t.parentJiraKey || t.parentJiraTask || t.jiraTask,
1250
+ summary: t.summary || t.title,
1251
+ dueDate: t.dueDate || t.planFinishTime,
1252
+ storyPoints: t.storyPoints,
1253
+ status: t.status || t.taskStatus,
1254
+ stage: t.stage || t.jiraSubtaskStage,
1255
+ fixVersions: t.parentFixVersions || t.fixVersions || [],
1256
+ raw: t
1257
+ }));
1258
+ printResult({
1259
+ ok: resp.ok,
1260
+ code: resp.code,
1261
+ msg: resp.msg,
1262
+ page,
1263
+ pageSize,
1264
+ type,
1265
+ total: result.total || subtasks.length,
1266
+ count: subtasks.length,
1267
+ subtasks
1268
+ }, options);
1269
+ }
1270
+ async function cmdCreateSubtask(args, options) {
1271
+ const client = ensureClientOrThrow();
1272
+ const parsed = parseFlags(args);
1273
+ const parentJiraKey = getFlag(parsed.flags, "parentJiraKey") || parsed.positionals[0];
1274
+ if (!parentJiraKey) {
1275
+ printResult({ ok: false, error: "Missing argument: parentJiraKey" }, options);
1276
+ process.exit(1);
1277
+ }
1278
+ const summaryOrJson = parsed.positionals[1] || "";
1279
+ const requirementTypePos = parsed.positionals[2] || "";
1280
+ const payloadRaw = getFlag(parsed.flags, "payload") || (summaryOrJson.startsWith("{") ? summaryOrJson : "");
1281
+ let body = { parentJiraKey };
1282
+ if (payloadRaw) {
1283
+ const parsedPayload = safeJsonParse(payloadRaw);
1284
+ if (!parsedPayload) {
1285
+ printResult({ ok: false, error: "Invalid payload JSON" }, options);
1286
+ process.exit(1);
1287
+ }
1288
+ body = { ...parsedPayload, parentJiraKey };
1289
+ } else {
1290
+ const summary = getFlag(parsed.flags, "summary") || (summaryOrJson && !summaryOrJson.startsWith("{") ? summaryOrJson : "");
1291
+ const assignee = getFlag(parsed.flags, "assignee");
1292
+ const phase = getFlag(parsed.flags, "phase", "stage");
1293
+ const priority = getFlag(parsed.flags, "priority");
1294
+ const storyPoints = getFlag(parsed.flags, "storyPoints", "story_points");
1295
+ const startDate = getFlag(parsed.flags, "startDate", "start_date");
1296
+ const dueDate = getFlag(parsed.flags, "dueDate", "due_date");
1297
+ const description = getFlag(parsed.flags, "description");
1298
+ const requirementType = getFlag(parsed.flags, "requirementType", "subtaskType", "jiraSubtaskType") || requirementTypePos;
1299
+ if (summary) body.summary = summary;
1300
+ if (assignee) body.assignee = normalizeAssignee(assignee);
1301
+ if (phase) body.phase = phase;
1302
+ if (priority) body.priority = normalizePriority(priority);
1303
+ if (storyPoints && Number.isFinite(Number(storyPoints))) body.storyPoints = Number(storyPoints);
1304
+ if (startDate) body.startDate = startDate;
1305
+ if (dueDate) body.dueDate = dueDate;
1306
+ if (description) body.description = description;
1307
+ if (requirementType) body.requirementType = normalizeRequirementType(requirementType);
1308
+ }
1309
+ const send = async (oneBody) => {
1310
+ const resp = await client.post("/casement/jira-subtask/add-v2", oneBody);
1311
+ const result2 = resp.result || {};
1312
+ return {
1313
+ ok: resp.ok,
1314
+ code: resp.code,
1315
+ msg: resp.msg,
1316
+ httpStatus: resp.httpStatus,
1317
+ result: result2,
1318
+ jiraSubtask: result2.jiraSubtask || result2.jiraKey || result2.key || null
1319
+ };
1320
+ };
1321
+ let finalBody = { ...body };
1322
+ let result = await send(finalBody);
1323
+ let fallback = { assigneeTried: false, assigneeUsed: null };
1324
+ if (!result.ok && /User\s+'.+'\s+does not exist/i.test(toText(result.msg))) {
1325
+ const assignee = toText(finalBody.assignee);
1326
+ if (assignee && !assignee.includes("@")) {
1327
+ const candidates = buildAssigneeFallbackCandidates(body.assignee, assignee);
1328
+ for (const candidate of candidates) {
1329
+ fallback.assigneeTried = true;
1330
+ const retryBody = { ...finalBody, assignee: candidate };
1331
+ const retry = await send(retryBody);
1332
+ if (retry.ok || !/User\s+'.+'\s+does not exist/i.test(toText(retry.msg))) {
1333
+ finalBody = retryBody;
1334
+ result = retry;
1335
+ fallback.assigneeUsed = candidate;
1336
+ break;
1337
+ }
1338
+ }
1339
+ }
1340
+ }
1341
+ printResult({
1342
+ ok: result.ok,
1343
+ code: result.code,
1344
+ msg: result.msg,
1345
+ httpStatus: result.httpStatus,
1346
+ parentJiraKey,
1347
+ jiraSubtask: result.jiraSubtask,
1348
+ result: result.result,
1349
+ requestBody: finalBody,
1350
+ fallback
1351
+ }, options);
1352
+ }
1353
+ async function cmdUpdateStatus(args, options) {
1354
+ const client = ensureClientOrThrow();
1355
+ const parsed = parseFlags(args);
1356
+ const jiraKey = getFlag(parsed.flags, "jiraKey") || parsed.positionals[0];
1357
+ const statusRaw = getFlag(parsed.flags, "status") || parsed.positionals[1];
1358
+ if (!jiraKey) {
1359
+ printResult({ ok: false, error: "Missing argument: jiraKey" }, options);
1360
+ process.exit(1);
1361
+ }
1362
+ if (!statusRaw) {
1363
+ printResult({ ok: false, error: "Missing argument: status" }, options);
1364
+ process.exit(1);
1365
+ }
1366
+ const statusMap = {
1367
+ "to do": "TO DO",
1368
+ todo: "TO DO",
1369
+ doing: "Doing",
1370
+ done: "Done",
1371
+ closed: "Closed"
1372
+ };
1373
+ const normalized = statusRaw.toLowerCase();
1374
+ const statusId = statusMap[normalized];
1375
+ if (!statusId) {
1376
+ printResult({ ok: false, error: `Invalid status: ${statusRaw}`, hint: "Use one of: To Do, Doing, Done, Closed" }, options);
1377
+ process.exit(1);
1378
+ }
1379
+ async function setStatus(nextStatusName) {
1380
+ return client.post("/casement/jira-subtask/update_field", {
1381
+ jiraKey,
1382
+ field: "status",
1383
+ value: nextStatusName
1384
+ });
1385
+ }
1386
+ const first = await setStatus(statusId);
1387
+ if (!first.ok && normalized === "done") {
1388
+ const stepDoing = await setStatus(statusMap.doing);
1389
+ const stepDone = stepDoing.ok ? await setStatus(statusId) : null;
1390
+ printResult({
1391
+ ok: !!(stepDone && stepDone.ok),
1392
+ code: stepDone ? stepDone.code : first.code,
1393
+ msg: stepDone ? stepDone.msg : first.msg,
1394
+ jiraKey,
1395
+ status: statusRaw,
1396
+ statusId,
1397
+ autoTransition: true,
1398
+ transitions: [
1399
+ { status: "Doing", statusId: statusMap.doing, ok: stepDoing.ok, code: stepDoing.code, msg: stepDoing.msg, httpStatus: stepDoing.httpStatus },
1400
+ { status: "Done", statusId, ok: stepDone ? stepDone.ok : false, code: stepDone ? stepDone.code : null, msg: stepDone ? stepDone.msg : "Skip because Doing failed" }
1401
+ ],
1402
+ result: stepDone ? stepDone.result : first.result
1403
+ }, options);
1404
+ return;
1405
+ }
1406
+ printResult({
1407
+ ok: first.ok,
1408
+ code: first.code,
1409
+ msg: first.msg,
1410
+ jiraKey,
1411
+ status: statusRaw,
1412
+ statusId,
1413
+ httpStatus: first.httpStatus,
1414
+ result: first.result
1415
+ }, options);
1416
+ }
1417
+ async function cmdUpdateSubtask(args, options) {
1418
+ const client = ensureClientOrThrow();
1419
+ const parsed = parseFlags(args);
1420
+ const jiraKey = getFlag(parsed.flags, "jiraKey") || parsed.positionals[0];
1421
+ if (!jiraKey) {
1422
+ printResult({ ok: false, error: "Missing argument: jiraKey" }, options);
1423
+ process.exit(1);
1424
+ }
1425
+ const secondPos = parsed.positionals[1] || "";
1426
+ const thirdPos = parsed.positionals[2] || "";
1427
+ const payloadRaw = getFlag(parsed.flags, "payload") || (secondPos.startsWith("{") ? secondPos : "");
1428
+ const updates = [];
1429
+ const payloadObj = payloadRaw ? safeJsonParse(payloadRaw) : null;
1430
+ if (payloadObj && typeof payloadObj === "object") {
1431
+ for (const [k, v] of Object.entries(payloadObj)) {
1432
+ if (v === void 0 || v === null || toText(v) === "") continue;
1433
+ updates.push({ field: k, value: v, source: "payload" });
1434
+ }
1435
+ }
1436
+ if (updates.length === 0) {
1437
+ const field = getFlag(parsed.flags, "field") || secondPos;
1438
+ const value = getFlag(parsed.flags, "value") || thirdPos;
1439
+ if (field && value !== "") {
1440
+ updates.push({ field, value, source: "single" });
1441
+ }
1442
+ }
1443
+ if (updates.length === 0) {
1444
+ const directFields = ["summary", "storyPoints", "priority", "assignee", "phase", "startDate", "dueDate", "description", "requirementType"];
1445
+ for (const key of directFields) {
1446
+ const val = getFlag(parsed.flags, key);
1447
+ if (val !== "") updates.push({ field: key, value: val, source: "direct" });
1448
+ }
1449
+ }
1450
+ if (updates.length === 0) {
1451
+ printResult({ ok: false, error: "No fields to update", hint: "Use <jiraKey> <field> <value> OR JSON payload" }, options);
1452
+ process.exit(1);
1453
+ }
1454
+ const results = [];
1455
+ for (const item of updates) {
1456
+ const canonicalField = normalizeFieldName(item.field);
1457
+ let value = item.value;
1458
+ if (canonicalField === "priority") value = normalizePriority(item.value);
1459
+ else if (canonicalField === "assignee") value = normalizeAssignee(item.value);
1460
+ else if (canonicalField === "requirementType") value = normalizeRequirementType(item.value);
1461
+ else value = toText(item.value);
1462
+ const send = async (v) => client.post("/casement/jira-subtask/update_field", {
1463
+ jiraKey,
1464
+ field: canonicalField,
1465
+ value: v
1466
+ });
1467
+ let response = await send(value);
1468
+ let fallback = { tried: false, used: null };
1469
+ if (canonicalField === "assignee" && !response.ok && /User\s+'.+'\s+does not exist/i.test(toText(response.msg))) {
1470
+ const candidates = buildAssigneeFallbackCandidates(item.value, value);
1471
+ for (const candidate of candidates) {
1472
+ fallback.tried = true;
1473
+ const retry = await send(candidate);
1474
+ if (retry.ok || !/User\s+'.+'\s+does not exist/i.test(toText(retry.msg))) {
1475
+ response = retry;
1476
+ fallback.used = candidate;
1477
+ value = candidate;
1478
+ break;
1479
+ }
1480
+ }
1481
+ }
1482
+ results.push({
1483
+ field: canonicalField,
1484
+ inputField: item.field,
1485
+ inputValue: item.value,
1486
+ value,
1487
+ source: item.source,
1488
+ ok: response.ok,
1489
+ code: response.code,
1490
+ msg: response.msg,
1491
+ httpStatus: response.httpStatus,
1492
+ fallback,
1493
+ result: response.result
1494
+ });
1495
+ }
1496
+ const successCount = results.filter((x) => x.ok).length;
1497
+ printResult({
1498
+ ok: successCount === results.length,
1499
+ jiraKey,
1500
+ total: results.length,
1501
+ successCount,
1502
+ failCount: results.length - successCount,
1503
+ updates: results
1504
+ }, options);
1505
+ }
1506
+ function inferArgType(name, description) {
1507
+ const n = name.toLowerCase();
1508
+ const d = description.toLowerCase();
1509
+ if (n.includes("include") || d.includes("true/false") || d.includes("\u662F\u5426")) return "boolean";
1510
+ if (n.includes("stage")) return "string";
1511
+ const rawName = toText(name);
1512
+ const isNumberName = /Id$/.test(rawName) || /Page$/.test(rawName) || /PageSize$/.test(rawName) || /Limit$/.test(rawName) || /^storyPoints?$/i.test(rawName);
1513
+ if (isNumberName) return "number";
1514
+ return "string";
1515
+ }
1516
+ function extractDefaultValue(description) {
1517
+ const text = toText(description);
1518
+ if (!text) return null;
1519
+ const m = text.match(/默认\s*([^\s,,))]+)/);
1520
+ return m ? m[1] : null;
1521
+ }
1522
+ function tokenizePositionalUsage(positionalUsage, args) {
1523
+ const tokens = positionalUsage.match(/<[^>]+>|\[[^\]]+\]/g) || [];
1524
+ return tokens.map((raw, idx) => {
1525
+ const required = raw.startsWith("<");
1526
+ const body = raw.slice(1, -1).trim();
1527
+ const enumValues = body.includes("|") ? body.split("|").map((x) => x.trim()).filter(Boolean) : [];
1528
+ const name = enumValues[0] || body;
1529
+ const description = args[name] || null;
1530
+ const inferredType = inferArgType(name, description || "");
1531
+ return {
1532
+ index: idx,
1533
+ raw,
1534
+ name,
1535
+ required,
1536
+ inferredType,
1537
+ enumValues,
1538
+ default: extractDefaultValue(description || ""),
1539
+ description
1540
+ };
1541
+ });
1542
+ }
1543
+ function buildAgentCommandContract(name, cmd) {
1544
+ const positionalArgs = tokenizePositionalUsage(cmd.positionalUsage, cmd.args);
1545
+ const positionalArgNames = new Set(positionalArgs.map((x) => x.name));
1546
+ const namedArgs = Object.entries(cmd.args).filter(([k]) => !positionalArgNames.has(k)).map(([k, v]) => ({
1547
+ name: k,
1548
+ description: v,
1549
+ inferredType: inferArgType(k, v),
1550
+ default: extractDefaultValue(v)
1551
+ }));
1552
+ return {
1553
+ command: name,
1554
+ readOnly: cmd.readOnly,
1555
+ positionalArgs,
1556
+ namedArgs,
1557
+ parseRules: [
1558
+ "\u4F18\u5148\u6309 positionalUsage \u7684\u987A\u5E8F\u4F20\u4F4D\u7F6E\u53C2\u6570\u3002",
1559
+ "\u590D\u6742\u5185\u5BB9\u4F18\u5148\u4F20 JSON \u5B57\u7B26\u4E32\uFF0C\u907F\u514D shell \u89E3\u6790\u6B67\u4E49\u3002",
1560
+ "\u672A\u63D0\u4F9B\u53EF\u9009\u53C2\u6570\u65F6\uFF0C\u6309\u547D\u4EE4\u5185\u90E8\u9ED8\u8BA4\u503C\u6267\u884C\u3002"
1561
+ ],
1562
+ successSignal: cmd.readOnly ? "\u8FD4\u56DE ok=true \u4E14\u5305\u542B\u5217\u8868/\u8BE6\u60C5\u5B57\u6BB5" : "\u8FD4\u56DE ok=true\uFF0C\u4E14 successCount==total\uFF08\u6279\u91CF\u66F4\u65B0\u573A\u666F\uFF09",
1563
+ examples: cmd.examples
1564
+ };
1565
+ }
1566
+ var HELP_COMMANDS = {
1567
+ projects: {
1568
+ title: "casement projects",
1569
+ purpose: "\u67E5\u8BE2\u9879\u76EE\u5217\u8868",
1570
+ positionalUsage: "casement projects [keyword] [limit]",
1571
+ readOnly: true,
1572
+ args: {
1573
+ keyword: "\u9879\u76EE\u5173\u952E\u5B57\uFF08projectName/jiraLibraryKey/principal/groupName\uFF09",
1574
+ limit: "\u6700\u591A\u8FD4\u56DE\u6761\u6570\uFF0C\u9ED8\u8BA4 50",
1575
+ projectStatus: "\u9879\u76EE\u72B6\u6001\uFF08\u9ED8\u8BA4 1\uFF09",
1576
+ groupId: "\u6309 groupId \u8FC7\u6EE4\uFF08\u53EF\u9009\uFF09"
1577
+ },
1578
+ examples: ["casement projects credit 20"]
1579
+ },
1580
+ "project-detail": {
1581
+ title: "casement project-detail",
1582
+ purpose: "\u67E5\u8BE2\u9879\u76EE\u8BE6\u60C5",
1583
+ positionalUsage: "casement project-detail <projectId> [includeMembers] [includeVersions]",
1584
+ readOnly: true,
1585
+ args: {
1586
+ projectId: "\u9879\u76EE ID\uFF08\u5FC5\u586B\uFF09",
1587
+ includeMembers: "true/false\uFF0C\u662F\u5426\u5E26\u6210\u5458",
1588
+ includeVersions: "true/false\uFF0C\u662F\u5426\u5E26\u7248\u672C\u6458\u8981",
1589
+ memberPageSize: "\u6210\u5458\u5206\u9875\u5927\u5C0F\uFF0C\u9ED8\u8BA4 50",
1590
+ versionPageSize: "\u7248\u672C\u5206\u9875\u5927\u5C0F\uFF0C\u9ED8\u8BA4 20"
1591
+ },
1592
+ examples: ["casement project-detail 6 true true"]
1593
+ },
1594
+ versions: {
1595
+ title: "casement versions",
1596
+ purpose: "\u67E5\u8BE2\u9879\u76EE\u7248\u672C",
1597
+ positionalUsage: "casement versions <projectId> [page] [pageSize] [keyword|undone|done] [limit|owner] [owner]",
1598
+ readOnly: true,
1599
+ args: {
1600
+ projectId: "\u9879\u76EE ID\uFF08\u5FC5\u586B\uFF09",
1601
+ page: "\u9875\u7801\uFF0C\u9ED8\u8BA4 1",
1602
+ pageSize: "\u6BCF\u9875\u6570\u91CF\uFF0C\u9ED8\u8BA4 20",
1603
+ keyword: "\u7248\u672C\u5173\u952E\u5B57\uFF1B\u6216\u4F20 undone/done",
1604
+ owner: "\u6309\u8D1F\u8D23\u4EBA\u8FC7\u6EE4\uFF08\u90AE\u7BB1\u6216\u524D\u7F00\uFF09",
1605
+ limit: "\u6700\u591A\u8FD4\u56DE\u6761\u6570"
1606
+ },
1607
+ examples: ["casement versions 6 1 100 undone", "casement versions 6 1 100 undone shipeng.chen"]
1608
+ },
1609
+ "version-detail": {
1610
+ title: "casement version-detail",
1611
+ purpose: "\u67E5\u8BE2\u7248\u672C\u8BE6\u60C5\uFF08\u53EF\u5E26 tasks/checkpoint\uFF09",
1612
+ positionalUsage: "casement version-detail <versionId> [includeProject] [includeTasks] [taskPage] [taskPageSize] [includeCheckpoint] [checkpointStage] [checkpointDetailLimit]",
1613
+ readOnly: true,
1614
+ args: {
1615
+ versionId: "\u7248\u672C ID\uFF08\u5FC5\u586B\uFF09",
1616
+ includeProject: "\u662F\u5426\u9644\u5E26\u9879\u76EE\u4FE1\u606F\uFF0C\u9ED8\u8BA4 true",
1617
+ includeTasks: "\u662F\u5426\u9644\u5E26\u7248\u672C\u4EFB\u52A1\uFF0C\u9ED8\u8BA4 true",
1618
+ taskPageSize: "\u4EFB\u52A1\u5206\u9875\u5927\u5C0F\uFF0C\u9ED8\u8BA4 50",
1619
+ includeCheckpoint: "\u662F\u5426\u9644\u5E26 checkpoint\uFF0C\u9ED8\u8BA4 true",
1620
+ checkpointStage: "\u624B\u52A8\u6307\u5B9A stage\uFF08\u5982 DevToTest\uFF09"
1621
+ },
1622
+ examples: ["casement version-detail 9099", "casement version-detail 9179 true true 1 50 true DevToTest 50"]
1623
+ },
1624
+ "project-members": {
1625
+ title: "casement project-members",
1626
+ purpose: "\u67E5\u8BE2\u9879\u76EE\u6210\u5458",
1627
+ positionalUsage: "casement project-members <projectId> [page] [pageSize] [keyword] [limit]",
1628
+ readOnly: true,
1629
+ args: {
1630
+ projectId: "\u9879\u76EE ID\uFF08\u5FC5\u586B\uFF09",
1631
+ page: "\u9875\u7801\uFF0C\u9ED8\u8BA4 1",
1632
+ pageSize: "\u6BCF\u9875\u6570\u91CF\uFF0C\u9ED8\u8BA4 50",
1633
+ keyword: "\u6309\u90AE\u7BB1/team \u8FC7\u6EE4",
1634
+ limit: "\u6700\u591A\u8FD4\u56DE\u6761\u6570"
1635
+ },
1636
+ examples: ["casement project-members 6 1 100"]
1637
+ },
1638
+ tasks: {
1639
+ title: "casement tasks",
1640
+ purpose: "\u67E5\u8BE2 workspace my task",
1641
+ positionalUsage: "casement tasks [type] [email] [page] [pageSize]",
1642
+ readOnly: true,
1643
+ args: {
1644
+ type: "assignToMe | creatorByMe | relatedToMe",
1645
+ email: "\u76EE\u6807\u90AE\u7BB1\uFF08\u9ED8\u8BA4\u5F53\u524D\u767B\u5F55\u7528\u6237\uFF09",
1646
+ page: "\u9875\u7801\uFF0C\u9ED8\u8BA4 1",
1647
+ pageSize: "\u6BCF\u9875\u6570\u91CF\uFF0C\u9ED8\u8BA4 20"
1648
+ },
1649
+ examples: ["casement tasks assignToMe"]
1650
+ },
1651
+ subtasks: {
1652
+ title: "casement subtasks",
1653
+ purpose: "\u67E5\u8BE2 workspace my subtask",
1654
+ positionalUsage: "casement subtasks [type] [email] [page] [pageSize]",
1655
+ readOnly: true,
1656
+ args: {
1657
+ type: "undone/weekly_done/lastWeek_done/nextWeek_done/past_due/all/createdByMe",
1658
+ email: "\u76EE\u6807\u90AE\u7BB1\uFF08\u9ED8\u8BA4\u5F53\u524D\u767B\u5F55\u7528\u6237\uFF09",
1659
+ page: "\u9875\u7801\uFF0C\u9ED8\u8BA4 1",
1660
+ pageSize: "\u6BCF\u9875\u6570\u91CF\uFF0C\u9ED8\u8BA4 20"
1661
+ },
1662
+ examples: ["casement subtasks undone"]
1663
+ },
1664
+ "create-subtask": {
1665
+ title: "casement create-subtask",
1666
+ purpose: "\u521B\u5EFA subtask",
1667
+ positionalUsage: "casement create-subtask <parentJiraKey> [summary-or-json]",
1668
+ readOnly: false,
1669
+ args: {
1670
+ parentJiraKey: "\u7236\u4EFB\u52A1 key\uFF08\u5FC5\u586B\uFF09",
1671
+ summary: "\u6807\u9898",
1672
+ assignee: "\u6307\u6D3E\u4EBA\uFF08\u90AE\u7BB1\u4F1A\u81EA\u52A8\u8F6C\u77ED\u540D\uFF09",
1673
+ phase: "\u9636\u6BB5\uFF08Dev/Test/UAT/Live\uFF09",
1674
+ priority: "Highest/High/Medium/Low/Lowest\uFF08\u652F\u6301 P0~P4\uFF09",
1675
+ storyPoints: "\u6545\u4E8B\u70B9",
1676
+ startDate: "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD",
1677
+ dueDate: "\u622A\u6B62\u65E5\u671F YYYY-MM-DD",
1678
+ requirementType: "\u5B50\u4EFB\u52A1\u7C7B\u578B",
1679
+ description: "\u63CF\u8FF0",
1680
+ payload: "\u539F\u59CB JSON"
1681
+ },
1682
+ notes: ["\u63A8\u8350\u7B2C 2 \u4E2A\u4F4D\u7F6E\u53C2\u6570\u76F4\u63A5\u4F20 JSON"],
1683
+ examples: [
1684
+ `casement create-subtask SPSK-245038 '{"summary":"[BE] demo","assignee":"shipeng.chen","priority":"High"}'`
1685
+ ]
1686
+ },
1687
+ "update-status": {
1688
+ title: "casement update-status",
1689
+ purpose: "\u66F4\u65B0 subtask \u72B6\u6001",
1690
+ positionalUsage: "casement update-status <jiraKey> <status>",
1691
+ readOnly: false,
1692
+ args: {
1693
+ jiraKey: "\u5B50\u4EFB\u52A1 key\uFF08\u5FC5\u586B\uFF09",
1694
+ status: "To Do | Doing | Done | Closed"
1695
+ },
1696
+ examples: ["casement update-status SPSK-273098 Doing"]
1697
+ },
1698
+ "update-subtask": {
1699
+ title: "casement update-subtask",
1700
+ purpose: "\u66F4\u65B0 subtask \u5B57\u6BB5\uFF08summary/storyPoints/priority/assignee...\uFF09",
1701
+ positionalUsage: "casement update-subtask <jiraKey> <field|json> [value]",
1702
+ readOnly: false,
1703
+ args: {
1704
+ jiraKey: "\u5B50\u4EFB\u52A1 key\uFF08\u5FC5\u586B\uFF09",
1705
+ field: "\u5B57\u6BB5\u540D",
1706
+ value: "\u5B57\u6BB5\u503C",
1707
+ payload: "\u6279\u91CF JSON",
1708
+ summary: "\u76F4\u63A5\u66F4\u65B0 summary",
1709
+ storyPoints: "\u76F4\u63A5\u66F4\u65B0 storyPoints",
1710
+ priority: "\u76F4\u63A5\u66F4\u65B0 priority\uFF08\u652F\u6301 P0~P4\uFF09",
1711
+ assignee: "\u76F4\u63A5\u66F4\u65B0 assignee\uFF08\u652F\u6301\u90AE\u7BB1/\u7528\u6237\u540D\uFF09",
1712
+ phase: "\u76F4\u63A5\u66F4\u65B0 phase",
1713
+ startDate: "\u76F4\u63A5\u66F4\u65B0 startDate",
1714
+ dueDate: "\u76F4\u63A5\u66F4\u65B0 dueDate",
1715
+ description: "\u76F4\u63A5\u66F4\u65B0 description",
1716
+ requirementType: "\u76F4\u63A5\u66F4\u65B0 requirementType"
1717
+ },
1718
+ examples: [
1719
+ "casement update-subtask SPSK-273096 summary '[BE] new title'",
1720
+ "casement update-subtask SPSK-273096 storyPoints 2.5",
1721
+ `casement update-subtask SPSK-273096 '{"summary":"new","storyPoints":2.5,"priority":"High"}'`
1722
+ ]
1723
+ },
1724
+ auth: {
1725
+ title: "casement auth",
1726
+ purpose: "\u767B\u5F55\u4F1A\u8BDD\u7BA1\u7406",
1727
+ positionalUsage: "casement auth <login|status|logout>",
1728
+ readOnly: false,
1729
+ args: {
1730
+ login: "\u4FDD\u5B58 token/cookie \u5230\u672C\u5730\u4F1A\u8BDD",
1731
+ status: "\u68C0\u67E5\u4F1A\u8BDD\u53EF\u7528\u6027",
1732
+ logout: "\u6E05\u9664\u672C\u5730\u4F1A\u8BDD",
1733
+ token: "CASEMENT_LOGIN_TOKEN",
1734
+ cookie: "\u6D4F\u89C8\u5668 Cookie \u5B57\u7B26\u4E32",
1735
+ from: "\u4ECE JSON \u6587\u4EF6\u5BFC\u5165 token/cookie",
1736
+ mode: "\u767B\u5F55\u6A21\u5F0F\uFF1Aauto(\u9ED8\u8BA4) | manual",
1737
+ auto: "\u662F\u5426\u542F\u7528\u81EA\u52A8\u6D4F\u89C8\u5668\u91C7\u96C6\uFF08\u9ED8\u8BA4 true\uFF09",
1738
+ browser: "Playwright \u6D4F\u89C8\u5668\u901A\u9053\uFF08\u9ED8\u8BA4 chrome\uFF09",
1739
+ timeout: "\u81EA\u52A8\u91C7\u96C6\u8D85\u65F6\u79D2\u6570\uFF08\u9ED8\u8BA4 300\uFF09",
1740
+ open: "\u662F\u5426\u81EA\u52A8\u6253\u5F00\u767B\u5F55\u9875\uFF08\u9ED8\u8BA4 true\uFF09"
1741
+ },
1742
+ examples: [
1743
+ "casement auth login",
1744
+ "casement auth login --mode manual",
1745
+ "casement auth login --token <TOKEN> --cookie 'a=b; c=d'",
1746
+ "casement auth status",
1747
+ "casement auth logout"
1748
+ ]
1749
+ }
1750
+ };
1751
+ function showHelp(target, options) {
1752
+ const order = [
1753
+ "auth",
1754
+ "projects",
1755
+ "project-detail",
1756
+ "versions",
1757
+ "version-detail",
1758
+ "project-members",
1759
+ "tasks",
1760
+ "subtasks",
1761
+ "create-subtask",
1762
+ "update-subtask",
1763
+ "update-status"
1764
+ ];
1765
+ if (target) {
1766
+ const cmd = HELP_COMMANDS[target];
1767
+ if (!cmd) {
1768
+ printResult({ ok: false, error: `Unknown command: ${target}`, availableCommands: order }, options);
1769
+ return;
1770
+ }
1771
+ printResult({
1772
+ project: "casement-lite",
1773
+ command: target,
1774
+ detail: cmd,
1775
+ agentContract: buildAgentCommandContract(target, cmd),
1776
+ usageGuide: [
1777
+ "\u63A8\u8350\u4F18\u5148\u7528\u4F4D\u7F6E\u53C2\u6570\uFF1B\u590D\u6742\u66F4\u65B0\u5EFA\u8BAE\u4F20 JSON\u3002",
1778
+ "\u5148\u6267\u884C `casement auth login` \u5EFA\u7ACB\u4F1A\u8BDD\u3002",
1779
+ "`auth login` \u9ED8\u8BA4\u81EA\u52A8\u62C9\u8D77\u53D7\u63A7\u6D4F\u89C8\u5668\u5E76\u91C7\u96C6 token/cookie\uFF1B\u53EF\u7528 `--mode manual` \u6539\u4E3A\u624B\u52A8\u8F93\u5165\u3002"
1780
+ ]
1781
+ }, options);
1782
+ return;
1783
+ }
1784
+ printResult({
1785
+ project: "casement-lite",
1786
+ description: "Casement \u8F7B\u91CF CLI\uFF08\u76F4\u63A5\u901A\u8FC7\u63A5\u53E3\u8C03\u7528\uFF09",
1787
+ helpFormat: "agent-friendly-json-v1",
1788
+ sessionFile: SESSION_FILE,
1789
+ availableCommands: order,
1790
+ commands: order.map((k) => ({
1791
+ name: k,
1792
+ title: HELP_COMMANDS[k].title,
1793
+ purpose: HELP_COMMANDS[k].purpose,
1794
+ readOnly: HELP_COMMANDS[k].readOnly,
1795
+ positionalUsage: HELP_COMMANDS[k].positionalUsage,
1796
+ positionalSpec: tokenizePositionalUsage(HELP_COMMANDS[k].positionalUsage, HELP_COMMANDS[k].args),
1797
+ examples: HELP_COMMANDS[k].examples
1798
+ })),
1799
+ agentGuide: {
1800
+ recommendedFlow: [
1801
+ "1) \u5148\u6267\u884C `casement auth login` \u5EFA\u7ACB\u4F1A\u8BDD",
1802
+ "2) \u5148\u67E5\u8BE2 projects \u62FF\u5230 projectId",
1803
+ "3) \u518D\u67E5\u8BE2 versions/version-detail",
1804
+ "4) \u9700\u8981\u4FEE\u6539\u65F6\u518D\u6267\u884C create-subtask/update-*"
1805
+ ],
1806
+ readOnlyCommands: order.filter((k) => HELP_COMMANDS[k].readOnly),
1807
+ writeCommands: order.filter((k) => !HELP_COMMANDS[k].readOnly),
1808
+ commandContracts: order.reduce((acc, k) => {
1809
+ acc[k] = buildAgentCommandContract(k, HELP_COMMANDS[k]);
1810
+ return acc;
1811
+ }, {})
1812
+ },
1813
+ quickStart: [
1814
+ "casement auth login",
1815
+ "casement projects credit 10",
1816
+ "casement versions 6 1 100 undone",
1817
+ "casement version-detail 9099"
1818
+ ]
1819
+ }, options);
1820
+ }
1821
+ async function runCommand(command, args, options) {
1822
+ const cmd = command ? command.replace(/^casement\//, "") : command;
1823
+ const rest = args;
1824
+ if (!cmd || cmd === "help" || cmd === "-h" || cmd === "--help") {
1825
+ showHelp(rest[0] || null, options);
1826
+ return;
1827
+ }
1828
+ if (cmd === "auth") {
1829
+ const sub = rest[0] || "";
1830
+ const subArgs = rest.slice(1);
1831
+ if (!sub || sub === "help") {
1832
+ showHelp("auth", options);
1833
+ return;
1834
+ }
1835
+ if (sub === "login") return authLogin(subArgs, options);
1836
+ if (sub === "status") return authStatus(options);
1837
+ if (sub === "logout") return authLogout(options);
1838
+ printResult({ ok: false, error: `Unknown auth subcommand: ${sub}`, available: ["login", "status", "logout"] }, options);
1839
+ process.exit(1);
1840
+ }
1841
+ if (cmd === "projects") return cmdProjects(rest, options);
1842
+ if (cmd === "project-detail") return cmdProjectDetail(rest, options);
1843
+ if (cmd === "project-members") return cmdProjectMembers(rest, options);
1844
+ if (cmd === "versions") return cmdVersions(rest, options);
1845
+ if (cmd === "version-detail") return cmdVersionDetail(rest, options);
1846
+ if (cmd === "tasks") return cmdTasks(rest, options);
1847
+ if (cmd === "subtasks") return cmdSubtasks(rest, options);
1848
+ if (cmd === "create-subtask") return cmdCreateSubtask(rest, options);
1849
+ if (cmd === "update-status") return cmdUpdateStatus(rest, options);
1850
+ if (cmd === "update-subtask") return cmdUpdateSubtask(rest, options);
1851
+ printResult({ ok: false, error: `Unknown command: ${cmd}`, hint: "Use: casement help" }, options);
1852
+ process.exit(1);
1853
+ }
1854
+ async function main() {
1855
+ const parsed = parseGlobalArgv(process.argv);
1856
+ await runCommand(parsed.command, parsed.args, parsed.options);
1857
+ }
1858
+ main().catch((err) => {
1859
+ const msg = err instanceof Error ? err.message : String(err);
1860
+ console.error(JSON.stringify({ ok: false, error: msg }, null, 2));
1861
+ process.exit(1);
1862
+ });
1863
+ //# sourceMappingURL=index.js.map