@pmate/cli 0.20.0 → 0.20.2

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.d.ts CHANGED
@@ -121,12 +121,31 @@ type SummaryCommandArgs = {
121
121
  };
122
122
  type QuizCommandArgs = {
123
123
  type: Command.Quiz;
124
- action: "publish";
124
+ action: "create-draft";
125
+ paperId: string;
126
+ apiBaseUrl?: string;
127
+ linkBaseUrl?: string;
128
+ } | {
129
+ type: Command.Quiz;
130
+ action: "update-meta";
131
+ paperId: string;
125
132
  filePath: string;
133
+ apiBaseUrl?: string;
134
+ linkBaseUrl?: string;
135
+ } | {
136
+ type: Command.Quiz;
137
+ action: "append-step";
126
138
  paperId: string;
139
+ filePath: string;
140
+ apiBaseUrl?: string;
141
+ linkBaseUrl?: string;
142
+ } | {
143
+ type: Command.Quiz;
144
+ action: "reorder";
145
+ paperId: string;
146
+ stepIds: string[];
127
147
  apiBaseUrl?: string;
128
148
  linkBaseUrl?: string;
129
- activate: boolean;
130
149
  } | {
131
150
  type: Command.Quiz;
132
151
  action: "activate";
package/dist/index.js CHANGED
@@ -96,7 +96,7 @@ function assertValidAppRef(appRef) {
96
96
  function saveLoginSession(session, requestedApp = DEFAULT_APP_ID) {
97
97
  const store = loadExistingSessionStoreOrEmpty();
98
98
  const app = assertValidAppRef(requestedApp);
99
- const sessionApp = session.session?.app?.trim();
99
+ const sessionApp = session.session?.identity?.app?.trim();
100
100
  if (!sessionApp) {
101
101
  throw new Error("Missing app in login session payload.");
102
102
  }
@@ -13202,7 +13202,8 @@ var LoginCommandHandler = class {
13202
13202
  );
13203
13203
  }
13204
13204
  const resp = await response.json();
13205
- if (!resp?.data?.session?.app || resp.data.session.app !== appId) {
13205
+ const returnedApp = resp?.data?.session?.identity?.app?.trim();
13206
+ if (!returnedApp || returnedApp !== appId) {
13206
13207
  throw new Error("Invalid exchange response: app-scoped session mismatch.");
13207
13208
  }
13208
13209
  return resp.data;
@@ -13214,9 +13215,16 @@ var LoginCommandHandler = class {
13214
13215
  resolveToken = resolve;
13215
13216
  rejectToken = reject;
13216
13217
  });
13218
+ const openSockets = /* @__PURE__ */ new Set();
13217
13219
  const server = (0, import_node_http.createServer)((request, response) => {
13218
13220
  this.handleCallbackRequest(request, response, resolveToken, rejectToken);
13219
13221
  });
13222
+ server.on("connection", (socket) => {
13223
+ openSockets.add(socket);
13224
+ socket.on("close", () => {
13225
+ openSockets.delete(socket);
13226
+ });
13227
+ });
13220
13228
  await new Promise((resolve, reject) => {
13221
13229
  server.once("error", reject);
13222
13230
  server.listen(0, "127.0.0.1", () => resolve());
@@ -13236,9 +13244,15 @@ var LoginCommandHandler = class {
13236
13244
  }, LOGIN_TIMEOUT_MS);
13237
13245
  const close = async () => {
13238
13246
  clearTimeout(timeout);
13239
- await new Promise((resolve) => {
13247
+ const closePromise = new Promise((resolve) => {
13240
13248
  server.close(() => resolve());
13241
13249
  });
13250
+ server.closeIdleConnections?.();
13251
+ server.closeAllConnections?.();
13252
+ for (const socket of openSockets) {
13253
+ socket.destroy();
13254
+ }
13255
+ await closePromise;
13242
13256
  };
13243
13257
  return {
13244
13258
  redirectUrl,
@@ -13257,38 +13271,122 @@ var LoginCommandHandler = class {
13257
13271
  if (!token) {
13258
13272
  response.statusCode = 400;
13259
13273
  response.setHeader("Content-Type", "text/html; charset=utf-8");
13260
- response.end(this.renderHtml("Login failed", "Missing sessionId in redirect URL."));
13274
+ response.end(
13275
+ this.renderHtml({
13276
+ state: "error",
13277
+ title: "Login failed",
13278
+ message: "Missing sessionId in redirect URL.",
13279
+ hint: "Please retry from your terminal with `pmate login`."
13280
+ })
13281
+ );
13261
13282
  rejectToken(new Error("Login callback did not include sessionId."));
13262
13283
  return;
13263
13284
  }
13264
13285
  response.statusCode = 200;
13265
13286
  response.setHeader("Content-Type", "text/html; charset=utf-8");
13287
+ response.setHeader("Connection", "close");
13266
13288
  response.end(
13267
13289
  this.renderHtml(
13268
- "Login complete",
13269
- "You can close this tab and return to the terminal."
13270
- )
13290
+ {
13291
+ state: "success",
13292
+ title: "Login complete",
13293
+ message: "You can close this tab and return to the terminal.",
13294
+ hint: "This window is only used to hand the session back to PMate CLI."
13295
+ }
13296
+ ),
13297
+ () => resolveToken(token)
13271
13298
  );
13272
- resolveToken(token);
13273
13299
  }
13274
- renderHtml(title, message) {
13300
+ renderHtml(input) {
13301
+ const badgeBg = input.state === "success" ? "#ecfdf3" : "#fff1f2";
13302
+ const badgeText = input.state === "success" ? "#027a48" : "#be123c";
13303
+ const dotColor = input.state === "success" ? "#10b981" : "#f43f5e";
13304
+ const stateText = input.state === "success" ? "Ready" : "Action required";
13305
+ const hint = input.hint?.trim();
13275
13306
  return `<!doctype html>
13276
13307
  <html lang="en">
13277
13308
  <head>
13278
13309
  <meta charset="utf-8" />
13279
13310
  <meta name="viewport" content="width=device-width, initial-scale=1" />
13280
- <title>${title}</title>
13311
+ <title>${input.title}</title>
13281
13312
  <style>
13282
- body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; padding: 32px; color: #111; }
13283
- main { max-width: 560px; margin: 10vh auto; }
13284
- h1 { font-size: 28px; margin-bottom: 12px; }
13285
- p { font-size: 16px; line-height: 1.5; }
13313
+ :root {
13314
+ color-scheme: light;
13315
+ }
13316
+ * {
13317
+ box-sizing: border-box;
13318
+ }
13319
+ body {
13320
+ margin: 0;
13321
+ min-height: 100vh;
13322
+ font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
13323
+ color: #0f172a;
13324
+ background:
13325
+ radial-gradient(circle at 18% 18%, #e0f2fe 0, transparent 36%),
13326
+ radial-gradient(circle at 82% 14%, #e0e7ff 0, transparent 34%),
13327
+ linear-gradient(180deg, #f8fafc 0%, #eef2ff 100%);
13328
+ display: grid;
13329
+ place-items: center;
13330
+ padding: 24px;
13331
+ }
13332
+ main {
13333
+ width: min(560px, 100%);
13334
+ border-radius: 28px;
13335
+ border: 1px solid rgba(15, 23, 42, 0.08);
13336
+ background: rgba(255, 255, 255, 0.9);
13337
+ box-shadow:
13338
+ 0 22px 56px rgba(15, 23, 42, 0.14),
13339
+ inset 0 1px 0 rgba(255, 255, 255, 0.75);
13340
+ backdrop-filter: blur(8px);
13341
+ padding: 28px;
13342
+ }
13343
+ .badge {
13344
+ display: inline-flex;
13345
+ align-items: center;
13346
+ gap: 8px;
13347
+ border-radius: 999px;
13348
+ padding: 8px 12px;
13349
+ font-size: 14px;
13350
+ font-weight: 600;
13351
+ background: ${badgeBg};
13352
+ color: ${badgeText};
13353
+ }
13354
+ .dot {
13355
+ width: 10px;
13356
+ height: 10px;
13357
+ border-radius: 999px;
13358
+ background: ${dotColor};
13359
+ }
13360
+ h1 {
13361
+ margin: 16px 0 8px;
13362
+ font-size: 32px;
13363
+ line-height: 1.18;
13364
+ letter-spacing: -0.03em;
13365
+ }
13366
+ p {
13367
+ margin: 0;
13368
+ font-size: 16px;
13369
+ line-height: 1.6;
13370
+ color: #334155;
13371
+ }
13372
+ .hint {
13373
+ margin-top: 16px;
13374
+ border-radius: 14px;
13375
+ background: rgba(148, 163, 184, 0.14);
13376
+ padding: 12px 14px;
13377
+ font-size: 14px;
13378
+ }
13286
13379
  </style>
13287
13380
  </head>
13288
13381
  <body>
13289
13382
  <main>
13290
- <h1>${title}</h1>
13291
- <p>${message}</p>
13383
+ <div class="badge">
13384
+ <span class="dot"></span>
13385
+ <span>${stateText}</span>
13386
+ </div>
13387
+ <h1>${input.title}</h1>
13388
+ <p>${input.message}</p>
13389
+ ${hint ? `<p class="hint">${hint}</p>` : ""}
13292
13390
  </main>
13293
13391
  </body>
13294
13392
  </html>`;
@@ -17951,22 +18049,22 @@ init_sessionStore();
17951
18049
  var INTRO_STEP_COUNT = 2;
17952
18050
  var QUIZ_APP_ID = "quiz";
17953
18051
  var DEFAULT_MIN_LENGTHS = Object.freeze({
17954
- knowledgeSummary: 36,
18052
+ teachingArticle: 36,
17955
18053
  explanation: 60
17956
18054
  });
17957
18055
  var INTRO_MIN_LENGTHS = Object.freeze({
17958
- knowledgeSummary: 90,
17959
- knowledgeNote: 36,
18056
+ teachingArticle: 90,
18057
+ teachingTip: 36,
17960
18058
  explanation: 90
17961
18059
  });
17962
18060
  var FIELD_RULES = Object.freeze({
17963
- knowledgeSummary: Object.freeze({
17964
- label: "knowledgeSummary\uFF08\u77E5\u8BC6\u8BB2\u89E3\uFF09",
18061
+ teachingArticle: Object.freeze({
18062
+ label: "teachingArticle\uFF08\u6559\u5B66\u77ED\u6587\uFF09",
17965
18063
  defaultHint: "\u77E5\u8BC6\u8BB2\u89E3\u81F3\u5C11\u8981\u5148\u628A\u9898\u76EE\u524D\u63D0\u8BF4\u6E05\u695A\uFF0C\u518D\u8BA9\u7528\u6237\u53BB\u9009\u7B54\u6848\u3002",
17966
18064
  introHint: "\u5F00\u573A\u9898\u81F3\u5C11\u8981\u8BB2\u6E05\u5B83\u662F\u4EC0\u4E48\u3001\u89E3\u51B3\u4EC0\u4E48\u95EE\u9898\uFF0C\u4EE5\u53CA\u8BFB\u8005\u63A5\u4E0B\u6765\u8BE5\u5E26\u7740\u4EC0\u4E48\u7406\u89E3\u8DEF\u5F84\u7EE7\u7EED\u5F80\u4E0B\u770B\u3002"
17967
18065
  }),
17968
- knowledgeNote: Object.freeze({
17969
- label: "knowledgeNote\uFF08\u8865\u5145\u8BF4\u660E\uFF09",
18066
+ teachingTip: Object.freeze({
18067
+ label: "teachingTip\uFF08\u8865\u5145\u8BF4\u660E\uFF09",
17970
18068
  introHint: "\u5F00\u573A\u9898\u8981\u8865\u5145\u4E3A\u4EC0\u4E48\u5BB9\u6613\u8BEF\u89E3\uFF0C\u6216\u8005\u7B2C\u4E00\u6B21\u8BFB\u8FD9\u5757\u5185\u5BB9\u65F6\u5E94\u8BE5\u5148\u770B\u54EA\u91CC\u3002"
17971
18069
  }),
17972
18070
  explanation: Object.freeze({
@@ -18006,9 +18104,9 @@ function validateQuizPaperPayload(payload) {
18006
18104
  assertMinLength(
18007
18105
  step,
18008
18106
  index,
18009
- "knowledgeSummary",
18010
- DEFAULT_MIN_LENGTHS.knowledgeSummary,
18011
- FIELD_RULES.knowledgeSummary.defaultHint
18107
+ "teachingArticle",
18108
+ DEFAULT_MIN_LENGTHS.teachingArticle,
18109
+ FIELD_RULES.teachingArticle.defaultHint
18012
18110
  );
18013
18111
  assertMinLength(
18014
18112
  step,
@@ -18023,16 +18121,16 @@ function validateQuizPaperPayload(payload) {
18023
18121
  assertMinLength(
18024
18122
  step,
18025
18123
  index,
18026
- "knowledgeSummary",
18027
- INTRO_MIN_LENGTHS.knowledgeSummary,
18028
- FIELD_RULES.knowledgeSummary.introHint
18124
+ "teachingArticle",
18125
+ INTRO_MIN_LENGTHS.teachingArticle,
18126
+ FIELD_RULES.teachingArticle.introHint
18029
18127
  );
18030
18128
  assertMinLength(
18031
18129
  step,
18032
18130
  index,
18033
- "knowledgeNote",
18034
- INTRO_MIN_LENGTHS.knowledgeNote,
18035
- FIELD_RULES.knowledgeNote.introHint
18131
+ "teachingTip",
18132
+ INTRO_MIN_LENGTHS.teachingTip,
18133
+ FIELD_RULES.teachingTip.introHint
18036
18134
  );
18037
18135
  assertMinLength(
18038
18136
  step,
@@ -18180,6 +18278,131 @@ async function activateQuizPaper({
18180
18278
  data: activated
18181
18279
  };
18182
18280
  }
18281
+ async function createQuizDraft({
18282
+ fetchImpl = fetch,
18283
+ apiBaseUrl,
18284
+ linkBaseUrl,
18285
+ paperId,
18286
+ token
18287
+ }) {
18288
+ const trimmedApiBaseUrl = apiBaseUrl.replace(/\/+$/, "");
18289
+ const trimmedLinkBaseUrl = linkBaseUrl.replace(/\/+$/, "");
18290
+ const previewLink = `${trimmedLinkBaseUrl}/?paper=${encodeURIComponent(paperId)}`;
18291
+ const data = await requestJson(
18292
+ fetchImpl,
18293
+ `${trimmedApiBaseUrl}/api/quiz/papers`,
18294
+ {
18295
+ method: "POST",
18296
+ headers: {
18297
+ Authorization: `Bearer ${token}`,
18298
+ "Content-Type": "application/json"
18299
+ },
18300
+ body: JSON.stringify({ paperId })
18301
+ },
18302
+ "\u521B\u5EFA\u8349\u7A3F"
18303
+ );
18304
+ return {
18305
+ apiBaseUrl: trimmedApiBaseUrl,
18306
+ paperId,
18307
+ previewLink,
18308
+ data
18309
+ };
18310
+ }
18311
+ async function updateQuizPaperMetadata({
18312
+ fetchImpl = fetch,
18313
+ apiBaseUrl,
18314
+ linkBaseUrl,
18315
+ paperId,
18316
+ metadata,
18317
+ token
18318
+ }) {
18319
+ const trimmedApiBaseUrl = apiBaseUrl.replace(/\/+$/, "");
18320
+ const trimmedLinkBaseUrl = linkBaseUrl.replace(/\/+$/, "");
18321
+ const previewLink = `${trimmedLinkBaseUrl}/?paper=${encodeURIComponent(paperId)}`;
18322
+ const data = await requestJson(
18323
+ fetchImpl,
18324
+ `${trimmedApiBaseUrl}/api/quiz/papers/${encodeURIComponent(paperId)}`,
18325
+ {
18326
+ method: "PATCH",
18327
+ headers: {
18328
+ Authorization: `Bearer ${token}`,
18329
+ "Content-Type": "application/json"
18330
+ },
18331
+ body: JSON.stringify(metadata)
18332
+ },
18333
+ "\u66F4\u65B0\u8BD5\u5377\u5143\u4FE1\u606F"
18334
+ );
18335
+ return {
18336
+ apiBaseUrl: trimmedApiBaseUrl,
18337
+ paperId,
18338
+ previewLink,
18339
+ data
18340
+ };
18341
+ }
18342
+ async function appendQuizPaperStep({
18343
+ fetchImpl = fetch,
18344
+ apiBaseUrl,
18345
+ linkBaseUrl,
18346
+ paperId,
18347
+ step,
18348
+ token
18349
+ }) {
18350
+ const trimmedApiBaseUrl = apiBaseUrl.replace(/\/+$/, "");
18351
+ const trimmedLinkBaseUrl = linkBaseUrl.replace(/\/+$/, "");
18352
+ const previewLink = `${trimmedLinkBaseUrl}/?paper=${encodeURIComponent(paperId)}`;
18353
+ const data = await requestJson(
18354
+ fetchImpl,
18355
+ `${trimmedApiBaseUrl}/api/quiz/papers/${encodeURIComponent(paperId)}/steps`,
18356
+ {
18357
+ method: "POST",
18358
+ headers: {
18359
+ Authorization: `Bearer ${token}`,
18360
+ "Content-Type": "application/json"
18361
+ },
18362
+ body: JSON.stringify(step)
18363
+ },
18364
+ `\u8FFD\u52A0\u9898\u76EE\uFF08${String(step?.stepId ?? "unknown-step")}\uFF09`
18365
+ );
18366
+ return {
18367
+ apiBaseUrl: trimmedApiBaseUrl,
18368
+ paperId,
18369
+ previewLink,
18370
+ data
18371
+ };
18372
+ }
18373
+ async function reorderQuizPaperSteps({
18374
+ fetchImpl = fetch,
18375
+ apiBaseUrl,
18376
+ linkBaseUrl,
18377
+ paperId,
18378
+ stepIds,
18379
+ token
18380
+ }) {
18381
+ const trimmedApiBaseUrl = apiBaseUrl.replace(/\/+$/, "");
18382
+ const trimmedLinkBaseUrl = linkBaseUrl.replace(/\/+$/, "");
18383
+ const previewLink = `${trimmedLinkBaseUrl}/?paper=${encodeURIComponent(paperId)}`;
18384
+ const data = await requestJson(
18385
+ fetchImpl,
18386
+ `${trimmedApiBaseUrl}/api/quiz/papers/${encodeURIComponent(paperId)}/steps/reorder`,
18387
+ {
18388
+ method: "POST",
18389
+ headers: {
18390
+ Authorization: `Bearer ${token}`,
18391
+ "Content-Type": "application/json"
18392
+ },
18393
+ body: JSON.stringify({
18394
+ stepIds
18395
+ })
18396
+ },
18397
+ "\u91CD\u6392\u9898\u76EE\u987A\u5E8F"
18398
+ );
18399
+ return {
18400
+ apiBaseUrl: trimmedApiBaseUrl,
18401
+ paperId,
18402
+ previewLink,
18403
+ data
18404
+ };
18405
+ }
18183
18406
  async function viewQuizPaper({
18184
18407
  fetchImpl = fetch,
18185
18408
  apiBaseUrl,
@@ -18242,26 +18465,76 @@ var QuizCommandHandler = class {
18242
18465
  const apiBaseUrl = args.apiBaseUrl || DEFAULT_QUIZ_API_BASE_URL;
18243
18466
  const linkBaseUrl = args.linkBaseUrl || DEFAULT_QUIZ_LINK_BASE_URL;
18244
18467
  if (args.action === "activate") {
18245
- const result2 = await activateQuizPaper({
18468
+ const result = await activateQuizPaper({
18469
+ apiBaseUrl,
18470
+ linkBaseUrl,
18471
+ paperId: args.paperId,
18472
+ token: session.token
18473
+ });
18474
+ ui.success("Quiz \u5DF2\u6FC0\u6D3B\u53D1\u5E03", result.link);
18475
+ return;
18476
+ }
18477
+ if (args.action === "create-draft") {
18478
+ const result = await createQuizDraft({
18479
+ apiBaseUrl,
18480
+ linkBaseUrl,
18481
+ paperId: args.paperId,
18482
+ token: session.token
18483
+ });
18484
+ ui.success("Quiz \u8349\u7A3F\u5DF2\u521B\u5EFA", result.previewLink);
18485
+ ui.info("\u72B6\u6001", "draft");
18486
+ return;
18487
+ }
18488
+ if (args.action === "update-meta") {
18489
+ const resolvedFilePath = import_node_path14.default.resolve(process.cwd(), args.filePath);
18490
+ const metadata = JSON.parse((0, import_node_fs9.readFileSync)(resolvedFilePath, "utf8"));
18491
+ const result = await updateQuizPaperMetadata({
18246
18492
  apiBaseUrl,
18247
18493
  linkBaseUrl,
18248
18494
  paperId: args.paperId,
18495
+ metadata,
18249
18496
  token: session.token
18250
18497
  });
18251
- ui.success("Quiz \u5DF2\u6FC0\u6D3B\u53D1\u5E03", result2.link);
18498
+ ui.success("Quiz \u5143\u4FE1\u606F\u5DF2\u66F4\u65B0", result.previewLink);
18499
+ return;
18500
+ }
18501
+ if (args.action === "append-step") {
18502
+ const resolvedFilePath = import_node_path14.default.resolve(process.cwd(), args.filePath);
18503
+ const step = JSON.parse((0, import_node_fs9.readFileSync)(resolvedFilePath, "utf8"));
18504
+ const result = await appendQuizPaperStep({
18505
+ apiBaseUrl,
18506
+ linkBaseUrl,
18507
+ paperId: args.paperId,
18508
+ step,
18509
+ token: session.token
18510
+ });
18511
+ ui.success("Quiz \u9898\u76EE\u5DF2\u8FFD\u52A0", result.previewLink);
18512
+ ui.info("stepId", String(step.stepId));
18513
+ return;
18514
+ }
18515
+ if (args.action === "reorder") {
18516
+ const result = await reorderQuizPaperSteps({
18517
+ apiBaseUrl,
18518
+ linkBaseUrl,
18519
+ paperId: args.paperId,
18520
+ stepIds: args.stepIds,
18521
+ token: session.token
18522
+ });
18523
+ ui.success("Quiz \u9898\u76EE\u987A\u5E8F\u5DF2\u66F4\u65B0", result.previewLink);
18524
+ ui.info("\u6570\u91CF", String(args.stepIds.length));
18252
18525
  return;
18253
18526
  }
18254
18527
  if (args.action === "view") {
18255
- const result2 = await viewQuizPaper({
18528
+ const result = await viewQuizPaper({
18256
18529
  apiBaseUrl,
18257
18530
  linkBaseUrl,
18258
18531
  paperId: args.paperId,
18259
18532
  token: session.token
18260
18533
  });
18261
- ui.success("Quiz \u8BD5\u5377\u5DF2\u627E\u5230", result2.previewLink);
18262
- ui.info("\u72B6\u6001", result2.paperStatus);
18263
- if (typeof result2.stepCount === "number") {
18264
- ui.info("\u9898\u76EE\u6570", String(result2.stepCount));
18534
+ ui.success("Quiz \u8BD5\u5377\u5DF2\u627E\u5230", result.previewLink);
18535
+ ui.info("\u72B6\u6001", result.paperStatus);
18536
+ if (typeof result.stepCount === "number") {
18537
+ ui.info("\u9898\u76EE\u6570", String(result.stepCount));
18265
18538
  }
18266
18539
  return;
18267
18540
  }
@@ -18276,13 +18549,13 @@ var QuizCommandHandler = class {
18276
18549
  if (targetIndex < 0) {
18277
18550
  throw new Error(`\u672A\u627E\u5230 stepId=${args.stepId}\uFF0C\u65E0\u6CD5\u66F4\u65B0\u8BE5\u9898\u76EE\u3002`);
18278
18551
  }
18279
- const resolvedFilePath2 = import_node_path14.default.resolve(process.cwd(), args.filePath);
18280
- const nextStep = JSON.parse((0, import_node_fs9.readFileSync)(resolvedFilePath2, "utf8"));
18552
+ const resolvedFilePath = import_node_path14.default.resolve(process.cwd(), args.filePath);
18553
+ const nextStep = JSON.parse((0, import_node_fs9.readFileSync)(resolvedFilePath, "utf8"));
18281
18554
  nextStep.stepId = args.stepId;
18282
18555
  const nextSteps = [...basePaper.steps];
18283
18556
  nextSteps[targetIndex] = nextStep;
18284
18557
  const nextPaperId = args.newPaperId || `${args.paperId}-rev-${this.timestampTag()}`;
18285
- const result2 = await publishQuizPaper({
18558
+ const result = await publishQuizPaper({
18286
18559
  apiBaseUrl,
18287
18560
  linkBaseUrl,
18288
18561
  paperId: nextPaperId,
@@ -18296,37 +18569,20 @@ var QuizCommandHandler = class {
18296
18569
  token: session.token,
18297
18570
  activate: args.activate
18298
18571
  });
18299
- if (result2.status === "draft") {
18300
- ui.success("Quiz \u5355\u9898\u4FEE\u8BA2\u7A3F\u5DF2\u521B\u5EFA", result2.previewLink);
18572
+ if (result.status === "draft") {
18573
+ ui.success("Quiz \u5355\u9898\u4FEE\u8BA2\u7A3F\u5DF2\u521B\u5EFA", result.previewLink);
18301
18574
  ui.info("\u6765\u6E90\u8BD5\u5377", args.paperId);
18302
18575
  ui.info("\u4FEE\u8BA2\u9898\u76EE", args.stepId);
18303
18576
  ui.info("\u65B0\u8BD5\u5377", nextPaperId);
18304
18577
  ui.info("\u4E0B\u4E00\u6B65", `pmate quiz activate --paper-id ${nextPaperId}`);
18305
18578
  return;
18306
18579
  }
18307
- ui.success("Quiz \u5355\u9898\u4FEE\u8BA2\u7A3F\u5DF2\u53D1\u5E03", result2.link);
18580
+ ui.success("Quiz \u5355\u9898\u4FEE\u8BA2\u7A3F\u5DF2\u53D1\u5E03", result.link);
18308
18581
  ui.info("\u6765\u6E90\u8BD5\u5377", args.paperId);
18309
18582
  ui.info("\u4FEE\u8BA2\u9898\u76EE", args.stepId);
18310
18583
  ui.info("\u65B0\u8BD5\u5377", nextPaperId);
18311
18584
  return;
18312
18585
  }
18313
- const resolvedFilePath = import_node_path14.default.resolve(process.cwd(), args.filePath);
18314
- const payload = JSON.parse((0, import_node_fs9.readFileSync)(resolvedFilePath, "utf8"));
18315
- const result = await publishQuizPaper({
18316
- apiBaseUrl,
18317
- linkBaseUrl,
18318
- paperId: args.paperId,
18319
- payload,
18320
- token: session.token,
18321
- activate: args.activate
18322
- });
18323
- if (result.status === "draft") {
18324
- ui.success("Quiz \u8349\u7A3F\u5DF2\u521B\u5EFA", result.previewLink);
18325
- ui.info("\u72B6\u6001", "draft");
18326
- ui.info("\u4E0B\u4E00\u6B65", `pmate quiz activate --paper-id ${args.paperId}`);
18327
- return;
18328
- }
18329
- ui.success("Quiz \u5DF2\u53D1\u5E03", result.link);
18330
18586
  }
18331
18587
  timestampTag() {
18332
18588
  const now = /* @__PURE__ */ new Date();
@@ -21977,7 +22233,10 @@ var HELP_TEXT = [
21977
22233
  ` ${import_chalk3.default.cyan("summary")} Summarize a GitHub PR with AI`,
21978
22234
  ` ${import_chalk3.default.gray(" --pr <id> [--cwd <dir>]")}`,
21979
22235
  ` ${import_chalk3.default.cyan("quiz")} Manage quiz papers on quiz-api`,
21980
- ` ${import_chalk3.default.gray(" publish --file <json> --paper-id <id> [--activate]")}`,
22236
+ ` ${import_chalk3.default.gray(" create-draft --paper-id <id>")}`,
22237
+ ` ${import_chalk3.default.gray(" update-meta --paper-id <id> --file <metadata.json>")}`,
22238
+ ` ${import_chalk3.default.gray(" append-step --paper-id <id> --file <step.json>")}`,
22239
+ ` ${import_chalk3.default.gray(" reorder --paper-id <id> --step-ids <id1,id2,...>")}`,
21981
22240
  ` ${import_chalk3.default.gray(" activate --paper-id <id>")}`,
21982
22241
  ` ${import_chalk3.default.gray(" view --paper-id <id>")}`,
21983
22242
  ` ${import_chalk3.default.gray(" edit-step --paper-id <id> --step-id <step-id> --file <step.json> [--new-paper-id <id>] [--activate]")}`,
@@ -22577,50 +22836,158 @@ function parseQuizArgs(tokens) {
22577
22836
  const rest = [...tokens];
22578
22837
  const action = rest.shift();
22579
22838
  if (!action) {
22580
- throw new Error("Missing quiz action. Usage: quiz publish|activate|view");
22839
+ throw new Error("Missing quiz action. Usage: quiz create-draft|update-meta|append-step|reorder|activate|view|edit-step");
22581
22840
  }
22582
- if (action === "publish") {
22583
- let filePath;
22841
+ if (action === "create-draft") {
22584
22842
  let paperId;
22585
22843
  let apiBaseUrl;
22586
22844
  let linkBaseUrl;
22587
- let activate = false;
22588
22845
  while (rest.length > 0) {
22589
22846
  const token = rest.shift();
22590
22847
  switch (token) {
22848
+ case "--paper-id":
22849
+ paperId = requireValue8(rest, token);
22850
+ break;
22851
+ case "--base-url":
22852
+ apiBaseUrl = requireValue8(rest, token);
22853
+ break;
22854
+ case "--link-base":
22855
+ linkBaseUrl = requireValue8(rest, token);
22856
+ break;
22857
+ default:
22858
+ throw new Error(`Unknown quiz create-draft option: ${token}`);
22859
+ }
22860
+ }
22861
+ if (!paperId) {
22862
+ throw new Error("Missing --paper-id. Usage: quiz create-draft --paper-id <id>.");
22863
+ }
22864
+ return {
22865
+ type: "quiz" /* Quiz */,
22866
+ action: "create-draft",
22867
+ paperId,
22868
+ apiBaseUrl,
22869
+ linkBaseUrl
22870
+ };
22871
+ }
22872
+ if (action === "update-meta") {
22873
+ let paperId;
22874
+ let filePath;
22875
+ let apiBaseUrl;
22876
+ let linkBaseUrl;
22877
+ while (rest.length > 0) {
22878
+ const token = rest.shift();
22879
+ switch (token) {
22880
+ case "--paper-id":
22881
+ paperId = requireValue8(rest, token);
22882
+ break;
22591
22883
  case "--file":
22592
22884
  filePath = requireValue8(rest, token);
22593
22885
  break;
22886
+ case "--base-url":
22887
+ apiBaseUrl = requireValue8(rest, token);
22888
+ break;
22889
+ case "--link-base":
22890
+ linkBaseUrl = requireValue8(rest, token);
22891
+ break;
22892
+ default:
22893
+ throw new Error(`Unknown quiz update-meta option: ${token}`);
22894
+ }
22895
+ }
22896
+ if (!paperId) {
22897
+ throw new Error("Missing --paper-id. Usage: quiz update-meta --paper-id <id> --file <metadata.json>.");
22898
+ }
22899
+ if (!filePath) {
22900
+ throw new Error("Missing --file. Usage: quiz update-meta --paper-id <id> --file <metadata.json>.");
22901
+ }
22902
+ return {
22903
+ type: "quiz" /* Quiz */,
22904
+ action: "update-meta",
22905
+ paperId,
22906
+ filePath,
22907
+ apiBaseUrl,
22908
+ linkBaseUrl
22909
+ };
22910
+ }
22911
+ if (action === "append-step") {
22912
+ let paperId;
22913
+ let filePath;
22914
+ let apiBaseUrl;
22915
+ let linkBaseUrl;
22916
+ while (rest.length > 0) {
22917
+ const token = rest.shift();
22918
+ switch (token) {
22594
22919
  case "--paper-id":
22595
22920
  paperId = requireValue8(rest, token);
22596
22921
  break;
22922
+ case "--file":
22923
+ filePath = requireValue8(rest, token);
22924
+ break;
22597
22925
  case "--base-url":
22598
22926
  apiBaseUrl = requireValue8(rest, token);
22599
22927
  break;
22600
22928
  case "--link-base":
22601
22929
  linkBaseUrl = requireValue8(rest, token);
22602
22930
  break;
22603
- case "--activate":
22604
- activate = true;
22605
- break;
22606
22931
  default:
22607
- throw new Error(`Unknown quiz publish option: ${token}`);
22932
+ throw new Error(`Unknown quiz append-step option: ${token}`);
22608
22933
  }
22609
22934
  }
22935
+ if (!paperId) {
22936
+ throw new Error("Missing --paper-id. Usage: quiz append-step --paper-id <id> --file <step.json>.");
22937
+ }
22610
22938
  if (!filePath) {
22611
- throw new Error("Missing --file. Usage: quiz publish --file <json> --paper-id <id>.");
22939
+ throw new Error("Missing --file. Usage: quiz append-step --paper-id <id> --file <step.json>.");
22940
+ }
22941
+ return {
22942
+ type: "quiz" /* Quiz */,
22943
+ action: "append-step",
22944
+ paperId,
22945
+ filePath,
22946
+ apiBaseUrl,
22947
+ linkBaseUrl
22948
+ };
22949
+ }
22950
+ if (action === "reorder") {
22951
+ let paperId;
22952
+ let stepIdsRaw;
22953
+ let apiBaseUrl;
22954
+ let linkBaseUrl;
22955
+ while (rest.length > 0) {
22956
+ const token = rest.shift();
22957
+ switch (token) {
22958
+ case "--paper-id":
22959
+ paperId = requireValue8(rest, token);
22960
+ break;
22961
+ case "--step-ids":
22962
+ stepIdsRaw = requireValue8(rest, token);
22963
+ break;
22964
+ case "--base-url":
22965
+ apiBaseUrl = requireValue8(rest, token);
22966
+ break;
22967
+ case "--link-base":
22968
+ linkBaseUrl = requireValue8(rest, token);
22969
+ break;
22970
+ default:
22971
+ throw new Error(`Unknown quiz reorder option: ${token}`);
22972
+ }
22612
22973
  }
22613
22974
  if (!paperId) {
22614
- throw new Error("Missing --paper-id. Usage: quiz publish --file <json> --paper-id <id>.");
22975
+ throw new Error("Missing --paper-id. Usage: quiz reorder --paper-id <id> --step-ids <id1,id2,...>.");
22976
+ }
22977
+ if (!stepIdsRaw) {
22978
+ throw new Error("Missing --step-ids. Usage: quiz reorder --paper-id <id> --step-ids <id1,id2,...>.");
22979
+ }
22980
+ const stepIds = stepIdsRaw.split(",").map((item) => item.trim()).filter(Boolean);
22981
+ if (stepIds.length === 0) {
22982
+ throw new Error("Invalid --step-ids. At least one step id is required.");
22615
22983
  }
22616
22984
  return {
22617
22985
  type: "quiz" /* Quiz */,
22618
- action: "publish",
22619
- filePath,
22986
+ action: "reorder",
22620
22987
  paperId,
22988
+ stepIds,
22621
22989
  apiBaseUrl,
22622
- linkBaseUrl,
22623
- activate
22990
+ linkBaseUrl
22624
22991
  };
22625
22992
  }
22626
22993
  if (action === "activate") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmate/cli",
3
- "version": "0.20.0",
3
+ "version": "0.20.2",
4
4
  "description": "CLI utilities for the pMate project",
5
5
  "license": "MIT",
6
6
  "bin": {