@deployxai/dxc 0.2.6 → 0.3.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.
Files changed (28) hide show
  1. package/README.md +13 -4
  2. package/dist/chunks/{chunk-7G7C3AZW.js → chunk-6JNQZHAQ.js} +209 -56
  3. package/dist/chunks/{knowledge-LSRBVSCZ.js → knowledge-JCYMCMSP.js} +140 -65
  4. package/dist/index.js +1904 -965
  5. package/package.json +1 -1
  6. package/skills/dxc-article-outline/SKILL.md +8 -19
  7. package/skills/dxc-article-outline/references/outline-methods.md +17 -48
  8. package/skills/dxc-article-write/SKILL.md +8 -34
  9. package/skills/dxc-article-write/references/writing-methods.md +26 -46
  10. package/skills/dxc-content-brief/SKILL.md +9 -25
  11. package/skills/dxc-content-brief/references/brief-method.md +15 -43
  12. package/skills/dxc-content-review/SKILL.md +8 -29
  13. package/skills/dxc-content-review/references/review-checklist.md +15 -38
  14. package/skills/dxc-content-workflow/SKILL.md +20 -109
  15. package/skills/dxc-content-workflow/references/onboarding-questions.md +4 -174
  16. package/skills/dxc-content-workflow/references/stages.md +6 -79
  17. package/skills/dxc-knowledge/SKILL.md +11 -25
  18. package/skills/dxc-memory/SKILL.md +10 -25
  19. package/skills/dxc-profile/SKILL.md +11 -35
  20. package/skills/dxc-project-overview/SKILL.md +7 -18
  21. package/skills/dxc-quote-curator/SKILL.md +10 -28
  22. package/skills/dxc-research/SKILL.md +9 -33
  23. package/skills/dxc-research/references/research-method.md +20 -66
  24. package/skills/dxc-title-write/SKILL.md +9 -26
  25. package/skills/dxc-title-write/references/title-methods.md +15 -36
  26. package/skills/dxc-visual-plan/SKILL.md +18 -36
  27. package/skills/dxc-visual-plan/references/visual-methods.md +19 -45
  28. package/skills/dxc-wechat-publisher/SKILL.md +11 -29
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { createRequire as __dxcCreateRequire } from "node:module"; const require = __dxcCreateRequire(import.meta.url);
3
3
  import {
4
4
  CONTENT_ARTIFACT_KINDS,
5
- CONTENT_STAGE_INPUTS,
5
+ CONTENT_STAGE_DEFINITIONS,
6
6
  CONTENT_WORKFLOW_ID,
7
7
  LocalStateError,
8
8
  __commonJS,
@@ -15,15 +15,14 @@ import {
15
15
  articleBundleSchema,
16
16
  contentMemoryAddResultSchema,
17
17
  contentMemoryArchiveResultSchema,
18
+ contentMemoryCorrectResultSchema,
18
19
  contentMemoryInputSchema,
19
20
  contentMemoryListResultSchema,
20
21
  contentMemoryStoreSchema,
21
22
  contentProfileInputSchema,
22
- contentProfileSaveResultSchema,
23
23
  contentProfileSchema,
24
+ contentProfileSessionResultSchema,
24
25
  contentProfileStatusResultSchema,
25
- contentProfileUpdateInputSchema,
26
- contentProfileUpdateResultSchema,
27
26
  contentProjectIndexSchema,
28
27
  contentProjectManifestSchema,
29
28
  createDeviceBindingSessionResponseSchema,
@@ -53,7 +52,7 @@ import {
53
52
  wechatRenderResultSchema,
54
53
  writePrivateBuffer,
55
54
  writePrivateJson
56
- } from "./chunks/chunk-7G7C3AZW.js";
55
+ } from "./chunks/chunk-6JNQZHAQ.js";
57
56
 
58
57
  // node_modules/.pnpm/pino-std-serializers@7.1.0/node_modules/pino-std-serializers/lib/err-helpers.js
59
58
  var require_err_helpers = __commonJS({
@@ -1307,7 +1306,7 @@ var require_sonic_boom = __commonJS({
1307
1306
  if (!(this instanceof SonicBoom)) {
1308
1307
  return new SonicBoom(opts);
1309
1308
  }
1310
- let { fd, dest, minLength, maxLength, maxWrite, periodicFlush, sync, append: append2 = true, mkdir: mkdir5, retryEAGAIN, fsync, contentMode, mode } = opts || {};
1309
+ let { fd, dest, minLength, maxLength, maxWrite, periodicFlush, sync, append: append2 = true, mkdir: mkdir6, retryEAGAIN, fsync, contentMode, mode } = opts || {};
1311
1310
  fd = fd || dest;
1312
1311
  this._len = 0;
1313
1312
  this.fd = -1;
@@ -1332,7 +1331,7 @@ var require_sonic_boom = __commonJS({
1332
1331
  this.append = append2 || false;
1333
1332
  this.mode = mode;
1334
1333
  this.retryEAGAIN = retryEAGAIN || (() => true);
1335
- this.mkdir = mkdir5 || false;
1334
+ this.mkdir = mkdir6 || false;
1336
1335
  let fsWriteSync;
1337
1336
  let fsWrite;
1338
1337
  if (contentMode === kContentModeBuffer) {
@@ -23267,7 +23266,7 @@ import os3 from "node:os";
23267
23266
  // apps/cli/package.json
23268
23267
  var package_default = {
23269
23268
  name: "@dxc/cli",
23270
- version: "0.2.6",
23269
+ version: "0.3.1",
23271
23270
  private: true,
23272
23271
  type: "module",
23273
23272
  bin: {
@@ -26712,13 +26711,78 @@ async function runDoctor(runtime) {
26712
26711
 
26713
26712
  // apps/cli/src/profile.ts
26714
26713
  import { randomUUID } from "node:crypto";
26715
- import { lstat, readFile, rename, stat } from "node:fs/promises";
26714
+ import { unlink } from "node:fs/promises";
26716
26715
  import path2 from "node:path";
26717
26716
  var PROFILE_DISPLAY_PATH = "~/.dxc/content-profile.json";
26718
26717
  var MEMORY_DISPLAY_PATH = "~/.dxc/content-memory.json";
26719
26718
  var USER_MEMORY_DISPLAY_PATH = "~/.dxc/USER.md";
26720
26719
  var PROFILE_MAXIMUM_BYTES = 64 * 1024;
26721
26720
  var MEMORY_MAXIMUM_BYTES = 128 * 1024;
26721
+ var PROFILE_SESSION_MAXIMUM_BYTES = 32 * 1024;
26722
+ var PROFILE_QUESTIONS = [
26723
+ {
26724
+ choices: [
26725
+ { choiceId: "profile_creator.expert", label: "\u4E2A\u4EBA\u4E13\u5BB6\u6216\u987E\u95EE" },
26726
+ { choiceId: "profile_creator.creator", label: "\u804C\u4E1A\u5185\u5BB9\u521B\u4F5C\u8005" },
26727
+ { choiceId: "profile_creator.brand", label: "\u54C1\u724C\u6216\u4F01\u4E1A\u5185\u5BB9\u56E2\u961F" },
26728
+ { choiceId: "profile_creator.educator", label: "\u6559\u80B2\u6216\u57F9\u8BAD\u8005" }
26729
+ ],
26730
+ inputType: "single-choice",
26731
+ prompt: "\u8BF7\u9009\u62E9\u6700\u7B26\u5408\u4F60\u7684\u535A\u4E3B\u8EAB\u4EFD\u3002",
26732
+ questionId: "creator-role"
26733
+ },
26734
+ {
26735
+ choices: [],
26736
+ inputType: "text",
26737
+ prompt: "\u8BF7\u8F93\u5165\u5E0C\u671B\u8BFB\u8005\u770B\u5230\u7684\u540D\u79F0\u6216\u7B14\u540D\u3002",
26738
+ questionId: "display-name"
26739
+ },
26740
+ {
26741
+ choices: [
26742
+ { choiceId: "profile_domain.business", label: "\u5546\u4E1A\u4E0E\u521B\u4E1A" },
26743
+ { choiceId: "profile_domain.technology", label: "\u6280\u672F\u4E0E AI" },
26744
+ { choiceId: "profile_domain.career", label: "\u804C\u573A\u4E0E\u4E2A\u4EBA\u6210\u957F" },
26745
+ { choiceId: "profile_domain.lifestyle", label: "\u751F\u6D3B\u65B9\u5F0F" },
26746
+ { choiceId: "profile_domain.education", label: "\u6559\u80B2\u4E0E\u77E5\u8BC6\u4F20\u64AD" }
26747
+ ],
26748
+ inputType: "multi-choice",
26749
+ prompt: "\u8BF7\u9009\u62E9 1\u20135 \u4E2A\u4E3B\u8981\u5185\u5BB9\u9886\u57DF\u3002",
26750
+ questionId: "primary-domains"
26751
+ },
26752
+ {
26753
+ choices: [
26754
+ { choiceId: "profile_audience.beginner", label: "\u521A\u5F00\u59CB\u4E86\u89E3" },
26755
+ { choiceId: "profile_audience.problem-aware", label: "\u5DF2\u610F\u8BC6\u5230\u95EE\u9898" },
26756
+ { choiceId: "profile_audience.evaluating", label: "\u6B63\u5728\u6BD4\u8F83\u65B9\u6848" },
26757
+ { choiceId: "profile_audience.customer", label: "\u5DF2\u6709\u7ECF\u9A8C\u6216\u5BA2\u6237" }
26758
+ ],
26759
+ inputType: "single-choice",
26760
+ prompt: "\u8BF7\u9009\u62E9\u6838\u5FC3\u8BFB\u8005\u5F53\u524D\u6240\u5904\u9636\u6BB5\u3002",
26761
+ questionId: "target-audience"
26762
+ },
26763
+ {
26764
+ choices: [
26765
+ { choiceId: "profile_value.action", label: "\u7ED9\u4E00\u4E2A\u53EF\u6267\u884C\u7684\u65B9\u6CD5" },
26766
+ { choiceId: "profile_value.explain", label: "\u89E3\u91CA\u590D\u6742\u95EE\u9898" },
26767
+ { choiceId: "profile_value.case", label: "\u63D0\u4F9B\u771F\u5B9E\u6848\u4F8B" },
26768
+ { choiceId: "profile_value.decision", label: "\u5E2E\u52A9\u505A\u51B3\u7B56" },
26769
+ { choiceId: "profile_value.trust", label: "\u5EFA\u7ACB\u4E13\u4E1A\u4FE1\u4EFB" }
26770
+ ],
26771
+ inputType: "single-choice",
26772
+ prompt: "\u8BF7\u9009\u62E9\u5185\u5BB9\u6700\u6838\u5FC3\u7684\u8BFB\u8005\u627F\u8BFA\u3002",
26773
+ questionId: "content-value-promise"
26774
+ },
26775
+ {
26776
+ choices: [
26777
+ { choiceId: "profile_confirmation.guided", label: "\u5173\u952E\u9636\u6BB5\u786E\u8BA4" },
26778
+ { choiceId: "profile_confirmation.review-every-stage", label: "\u6BCF\u4E2A\u5185\u5BB9\u9636\u6BB5\u90FD\u786E\u8BA4" },
26779
+ { choiceId: "profile_confirmation.auto-until-delivery", label: "\u53EA\u5728\u6700\u7EC8\u9884\u89C8\u786E\u8BA4" }
26780
+ ],
26781
+ inputType: "single-choice",
26782
+ prompt: "\u8BF7\u9009\u62E9\u5185\u5BB9\u5DE5\u4F5C\u6D41\u7684\u786E\u8BA4\u65B9\u5F0F\u3002",
26783
+ questionId: "confirmation-policy"
26784
+ }
26785
+ ];
26722
26786
  var ProfileCliError = class extends Error {
26723
26787
  constructor(code, message, hint, fields) {
26724
26788
  super(message);
@@ -26748,6 +26812,23 @@ function userMemoryPath(homeDirectory) {
26748
26812
  function memoryPath(homeDirectory) {
26749
26813
  return path2.join(homeDirectory, ".dxc", "content-memory.json");
26750
26814
  }
26815
+ function profileSessionPath(homeDirectory, profileSessionId) {
26816
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(
26817
+ profileSessionId
26818
+ )) {
26819
+ throw new ProfileCliError("DXC_PROFILE_INPUT_INVALID", "\u753B\u50CF\u4F1A\u8BDD ID \u65E0\u6548\u3002");
26820
+ }
26821
+ return path2.join(homeDirectory, ".dxc", "profile-sessions", `${profileSessionId}.json`);
26822
+ }
26823
+ function profileSession(value, expectedId) {
26824
+ if (typeof value !== "object" || value === null || Array.isArray(value) || !("version" in value) || value.version !== 1 || !("profileSessionId" in value) || value.profileSessionId !== expectedId || !("index" in value) || !Number.isInteger(value.index) || Number(value.index) < 0 || Number(value.index) > PROFILE_QUESTIONS.length || !("createdAt" in value) || typeof value.createdAt !== "string" || !("answers" in value) || typeof value.answers !== "object" || value.answers === null || Array.isArray(value.answers)) {
26825
+ return void 0;
26826
+ }
26827
+ return value;
26828
+ }
26829
+ function choiceLabel(questionIndex, choiceId) {
26830
+ return PROFILE_QUESTIONS[questionIndex]?.choices.find((choice) => choice.choiceId === choiceId)?.label;
26831
+ }
26751
26832
  function singleLine(value) {
26752
26833
  return value.replace(/[\r\n]+/gu, " ").replace(/\s+/gu, " ").trim();
26753
26834
  }
@@ -26907,149 +26988,133 @@ async function refreshUserMemory(homeDirectory, user, now) {
26907
26988
  Buffer.from(renderUserMemory(user, memory.memories))
26908
26989
  );
26909
26990
  }
26910
- async function readProfileInput(currentDirectory, homeDirectory, inputPath) {
26911
- try {
26912
- const target = resolveUserPath({ currentDirectory, homeDirectory, input: inputPath });
26913
- const metadata = await stat(target);
26914
- if (!metadata.isFile() || metadata.size > PROFILE_MAXIMUM_BYTES) {
26915
- throw new ProfileCliError("DXC_PROFILE_INPUT_INVALID", "Profile input is invalid");
26916
- }
26917
- return parseJson(
26918
- await readFile(target, "utf8"),
26919
- "DXC_PROFILE_INPUT_INVALID",
26920
- "Profile input is invalid"
26921
- );
26922
- } catch (error) {
26923
- if (error instanceof ProfileCliError) {
26924
- throw error;
26925
- }
26926
- throw new ProfileCliError(
26927
- "DXC_PROFILE_INPUT_INVALID",
26928
- "\u753B\u50CF\u8F93\u5165\u6587\u4EF6\u65E0\u6CD5\u8BFB\u53D6\u6216\u4E0D\u662F\u6709\u6548 JSON\u3002",
26929
- "\u8BF7\u8BA9 DxC \u5DE5\u4F5C\u6D41\u91CD\u65B0\u751F\u6210\u753B\u50CF\u8F93\u5165\uFF1B\u65E0\u9700\u624B\u52A8\u4FEE\u6539\u8DEF\u5F84\u6216\u5E94\u7528\u6570\u636E\u6587\u4EF6\u3002"
26930
- );
26931
- }
26932
- }
26933
- function issueFields(issues) {
26934
- return [
26935
- ...new Set(
26936
- issues.map((issue) => issue.path.map(String).join(".")).filter((field) => field.length > 0)
26937
- )
26938
- ];
26939
- }
26940
- async function readProfileForSave(target, timestamp) {
26941
- try {
26942
- return await readProfile(target);
26943
- } catch (error) {
26944
- if (!(error instanceof ProfileCliError) || error.code !== "DXC_PROFILE_INVALID") {
26945
- throw error;
26946
- }
26947
- try {
26948
- const metadata = await lstat(target);
26949
- if (!metadata.isFile() || metadata.isSymbolicLink()) {
26950
- throw error;
26951
- }
26952
- const suffix = timestamp.replaceAll(":", "-");
26953
- await rename(target, `${target}.invalid-${suffix}.bak`);
26954
- return void 0;
26955
- } catch (recoveryError) {
26956
- if (recoveryError === error) {
26957
- throw error;
26958
- }
26959
- throw new ProfileCliError(
26960
- "DXC_PROFILE_INVALID",
26961
- "\u53D1\u73B0\u65E0\u6548\u7684\u65E7\u753B\u50CF\uFF0C\u4F46\u65E0\u6CD5\u5B89\u5168\u5907\u4EFD\u5E76\u6062\u590D\u3002",
26962
- "\u8BF7\u8BA9 DxC \u5DE5\u4F5C\u6D41\u91CD\u8BD5\uFF1B\u4E0D\u8981\u624B\u52A8\u5220\u9664 ~/.dxc \u4E2D\u7684\u6587\u4EF6\u3002"
26963
- );
26964
- }
26965
- }
26966
- }
26967
26991
  var ProfileCli = class {
26968
- #currentDirectory;
26969
26992
  #homeDirectory;
26970
26993
  #now;
26971
26994
  #randomUUID;
26972
26995
  constructor(dependencies) {
26973
- this.#currentDirectory = dependencies.currentDirectory;
26974
26996
  this.#homeDirectory = dependencies.homeDirectory;
26975
26997
  this.#now = dependencies.now ?? (() => /* @__PURE__ */ new Date());
26976
26998
  this.#randomUUID = dependencies.randomUUID ?? randomUUID;
26977
26999
  }
26978
- async save(inputPath) {
26979
- const input = contentProfileInputSchema.safeParse(
26980
- await readProfileInput(this.#currentDirectory, this.#homeDirectory, inputPath)
26981
- );
26982
- if (!input.success) {
26983
- const fields = issueFields(input.error.issues);
26984
- throw new ProfileCliError(
26985
- "DXC_PROFILE_INPUT_INVALID",
26986
- fields.length === 0 ? "\u753B\u50CF\u8F93\u5165\u4E0D\u7B26\u5408\u5F53\u524D\u7248\u672C\u8981\u6C42\u3002" : `\u753B\u50CF\u8F93\u5165\u7F3A\u5C11\u6216\u5305\u542B\u65E0\u6548\u5B57\u6BB5\uFF1A${fields.join("\u3001")}\u3002`,
26987
- "\u8BF7\u8BA9 DxC \u5DE5\u4F5C\u6D41\u8865\u9F50\u8FD9\u4E9B\u5B57\u6BB5\u540E\u81EA\u52A8\u91CD\u8BD5\uFF1B\u65E0\u9700\u624B\u5199 JSON\u3002",
26988
- fields
26989
- );
26990
- }
26991
- const target = profilePath(this.#homeDirectory);
26992
- const timestamp = this.#now().toISOString();
26993
- const existing = await readProfileForSave(target, timestamp);
26994
- const profile = contentProfileSchema.parse({
26995
- ...input.data,
26996
- createdAt: existing?.createdAt ?? timestamp,
26997
- profileId: existing?.profileId ?? this.#randomUUID(),
26998
- schemaVersion: "1",
26999
- updatedAt: timestamp
27000
- });
27001
- try {
27002
- await writePrivateJson(target, profile);
27003
- await refreshUserMemory(this.#homeDirectory, profile.user, this.#now());
27004
- } catch {
27005
- throw new ProfileCliError("DXC_PROFILE_IO_FAILED", "Unable to save local content profile");
27006
- }
27007
- return contentProfileSaveResultSchema.parse({
27008
- command: "profile.save",
27009
- data: {
27010
- profile,
27011
- profilePath: PROFILE_DISPLAY_PATH,
27012
- userMemoryPath: USER_MEMORY_DISPLAY_PATH
27013
- },
27000
+ async begin() {
27001
+ const profileSessionId = this.#randomUUID();
27002
+ const session = {
27003
+ answers: {},
27004
+ createdAt: this.#now().toISOString(),
27005
+ index: 0,
27006
+ profileSessionId,
27007
+ version: 1
27008
+ };
27009
+ await writePrivateJson(profileSessionPath(this.#homeDirectory, profileSessionId), session);
27010
+ return contentProfileSessionResultSchema.parse({
27011
+ command: "profile.begin",
27012
+ data: { profileSessionId, question: PROFILE_QUESTIONS[0], status: "question" },
27014
27013
  ok: true
27015
27014
  });
27016
27015
  }
27017
- async update(inputPath) {
27018
- const input = contentProfileUpdateInputSchema.safeParse(
27019
- await readProfileInput(this.#currentDirectory, this.#homeDirectory, inputPath)
27020
- );
27021
- if (!input.success) {
27022
- throw new ProfileCliError("DXC_PROFILE_INPUT_INVALID", "Profile update input is invalid");
27016
+ async answer(profileSessionId, input) {
27017
+ const target = profileSessionPath(this.#homeDirectory, profileSessionId);
27018
+ let session;
27019
+ try {
27020
+ const parsed = profileSession(
27021
+ JSON.parse(await readPrivateFile(target, PROFILE_SESSION_MAXIMUM_BYTES)),
27022
+ profileSessionId
27023
+ );
27024
+ if (parsed === void 0) {
27025
+ throw new ProfileCliError("DXC_PROFILE_INPUT_INVALID", "\u753B\u50CF\u4F1A\u8BDD\u5DF2\u635F\u574F\u3002");
27026
+ }
27027
+ session = parsed;
27028
+ } catch (error) {
27029
+ if (error instanceof ProfileCliError) {
27030
+ throw error;
27031
+ }
27032
+ throw new ProfileCliError("DXC_PROFILE_INPUT_INVALID", "\u753B\u50CF\u4F1A\u8BDD\u4E0D\u5B58\u5728\u6216\u5DF2\u5B8C\u6210\u3002");
27023
27033
  }
27024
- const target = profilePath(this.#homeDirectory);
27025
- const existing = await readProfile(target);
27026
- if (existing === void 0) {
27027
- throw new ProfileCliError("DXC_PROFILE_INVALID", "Content profile is not initialized");
27034
+ const question = PROFILE_QUESTIONS[session.index];
27035
+ if (question === void 0) {
27036
+ throw new ProfileCliError("DXC_PROFILE_INPUT_INVALID", "\u753B\u50CF\u4F1A\u8BDD\u5DF2\u7ECF\u5B8C\u6210\u3002");
27028
27037
  }
27029
- const answerByQuestionId = new Map(
27030
- existing.answers.map((answer) => [answer.questionId, answer])
27038
+ const choices = [...new Set(input.choices)];
27039
+ if (question.inputType === "text") {
27040
+ if (input.text === void 0 || input.text.trim().length === 0 || input.text.length > 120) {
27041
+ throw new ProfileCliError("DXC_PROFILE_INPUT_INVALID", "\u5F53\u524D\u753B\u50CF\u95EE\u9898\u9700\u8981\u6709\u6548\u6587\u672C\u7B54\u6848\u3002");
27042
+ }
27043
+ } else {
27044
+ const maximum = question.inputType === "multi-choice" ? 5 : 1;
27045
+ if (choices.length < 1 || choices.length > maximum || choices.some((choice) => choiceLabel(session.index, choice) === void 0)) {
27046
+ throw new ProfileCliError("DXC_PROFILE_INPUT_INVALID", "\u753B\u50CF\u9009\u62E9\u4E0D\u5C5E\u4E8E\u5F53\u524D\u95EE\u9898\u3002");
27047
+ }
27048
+ }
27049
+ const answer = {
27050
+ choices,
27051
+ ...input.note === void 0 ? {} : { note: input.note.trim().slice(0, 500) },
27052
+ ...input.text === void 0 ? {} : { text: input.text.trim() }
27053
+ };
27054
+ const answers = { ...session.answers, [question.questionId]: answer };
27055
+ const nextIndex = session.index + 1;
27056
+ if (nextIndex < PROFILE_QUESTIONS.length) {
27057
+ await writePrivateJson(target, { ...session, answers, index: nextIndex });
27058
+ return contentProfileSessionResultSchema.parse({
27059
+ command: "profile.answer",
27060
+ data: {
27061
+ profileSessionId,
27062
+ question: PROFILE_QUESTIONS[nextIndex],
27063
+ status: "question"
27064
+ },
27065
+ ok: true
27066
+ });
27067
+ }
27068
+ const creatorRole = choiceLabel(0, answers["creator-role"]?.choices[0] ?? "");
27069
+ const displayName = answers["display-name"]?.text;
27070
+ const primaryDomains = (answers["primary-domains"]?.choices ?? []).map((choice) => choiceLabel(2, choice)).filter((label) => label !== void 0);
27071
+ const targetAudience = choiceLabel(3, answers["target-audience"]?.choices[0] ?? "");
27072
+ const contentValuePromise = choiceLabel(4, answers["content-value-promise"]?.choices[0] ?? "");
27073
+ const confirmationChoice = answers["confirmation-policy"]?.choices[0];
27074
+ const confirmationPolicy = confirmationChoice?.replace("profile_confirmation.", "");
27075
+ const existing = await readProfile(profilePath(this.#homeDirectory));
27076
+ const existingUser = existing?.user;
27077
+ const userWithoutRetiredPolicy = existingUser === void 0 ? {} : Object.fromEntries(
27078
+ Object.entries(existingUser).filter(([key]) => key !== "visualGenerationAuthorization")
27031
27079
  );
27032
- for (const answer of input.data.answers ?? []) {
27033
- answerByQuestionId.set(answer.questionId, answer);
27080
+ const parsedInput = contentProfileInputSchema.safeParse({
27081
+ answers: existing?.answers ?? [],
27082
+ questionnaireVersion: "dxc-onboarding@1",
27083
+ user: {
27084
+ ...userWithoutRetiredPolicy,
27085
+ confirmationPolicy,
27086
+ contentValuePromise,
27087
+ creatorRole,
27088
+ displayName,
27089
+ primaryDomains,
27090
+ primaryPlatform: "wechat-official-account",
27091
+ targetAudience
27092
+ }
27093
+ });
27094
+ if (!parsedInput.success) {
27095
+ throw new ProfileCliError("DXC_PROFILE_INPUT_INVALID", "\u753B\u50CF\u7B54\u6848\u65E0\u6CD5\u5F62\u6210\u6709\u6548\u753B\u50CF\u3002");
27034
27096
  }
27035
27097
  const timestamp = this.#now().toISOString();
27036
27098
  const profile = contentProfileSchema.parse({
27037
- ...existing,
27038
- answers: [...answerByQuestionId.values()],
27039
- ...input.data.user === void 0 ? {} : { user: { ...existing.user, ...input.data.user } },
27099
+ ...parsedInput.data,
27100
+ createdAt: existing?.createdAt ?? timestamp,
27101
+ profileId: existing?.profileId ?? this.#randomUUID(),
27102
+ schemaVersion: "1",
27040
27103
  updatedAt: timestamp
27041
27104
  });
27042
27105
  try {
27043
- await writePrivateJson(target, profile);
27106
+ await writePrivateJson(profilePath(this.#homeDirectory), profile);
27044
27107
  await refreshUserMemory(this.#homeDirectory, profile.user, this.#now());
27108
+ await unlink(target);
27045
27109
  } catch {
27046
- throw new ProfileCliError("DXC_PROFILE_IO_FAILED", "Unable to update local content profile");
27110
+ throw new ProfileCliError("DXC_PROFILE_IO_FAILED", "\u65E0\u6CD5\u539F\u5B50\u4FDD\u5B58\u753B\u50CF\u95EE\u7B54\u7ED3\u679C\u3002");
27047
27111
  }
27048
- return contentProfileUpdateResultSchema.parse({
27049
- command: "profile.update",
27112
+ return contentProfileSessionResultSchema.parse({
27113
+ command: "profile.answer",
27050
27114
  data: {
27051
27115
  profile,
27052
27116
  profilePath: PROFILE_DISPLAY_PATH,
27117
+ status: "completed",
27053
27118
  userMemoryPath: USER_MEMORY_DISPLAY_PATH
27054
27119
  },
27055
27120
  ok: true
@@ -27074,20 +27139,16 @@ var ProfileCli = class {
27074
27139
  }
27075
27140
  };
27076
27141
  var ContentMemoryCli = class {
27077
- #currentDirectory;
27078
27142
  #homeDirectory;
27079
27143
  #now;
27080
27144
  #randomUUID;
27081
27145
  constructor(dependencies) {
27082
- this.#currentDirectory = dependencies.currentDirectory;
27083
27146
  this.#homeDirectory = dependencies.homeDirectory;
27084
27147
  this.#now = dependencies.now ?? (() => /* @__PURE__ */ new Date());
27085
27148
  this.#randomUUID = dependencies.randomUUID ?? randomUUID;
27086
27149
  }
27087
- async add(inputPath) {
27088
- const input = contentMemoryInputSchema.safeParse(
27089
- await readProfileInput(this.#currentDirectory, this.#homeDirectory, inputPath)
27090
- );
27150
+ async add(inputValue) {
27151
+ const input = contentMemoryInputSchema.safeParse(inputValue);
27091
27152
  if (!input.success) {
27092
27153
  throw new ContentMemoryCliError(
27093
27154
  "DXC_MEMORY_INPUT_INVALID",
@@ -27182,11 +27243,73 @@ var ContentMemoryCli = class {
27182
27243
  ok: true
27183
27244
  });
27184
27245
  }
27246
+ async correct(memoryId, inputValue) {
27247
+ const input = contentMemoryInputSchema.safeParse(inputValue);
27248
+ if (!input.success) {
27249
+ throw new ContentMemoryCliError(
27250
+ "DXC_MEMORY_INPUT_INVALID",
27251
+ "Content memory correction is invalid"
27252
+ );
27253
+ }
27254
+ const target = memoryPath(this.#homeDirectory);
27255
+ const store = await readMemoryStore(target);
27256
+ const index = store.memories.findIndex(
27257
+ (memory2) => memory2.id === memoryId && memory2.status === "active"
27258
+ );
27259
+ if (index < 0) {
27260
+ throw new ContentMemoryCliError(
27261
+ "DXC_MEMORY_NOT_FOUND",
27262
+ "Active content memory was not found"
27263
+ );
27264
+ }
27265
+ if (store.memories.length >= 100) {
27266
+ throw new ContentMemoryCliError("DXC_MEMORY_IO_FAILED", "Content memory capacity is reached");
27267
+ }
27268
+ const timestamp = this.#now().toISOString();
27269
+ const archivedMemory = {
27270
+ ...store.memories[index],
27271
+ status: "archived",
27272
+ updatedAt: timestamp
27273
+ };
27274
+ const memory = {
27275
+ ...input.data,
27276
+ createdAt: timestamp,
27277
+ id: this.#randomUUID(),
27278
+ source: "user-stated",
27279
+ status: "active",
27280
+ updatedAt: timestamp
27281
+ };
27282
+ const memories = [
27283
+ ...store.memories.map((entry, entryIndex) => entryIndex === index ? archivedMemory : entry),
27284
+ memory
27285
+ ];
27286
+ const next = contentMemoryStoreSchema.parse({ ...store, memories });
27287
+ try {
27288
+ await writePrivateJson(target, next);
27289
+ const profile = await readProfile(profilePath(this.#homeDirectory));
27290
+ await refreshUserMemory(this.#homeDirectory, profile?.user, this.#now());
27291
+ } catch (error) {
27292
+ if (error instanceof ContentMemoryCliError || error instanceof ProfileCliError) {
27293
+ throw error;
27294
+ }
27295
+ throw new ContentMemoryCliError("DXC_MEMORY_IO_FAILED", "Unable to correct content memory");
27296
+ }
27297
+ return contentMemoryCorrectResultSchema.parse({
27298
+ command: "memory.correct",
27299
+ data: {
27300
+ archivedMemory,
27301
+ memories: next.memories.filter((entry) => entry.status === "active"),
27302
+ memory,
27303
+ memoryPath: MEMORY_DISPLAY_PATH
27304
+ },
27305
+ ok: true
27306
+ });
27307
+ }
27185
27308
  };
27186
27309
 
27187
27310
  // apps/cli/src/project.ts
27188
27311
  import { randomUUID as randomUUID2 } from "node:crypto";
27189
- import { lstat as lstat2, mkdir } from "node:fs/promises";
27312
+ import { lstat, mkdir } from "node:fs/promises";
27190
27313
  import path3 from "node:path";
27191
27314
  var MANIFEST_NAME = "dxc.project.json";
27192
27315
  var MANIFEST_MAXIMUM_BYTES = 32 * 1024;
@@ -27292,7 +27415,7 @@ var ProjectCli = class {
27292
27415
  });
27293
27416
  const manifestTarget = path3.join(directory, MANIFEST_NAME);
27294
27417
  try {
27295
- await lstat2(manifestTarget);
27418
+ await lstat(manifestTarget);
27296
27419
  throw new ProjectCliError("DXC_PROJECT_EXISTS", "\u8BE5\u76EE\u5F55\u5DF2\u7ECF\u662F DxC \u5185\u5BB9\u9879\u76EE\u3002");
27297
27420
  } catch (error) {
27298
27421
  if (error instanceof ProjectCliError) {
@@ -27428,7 +27551,17 @@ var ProjectCli = class {
27428
27551
  };
27429
27552
 
27430
27553
  // apps/cli/src/skills.ts
27431
- import { cp, lstat as lstat3, mkdir as mkdir2, mkdtemp, readdir, rename as rename2, rm } from "node:fs/promises";
27554
+ import {
27555
+ cp,
27556
+ lstat as lstat2,
27557
+ mkdir as mkdir2,
27558
+ mkdtemp,
27559
+ readFile,
27560
+ readdir,
27561
+ rename,
27562
+ rm,
27563
+ writeFile
27564
+ } from "node:fs/promises";
27432
27565
  import os from "node:os";
27433
27566
  import path4 from "node:path";
27434
27567
  import { fileURLToPath } from "node:url";
@@ -27458,7 +27591,7 @@ var SkillInstallerError = class extends Error {
27458
27591
  };
27459
27592
  async function isDirectory(directory) {
27460
27593
  try {
27461
- return (await lstat3(directory)).isDirectory();
27594
+ return (await lstat2(directory)).isDirectory();
27462
27595
  } catch {
27463
27596
  return false;
27464
27597
  }
@@ -27482,7 +27615,7 @@ async function findBundledSkills() {
27482
27615
  async function assertRegularSkillTree(directory) {
27483
27616
  for (const entry of await readdir(directory, { withFileTypes: true })) {
27484
27617
  const entryPath = path4.join(directory, entry.name);
27485
- const entryStat = await lstat3(entryPath);
27618
+ const entryStat = await lstat2(entryPath);
27486
27619
  if (entryStat.isSymbolicLink()) {
27487
27620
  throw new SkillInstallerError(
27488
27621
  "DXC_SKILL_SOURCE_UNSAFE",
@@ -27501,6 +27634,26 @@ async function assertRegularSkillTree(directory) {
27501
27634
  }
27502
27635
  }
27503
27636
  }
27637
+ async function renderCliInvocation(sourceRoot, stagedSkill) {
27638
+ const skillFile = path4.join(stagedSkill, "SKILL.md");
27639
+ const skill = await readFile(skillFile, "utf8");
27640
+ if (!skill.includes("{{DXC_CLI_INVOCATION}}")) {
27641
+ return;
27642
+ }
27643
+ const invocationTemplate = await readFile(
27644
+ path4.join(sourceRoot, ".dxc-cli-invocation.template.md"),
27645
+ "utf8"
27646
+ );
27647
+ const invocation = invocationTemplate.replaceAll("{{VERSION}}", package_default.version).trim();
27648
+ const rendered = skill.replaceAll("{{DXC_CLI_INVOCATION}}", invocation);
27649
+ if (rendered.includes("{{DXC_CLI_INVOCATION}}") || rendered.includes("{{VERSION}}")) {
27650
+ throw new SkillInstallerError(
27651
+ "DXC_SKILL_TEMPLATE_INVALID",
27652
+ "Bundled Skill invocation template contains an unresolved placeholder"
27653
+ );
27654
+ }
27655
+ await writeFile(skillFile, rendered, { mode: 384 });
27656
+ }
27504
27657
  function defaultTargetDirectory(homeDirectory, target) {
27505
27658
  return path4.join(homeDirectory, target === "codex" ? ".codex" : ".workbuddy", "skills");
27506
27659
  }
@@ -27543,10 +27696,11 @@ var SkillInstaller = class {
27543
27696
  errorOnExist: true,
27544
27697
  recursive: true
27545
27698
  });
27699
+ await renderCliInvocation(sourceRoot, stagedSkill);
27546
27700
  if (destinationExists) {
27547
- await rename2(destination, backup);
27701
+ await rename(destination, backup);
27548
27702
  }
27549
- await rename2(stagedSkill, destination);
27703
+ await rename(stagedSkill, destination);
27550
27704
  installed.push(skill);
27551
27705
  if (destinationExists) {
27552
27706
  try {
@@ -27557,7 +27711,7 @@ var SkillInstaller = class {
27557
27711
  }
27558
27712
  } catch (error) {
27559
27713
  if (!await isDirectory(destination) && await isDirectory(backup)) {
27560
- await rename2(backup, destination);
27714
+ await rename(backup, destination);
27561
27715
  }
27562
27716
  throw error;
27563
27717
  } finally {
@@ -27588,7 +27742,7 @@ import {
27588
27742
  randomUUID as randomUUID4
27589
27743
  } from "node:crypto";
27590
27744
  import { constants as fileConstants3 } from "node:fs";
27591
- import { chmod, lstat as lstat6, mkdir as mkdir4, open as open3, rename as rename4, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
27745
+ import { chmod, lstat as lstat5, mkdir as mkdir5, open as open3, rename as rename3, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
27592
27746
  import os2 from "node:os";
27593
27747
  import path7 from "node:path";
27594
27748
 
@@ -27736,10 +27890,10 @@ var LOG_REDACTION_PATHS = Object.freeze([
27736
27890
  ]);
27737
27891
 
27738
27892
  // apps/cli/src/publishing-assets.ts
27739
- import { createHash as createHash4 } from "node:crypto";
27740
- import { constants as fileConstants } from "node:fs";
27741
- import { lstat as lstat4, open, readdir as readdir2, realpath } from "node:fs/promises";
27742
- import path5 from "node:path";
27893
+ import { createHash as createHash5 } from "node:crypto";
27894
+ import { constants as fileConstants2 } from "node:fs";
27895
+ import { lstat as lstat4, mkdir as mkdir4, open as open2, readdir as readdir2, readFile as readFile3, realpath as realpath2 } from "node:fs/promises";
27896
+ import path6 from "node:path";
27743
27897
 
27744
27898
  // packages/renderer-wechat/src/article.ts
27745
27899
  import { createHash as createHash2 } from "node:crypto";
@@ -29861,7 +30015,24 @@ function rewriteWechatAssetSources(html, replacements) {
29861
30015
  }
29862
30016
 
29863
30017
  // apps/cli/src/publishing-assets.ts
30018
+ var import_yaml3 = __toESM(require_dist(), 1);
30019
+ import sharp2 from "sharp";
30020
+
30021
+ // apps/cli/src/publishing-image-optimization.ts
29864
30022
  var import_yaml2 = __toESM(require_dist(), 1);
30023
+ import { createHash as createHash4, randomUUID as randomUUID3 } from "node:crypto";
30024
+ import { constants as fileConstants } from "node:fs";
30025
+ import {
30026
+ lstat as lstat3,
30027
+ mkdir as mkdir3,
30028
+ open,
30029
+ readFile as readFile2,
30030
+ realpath,
30031
+ rename as rename2,
30032
+ unlink as unlink2,
30033
+ writeFile as writeFile2
30034
+ } from "node:fs/promises";
30035
+ import path5 from "node:path";
29865
30036
  import sharp from "sharp";
29866
30037
 
29867
30038
  // apps/cli/src/wechat-error.ts
@@ -29880,47 +30051,402 @@ var WechatCliError = class extends Error {
29880
30051
  fields;
29881
30052
  };
29882
30053
 
30054
+ // apps/cli/src/publishing-image-optimization.ts
30055
+ var MAXIMUM_INPUT_BYTES = 32 * 1024 * 1024;
30056
+ var INLINE_IMAGE_LIMIT = 1024 * 1024 - 1;
30057
+ var INLINE_IMAGE_TARGET = 950 * 1024;
30058
+ var COVER_IMAGE_LIMIT = 5 * 1024 * 1024;
30059
+ var COVER_IMAGE_TARGET = 4500 * 1024;
30060
+ var VISUAL_PLAN_MAXIMUM_BYTES = 128 * 1024;
30061
+ var VISUAL_PLAN_FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)([\s\S]*)$/u;
30062
+ function sha256(value) {
30063
+ return createHash4("sha256").update(value).digest("hex");
30064
+ }
30065
+ function safeDerivativeBase(value) {
30066
+ return value.normalize("NFKC").replaceAll(/[^A-Za-z0-9._-]+/gu, "-").replaceAll(/^-+|-+$/gu, "").slice(0, 80) || "image";
30067
+ }
30068
+ function recordValue(value) {
30069
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
30070
+ }
30071
+ function portablePath(value, label) {
30072
+ if (typeof value !== "string" || value.trim().length === 0 || path5.isAbsolute(value) || value.includes("\\") || value.split("/").some((segment) => segment === "" || segment === "." || segment === "..")) {
30073
+ throw new WechatCliError(
30074
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30075
+ `${label} \u5FC5\u987B\u662F\u9879\u76EE\u5185\u4F7F\u7528 / \u5206\u9694\u7684\u5B89\u5168\u76F8\u5BF9\u8DEF\u5F84\u3002`
30076
+ );
30077
+ }
30078
+ return value;
30079
+ }
30080
+ function isWithin(target, directory) {
30081
+ const relative = path5.relative(directory, target);
30082
+ return relative === "" || !relative.startsWith(`..${path5.sep}`) && relative !== "..";
30083
+ }
30084
+ async function regularImage(target) {
30085
+ let handle;
30086
+ try {
30087
+ handle = await open(target, fileConstants.O_RDONLY | fileConstants.O_NOFOLLOW);
30088
+ const metadata = await handle.stat();
30089
+ if (!metadata.isFile() || metadata.size <= 0 || metadata.size > MAXIMUM_INPUT_BYTES) {
30090
+ throw new Error("invalid image");
30091
+ }
30092
+ return await handle.readFile();
30093
+ } catch {
30094
+ throw new WechatCliError(
30095
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30096
+ `\u65E0\u6CD5\u5B89\u5168\u8BFB\u53D6\u56FE\u7247 ${path5.basename(target)}\uFF1B\u6587\u4EF6\u5FC5\u987B\u662F\u5C0F\u4E8E ${MAXIMUM_INPUT_BYTES} bytes \u7684\u666E\u901A\u6587\u4EF6\u3002`
30097
+ );
30098
+ } finally {
30099
+ await handle?.close();
30100
+ }
30101
+ }
30102
+ async function requirePreservedAlpha(bytes) {
30103
+ const metadata = await sharp(bytes, {
30104
+ failOn: "error",
30105
+ limitInputPixels: 8e7
30106
+ }).metadata();
30107
+ if (metadata.format !== "png" || metadata.hasAlpha !== true) {
30108
+ throw new WechatCliError(
30109
+ "DXC_PUBLISHING_IMAGE_ALPHA_LOST",
30110
+ "\u900F\u660E\u56FE\u7247\u4F18\u5316\u540E\u4E22\u5931\u900F\u660E\u901A\u9053\uFF1B\u5DF2\u963B\u65AD\u4E14\u4E0D\u4F1A\u8986\u76D6\u539F\u56FE\u3002"
30111
+ );
30112
+ }
30113
+ }
30114
+ async function decodedImageMetadata(source, sourcePath) {
30115
+ let metadata;
30116
+ try {
30117
+ metadata = await sharp(source, { failOn: "error", limitInputPixels: 8e7 }).metadata();
30118
+ } catch {
30119
+ throw new WechatCliError(
30120
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30121
+ "\u56FE\u7247\u65E0\u6CD5\u88AB\u5B89\u5168\u89E3\u7801\uFF1B\u4EC5\u652F\u6301\u6709\u6548 PNG/JPEG\u3002"
30122
+ );
30123
+ }
30124
+ const extension = path5.extname(sourcePath).toLowerCase();
30125
+ const expectedFormat = extension === ".png" ? "png" : extension === ".jpg" || extension === ".jpeg" ? "jpeg" : void 0;
30126
+ if (expectedFormat === void 0 || metadata.format !== expectedFormat || metadata.width === void 0 || metadata.height === void 0) {
30127
+ throw new WechatCliError(
30128
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30129
+ "\u56FE\u7247\u6269\u5C55\u540D\u3001\u771F\u5B9E\u683C\u5F0F\u6216\u5C3A\u5BF8\u65E0\u6548\uFF1B\u4EC5\u652F\u6301\u6269\u5C55\u540D\u4E0E\u5185\u5BB9\u4E00\u81F4\u7684 PNG/JPEG\u3002"
30130
+ );
30131
+ }
30132
+ return {
30133
+ format: expectedFormat,
30134
+ hasAlpha: metadata.hasAlpha === true,
30135
+ height: metadata.height,
30136
+ width: metadata.width
30137
+ };
30138
+ }
30139
+ async function writeExclusiveOrVerify(target, bytes) {
30140
+ await mkdir3(path5.dirname(target), { recursive: true, mode: 448 });
30141
+ try {
30142
+ await writeFile2(target, bytes, { flag: "wx", mode: 384 });
30143
+ return true;
30144
+ } catch (error) {
30145
+ if (error.code !== "EEXIST") {
30146
+ throw error;
30147
+ }
30148
+ const existing = await regularImage(target);
30149
+ if (sha256(existing) !== sha256(bytes)) {
30150
+ throw new WechatCliError(
30151
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_CONFLICT",
30152
+ `\u4F18\u5316\u76EE\u6807 ${path5.basename(target)} \u5DF2\u5B58\u5728\u4F46\u5185\u5BB9\u4E0D\u540C\uFF1B\u672A\u8986\u76D6\u4EFB\u4F55\u6587\u4EF6\u3002`
30153
+ );
30154
+ }
30155
+ return false;
30156
+ }
30157
+ }
30158
+ async function safeRealPathWithin(target, directory, label) {
30159
+ const resolved = await realpath(target).catch(() => void 0);
30160
+ if (resolved === void 0 || !isWithin(resolved, directory)) {
30161
+ throw new WechatCliError(
30162
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30163
+ `${label} \u7684\u771F\u5B9E\u8DEF\u5F84\u4E0D\u5728\u5141\u8BB8\u7684\u9879\u76EE\u76EE\u5F55\u5185\u3002`
30164
+ );
30165
+ }
30166
+ return resolved;
30167
+ }
30168
+ async function ensureSafeOptimizedDirectory(assetsDirectory, realAssetsDirectory) {
30169
+ const optimizedDirectory = path5.join(assetsDirectory, "optimized");
30170
+ await mkdir3(optimizedDirectory, { recursive: true, mode: 448 });
30171
+ const metadata = await lstat3(optimizedDirectory).catch(() => void 0);
30172
+ if (metadata === void 0 || !metadata.isDirectory() || metadata.isSymbolicLink()) {
30173
+ throw new WechatCliError(
30174
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30175
+ "\u4F18\u5316\u76EE\u5F55\u5FC5\u987B\u662F\u7D20\u6750\u76EE\u5F55\u5185\u7684\u666E\u901A\u76EE\u5F55\uFF0C\u4E0D\u80FD\u662F\u7B26\u53F7\u94FE\u63A5\u3002"
30176
+ );
30177
+ }
30178
+ await safeRealPathWithin(optimizedDirectory, realAssetsDirectory, "\u4F18\u5316\u76EE\u5F55");
30179
+ return optimizedDirectory;
30180
+ }
30181
+ async function writePlanAtomically(target, expectedSource, source) {
30182
+ const temporary = `${target}.${randomUUID3()}.tmp`;
30183
+ try {
30184
+ await writeFile2(temporary, source, { encoding: "utf8", flag: "wx", mode: 384 });
30185
+ if (await readFile2(target, "utf8") !== expectedSource) {
30186
+ throw new WechatCliError(
30187
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_CONFLICT",
30188
+ "\u89C6\u89C9\u8BA1\u5212\u5728\u56FE\u7247\u4F18\u5316\u671F\u95F4\u5DF2\u88AB\u5176\u4ED6\u64CD\u4F5C\u4FEE\u6539\uFF1B\u672A\u8986\u76D6\u65B0\u5185\u5BB9\uFF0C\u8BF7\u91CD\u65B0\u8BFB\u53D6\u540E\u518D\u8BD5\u3002"
30189
+ );
30190
+ }
30191
+ await rename2(temporary, target);
30192
+ } finally {
30193
+ await unlink2(temporary).catch(() => void 0);
30194
+ }
30195
+ }
30196
+ function candidateWidths(width) {
30197
+ return [.../* @__PURE__ */ new Set([width, 2400, 2e3, 1600, 1280, 1080, 900, 720])].filter((candidate) => candidate > 0 && candidate <= width).sort((left, right) => right - left);
30198
+ }
30199
+ async function optimizedImage(source, targetBytes, metadata) {
30200
+ const hasAlpha = metadata.hasAlpha;
30201
+ for (const width of candidateWidths(metadata.width)) {
30202
+ if (hasAlpha) {
30203
+ const lossless = await sharp(source, { failOn: "error", limitInputPixels: 8e7 }).rotate().resize({ fit: "inside", width, withoutEnlargement: true }).png({ compressionLevel: 9, effort: 10 }).toBuffer();
30204
+ if (lossless.byteLength <= targetBytes) {
30205
+ await requirePreservedAlpha(lossless);
30206
+ return { alphaPreserved: true, bytes: lossless, extension: ".png" };
30207
+ }
30208
+ for (const colours of [256, 192, 128, 96, 64]) {
30209
+ const quantized = await sharp(source, {
30210
+ failOn: "error",
30211
+ limitInputPixels: 8e7
30212
+ }).rotate().resize({ fit: "inside", width, withoutEnlargement: true }).png({ colours, compressionLevel: 9, effort: 10, palette: true }).toBuffer();
30213
+ if (quantized.byteLength <= targetBytes) {
30214
+ await requirePreservedAlpha(quantized);
30215
+ return { alphaPreserved: true, bytes: quantized, extension: ".png" };
30216
+ }
30217
+ }
30218
+ continue;
30219
+ }
30220
+ for (const quality of [88, 82, 76, 70, 64, 58]) {
30221
+ const jpeg = await sharp(source, { failOn: "error", limitInputPixels: 8e7 }).rotate().resize({ fit: "inside", width, withoutEnlargement: true }).jpeg({ chromaSubsampling: "4:4:4", mozjpeg: true, progressive: true, quality }).toBuffer();
30222
+ if (jpeg.byteLength <= targetBytes) {
30223
+ return { alphaPreserved: true, bytes: jpeg, extension: ".jpg" };
30224
+ }
30225
+ }
30226
+ }
30227
+ throw new WechatCliError(
30228
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_LIMIT_UNREACHABLE",
30229
+ `\u65E0\u6CD5\u5728\u4E0D\u4E22\u5931\u900F\u660E\u901A\u9053\u6216\u4F4E\u4E8E\u6700\u5C0F\u5C3A\u5BF8\u7B56\u7565\u7684\u524D\u63D0\u4E0B\u538B\u5230 ${targetBytes} bytes\uFF1B\u8BF7\u66F4\u6362\u6216\u88C1\u526A\u56FE\u7247\u3002`
30230
+ );
30231
+ }
30232
+ async function optimizePublishingImages(input) {
30233
+ const projectDirectory = path5.resolve(input.projectDirectory);
30234
+ const visualPlanPath = path5.resolve(input.visualPlanFile);
30235
+ if (!isWithin(visualPlanPath, projectDirectory)) {
30236
+ throw new WechatCliError(
30237
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30238
+ "\u89C6\u89C9\u8BA1\u5212\u5FC5\u987B\u4F4D\u4E8E\u5F53\u524D\u5185\u5BB9\u9879\u76EE\u5185\u3002"
30239
+ );
30240
+ }
30241
+ const projectMetadata = await lstat3(projectDirectory).catch(() => void 0);
30242
+ const visualPlanMetadata = await lstat3(visualPlanPath).catch(() => void 0);
30243
+ if (projectMetadata === void 0 || !projectMetadata.isDirectory() || projectMetadata.isSymbolicLink() || visualPlanMetadata === void 0 || !visualPlanMetadata.isFile() || visualPlanMetadata.isSymbolicLink() || visualPlanMetadata.size > VISUAL_PLAN_MAXIMUM_BYTES) {
30244
+ throw new WechatCliError(
30245
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30246
+ "\u5185\u5BB9\u9879\u76EE\u548C\u89C6\u89C9\u8BA1\u5212\u5FC5\u987B\u662F\u975E\u7B26\u53F7\u94FE\u63A5\u7684\u666E\u901A\u76EE\u5F55\u4E0E\u6587\u4EF6\u3002"
30247
+ );
30248
+ }
30249
+ const realProjectDirectory = await realpath(projectDirectory);
30250
+ await safeRealPathWithin(visualPlanPath, realProjectDirectory, "\u89C6\u89C9\u8BA1\u5212");
30251
+ const source = await readFile2(visualPlanPath, "utf8").catch(() => "");
30252
+ if (Buffer.byteLength(source, "utf8") > VISUAL_PLAN_MAXIMUM_BYTES) {
30253
+ throw new WechatCliError("DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID");
30254
+ }
30255
+ const match = VISUAL_PLAN_FRONTMATTER.exec(source);
30256
+ if (match?.[1] === void 0 || match[2] === void 0) {
30257
+ throw new WechatCliError("DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID");
30258
+ }
30259
+ let frontmatter2;
30260
+ try {
30261
+ frontmatter2 = recordValue((0, import_yaml2.parse)(match[1], { maxAliasCount: 10, uniqueKeys: true }));
30262
+ } catch {
30263
+ throw new WechatCliError("DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID");
30264
+ }
30265
+ if (frontmatter2 === void 0) {
30266
+ throw new WechatCliError(
30267
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30268
+ "\u89C6\u89C9\u8BA1\u5212\u7684 YAML \u5934\u65E0\u6CD5\u8BFB\u53D6\u3002"
30269
+ );
30270
+ }
30271
+ const assetsRelative = frontmatter2["assetsDirectory"] === void 0 ? "assets" : portablePath(frontmatter2["assetsDirectory"], "assetsDirectory");
30272
+ frontmatter2["assetsDirectory"] = assetsRelative;
30273
+ frontmatter2["inlineImagesSupported"] = true;
30274
+ const assetsDirectory = path5.resolve(projectDirectory, assetsRelative);
30275
+ if (!isWithin(assetsDirectory, projectDirectory)) {
30276
+ throw new WechatCliError("DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID");
30277
+ }
30278
+ const assetsMetadata = await lstat3(assetsDirectory).catch(() => void 0);
30279
+ if (assetsMetadata === void 0 || !assetsMetadata.isDirectory() || assetsMetadata.isSymbolicLink()) {
30280
+ throw new WechatCliError("DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID");
30281
+ }
30282
+ const realAssetsDirectory = await safeRealPathWithin(
30283
+ assetsDirectory,
30284
+ realProjectDirectory,
30285
+ "\u7D20\u6750\u76EE\u5F55"
30286
+ );
30287
+ const inlineAssets = Array.isArray(frontmatter2["inlineAssets"]) ? frontmatter2["inlineAssets"] : [];
30288
+ const entries = [
30289
+ {
30290
+ assetId: "cover",
30291
+ limit: COVER_IMAGE_LIMIT,
30292
+ record: { path: frontmatter2["coverAsset"] },
30293
+ role: "cover",
30294
+ target: COVER_IMAGE_TARGET
30295
+ },
30296
+ ...inlineAssets.map((asset, index) => ({
30297
+ assetId: `inline-${String(index + 1).padStart(2, "0")}`,
30298
+ limit: INLINE_IMAGE_LIMIT,
30299
+ record: recordValue(asset),
30300
+ role: "inline",
30301
+ target: INLINE_IMAGE_TARGET
30302
+ }))
30303
+ ];
30304
+ const createdFiles = [];
30305
+ const mutations = [];
30306
+ let unchangedCount = 0;
30307
+ let optimizedDirectory;
30308
+ try {
30309
+ for (const entry of entries) {
30310
+ const assetPath = portablePath(entry.record?.["path"], `${entry.role} \u56FE\u7247\u8DEF\u5F84`);
30311
+ const absolutePath = path5.resolve(projectDirectory, assetPath);
30312
+ if (!isWithin(absolutePath, assetsDirectory)) {
30313
+ throw new WechatCliError(
30314
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30315
+ `${assetPath} \u4E0D\u5728\u89C6\u89C9\u8BA1\u5212\u58F0\u660E\u7684\u7D20\u6750\u76EE\u5F55\u5185\u3002`
30316
+ );
30317
+ }
30318
+ const assetMetadata = await lstat3(absolutePath).catch((error) => {
30319
+ if (error.code === "ENOENT") {
30320
+ return void 0;
30321
+ }
30322
+ throw error;
30323
+ });
30324
+ if (assetMetadata === void 0) {
30325
+ throw new WechatCliError(
30326
+ "DXC_PUBLISHING_VISUAL_ASSET_MISSING",
30327
+ `\u89C6\u89C9\u7D20\u6750 ${entry.assetId} \u7F3A\u5931\uFF1B\u8BF7\u5C06\u771F\u5B9E\u56FE\u7247\u5199\u5165 CLI \u5206\u914D\u7684\u4F4D\u7F6E\u540E\u91CD\u8BD5\u3002`,
30328
+ false,
30329
+ { assetId: entry.assetId, role: entry.role }
30330
+ );
30331
+ }
30332
+ await safeRealPathWithin(absolutePath, realAssetsDirectory, `${assetPath} \u56FE\u7247`);
30333
+ const bytes = await regularImage(absolutePath);
30334
+ const metadata = await decodedImageMetadata(bytes, assetPath);
30335
+ const beforeSha256 = sha256(bytes);
30336
+ let finalPath = assetPath;
30337
+ let finalBytes = bytes;
30338
+ let alphaPreserved = true;
30339
+ if (bytes.byteLength > entry.limit) {
30340
+ const optimized = await optimizedImage(bytes, entry.target, metadata);
30341
+ optimizedDirectory ??= await ensureSafeOptimizedDirectory(
30342
+ assetsDirectory,
30343
+ realAssetsDirectory
30344
+ );
30345
+ const baseName = safeDerivativeBase(path5.basename(assetPath, path5.extname(assetPath)));
30346
+ const optimizedSha256 = sha256(optimized.bytes);
30347
+ const optimizedName = `${baseName}-${entry.role}-${beforeSha256.slice(0, 12)}-${optimizedSha256.slice(0, 12)}${optimized.extension}`;
30348
+ const optimizedAbsolute = path5.join(optimizedDirectory, optimizedName);
30349
+ const created = await writeExclusiveOrVerify(optimizedAbsolute, optimized.bytes);
30350
+ if (created) {
30351
+ createdFiles.push(optimizedAbsolute);
30352
+ }
30353
+ finalPath = path5.relative(projectDirectory, optimizedAbsolute).split(path5.sep).join("/");
30354
+ finalBytes = optimized.bytes;
30355
+ alphaPreserved = optimized.alphaPreserved;
30356
+ mutations.push({
30357
+ alphaPreserved,
30358
+ afterBytes: finalBytes.byteLength,
30359
+ afterSha256: sha256(finalBytes),
30360
+ beforeBytes: bytes.byteLength,
30361
+ beforeSha256,
30362
+ from: assetPath,
30363
+ role: entry.role,
30364
+ to: finalPath
30365
+ });
30366
+ } else {
30367
+ unchangedCount += 1;
30368
+ }
30369
+ if (entry.role === "cover") {
30370
+ frontmatter2["coverAsset"] = finalPath;
30371
+ frontmatter2["coverSha256"] = sha256(finalBytes);
30372
+ } else if (entry.record !== void 0) {
30373
+ if (finalPath !== assetPath && entry.record["sourcePath"] === void 0) {
30374
+ entry.record["sourcePath"] = assetPath;
30375
+ }
30376
+ entry.record["path"] = finalPath;
30377
+ entry.record["sha256"] = sha256(finalBytes);
30378
+ }
30379
+ }
30380
+ frontmatter2["inlineAssets"] = inlineAssets;
30381
+ const normalizedSource = `---
30382
+ ${(0, import_yaml2.stringify)(frontmatter2, { lineWidth: 0 }).trimEnd()}
30383
+ ---
30384
+ ${match[2]}`;
30385
+ await writePlanAtomically(visualPlanPath, source, normalizedSource);
30386
+ } catch (error) {
30387
+ await Promise.all(createdFiles.map((target) => unlink2(target).catch(() => void 0)));
30388
+ if (error instanceof WechatCliError) {
30389
+ throw error;
30390
+ }
30391
+ throw new WechatCliError(
30392
+ "DXC_PUBLISHING_IMAGE_OPTIMIZATION_FAILED",
30393
+ "\u56FE\u7247\u4F18\u5316\u672A\u5B8C\u6210\uFF1B\u539F\u56FE\u548C\u89C6\u89C9\u8BA1\u5212\u5747\u672A\u88AB\u8986\u76D6\u3002"
30394
+ );
30395
+ }
30396
+ return {
30397
+ command: "wechat.draft.optimize-images",
30398
+ data: { mutations, unchangedCount, visualPlanPath },
30399
+ ok: true
30400
+ };
30401
+ }
30402
+
29883
30403
  // apps/cli/src/publishing-assets.ts
29884
30404
  var MAXIMUM_ARTICLE_BYTES = 512 * 1024;
29885
- var MAXIMUM_IMAGE_BYTES = 5 * 1024 * 1024;
29886
- var MAXIMUM_INLINE_IMAGE_BYTES = 1024 * 1024 - 1;
29887
- var MAXIMUM_OPTIMIZABLE_IMAGE_BYTES = 32 * 1024 * 1024;
29888
- var MAXIMUM_INLINE_IMAGES = 20;
30405
+ var PUBLISHING_COVER_MAXIMUM_BYTES = 5 * 1024 * 1024;
30406
+ var PUBLISHING_INLINE_IMAGE_MAXIMUM_BYTES = 1024 * 1024 - 1;
30407
+ var PUBLISHING_OPTIMIZABLE_IMAGE_MAXIMUM_BYTES = 32 * 1024 * 1024;
30408
+ var PUBLISHING_INLINE_IMAGE_MAXIMUM_COUNT = 20;
30409
+ var MAXIMUM_IMAGE_BYTES = PUBLISHING_COVER_MAXIMUM_BYTES;
30410
+ var MAXIMUM_INLINE_IMAGE_BYTES = PUBLISHING_INLINE_IMAGE_MAXIMUM_BYTES;
30411
+ var MAXIMUM_INLINE_IMAGES = PUBLISHING_INLINE_IMAGE_MAXIMUM_COUNT;
29889
30412
  var SUPPORTED_IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".jpeg", ".jpg", ".png"]);
29890
- var VISUAL_PLAN_MAXIMUM_BYTES = 128 * 1024;
30413
+ var VISUAL_PLAN_MAXIMUM_BYTES2 = 128 * 1024;
29891
30414
  var ARTICLE_FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)([\s\S]*)$/u;
29892
- var VISUAL_PLAN_FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/u;
30415
+ var VISUAL_PLAN_FRONTMATTER2 = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/u;
29893
30416
  var LOCAL_WORKFLOW_FRONTMATTER_KEYS = ["dxc", "goldenLinesPath", "goldenLinesSha256"];
29894
- function recordValue(value) {
30417
+ var WORKFLOW_VISUAL_SIDECAR_FILE = ".dxc/visual-assets.json";
30418
+ var WORKFLOW_PUBLISHING_VISUAL_PLAN_FILE = ".dxc/publishing-visual-plan.md";
30419
+ var WORKFLOW_LOCAL_PREVIEW_FILE = ".dxc/previews/current.html";
30420
+ function recordValue2(value) {
29895
30421
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
29896
30422
  }
29897
30423
  async function validatePublishingImage(bytes, target) {
29898
30424
  let metadata;
29899
30425
  try {
29900
- metadata = await sharp(bytes, { failOn: "error", limitInputPixels: 8e7 }).metadata();
30426
+ metadata = await sharp2(bytes, { failOn: "error", limitInputPixels: 8e7 }).metadata();
29901
30427
  } catch {
29902
30428
  throw new WechatCliError(
29903
30429
  "DXC_PUBLISHING_INPUT_INVALID",
29904
- `\u56FE\u7247 ${path5.basename(target)} \u65E0\u6CD5\u88AB\u5B89\u5168\u89E3\u7801\uFF1B\u4EC5\u652F\u6301\u6709\u6548 PNG/JPEG\u3002`
30430
+ `\u56FE\u7247 ${path6.basename(target)} \u65E0\u6CD5\u88AB\u5B89\u5168\u89E3\u7801\uFF1B\u4EC5\u652F\u6301\u6709\u6548 PNG/JPEG\u3002`
29905
30431
  );
29906
30432
  }
29907
- const extension = path5.extname(target).toLowerCase();
30433
+ const extension = path6.extname(target).toLowerCase();
29908
30434
  const expectedFormat = extension === ".png" ? "png" : extension === ".jpg" || extension === ".jpeg" ? "jpeg" : void 0;
29909
30435
  if (expectedFormat === void 0 || metadata.format !== expectedFormat || metadata.width === void 0 || metadata.height === void 0) {
29910
30436
  throw new WechatCliError(
29911
30437
  "DXC_PUBLISHING_INPUT_INVALID",
29912
- `\u56FE\u7247 ${path5.basename(target)} \u7684\u6269\u5C55\u540D\u4E0E\u771F\u5B9E PNG/JPEG \u683C\u5F0F\u4E0D\u4E00\u81F4\u3002`
30438
+ `\u56FE\u7247 ${path6.basename(target)} \u7684\u6269\u5C55\u540D\u4E0E\u771F\u5B9E PNG/JPEG \u683C\u5F0F\u4E0D\u4E00\u81F4\u3002`
29913
30439
  );
29914
30440
  }
29915
30441
  }
29916
30442
  async function readPublishingInput(inputPath, maximumBytes, role = "article") {
29917
- const target = path5.resolve(inputPath);
30443
+ const target = path6.resolve(inputPath);
29918
30444
  let handle;
29919
30445
  try {
29920
- handle = await open(target, fileConstants.O_RDONLY | fileConstants.O_NOFOLLOW);
30446
+ handle = await open2(target, fileConstants2.O_RDONLY | fileConstants2.O_NOFOLLOW);
29921
30447
  const metadata = await handle.stat();
29922
30448
  if (metadata.isFile() && metadata.size > maximumBytes) {
29923
- const fileName = path5.basename(target);
30449
+ const fileName = path6.basename(target);
29924
30450
  if (role === "inline-image") {
29925
30451
  throw new WechatCliError(
29926
30452
  "DXC_PUBLISHING_INLINE_IMAGE_TOO_LARGE",
@@ -29941,7 +30467,7 @@ async function readPublishingInput(inputPath, maximumBytes, role = "article") {
29941
30467
  if (!metadata.isFile() || metadata.size <= 0) {
29942
30468
  throw new WechatCliError(
29943
30469
  "DXC_PUBLISHING_INPUT_INVALID",
29944
- `\u6587\u4EF6 ${path5.basename(target)} \u4E0D\u662F\u53EF\u8BFB\u53D6\u7684\u975E\u7A7A\u666E\u901A\u6587\u4EF6\u3002`
30470
+ `\u6587\u4EF6 ${path6.basename(target)} \u4E0D\u662F\u53EF\u8BFB\u53D6\u7684\u975E\u7A7A\u666E\u901A\u6587\u4EF6\u3002`
29945
30471
  );
29946
30472
  }
29947
30473
  const bytes = await handle.readFile();
@@ -29955,7 +30481,7 @@ async function readPublishingInput(inputPath, maximumBytes, role = "article") {
29955
30481
  }
29956
30482
  throw new WechatCliError(
29957
30483
  "DXC_PUBLISHING_INPUT_INVALID",
29958
- `\u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6 ${path5.basename(target)}\uFF1B\u8BF7\u786E\u8BA4\u8DEF\u5F84\u3001\u6743\u9650\u4E14\u6587\u4EF6\u4E0D\u662F\u7B26\u53F7\u94FE\u63A5\u3002`
30484
+ `\u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6 ${path6.basename(target)}\uFF1B\u8BF7\u786E\u8BA4\u8DEF\u5F84\u3001\u6743\u9650\u4E14\u6587\u4EF6\u4E0D\u662F\u7B26\u53F7\u94FE\u63A5\u3002`
29959
30485
  );
29960
30486
  } finally {
29961
30487
  await handle?.close();
@@ -29964,16 +30490,16 @@ async function readPublishingInput(inputPath, maximumBytes, role = "article") {
29964
30490
  async function readContainedPublishingInput(inputPath, maximumBytes, allowedDirectories, role) {
29965
30491
  try {
29966
30492
  const [target, ...directories] = await Promise.all([
29967
- realpath(path5.resolve(inputPath)),
29968
- ...allowedDirectories.map((directory) => realpath(path5.resolve(directory)))
30493
+ realpath2(path6.resolve(inputPath)),
30494
+ ...allowedDirectories.map((directory) => realpath2(path6.resolve(directory)))
29969
30495
  ]);
29970
30496
  const contained = directories.some(
29971
- (directory) => target !== directory && target.startsWith(`${directory}${path5.sep}`)
30497
+ (directory) => target !== directory && target.startsWith(`${directory}${path6.sep}`)
29972
30498
  );
29973
30499
  if (!contained) {
29974
30500
  throw new WechatCliError(
29975
30501
  "DXC_PUBLISHING_INPUT_INVALID",
29976
- `\u81EA\u52A8\u89E3\u6790\u7684\u7D20\u6750 ${path5.basename(inputPath)} \u4E0D\u5728\u6587\u7AE0\u76EE\u5F55\u6216\u663E\u5F0F\u7D20\u6750\u76EE\u5F55\u4E2D\u3002`
30502
+ `\u81EA\u52A8\u89E3\u6790\u7684\u7D20\u6750 ${path6.basename(inputPath)} \u4E0D\u5728\u6587\u7AE0\u76EE\u5F55\u6216\u663E\u5F0F\u7D20\u6750\u76EE\u5F55\u4E2D\u3002`
29977
30503
  );
29978
30504
  }
29979
30505
  } catch (error) {
@@ -29982,13 +30508,13 @@ async function readContainedPublishingInput(inputPath, maximumBytes, allowedDire
29982
30508
  }
29983
30509
  throw new WechatCliError(
29984
30510
  "DXC_PUBLISHING_INPUT_INVALID",
29985
- `\u65E0\u6CD5\u5B89\u5168\u89E3\u6790\u7D20\u6750 ${path5.basename(inputPath)}\u3002`
30511
+ `\u65E0\u6CD5\u5B89\u5168\u89E3\u6790\u7D20\u6750 ${path6.basename(inputPath)}\u3002`
29986
30512
  );
29987
30513
  }
29988
30514
  return readPublishingInput(inputPath, maximumBytes, role);
29989
30515
  }
29990
30516
  function publishingImageMediaType(inputPath) {
29991
- const extension = path5.extname(inputPath).toLowerCase();
30517
+ const extension = path6.extname(inputPath).toLowerCase();
29992
30518
  if (extension === ".png") {
29993
30519
  return "image/png";
29994
30520
  }
@@ -30001,13 +30527,13 @@ function safeFileName(value) {
30001
30527
  return value.normalize("NFKC").replaceAll(/[^A-Za-z0-9._-]+/gu, "-").replaceAll(/^-+|-+$/gu, "").slice(0, 80);
30002
30528
  }
30003
30529
  function isWithinDirectory(target, directory) {
30004
- const relative = path5.relative(directory, target);
30005
- return relative === "" || !relative.startsWith(`..${path5.sep}`) && relative !== "..";
30530
+ const relative = path6.relative(directory, target);
30531
+ return relative === "" || !relative.startsWith(`..${path6.sep}`) && relative !== "..";
30006
30532
  }
30007
30533
  function commonDirectory(left, right) {
30008
- let directory = path5.resolve(left);
30534
+ let directory = path6.resolve(left);
30009
30535
  while (!isWithinDirectory(right, directory)) {
30010
- const parent = path5.dirname(directory);
30536
+ const parent = path6.dirname(directory);
30011
30537
  if (parent === directory) {
30012
30538
  return directory;
30013
30539
  }
@@ -30019,7 +30545,7 @@ async function collectedImages(directoryInput, articleDirectory) {
30019
30545
  if (directoryInput === void 0) {
30020
30546
  return [];
30021
30547
  }
30022
- const directory = path5.resolve(directoryInput);
30548
+ const directory = path6.resolve(directoryInput);
30023
30549
  let entries;
30024
30550
  try {
30025
30551
  const metadata = await lstat4(directory);
@@ -30031,12 +30557,12 @@ async function collectedImages(directoryInput, articleDirectory) {
30031
30557
  }
30032
30558
  const sharedDirectory = commonDirectory(articleDirectory, directory);
30033
30559
  const [resolvedDirectory, resolvedSharedDirectory] = await Promise.all([
30034
- realpath(directory),
30035
- realpath(sharedDirectory)
30560
+ realpath2(directory),
30561
+ realpath2(sharedDirectory)
30036
30562
  ]);
30037
- const expectedDirectory = path5.resolve(
30563
+ const expectedDirectory = path6.resolve(
30038
30564
  resolvedSharedDirectory,
30039
- path5.relative(sharedDirectory, directory)
30565
+ path6.relative(sharedDirectory, directory)
30040
30566
  );
30041
30567
  if (resolvedDirectory !== expectedDirectory) {
30042
30568
  throw new WechatCliError(
@@ -30052,8 +30578,8 @@ async function collectedImages(directoryInput, articleDirectory) {
30052
30578
  throw new WechatCliError("DXC_PUBLISHING_ASSETS_DIRECTORY_INVALID");
30053
30579
  }
30054
30580
  return entries.filter(
30055
- (entry) => entry.isFile() && SUPPORTED_IMAGE_EXTENSIONS.has(path5.extname(entry.name).toLowerCase())
30056
- ).map((entry) => path5.join(directory, entry.name)).sort((left, right) => left.localeCompare(right, "zh-CN"));
30581
+ (entry) => entry.isFile() && SUPPORTED_IMAGE_EXTENSIONS.has(path6.extname(entry.name).toLowerCase())
30582
+ ).map((entry) => path6.join(directory, entry.name)).sort((left, right) => left.localeCompare(right, "zh-CN"));
30057
30583
  }
30058
30584
  function injectCollectedImages(source, images) {
30059
30585
  const diagnosticText = (value, maximum = 120) => {
@@ -30083,8 +30609,14 @@ function injectCollectedImages(source, images) {
30083
30609
  }
30084
30610
  const insertions = /* @__PURE__ */ new Map();
30085
30611
  for (const [index, image] of images.entries()) {
30086
- let headingLine = headingLines[index];
30087
- if (image.placementAnchor !== void 0) {
30612
+ let headingLine = image.headingIndex === void 0 ? headingLines[index] : headingLines[image.headingIndex];
30613
+ if (image.headingIndex !== void 0 && headingLine === void 0) {
30614
+ throw new WechatCliError(
30615
+ "DXC_PUBLISHING_VISUAL_ANCHOR_INVALID",
30616
+ `\u89C6\u89C9\u8BA1\u5212\u7AE0\u8282\u5E8F\u53F7 ${image.headingIndex + 1} \u4E0D\u5B58\u5728\u3002`
30617
+ );
30618
+ }
30619
+ if (image.headingIndex === void 0 && image.placementAnchor !== void 0) {
30088
30620
  const match = /^after-heading:(.+)$/u.exec(image.placementAnchor);
30089
30621
  const requestedHeading = match?.[1]?.trim().replace(/^##\s+/u, "").replace(/\s+#+\s*$/u, "").trim();
30090
30622
  const matches = requestedHeading === void 0 ? void 0 : headingLineByText.get(requestedHeading);
@@ -30114,12 +30646,12 @@ function injectCollectedImages(source, images) {
30114
30646
  return output.join("\n");
30115
30647
  }
30116
30648
  function parsedVisualPlan(source) {
30117
- const yamlSource = VISUAL_PLAN_FRONTMATTER.exec(source)?.[1];
30649
+ const yamlSource = VISUAL_PLAN_FRONTMATTER2.exec(source)?.[1];
30118
30650
  if (yamlSource === void 0) {
30119
30651
  throw new WechatCliError("DXC_PUBLISHING_VISUAL_PLAN_INVALID");
30120
30652
  }
30121
30653
  try {
30122
- const plan = recordValue((0, import_yaml2.parse)(yamlSource, { maxAliasCount: 10, uniqueKeys: true }));
30654
+ const plan = recordValue2((0, import_yaml3.parse)(yamlSource, { maxAliasCount: 10, uniqueKeys: true }));
30123
30655
  if (plan === void 0) {
30124
30656
  throw new WechatCliError("DXC_PUBLISHING_VISUAL_PLAN_INVALID");
30125
30657
  }
@@ -30131,29 +30663,299 @@ function parsedVisualPlan(source) {
30131
30663
  throw new WechatCliError("DXC_PUBLISHING_VISUAL_PLAN_INVALID");
30132
30664
  }
30133
30665
  }
30134
- function validateVisualPlanPlacementAnchors(articleSource, visualPlanSource) {
30135
- const plan = parsedVisualPlan(visualPlanSource);
30136
- const inlineAssets = Array.isArray(plan["inlineAssets"]) ? plan["inlineAssets"] : [];
30137
- const images = inlineAssets.map((rawAsset) => {
30138
- const asset = recordValue(rawAsset);
30139
- if (typeof asset?.["path"] !== "string" || typeof asset["placementAnchor"] !== "string") {
30666
+ function parsedWorkflowVisualAssetsSidecar(value) {
30667
+ const sidecar = recordValue2(value);
30668
+ const assets = sidecar?.["assets"];
30669
+ if (sidecar?.["version"] !== 1 || typeof sidecar["articleSha256"] !== "string" || !/^[a-f0-9]{64}$/u.test(sidecar["articleSha256"]) || typeof sidecar["visualPlanSha256"] !== "string" || !/^[a-f0-9]{64}$/u.test(sidecar["visualPlanSha256"]) || typeof sidecar["localPreviewPath"] !== "string" || typeof sidecar["publishingVisualPlanPath"] !== "string" || !Array.isArray(assets) || assets.length < 1 || assets.length > MAXIMUM_INLINE_IMAGES + 1) {
30670
+ throw new WechatCliError(
30671
+ "DXC_PUBLISHING_VISUAL_PLAN_INVALID",
30672
+ "\u89C6\u89C9\u7D20\u6750 sidecar\uFF08\u65C1\u8F66\u5143\u6570\u636E\uFF09\u65E0\u6548\u3002"
30673
+ );
30674
+ }
30675
+ const parsedAssets = assets.map((entry, index) => {
30676
+ const asset = recordValue2(entry);
30677
+ const expectedAssetId = index === 0 ? "cover" : `inline-${String(index).padStart(2, "0")}`;
30678
+ const relativePath = asset?.["relativePath"];
30679
+ const sectionId = asset?.["sectionId"];
30680
+ if (asset?.["assetId"] !== expectedAssetId || asset["role"] !== (index === 0 ? "cover" : "inline") || typeof relativePath !== "string" || relativePath.includes("\\") || path6.posix.isAbsolute(relativePath) || relativePath.split("/").some((segment) => segment === "" || segment === "." || segment === "..") || typeof asset["sha256"] !== "string" || !/^[a-f0-9]{64}$/u.test(asset["sha256"]) || index > 0 && (typeof sectionId !== "string" || !/^sec_[0-9]{2}_[a-f0-9]{8}$/u.test(sectionId)) || index === 0 && sectionId !== void 0) {
30140
30681
  throw new WechatCliError(
30141
30682
  "DXC_PUBLISHING_VISUAL_PLAN_INVALID",
30142
- "\u6B63\u6587\u914D\u56FE\u9700\u8981 path \u548C placementAnchor\u3002"
30683
+ `\u89C6\u89C9\u7D20\u6750 ${expectedAssetId} \u7684 sidecar\uFF08\u65C1\u8F66\u5143\u6570\u636E\uFF09\u65E0\u6548\u3002`
30143
30684
  );
30144
30685
  }
30686
+ const parsedSectionId = typeof sectionId === "string" ? sectionId : void 0;
30145
30687
  return {
30146
- alt: "\u89C6\u89C9\u8BA1\u5212\u951A\u70B9\u6821\u9A8C",
30147
- logicalPath: asset["path"],
30148
- placementAnchor: asset["placementAnchor"]
30688
+ assetId: expectedAssetId,
30689
+ relativePath,
30690
+ role: index === 0 ? "cover" : "inline",
30691
+ ...parsedSectionId === void 0 ? {} : { sectionId: parsedSectionId },
30692
+ sha256: asset["sha256"]
30149
30693
  };
30150
30694
  });
30151
- injectCollectedImages(articleSource, images);
30695
+ return {
30696
+ articleSha256: sidecar["articleSha256"],
30697
+ assets: parsedAssets,
30698
+ localPreviewPath: sidecar["localPreviewPath"],
30699
+ publishingVisualPlanPath: sidecar["publishingVisualPlanPath"],
30700
+ version: 1,
30701
+ visualPlanSha256: sidecar["visualPlanSha256"]
30702
+ };
30703
+ }
30704
+ function workflowSha256(value) {
30705
+ return createHash5("sha256").update(value).digest("hex");
30706
+ }
30707
+ function semanticVisualPlan(source, sections) {
30708
+ const plan = parsedVisualPlan(source);
30709
+ const cover = recordValue2(plan["cover"]);
30710
+ const inline = Array.isArray(plan["inline"]) ? plan["inline"] : [];
30711
+ if (typeof cover?.["purpose"] !== "string" || cover["purpose"].trim().length === 0) {
30712
+ throw new WechatCliError(
30713
+ "DXC_PUBLISHING_VISUAL_PLAN_INVALID",
30714
+ "\u89C6\u89C9\u8BA1\u5212\u9700\u8981\u8BF4\u660E\u5C01\u9762\u7684\u5185\u5BB9\u76EE\u7684\u3002"
30715
+ );
30716
+ }
30717
+ const sectionIds = new Set(sections.map((section) => section.sectionId));
30718
+ if (inline.length > MAXIMUM_INLINE_IMAGES) {
30719
+ throw new WechatCliError("DXC_PUBLISHING_TOO_MANY_INLINE_IMAGES");
30720
+ }
30721
+ const seenSections = /* @__PURE__ */ new Set();
30722
+ const parsedInline = inline.map((entry) => {
30723
+ const item = recordValue2(entry);
30724
+ if (typeof item?.["sectionId"] !== "string" || !sectionIds.has(item["sectionId"]) || typeof item["purpose"] !== "string" || item["purpose"].trim().length === 0 || seenSections.has(item["sectionId"])) {
30725
+ throw new WechatCliError(
30726
+ "DXC_PUBLISHING_VISUAL_PLAN_INVALID",
30727
+ "\u6B63\u6587\u89C6\u89C9\u8BA1\u5212\u5FC5\u987B\u4E3A\u6709\u6548\u4E14\u552F\u4E00\u7684 sectionId \u586B\u5199\u56FE\u7247\u76EE\u7684\u3002"
30728
+ );
30729
+ }
30730
+ seenSections.add(item["sectionId"]);
30731
+ return { purpose: item["purpose"].trim(), sectionId: item["sectionId"] };
30732
+ });
30733
+ return { cover: { purpose: cover["purpose"].trim() }, inline: parsedInline };
30734
+ }
30735
+ function parseWorkflowArticleSections(articleSource) {
30736
+ const sourceHash = workflowSha256(articleSource);
30737
+ const lines = articleSource.split(/\r?\n/u);
30738
+ const sections = [];
30739
+ let inFence = false;
30740
+ for (const line of lines) {
30741
+ if (/^\s*```/u.test(line)) {
30742
+ inFence = !inFence;
30743
+ continue;
30744
+ }
30745
+ if (inFence || !/^##\s+\S/u.test(line)) {
30746
+ continue;
30747
+ }
30748
+ const heading = line.replace(/^##\s+/u, "").replace(/\s+#+\s*$/u, "").trim();
30749
+ const ordinal = sections.length + 1;
30750
+ const identity = workflowSha256(`${sourceHash}:${ordinal}:${heading}`).slice(0, 8);
30751
+ sections.push({
30752
+ heading,
30753
+ sectionId: `sec_${String(ordinal).padStart(2, "0")}_${identity}`
30754
+ });
30755
+ }
30756
+ return sections;
30757
+ }
30758
+ function validateWorkflowSemanticVisualPlan(input) {
30759
+ return semanticVisualPlan(input.source, input.sections);
30760
+ }
30761
+ function workflowVisualAssetAssignments(input) {
30762
+ const plan = semanticVisualPlan(input.visualPlanSource, input.sections);
30763
+ const directory = path6.join(
30764
+ input.assetsDirectory ?? path6.join(input.projectDirectory, "assets"),
30765
+ "visuals"
30766
+ );
30767
+ return [
30768
+ { assetId: "cover", path: path6.join(directory, "cover.png"), role: "cover" },
30769
+ ...plan.inline.map((item, index) => ({
30770
+ assetId: `inline-${String(index + 1).padStart(2, "0")}`,
30771
+ path: path6.join(directory, `inline-${String(index + 1).padStart(2, "0")}.png`),
30772
+ role: "inline",
30773
+ sectionId: item.sectionId
30774
+ }))
30775
+ ];
30776
+ }
30777
+ function localWorkflowPreviewHtml(prepared) {
30778
+ const imageSources = new Map(
30779
+ prepared.inlineAssets.map((asset) => [
30780
+ asset.logicalPath,
30781
+ `data:${asset.mediaType};base64,${asset.bytes.toString("base64")}`
30782
+ ])
30783
+ );
30784
+ const article = rewriteWechatAssetSources(prepared.localRender.html, imageSources);
30785
+ const coverMediaType = publishingImageMediaType(prepared.cover.path);
30786
+ return `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="referrer" content="no-referrer"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src data:; style-src 'unsafe-inline'"><title>\u672C\u5730\u5E26\u56FE\u9884\u89C8</title></head><body><main><p>\u672C\u5730\u5185\u5BB9\u4E0E\u914D\u56FE\u9884\u89C8\uFF1B\u6700\u7EC8\u4EA4\u4ED8\u4EE5\u4E91\u7AEF\u4E0D\u53EF\u53D8\u9884\u89C8\u4E3A\u51C6\u3002</p><img alt="\u5C01\u9762" style="max-width:100%" src="data:${coverMediaType};base64,${prepared.cover.bytes.toString("base64")}"><article>${article}</article></main></body></html>`;
30787
+ }
30788
+ async function finalizeWorkflowVisualAssets(input) {
30789
+ const plan = semanticVisualPlan(input.visualPlanSource, input.sections);
30790
+ const expected = workflowVisualAssetAssignments(input);
30791
+ if (expected.length !== input.assignments.length || expected.some(
30792
+ (asset, index) => asset.assetId !== input.assignments[index]?.assetId || path6.resolve(asset.path) !== path6.resolve(input.assignments[index]?.path)
30793
+ )) {
30794
+ throw new WechatCliError(
30795
+ "DXC_PUBLISHING_VISUAL_PLAN_INVALID",
30796
+ "\u89C6\u89C9\u7D20\u6750\u5206\u914D\u4E0E\u5F53\u524D\u5DE5\u4F5C\u9879\u4E0D\u4E00\u81F4\u3002"
30797
+ );
30798
+ }
30799
+ await mkdir4(path6.join(input.assetsDirectory, "visuals"), { recursive: true });
30800
+ const sectionIndex = new Map(input.sections.map((section, index) => [section.sectionId, index]));
30801
+ const publishingPlanPath = path6.join(
30802
+ input.projectDirectory,
30803
+ WORKFLOW_PUBLISHING_VISUAL_PLAN_FILE
30804
+ );
30805
+ const relative = (target) => path6.relative(input.projectDirectory, target).split(path6.sep).join("/");
30806
+ const publishingPlan = {
30807
+ assetsDirectory: relative(input.assetsDirectory),
30808
+ coverAsset: relative(expected[0].path),
30809
+ inlineAssets: expected.slice(1).map((asset, index) => ({
30810
+ assetId: asset.assetId,
30811
+ path: relative(asset.path),
30812
+ placementHeadingIndex: sectionIndex.get(asset.sectionId ?? ""),
30813
+ purpose: plan.inline[index]?.purpose,
30814
+ source: "agent-generated"
30815
+ }))
30816
+ };
30817
+ await writePrivateBuffer(
30818
+ publishingPlanPath,
30819
+ Buffer.from(`---
30820
+ ${(0, import_yaml3.stringify)(publishingPlan, { lineWidth: 0 }).trimEnd()}
30821
+ ---
30822
+ `, "utf8")
30823
+ );
30824
+ await optimizePublishingImages({
30825
+ projectDirectory: input.projectDirectory,
30826
+ visualPlanFile: publishingPlanPath
30827
+ });
30828
+ const prepared = await preparePublishingDraft({
30829
+ articleFile: input.articleFile,
30830
+ assetsDirectory: input.assetsDirectory,
30831
+ coverFile: "auto",
30832
+ visualPlanFile: publishingPlanPath
30833
+ });
30834
+ const localPreviewPath = path6.join(input.projectDirectory, WORKFLOW_LOCAL_PREVIEW_FILE);
30835
+ await writePrivateBuffer(
30836
+ localPreviewPath,
30837
+ Buffer.from(localWorkflowPreviewHtml(prepared), "utf8")
30838
+ );
30839
+ const materializedPlan = parsedVisualPlan(await readFile3(publishingPlanPath, "utf8"));
30840
+ const materializedInline = Array.isArray(materializedPlan["inlineAssets"]) ? materializedPlan["inlineAssets"] : [];
30841
+ const materializedCover = String(materializedPlan["coverAsset"]);
30842
+ const materialized = [
30843
+ { assetId: "cover", path: materializedCover, role: "cover" },
30844
+ ...materializedInline.map((entry, index) => {
30845
+ const item = recordValue2(entry);
30846
+ const sectionId = expected[index + 1]?.sectionId;
30847
+ return {
30848
+ assetId: expected[index + 1]?.assetId ?? `inline-${index + 1}`,
30849
+ path: String(item?.["path"]),
30850
+ role: "inline",
30851
+ ...sectionId === void 0 ? {} : { sectionId }
30852
+ };
30853
+ })
30854
+ ];
30855
+ const sidecar = {
30856
+ articleSha256: workflowSha256(input.articleSource),
30857
+ assets: await Promise.all(
30858
+ materialized.map(async (asset) => {
30859
+ const bytes = await readFile3(path6.join(input.projectDirectory, asset.path));
30860
+ return {
30861
+ assetId: asset.assetId,
30862
+ relativePath: asset.path,
30863
+ role: asset.role,
30864
+ ...asset.sectionId === void 0 ? {} : { sectionId: asset.sectionId },
30865
+ sha256: workflowSha256(bytes)
30866
+ };
30867
+ })
30868
+ ),
30869
+ localPreviewPath,
30870
+ publishingVisualPlanPath: publishingPlanPath,
30871
+ version: 1,
30872
+ visualPlanSha256: workflowSha256(input.visualPlanSource)
30873
+ };
30874
+ await writePrivateJson(path6.join(input.projectDirectory, WORKFLOW_VISUAL_SIDECAR_FILE), sidecar);
30875
+ return sidecar;
30876
+ }
30877
+ async function readWorkflowVisualAssetsSidecar(projectDirectory) {
30878
+ const input = await readPublishingInput(
30879
+ path6.join(projectDirectory, WORKFLOW_VISUAL_SIDECAR_FILE),
30880
+ VISUAL_PLAN_MAXIMUM_BYTES2
30881
+ );
30882
+ try {
30883
+ return parsedWorkflowVisualAssetsSidecar(JSON.parse(input.bytes.toString("utf8")));
30884
+ } catch (error) {
30885
+ if (error instanceof WechatCliError) {
30886
+ throw error;
30887
+ }
30888
+ throw new WechatCliError(
30889
+ "DXC_PUBLISHING_VISUAL_PLAN_INVALID",
30890
+ "\u89C6\u89C9\u7D20\u6750 sidecar\uFF08\u65C1\u8F66\u5143\u6570\u636E\uFF09\u65E0\u6CD5\u89E3\u6790\u3002"
30891
+ );
30892
+ }
30893
+ }
30894
+ async function workflowVisualAssetsFingerprint(projectDirectory) {
30895
+ try {
30896
+ const sidecar = await readWorkflowVisualAssetsSidecar(projectDirectory);
30897
+ const assets = await Promise.all(
30898
+ sidecar.assets.map(async (asset) => {
30899
+ const target = path6.resolve(projectDirectory, asset.relativePath);
30900
+ if (!isWithinDirectory(target, projectDirectory)) {
30901
+ return { assetId: asset.assetId, state: "invalid" };
30902
+ }
30903
+ try {
30904
+ const input = await readPublishingInput(
30905
+ target,
30906
+ asset.role === "cover" ? MAXIMUM_IMAGE_BYTES : MAXIMUM_INLINE_IMAGE_BYTES,
30907
+ asset.role === "cover" ? "cover" : "inline-image"
30908
+ );
30909
+ return { assetId: asset.assetId, sha256: workflowSha256(input.bytes) };
30910
+ } catch {
30911
+ return { assetId: asset.assetId, state: "invalid" };
30912
+ }
30913
+ })
30914
+ );
30915
+ return workflowSha256(JSON.stringify({ assets, sidecar }));
30916
+ } catch {
30917
+ return workflowSha256("visual-assets:missing-or-invalid");
30918
+ }
30919
+ }
30920
+ async function validateWorkflowVisualAssets(input) {
30921
+ const sidecar = await readWorkflowVisualAssetsSidecar(input.projectDirectory);
30922
+ if (sidecar.articleSha256 !== workflowSha256(input.articleSource) || sidecar.visualPlanSha256 !== workflowSha256(input.visualPlanSource) || path6.resolve(sidecar.localPreviewPath) !== path6.resolve(input.projectDirectory, WORKFLOW_LOCAL_PREVIEW_FILE) || path6.resolve(sidecar.publishingVisualPlanPath) !== path6.resolve(input.projectDirectory, WORKFLOW_PUBLISHING_VISUAL_PLAN_FILE)) {
30923
+ throw new WechatCliError(
30924
+ "DXC_PUBLISHING_VISUAL_PLAN_INVALID",
30925
+ "\u6587\u7AE0\u3001\u89C6\u89C9\u8BA1\u5212\u6216\u672C\u5730\u9884\u89C8\u4E0E\u89C6\u89C9\u7D20\u6750 sidecar\uFF08\u65C1\u8F66\u5143\u6570\u636E\uFF09\u4E0D\u4E00\u81F4\u3002"
30926
+ );
30927
+ }
30928
+ for (const asset of sidecar.assets) {
30929
+ const target = path6.resolve(input.projectDirectory, asset.relativePath);
30930
+ if (!isWithinDirectory(target, path6.resolve(input.assetsDirectory))) {
30931
+ throw new WechatCliError(
30932
+ "DXC_PUBLISHING_VISUAL_PLAN_INVALID",
30933
+ `\u89C6\u89C9\u7D20\u6750 ${asset.assetId} \u4E0D\u5728\u9879\u76EE\u7D20\u6750\u76EE\u5F55\u4E2D\u3002`
30934
+ );
30935
+ }
30936
+ const materialized = await readPublishingInput(
30937
+ target,
30938
+ asset.role === "cover" ? MAXIMUM_IMAGE_BYTES : MAXIMUM_INLINE_IMAGE_BYTES,
30939
+ asset.role === "cover" ? "cover" : "inline-image"
30940
+ );
30941
+ if (workflowSha256(materialized.bytes) !== asset.sha256) {
30942
+ throw new WechatCliError(
30943
+ "DXC_PUBLISHING_VISUAL_PLAN_INVALID",
30944
+ `\u89C6\u89C9\u7D20\u6750 ${asset.assetId} \u5DF2\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u5B8C\u6210 visual-plan \u9636\u6BB5\u3002`
30945
+ );
30946
+ }
30947
+ }
30948
+ await readPublishingInput(
30949
+ sidecar.publishingVisualPlanPath,
30950
+ VISUAL_PLAN_MAXIMUM_BYTES2,
30951
+ "visual-plan"
30952
+ );
30953
+ return sidecar;
30152
30954
  }
30153
30955
  async function plannedVisualAssets(visualPlanFile, assetsDirectoryInput, maximumCoverBytes = MAXIMUM_IMAGE_BYTES, maximumInlineBytes = MAXIMUM_INLINE_IMAGE_BYTES) {
30154
30956
  let source;
30155
30957
  try {
30156
- source = (await readPublishingInput(visualPlanFile, VISUAL_PLAN_MAXIMUM_BYTES, "visual-plan")).bytes.toString("utf8");
30958
+ source = (await readPublishingInput(visualPlanFile, VISUAL_PLAN_MAXIMUM_BYTES2, "visual-plan")).bytes.toString("utf8");
30157
30959
  } catch {
30158
30960
  throw new WechatCliError("DXC_PUBLISHING_VISUAL_PLAN_INVALID");
30159
30961
  }
@@ -30169,12 +30971,12 @@ async function plannedVisualAssets(visualPlanFile, assetsDirectoryInput, maximum
30169
30971
  "\u4F7F\u7528\u89C6\u89C9\u8BA1\u5212\u65F6\u5FC5\u987B\u663E\u5F0F\u63D0\u4F9B\u5176\u58F0\u660E\u7684\u7D20\u6750\u76EE\u5F55\u3002"
30170
30972
  );
30171
30973
  }
30172
- const assetsDirectory = path5.resolve(assetsDirectoryInput);
30974
+ const assetsDirectory = path6.resolve(assetsDirectoryInput);
30173
30975
  const projectDirectory = commonDirectory(
30174
- path5.dirname(path5.resolve(visualPlanFile)),
30976
+ path6.dirname(path6.resolve(visualPlanFile)),
30175
30977
  assetsDirectory
30176
30978
  );
30177
- const declaredAssetsDirectory = typeof plan["assetsDirectory"] === "string" ? path5.resolve(projectDirectory, plan["assetsDirectory"]) : assetsDirectory;
30979
+ const declaredAssetsDirectory = typeof plan["assetsDirectory"] === "string" ? path6.resolve(projectDirectory, plan["assetsDirectory"]) : assetsDirectory;
30178
30980
  if (!isWithinDirectory(assetsDirectory, declaredAssetsDirectory)) {
30179
30981
  throw new WechatCliError(
30180
30982
  "DXC_PUBLISHING_VISUAL_PLAN_INVALID",
@@ -30182,7 +30984,7 @@ async function plannedVisualAssets(visualPlanFile, assetsDirectoryInput, maximum
30182
30984
  );
30183
30985
  }
30184
30986
  const resolvePlannedAsset = (assetPath) => {
30185
- const target = path5.resolve(projectDirectory, assetPath);
30987
+ const target = path6.resolve(projectDirectory, assetPath);
30186
30988
  if (!isWithinDirectory(target, declaredAssetsDirectory) || !isWithinDirectory(target, assetsDirectory)) {
30187
30989
  throw new WechatCliError("DXC_PUBLISHING_VISUAL_PLAN_INVALID");
30188
30990
  }
@@ -30199,18 +31001,19 @@ async function plannedVisualAssets(visualPlanFile, assetsDirectoryInput, maximum
30199
31001
  coverAbsolutePath,
30200
31002
  inline: await Promise.all(
30201
31003
  inlineAssets.map(async (rawAsset) => {
30202
- const asset = recordValue(rawAsset);
30203
- if (typeof asset?.["path"] !== "string" || typeof asset["placementAnchor"] !== "string") {
31004
+ const asset = recordValue2(rawAsset);
31005
+ const headingIndex = asset?.["placementHeadingIndex"];
31006
+ if (typeof asset?.["path"] !== "string" || typeof asset["placementAnchor"] !== "string" && (!Number.isInteger(headingIndex) || Number(headingIndex) < 0)) {
30204
31007
  throw new WechatCliError(
30205
31008
  "DXC_PUBLISHING_VISUAL_PLAN_INVALID",
30206
- "\u6B63\u6587\u914D\u56FE\u9700\u8981 path \u548C placementAnchor\u3002"
31009
+ "\u6B63\u6587\u914D\u56FE\u9700\u8981\u5B89\u5168\u8DEF\u5F84\u548C\u6709\u6548\u7AE0\u8282\u5F15\u7528\u3002"
30207
31010
  );
30208
31011
  }
30209
31012
  const absolutePath = resolvePlannedAsset(asset["path"]);
30210
31013
  if (!isWithinDirectory(absolutePath, assetsDirectory)) {
30211
31014
  throw new WechatCliError(
30212
31015
  "DXC_PUBLISHING_VISUAL_PLAN_INVALID",
30213
- `\u89C6\u89C9\u8BA1\u5212\u58F0\u660E\u7684\u7D20\u6750 ${path5.posix.basename(asset["path"])} \u4E0D\u5728\u6307\u5B9A\u7D20\u6750\u76EE\u5F55\u4E2D\u3002`
31016
+ `\u89C6\u89C9\u8BA1\u5212\u58F0\u660E\u7684\u7D20\u6750 ${path6.posix.basename(asset["path"])} \u4E0D\u5728\u6307\u5B9A\u7D20\u6750\u76EE\u5F55\u4E2D\u3002`
30214
31017
  );
30215
31018
  }
30216
31019
  await readContainedPublishingInput(
@@ -30226,13 +31029,14 @@ async function plannedVisualAssets(visualPlanFile, assetsDirectoryInput, maximum
30226
31029
  "\u6B63\u6587\u914D\u56FE\u7684\u539F\u59CB\u8DEF\u5F84\u5FC5\u987B\u662F\u9879\u76EE\u5185\u5B89\u5168\u76F8\u5BF9\u8DEF\u5F84\u3002"
30227
31030
  );
30228
31031
  }
30229
- const digest = createHash4("sha256").update(absolutePath).digest("hex").slice(0, 12);
30230
- const fileName = safeFileName(path5.basename(absolutePath)) || `visual-${digest}.png`;
31032
+ const digest = createHash5("sha256").update(absolutePath).digest("hex").slice(0, 12);
31033
+ const fileName = safeFileName(path6.basename(absolutePath)) || `visual-${digest}.png`;
30231
31034
  return {
30232
31035
  absolutePath,
30233
- alt: typeof asset["purpose"] === "string" ? asset["purpose"] : path5.basename(absolutePath, path5.extname(absolutePath)),
31036
+ alt: typeof asset["purpose"] === "string" ? asset["purpose"] : path6.basename(absolutePath, path6.extname(absolutePath)),
30234
31037
  logicalPath: `assets/dxc-visuals/${digest}-${fileName}`,
30235
- placementAnchor: asset["placementAnchor"],
31038
+ ...typeof headingIndex === "number" ? { headingIndex } : {},
31039
+ ...typeof asset["placementAnchor"] === "string" ? { placementAnchor: asset["placementAnchor"] } : {},
30236
31040
  plannedPath: asset["path"],
30237
31041
  ...sourceLogicalPath === void 0 ? {} : { sourceLogicalPath }
30238
31042
  };
@@ -30240,30 +31044,6 @@ async function plannedVisualAssets(visualPlanFile, assetsDirectoryInput, maximum
30240
31044
  )
30241
31045
  };
30242
31046
  }
30243
- async function validateWorkflowVisualPlan(input) {
30244
- const plan = parsedVisualPlan(input.visualPlanSource);
30245
- if (typeof plan["assetsDirectory"] !== "string" || plan["assetsDirectory"].trim().length === 0) {
30246
- throw new WechatCliError(
30247
- "DXC_PUBLISHING_VISUAL_PLAN_INVALID",
30248
- "\u89C6\u89C9\u8BA1\u5212\u5FC5\u987B\u58F0\u660E\u9879\u76EE\u7D20\u6750\u57FA\u7840\u76EE\u5F55 assetsDirectory\u3002"
30249
- );
30250
- }
30251
- const expectedAssetsDirectory = path5.resolve(input.assetsDirectory);
30252
- const declaredAssetsDirectory = path5.resolve(input.projectDirectory, plan["assetsDirectory"]);
30253
- if (declaredAssetsDirectory !== expectedAssetsDirectory) {
30254
- throw new WechatCliError(
30255
- "DXC_PUBLISHING_VISUAL_PLAN_INVALID",
30256
- `\u89C6\u89C9\u8BA1\u5212 assetsDirectory \u5FC5\u987B\u662F\u9879\u76EE\u7D20\u6750\u57FA\u7840\u76EE\u5F55 ${path5.relative(input.projectDirectory, expectedAssetsDirectory) || "."}\uFF0C\u4E0D\u80FD\u586B\u5199\u5176\u5B50\u76EE\u5F55\u3002`
30257
- );
30258
- }
30259
- await plannedVisualAssets(
30260
- input.visualPlanFile,
30261
- expectedAssetsDirectory,
30262
- MAXIMUM_OPTIMIZABLE_IMAGE_BYTES,
30263
- MAXIMUM_OPTIMIZABLE_IMAGE_BYTES
30264
- );
30265
- validateVisualPlanPlacementAnchors(input.articleSource, input.visualPlanSource);
30266
- }
30267
31047
  function logicalInlinePaths(bundle) {
30268
31048
  return bundle.assets.filter((asset) => asset.role === "inline").map((asset) => asset.logicalPath);
30269
31049
  }
@@ -30274,7 +31054,7 @@ function publishingArticleSource(source, digestOverride) {
30274
31054
  if (yamlSource === void 0 || body === void 0) {
30275
31055
  throw new WechatCliError("DXC_PUBLISHING_ARTICLE_INVALID");
30276
31056
  }
30277
- const document = (0, import_yaml2.parseDocument)(yamlSource, { uniqueKeys: true });
31057
+ const document = (0, import_yaml3.parseDocument)(yamlSource, { uniqueKeys: true });
30278
31058
  if (document.errors.length > 0) {
30279
31059
  throw new WechatCliError("DXC_PUBLISHING_ARTICLE_INVALID");
30280
31060
  }
@@ -30299,21 +31079,21 @@ async function preparePublishingDraft(input) {
30299
31079
  } catch {
30300
31080
  throw new WechatCliError("DXC_PUBLISHING_ARTICLE_INVALID");
30301
31081
  }
30302
- const articleDirectory = path5.dirname(articleInput.path);
31082
+ const articleDirectory = path6.dirname(articleInput.path);
30303
31083
  const availableImages = await collectedImages(input.assetsDirectory, articleDirectory);
30304
31084
  const availableByFileName = new Map(
30305
- availableImages.map((candidate) => [path5.basename(candidate), candidate])
31085
+ availableImages.map((candidate) => [path6.basename(candidate), candidate])
30306
31086
  );
30307
- const projectDirectory = input.assetsDirectory === void 0 ? articleDirectory : commonDirectory(articleDirectory, path5.resolve(input.assetsDirectory));
30308
- const resolveDeclaredAsset = (logicalPath) => availableByFileName.get(path5.posix.basename(logicalPath)) ?? path5.resolve(projectDirectory, logicalPath);
31087
+ const projectDirectory = input.assetsDirectory === void 0 ? articleDirectory : commonDirectory(articleDirectory, path6.resolve(input.assetsDirectory));
31088
+ const resolveDeclaredAsset = (logicalPath) => availableByFileName.get(path6.posix.basename(logicalPath)) ?? path6.resolve(projectDirectory, logicalPath);
30309
31089
  const referenced = new Set(logicalInlinePaths(originalBundle));
30310
31090
  const automaticAssetDirectories = [
30311
31091
  articleDirectory,
30312
- ...input.assetsDirectory === void 0 ? [] : [path5.resolve(input.assetsDirectory)]
31092
+ ...input.assetsDirectory === void 0 ? [] : [path6.resolve(input.assetsDirectory)]
30313
31093
  ];
30314
31094
  const articleCoverCandidate = originalBundle.coverImage !== void 0 && isSafeLogicalAssetPath(originalBundle.coverImage) ? resolveDeclaredAsset(originalBundle.coverImage) : void 0;
30315
31095
  const namedCoverCandidate = availableImages.find(
30316
- (candidate) => /^cover(?:[._-]|$)/iu.test(path5.basename(candidate))
31096
+ (candidate) => /^cover(?:[._-]|$)/iu.test(path6.basename(candidate))
30317
31097
  );
30318
31098
  const explicitCover = input.coverFile !== void 0 && input.coverFile !== "auto" ? input.coverFile : void 0;
30319
31099
  const planned = input.visualPlanFile === void 0 ? void 0 : await plannedVisualAssets(input.visualPlanFile, input.assetsDirectory);
@@ -30339,16 +31119,16 @@ async function preparePublishingDraft(input) {
30339
31119
  const coverCandidate = explicitCover ?? planned?.coverAbsolutePath ?? articleCoverCandidate ?? namedCoverCandidate;
30340
31120
  const coverMode = explicitCover !== void 0 ? "explicit-file" : planned?.coverAbsolutePath !== void 0 ? "visual-plan" : articleCoverCandidate !== void 0 ? "article-frontmatter" : namedCoverCandidate !== void 0 ? "assets-directory" : void 0;
30341
31121
  const pickupCandidates = availableImages.filter(
30342
- (candidate) => candidate !== namedCoverCandidate && candidate !== articleCoverCandidate && !referencedFiles.has(path5.resolve(candidate)) && input.visualPlanFile === void 0
31122
+ (candidate) => candidate !== namedCoverCandidate && candidate !== articleCoverCandidate && !referencedFiles.has(path6.resolve(candidate)) && input.visualPlanFile === void 0
30343
31123
  );
30344
31124
  const injected = [
30345
31125
  ...plannedInlineForInjection,
30346
31126
  ...pickupCandidates.slice(0, Math.max(0, MAXIMUM_INLINE_IMAGES - referenced.size)).map((candidate) => {
30347
- const digest = createHash4("sha256").update(candidate).digest("hex").slice(0, 12);
30348
- const fileName = safeFileName(path5.basename(candidate)) || `visual-${digest}.png`;
31127
+ const digest = createHash5("sha256").update(candidate).digest("hex").slice(0, 12);
31128
+ const fileName = safeFileName(path6.basename(candidate)) || `visual-${digest}.png`;
30349
31129
  return {
30350
31130
  absolutePath: candidate,
30351
- alt: path5.basename(candidate, path5.extname(candidate)),
31131
+ alt: path6.basename(candidate, path6.extname(candidate)),
30352
31132
  logicalPath: `assets/dxc-visuals/${digest}-${fileName}`
30353
31133
  };
30354
31134
  }).filter((asset) => !referenced.has(asset.logicalPath))
@@ -30392,7 +31172,7 @@ async function preparePublishingDraft(input) {
30392
31172
  );
30393
31173
  const inlineAssets = await Promise.all(
30394
31174
  logicalInlinePaths(bundle).map(async (logicalPath) => {
30395
- const target = injectedByLogicalPath.get(logicalPath) ?? referencedFilesByLogicalPath.get(logicalPath) ?? path5.resolve(articleDirectory, logicalPath);
31175
+ const target = injectedByLogicalPath.get(logicalPath) ?? referencedFilesByLogicalPath.get(logicalPath) ?? path6.resolve(articleDirectory, logicalPath);
30396
31176
  const asset = await readContainedPublishingInput(
30397
31177
  target,
30398
31178
  MAXIMUM_INLINE_IMAGE_BYTES,
@@ -30433,354 +31213,6 @@ async function preparePublishingDraft(input) {
30433
31213
  };
30434
31214
  }
30435
31215
 
30436
- // apps/cli/src/publishing-image-optimization.ts
30437
- var import_yaml3 = __toESM(require_dist(), 1);
30438
- import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypto";
30439
- import { constants as fileConstants2 } from "node:fs";
30440
- import {
30441
- lstat as lstat5,
30442
- mkdir as mkdir3,
30443
- open as open2,
30444
- readFile as readFile2,
30445
- realpath as realpath2,
30446
- rename as rename3,
30447
- unlink,
30448
- writeFile
30449
- } from "node:fs/promises";
30450
- import path6 from "node:path";
30451
- import sharp2 from "sharp";
30452
- var MAXIMUM_INPUT_BYTES = 32 * 1024 * 1024;
30453
- var INLINE_IMAGE_LIMIT = 1024 * 1024 - 1;
30454
- var INLINE_IMAGE_TARGET = 950 * 1024;
30455
- var COVER_IMAGE_LIMIT = 5 * 1024 * 1024;
30456
- var COVER_IMAGE_TARGET = 4500 * 1024;
30457
- var VISUAL_PLAN_MAXIMUM_BYTES2 = 128 * 1024;
30458
- var VISUAL_PLAN_FRONTMATTER2 = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)([\s\S]*)$/u;
30459
- function sha256(value) {
30460
- return createHash5("sha256").update(value).digest("hex");
30461
- }
30462
- function safeDerivativeBase(value) {
30463
- return value.normalize("NFKC").replaceAll(/[^A-Za-z0-9._-]+/gu, "-").replaceAll(/^-+|-+$/gu, "").slice(0, 80) || "image";
30464
- }
30465
- function recordValue2(value) {
30466
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
30467
- }
30468
- function portablePath(value, label) {
30469
- if (typeof value !== "string" || value.trim().length === 0 || path6.isAbsolute(value) || value.includes("\\") || value.split("/").some((segment) => segment === "" || segment === "." || segment === "..")) {
30470
- throw new WechatCliError(
30471
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30472
- `${label} \u5FC5\u987B\u662F\u9879\u76EE\u5185\u4F7F\u7528 / \u5206\u9694\u7684\u5B89\u5168\u76F8\u5BF9\u8DEF\u5F84\u3002`
30473
- );
30474
- }
30475
- return value;
30476
- }
30477
- function isWithin(target, directory) {
30478
- const relative = path6.relative(directory, target);
30479
- return relative === "" || !relative.startsWith(`..${path6.sep}`) && relative !== "..";
30480
- }
30481
- async function regularImage(target) {
30482
- let handle;
30483
- try {
30484
- handle = await open2(target, fileConstants2.O_RDONLY | fileConstants2.O_NOFOLLOW);
30485
- const metadata = await handle.stat();
30486
- if (!metadata.isFile() || metadata.size <= 0 || metadata.size > MAXIMUM_INPUT_BYTES) {
30487
- throw new Error("invalid image");
30488
- }
30489
- return await handle.readFile();
30490
- } catch {
30491
- throw new WechatCliError(
30492
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30493
- `\u65E0\u6CD5\u5B89\u5168\u8BFB\u53D6\u56FE\u7247 ${path6.basename(target)}\uFF1B\u6587\u4EF6\u5FC5\u987B\u662F\u5C0F\u4E8E ${MAXIMUM_INPUT_BYTES} bytes \u7684\u666E\u901A\u6587\u4EF6\u3002`
30494
- );
30495
- } finally {
30496
- await handle?.close();
30497
- }
30498
- }
30499
- async function requirePreservedAlpha(bytes) {
30500
- const metadata = await sharp2(bytes, {
30501
- failOn: "error",
30502
- limitInputPixels: 8e7
30503
- }).metadata();
30504
- if (metadata.format !== "png" || metadata.hasAlpha !== true) {
30505
- throw new WechatCliError(
30506
- "DXC_PUBLISHING_IMAGE_ALPHA_LOST",
30507
- "\u900F\u660E\u56FE\u7247\u4F18\u5316\u540E\u4E22\u5931\u900F\u660E\u901A\u9053\uFF1B\u5DF2\u963B\u65AD\u4E14\u4E0D\u4F1A\u8986\u76D6\u539F\u56FE\u3002"
30508
- );
30509
- }
30510
- }
30511
- async function decodedImageMetadata(source, sourcePath) {
30512
- let metadata;
30513
- try {
30514
- metadata = await sharp2(source, { failOn: "error", limitInputPixels: 8e7 }).metadata();
30515
- } catch {
30516
- throw new WechatCliError(
30517
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30518
- "\u56FE\u7247\u65E0\u6CD5\u88AB\u5B89\u5168\u89E3\u7801\uFF1B\u4EC5\u652F\u6301\u6709\u6548 PNG/JPEG\u3002"
30519
- );
30520
- }
30521
- const extension = path6.extname(sourcePath).toLowerCase();
30522
- const expectedFormat = extension === ".png" ? "png" : extension === ".jpg" || extension === ".jpeg" ? "jpeg" : void 0;
30523
- if (expectedFormat === void 0 || metadata.format !== expectedFormat || metadata.width === void 0 || metadata.height === void 0) {
30524
- throw new WechatCliError(
30525
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30526
- "\u56FE\u7247\u6269\u5C55\u540D\u3001\u771F\u5B9E\u683C\u5F0F\u6216\u5C3A\u5BF8\u65E0\u6548\uFF1B\u4EC5\u652F\u6301\u6269\u5C55\u540D\u4E0E\u5185\u5BB9\u4E00\u81F4\u7684 PNG/JPEG\u3002"
30527
- );
30528
- }
30529
- return {
30530
- format: expectedFormat,
30531
- hasAlpha: metadata.hasAlpha === true,
30532
- height: metadata.height,
30533
- width: metadata.width
30534
- };
30535
- }
30536
- async function writeExclusiveOrVerify(target, bytes) {
30537
- await mkdir3(path6.dirname(target), { recursive: true, mode: 448 });
30538
- try {
30539
- await writeFile(target, bytes, { flag: "wx", mode: 384 });
30540
- return true;
30541
- } catch (error) {
30542
- if (error.code !== "EEXIST") {
30543
- throw error;
30544
- }
30545
- const existing = await regularImage(target);
30546
- if (sha256(existing) !== sha256(bytes)) {
30547
- throw new WechatCliError(
30548
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_CONFLICT",
30549
- `\u4F18\u5316\u76EE\u6807 ${path6.basename(target)} \u5DF2\u5B58\u5728\u4F46\u5185\u5BB9\u4E0D\u540C\uFF1B\u672A\u8986\u76D6\u4EFB\u4F55\u6587\u4EF6\u3002`
30550
- );
30551
- }
30552
- return false;
30553
- }
30554
- }
30555
- async function safeRealPathWithin(target, directory, label) {
30556
- const resolved = await realpath2(target).catch(() => void 0);
30557
- if (resolved === void 0 || !isWithin(resolved, directory)) {
30558
- throw new WechatCliError(
30559
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30560
- `${label} \u7684\u771F\u5B9E\u8DEF\u5F84\u4E0D\u5728\u5141\u8BB8\u7684\u9879\u76EE\u76EE\u5F55\u5185\u3002`
30561
- );
30562
- }
30563
- return resolved;
30564
- }
30565
- async function ensureSafeOptimizedDirectory(assetsDirectory, realAssetsDirectory) {
30566
- const optimizedDirectory = path6.join(assetsDirectory, "optimized");
30567
- await mkdir3(optimizedDirectory, { recursive: true, mode: 448 });
30568
- const metadata = await lstat5(optimizedDirectory).catch(() => void 0);
30569
- if (metadata === void 0 || !metadata.isDirectory() || metadata.isSymbolicLink()) {
30570
- throw new WechatCliError(
30571
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30572
- "\u4F18\u5316\u76EE\u5F55\u5FC5\u987B\u662F\u7D20\u6750\u76EE\u5F55\u5185\u7684\u666E\u901A\u76EE\u5F55\uFF0C\u4E0D\u80FD\u662F\u7B26\u53F7\u94FE\u63A5\u3002"
30573
- );
30574
- }
30575
- await safeRealPathWithin(optimizedDirectory, realAssetsDirectory, "\u4F18\u5316\u76EE\u5F55");
30576
- return optimizedDirectory;
30577
- }
30578
- async function writePlanAtomically(target, expectedSource, source) {
30579
- const temporary = `${target}.${randomUUID3()}.tmp`;
30580
- try {
30581
- await writeFile(temporary, source, { encoding: "utf8", flag: "wx", mode: 384 });
30582
- if (await readFile2(target, "utf8") !== expectedSource) {
30583
- throw new WechatCliError(
30584
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_CONFLICT",
30585
- "\u89C6\u89C9\u8BA1\u5212\u5728\u56FE\u7247\u4F18\u5316\u671F\u95F4\u5DF2\u88AB\u5176\u4ED6\u64CD\u4F5C\u4FEE\u6539\uFF1B\u672A\u8986\u76D6\u65B0\u5185\u5BB9\uFF0C\u8BF7\u91CD\u65B0\u8BFB\u53D6\u540E\u518D\u8BD5\u3002"
30586
- );
30587
- }
30588
- await rename3(temporary, target);
30589
- } finally {
30590
- await unlink(temporary).catch(() => void 0);
30591
- }
30592
- }
30593
- function candidateWidths(width) {
30594
- return [.../* @__PURE__ */ new Set([width, 2400, 2e3, 1600, 1280, 1080, 900, 720])].filter((candidate) => candidate > 0 && candidate <= width).sort((left, right) => right - left);
30595
- }
30596
- async function optimizedImage(source, targetBytes, metadata) {
30597
- const hasAlpha = metadata.hasAlpha;
30598
- for (const width of candidateWidths(metadata.width)) {
30599
- if (hasAlpha) {
30600
- const lossless = await sharp2(source, { failOn: "error", limitInputPixels: 8e7 }).rotate().resize({ fit: "inside", width, withoutEnlargement: true }).png({ compressionLevel: 9, effort: 10 }).toBuffer();
30601
- if (lossless.byteLength <= targetBytes) {
30602
- await requirePreservedAlpha(lossless);
30603
- return { alphaPreserved: true, bytes: lossless, extension: ".png" };
30604
- }
30605
- for (const colours of [256, 192, 128, 96, 64]) {
30606
- const quantized = await sharp2(source, {
30607
- failOn: "error",
30608
- limitInputPixels: 8e7
30609
- }).rotate().resize({ fit: "inside", width, withoutEnlargement: true }).png({ colours, compressionLevel: 9, effort: 10, palette: true }).toBuffer();
30610
- if (quantized.byteLength <= targetBytes) {
30611
- await requirePreservedAlpha(quantized);
30612
- return { alphaPreserved: true, bytes: quantized, extension: ".png" };
30613
- }
30614
- }
30615
- continue;
30616
- }
30617
- for (const quality of [88, 82, 76, 70, 64, 58]) {
30618
- const jpeg = await sharp2(source, { failOn: "error", limitInputPixels: 8e7 }).rotate().resize({ fit: "inside", width, withoutEnlargement: true }).jpeg({ chromaSubsampling: "4:4:4", mozjpeg: true, progressive: true, quality }).toBuffer();
30619
- if (jpeg.byteLength <= targetBytes) {
30620
- return { alphaPreserved: true, bytes: jpeg, extension: ".jpg" };
30621
- }
30622
- }
30623
- }
30624
- throw new WechatCliError(
30625
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_LIMIT_UNREACHABLE",
30626
- `\u65E0\u6CD5\u5728\u4E0D\u4E22\u5931\u900F\u660E\u901A\u9053\u6216\u4F4E\u4E8E\u6700\u5C0F\u5C3A\u5BF8\u7B56\u7565\u7684\u524D\u63D0\u4E0B\u538B\u5230 ${targetBytes} bytes\uFF1B\u8BF7\u66F4\u6362\u6216\u88C1\u526A\u56FE\u7247\u3002`
30627
- );
30628
- }
30629
- async function optimizePublishingImages(input) {
30630
- const projectDirectory = path6.resolve(input.projectDirectory);
30631
- const visualPlanPath = path6.resolve(input.visualPlanFile);
30632
- if (!isWithin(visualPlanPath, projectDirectory)) {
30633
- throw new WechatCliError(
30634
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30635
- "\u89C6\u89C9\u8BA1\u5212\u5FC5\u987B\u4F4D\u4E8E\u5F53\u524D\u5185\u5BB9\u9879\u76EE\u5185\u3002"
30636
- );
30637
- }
30638
- const projectMetadata = await lstat5(projectDirectory).catch(() => void 0);
30639
- const visualPlanMetadata = await lstat5(visualPlanPath).catch(() => void 0);
30640
- if (projectMetadata === void 0 || !projectMetadata.isDirectory() || projectMetadata.isSymbolicLink() || visualPlanMetadata === void 0 || !visualPlanMetadata.isFile() || visualPlanMetadata.isSymbolicLink() || visualPlanMetadata.size > VISUAL_PLAN_MAXIMUM_BYTES2) {
30641
- throw new WechatCliError(
30642
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30643
- "\u5185\u5BB9\u9879\u76EE\u548C\u89C6\u89C9\u8BA1\u5212\u5FC5\u987B\u662F\u975E\u7B26\u53F7\u94FE\u63A5\u7684\u666E\u901A\u76EE\u5F55\u4E0E\u6587\u4EF6\u3002"
30644
- );
30645
- }
30646
- const realProjectDirectory = await realpath2(projectDirectory);
30647
- await safeRealPathWithin(visualPlanPath, realProjectDirectory, "\u89C6\u89C9\u8BA1\u5212");
30648
- const source = await readFile2(visualPlanPath, "utf8").catch(() => "");
30649
- if (Buffer.byteLength(source, "utf8") > VISUAL_PLAN_MAXIMUM_BYTES2) {
30650
- throw new WechatCliError("DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID");
30651
- }
30652
- const match = VISUAL_PLAN_FRONTMATTER2.exec(source);
30653
- if (match?.[1] === void 0 || match[2] === void 0) {
30654
- throw new WechatCliError("DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID");
30655
- }
30656
- let frontmatter2;
30657
- try {
30658
- frontmatter2 = recordValue2((0, import_yaml3.parse)(match[1], { maxAliasCount: 10, uniqueKeys: true }));
30659
- } catch {
30660
- throw new WechatCliError("DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID");
30661
- }
30662
- if (frontmatter2 === void 0) {
30663
- throw new WechatCliError(
30664
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30665
- "\u89C6\u89C9\u8BA1\u5212\u7684 YAML \u5934\u65E0\u6CD5\u8BFB\u53D6\u3002"
30666
- );
30667
- }
30668
- const assetsRelative = frontmatter2["assetsDirectory"] === void 0 ? "assets" : portablePath(frontmatter2["assetsDirectory"], "assetsDirectory");
30669
- frontmatter2["assetsDirectory"] = assetsRelative;
30670
- frontmatter2["inlineImagesSupported"] = true;
30671
- const assetsDirectory = path6.resolve(projectDirectory, assetsRelative);
30672
- if (!isWithin(assetsDirectory, projectDirectory)) {
30673
- throw new WechatCliError("DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID");
30674
- }
30675
- const assetsMetadata = await lstat5(assetsDirectory).catch(() => void 0);
30676
- if (assetsMetadata === void 0 || !assetsMetadata.isDirectory() || assetsMetadata.isSymbolicLink()) {
30677
- throw new WechatCliError("DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID");
30678
- }
30679
- const realAssetsDirectory = await safeRealPathWithin(
30680
- assetsDirectory,
30681
- realProjectDirectory,
30682
- "\u7D20\u6750\u76EE\u5F55"
30683
- );
30684
- const inlineAssets = Array.isArray(frontmatter2["inlineAssets"]) ? frontmatter2["inlineAssets"] : [];
30685
- const entries = [
30686
- {
30687
- limit: COVER_IMAGE_LIMIT,
30688
- record: { path: frontmatter2["coverAsset"] },
30689
- role: "cover",
30690
- target: COVER_IMAGE_TARGET
30691
- },
30692
- ...inlineAssets.map((asset) => ({
30693
- limit: INLINE_IMAGE_LIMIT,
30694
- record: recordValue2(asset),
30695
- role: "inline",
30696
- target: INLINE_IMAGE_TARGET
30697
- }))
30698
- ];
30699
- const createdFiles = [];
30700
- const mutations = [];
30701
- let unchangedCount = 0;
30702
- let optimizedDirectory;
30703
- try {
30704
- for (const entry of entries) {
30705
- const assetPath = portablePath(entry.record?.["path"], `${entry.role} \u56FE\u7247\u8DEF\u5F84`);
30706
- const absolutePath = path6.resolve(projectDirectory, assetPath);
30707
- if (!isWithin(absolutePath, assetsDirectory)) {
30708
- throw new WechatCliError(
30709
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_INVALID",
30710
- `${assetPath} \u4E0D\u5728\u89C6\u89C9\u8BA1\u5212\u58F0\u660E\u7684\u7D20\u6750\u76EE\u5F55\u5185\u3002`
30711
- );
30712
- }
30713
- await safeRealPathWithin(absolutePath, realAssetsDirectory, `${assetPath} \u56FE\u7247`);
30714
- const bytes = await regularImage(absolutePath);
30715
- const metadata = await decodedImageMetadata(bytes, assetPath);
30716
- const beforeSha256 = sha256(bytes);
30717
- let finalPath = assetPath;
30718
- let finalBytes = bytes;
30719
- let alphaPreserved = true;
30720
- if (bytes.byteLength > entry.limit) {
30721
- const optimized = await optimizedImage(bytes, entry.target, metadata);
30722
- optimizedDirectory ??= await ensureSafeOptimizedDirectory(
30723
- assetsDirectory,
30724
- realAssetsDirectory
30725
- );
30726
- const baseName = safeDerivativeBase(path6.basename(assetPath, path6.extname(assetPath)));
30727
- const optimizedSha256 = sha256(optimized.bytes);
30728
- const optimizedName = `${baseName}-${entry.role}-${beforeSha256.slice(0, 12)}-${optimizedSha256.slice(0, 12)}${optimized.extension}`;
30729
- const optimizedAbsolute = path6.join(optimizedDirectory, optimizedName);
30730
- const created = await writeExclusiveOrVerify(optimizedAbsolute, optimized.bytes);
30731
- if (created) {
30732
- createdFiles.push(optimizedAbsolute);
30733
- }
30734
- finalPath = path6.relative(projectDirectory, optimizedAbsolute).split(path6.sep).join("/");
30735
- finalBytes = optimized.bytes;
30736
- alphaPreserved = optimized.alphaPreserved;
30737
- mutations.push({
30738
- alphaPreserved,
30739
- afterBytes: finalBytes.byteLength,
30740
- afterSha256: sha256(finalBytes),
30741
- beforeBytes: bytes.byteLength,
30742
- beforeSha256,
30743
- from: assetPath,
30744
- role: entry.role,
30745
- to: finalPath
30746
- });
30747
- } else {
30748
- unchangedCount += 1;
30749
- }
30750
- if (entry.role === "cover") {
30751
- frontmatter2["coverAsset"] = finalPath;
30752
- frontmatter2["coverSha256"] = sha256(finalBytes);
30753
- } else if (entry.record !== void 0) {
30754
- if (finalPath !== assetPath && entry.record["sourcePath"] === void 0) {
30755
- entry.record["sourcePath"] = assetPath;
30756
- }
30757
- entry.record["path"] = finalPath;
30758
- entry.record["sha256"] = sha256(finalBytes);
30759
- }
30760
- }
30761
- frontmatter2["inlineAssets"] = inlineAssets;
30762
- const normalizedSource = `---
30763
- ${(0, import_yaml3.stringify)(frontmatter2, { lineWidth: 0 }).trimEnd()}
30764
- ---
30765
- ${match[2]}`;
30766
- await writePlanAtomically(visualPlanPath, source, normalizedSource);
30767
- } catch (error) {
30768
- await Promise.all(createdFiles.map((target) => unlink(target).catch(() => void 0)));
30769
- if (error instanceof WechatCliError) {
30770
- throw error;
30771
- }
30772
- throw new WechatCliError(
30773
- "DXC_PUBLISHING_IMAGE_OPTIMIZATION_FAILED",
30774
- "\u56FE\u7247\u4F18\u5316\u672A\u5B8C\u6210\uFF1B\u539F\u56FE\u548C\u89C6\u89C9\u8BA1\u5212\u5747\u672A\u88AB\u8986\u76D6\u3002"
30775
- );
30776
- }
30777
- return {
30778
- command: "wechat.draft.optimize-images",
30779
- data: { mutations, unchangedCount, visualPlanPath },
30780
- ok: true
30781
- };
30782
- }
30783
-
30784
31216
  // apps/cli/src/wechat.ts
30785
31217
  var DEFAULT_API_BASE_URL = "https://content.deployxai.com";
30786
31218
  var DEFAULT_POLL_INTERVAL_MS = 2e3;
@@ -30894,19 +31326,19 @@ function localPreviewOutputConflict() {
30894
31326
  async function ensureLocalPreviewDirectory(directory) {
30895
31327
  let metadata;
30896
31328
  try {
30897
- metadata = await lstat6(directory);
31329
+ metadata = await lstat5(directory);
30898
31330
  } catch (error) {
30899
31331
  if (fileSystemErrorCode(error) !== "ENOENT") {
30900
31332
  throw error;
30901
31333
  }
30902
31334
  try {
30903
- await mkdir4(directory, { mode: 448 });
31335
+ await mkdir5(directory, { mode: 448 });
30904
31336
  } catch (mkdirError) {
30905
31337
  if (fileSystemErrorCode(mkdirError) !== "EEXIST") {
30906
31338
  throw mkdirError;
30907
31339
  }
30908
31340
  }
30909
- metadata = await lstat6(directory);
31341
+ metadata = await lstat5(directory);
30910
31342
  }
30911
31343
  if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
30912
31344
  throw localPreviewOutputConflict();
@@ -30921,7 +31353,7 @@ async function safeLocalPreviewOutput(options) {
30921
31353
  throw localPreviewOutputConflict();
30922
31354
  }
30923
31355
  try {
30924
- const projectMetadata = await lstat6(projectDirectory);
31356
+ const projectMetadata = await lstat5(projectDirectory);
30925
31357
  if (!projectMetadata.isDirectory() || projectMetadata.isSymbolicLink()) {
30926
31358
  throw localPreviewOutputConflict();
30927
31359
  }
@@ -30932,7 +31364,7 @@ async function safeLocalPreviewOutput(options) {
30932
31364
  await ensureLocalPreviewDirectory(directory);
30933
31365
  }
30934
31366
  try {
30935
- const outputMetadata = await lstat6(outputFile);
31367
+ const outputMetadata = await lstat5(outputFile);
30936
31368
  if (!outputMetadata.isFile() || outputMetadata.isSymbolicLink()) {
30937
31369
  throw localPreviewOutputConflict();
30938
31370
  }
@@ -31073,17 +31505,17 @@ async function readProtectedStateFile(target, invalidCode) {
31073
31505
  }
31074
31506
  }
31075
31507
  async function saveSession(stateDirectory, session) {
31076
- await mkdir4(stateDirectory, { mode: 448, recursive: true });
31508
+ await mkdir5(stateDirectory, { mode: 448, recursive: true });
31077
31509
  await chmod(stateDirectory, 448);
31078
31510
  const target = sessionPath(stateDirectory);
31079
31511
  const temporary = `${target}.${process.pid}.tmp`;
31080
- await writeFile2(temporary, `${JSON.stringify(session)}
31512
+ await writeFile3(temporary, `${JSON.stringify(session)}
31081
31513
  `, {
31082
31514
  encoding: "utf8",
31083
31515
  mode: 384
31084
31516
  });
31085
31517
  await chmod(temporary, 384);
31086
- await rename4(temporary, target);
31518
+ await rename3(temporary, target);
31087
31519
  await chmod(target, 384);
31088
31520
  }
31089
31521
  async function readSessionValue(stateDirectory) {
@@ -31206,17 +31638,17 @@ async function loadOrCreateDeviceIdentity(stateDirectory) {
31206
31638
  publicKeyFingerprint: keyPair.publicKeyFingerprint,
31207
31639
  publicKeyPem: keyPair.publicKeyPem
31208
31640
  };
31209
- await mkdir4(stateDirectory, { mode: 448, recursive: true });
31641
+ await mkdir5(stateDirectory, { mode: 448, recursive: true });
31210
31642
  await chmod(stateDirectory, 448);
31211
31643
  const target = deviceIdentityPath(stateDirectory);
31212
31644
  const temporary = `${target}.${process.pid}.tmp`;
31213
- await writeFile2(temporary, `${JSON.stringify(identity)}
31645
+ await writeFile3(temporary, `${JSON.stringify(identity)}
31214
31646
  `, {
31215
31647
  encoding: "utf8",
31216
31648
  mode: 384
31217
31649
  });
31218
31650
  await chmod(temporary, 384);
31219
- await rename4(temporary, target);
31651
+ await rename3(temporary, target);
31220
31652
  await chmod(target, 384);
31221
31653
  return { created: true, identity };
31222
31654
  }
@@ -32292,28 +32724,36 @@ ${session.authorizationUrl}
32292
32724
  };
32293
32725
 
32294
32726
  // apps/cli/src/workflow-delivery.ts
32295
- import { createHash as createHash7 } from "node:crypto";
32727
+ import { createHash as createHash8 } from "node:crypto";
32296
32728
  import path9 from "node:path";
32297
32729
 
32298
32730
  // apps/cli/src/workflow.ts
32299
- import { lstat as lstat7, readFile as readFile3 } from "node:fs/promises";
32731
+ import { createHash as createHash7 } from "node:crypto";
32732
+ import { lstat as lstat6, readFile as readFile4 } from "node:fs/promises";
32300
32733
  import path8 from "node:path";
32301
32734
  var import_yaml4 = __toESM(require_dist(), 1);
32302
32735
  var WORKFLOW_STATE_FILE = ".dxc/workflow.json";
32303
32736
  var WORKFLOW_STATE_MAXIMUM_BYTES = 16 * 1024;
32304
32737
  var ARTIFACT_MAXIMUM_BYTES = 5 * 1024 * 1024;
32305
32738
  var FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/u;
32739
+ var WORK_ITEM_PATTERN = /^wi1_([A-Za-z0-9_-]+)\.([a-f0-9]{32})$/u;
32740
+ var SCAFFOLD_MARKER = "dxcScaffold: true";
32306
32741
  var WorkflowCliError = class extends Error {
32307
- constructor(code, message) {
32742
+ constructor(code, message, details) {
32308
32743
  super(message);
32309
32744
  this.code = code;
32745
+ this.details = details;
32310
32746
  this.name = "WorkflowCliError";
32311
32747
  }
32312
32748
  code;
32749
+ details;
32313
32750
  };
32314
32751
  function isRecord(value) {
32315
32752
  return typeof value === "object" && value !== null && !Array.isArray(value);
32316
32753
  }
32754
+ function sha2562(value) {
32755
+ return createHash7("sha256").update(value).digest("hex");
32756
+ }
32317
32757
  function workflowState(value, projectId) {
32318
32758
  if (!isRecord(value) || value["version"] !== 1 || value["projectId"] !== projectId) {
32319
32759
  return void 0;
@@ -32334,11 +32774,7 @@ function statePath(directory) {
32334
32774
  return path8.join(directory, WORKFLOW_STATE_FILE);
32335
32775
  }
32336
32776
  function projectSummary(location) {
32337
- return {
32338
- directory: location.directory,
32339
- projectId: location.manifest.projectId,
32340
- title: location.manifest.title
32341
- };
32777
+ return { projectId: location.manifest.projectId, title: location.manifest.title };
32342
32778
  }
32343
32779
  function nextStep(lastCompletedStep) {
32344
32780
  const nextIndex = lastCompletedStep === null ? 0 : CONTENT_ARTIFACT_KINDS.indexOf(lastCompletedStep) + 1;
@@ -32349,23 +32785,16 @@ function previousStep(step) {
32349
32785
  return index <= 0 ? null : CONTENT_ARTIFACT_KINDS[index - 1] ?? null;
32350
32786
  }
32351
32787
  function requestedStep(value) {
32352
- const step = value;
32353
- if (!CONTENT_ARTIFACT_KINDS.includes(step)) {
32788
+ if (!CONTENT_ARTIFACT_KINDS.includes(value)) {
32354
32789
  throw new WorkflowCliError(
32355
32790
  "DXC_WORKFLOW_STEP_INVALID",
32356
- `\u672A\u77E5\u9636\u6BB5 ${step}\u3002\u8BF7\u4F7F\u7528 ${CONTENT_ARTIFACT_KINDS.join("\u3001")}\u3002`
32791
+ `\u672A\u77E5\u9636\u6BB5 ${value}\u3002\u8BF7\u4F7F\u7528 ${CONTENT_ARTIFACT_KINDS.join("\u3001")}\u3002`
32357
32792
  );
32358
32793
  }
32359
- return step;
32360
- }
32361
- function inputPaths(directory, manifest, step) {
32362
- const inputs = CONTENT_STAGE_INPUTS[step];
32363
- return [...inputs.required, ...inputs.optional].map(
32364
- (kind) => path8.join(directory, manifest.artifacts[kind])
32365
- );
32794
+ return value;
32366
32795
  }
32367
32796
  function requiresStageConfirmation(policy, step) {
32368
- if (step === null || step === "delivery" || policy === "auto-until-delivery") {
32797
+ if (step === "delivery" || policy === "auto-until-delivery") {
32369
32798
  return false;
32370
32799
  }
32371
32800
  if (policy === "review-every-stage") {
@@ -32385,71 +32814,264 @@ function stageLabel(step) {
32385
32814
  "visual-plan": "\u56FE\u7247\u548C\u89C6\u89C9\u7ED3\u679C"
32386
32815
  }[step];
32387
32816
  }
32388
- function workItem(location, state, confirmationPolicy) {
32389
- const step = nextStep(state.lastCompletedStep);
32390
- return {
32391
- confirmationPolicy,
32392
- done: step === null,
32393
- inputs: step === null ? [] : inputPaths(location.directory, location.manifest, step),
32394
- output: step === null ? null : path8.join(location.directory, location.manifest.artifacts[step]),
32395
- project: projectSummary(location),
32396
- requiresConfirmation: requiresStageConfirmation(confirmationPolicy, step),
32397
- step
32398
- };
32817
+ function frontmatter(source) {
32818
+ const yaml = FRONTMATTER.exec(source)?.[1];
32819
+ if (yaml === void 0) {
32820
+ return {};
32821
+ }
32822
+ try {
32823
+ const value = (0, import_yaml4.parse)(yaml, { maxAliasCount: 10, uniqueKeys: true });
32824
+ return isRecord(value) ? value : {};
32825
+ } catch {
32826
+ return {};
32827
+ }
32828
+ }
32829
+ function selectedTitle(value) {
32830
+ const title = value["selectedTitle"];
32831
+ return typeof title === "string" && title.trim().length > 0 ? title.trim() : void 0;
32832
+ }
32833
+ function artifactPath(location, kind) {
32834
+ return path8.join(location.directory, location.manifest.artifacts[kind]);
32399
32835
  }
32400
- async function readArtifact(directory, relativePath) {
32401
- const target = path8.resolve(directory, relativePath);
32402
- const relative = path8.relative(directory, target);
32836
+ async function readArtifact(location, kind) {
32837
+ const target = path8.resolve(location.directory, location.manifest.artifacts[kind]);
32838
+ const relative = path8.relative(location.directory, target);
32403
32839
  if (relative.startsWith("..") || path8.isAbsolute(relative)) {
32404
- throw new WorkflowCliError("DXC_WORKFLOW_ARTIFACT_INVALID", "\u6B65\u9AA4\u4EA7\u7269\u8DEF\u5F84\u4E0D\u5728\u9879\u76EE\u76EE\u5F55\u4E2D\u3002");
32840
+ throw new WorkflowCliError(
32841
+ "DXC_WORKFLOW_ARTIFACT_INVALID",
32842
+ `${kind} \u4EA7\u7269\u8DEF\u5F84\u4E0D\u5728\u9879\u76EE\u76EE\u5F55\u4E2D\u3002`,
32843
+ { path: target, stage: kind }
32844
+ );
32405
32845
  }
32406
32846
  try {
32407
- const metadata = await lstat7(target);
32847
+ const metadata = await lstat6(target);
32408
32848
  if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size === 0 || metadata.size > ARTIFACT_MAXIMUM_BYTES) {
32409
32849
  throw new WorkflowCliError(
32410
32850
  "DXC_WORKFLOW_ARTIFACT_INVALID",
32411
- "\u5F53\u524D\u6B65\u9AA4\u7684\u4EA7\u7269\u4E3A\u7A7A\u3001\u8FC7\u5927\u6216\u4E0D\u662F\u666E\u901A\u6587\u4EF6\u3002"
32851
+ `${kind} \u4EA7\u7269\u4E3A\u7A7A\u3001\u8FC7\u5927\u6216\u4E0D\u662F\u666E\u901A\u6587\u4EF6\u3002`,
32852
+ { path: target, stage: kind }
32412
32853
  );
32413
32854
  }
32414
- return await readFile3(target, "utf8");
32855
+ return await readFile4(target, "utf8");
32415
32856
  } catch (error) {
32416
32857
  if (error instanceof WorkflowCliError) {
32417
32858
  throw error;
32418
32859
  }
32419
- throw new WorkflowCliError(
32420
- "DXC_WORKFLOW_ARTIFACT_MISSING",
32421
- `\u5F53\u524D\u6B65\u9AA4\u5C1A\u672A\u5199\u5165 ${relativePath}\u3002`
32422
- );
32860
+ throw new WorkflowCliError("DXC_WORKFLOW_ARTIFACT_MISSING", `\u7F3A\u5C11\u547D\u540D\u8F93\u5165\u6216\u4EA7\u7269 ${kind}\u3002`, {
32861
+ path: target,
32862
+ stage: kind
32863
+ });
32423
32864
  }
32424
32865
  }
32425
- function frontmatter(source) {
32426
- const yaml = FRONTMATTER.exec(source)?.[1];
32427
- if (yaml === void 0) {
32428
- return {};
32866
+ async function artifactAvailability(location, kind) {
32867
+ const target = artifactPath(location, kind);
32868
+ try {
32869
+ const metadata = await lstat6(target);
32870
+ if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size <= 0) {
32871
+ return { availability: "missing" };
32872
+ }
32873
+ const source = await readFile4(target);
32874
+ return { availability: "present", hash: sha2562(source), path: target };
32875
+ } catch (error) {
32876
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
32877
+ return { availability: "missing" };
32878
+ }
32879
+ throw new WorkflowCliError("DXC_WORKFLOW_ARTIFACT_INVALID", `\u65E0\u6CD5\u5B89\u5168\u8BFB\u53D6\u547D\u540D\u8F93\u5165 ${kind}\u3002`, {
32880
+ path: target,
32881
+ stage: kind
32882
+ });
32429
32883
  }
32884
+ }
32885
+ function stageScaffold(stage, title) {
32886
+ switch (stage) {
32887
+ case "article":
32888
+ return `---
32889
+ title: ""
32890
+ author: ""
32891
+ digest: ""
32892
+ tags: []
32893
+ ${SCAFFOLD_MARKER}
32894
+ ---
32895
+
32896
+ # ${title}
32897
+ `;
32898
+ case "titles":
32899
+ return `---
32900
+ selectedTitle: ""
32901
+ ${SCAFFOLD_MARKER}
32902
+ ---
32903
+
32904
+ # \u6807\u9898\u5019\u9009
32905
+ `;
32906
+ case "visual-plan":
32907
+ return `---
32908
+ cover:
32909
+ purpose: ""
32910
+ inline: []
32911
+ ${SCAFFOLD_MARKER}
32912
+ ---
32913
+
32914
+ # \u89C6\u89C9\u8BA1\u5212
32915
+ `;
32916
+ case "quality-review":
32917
+ return `---
32918
+ verdict: block
32919
+ ${SCAFFOLD_MARKER}
32920
+ ---
32921
+
32922
+ # \u5BA1\u6821\u7ED3\u679C
32923
+ `;
32924
+ case "delivery":
32925
+ return "# \u5FAE\u4FE1\u8349\u7A3F\u4EA4\u4ED8\n";
32926
+ default:
32927
+ return `---
32928
+ ${SCAFFOLD_MARKER}
32929
+ ---
32930
+
32931
+ # ${stageLabel(stage)}
32932
+ `;
32933
+ }
32934
+ }
32935
+ async function ensureStageScaffold(location, stage) {
32936
+ if (stage === "delivery") {
32937
+ return "not-applicable";
32938
+ }
32939
+ const target = artifactPath(location, stage);
32430
32940
  try {
32431
- const value = (0, import_yaml4.parse)(yaml, { maxAliasCount: 10, uniqueKeys: true });
32432
- return isRecord(value) ? value : {};
32433
- } catch {
32434
- return {};
32941
+ const metadata = await lstat6(target);
32942
+ if (!metadata.isFile() || metadata.isSymbolicLink()) {
32943
+ throw new WorkflowCliError(
32944
+ "DXC_WORKFLOW_ARTIFACT_INVALID",
32945
+ `${stage} \u8F93\u51FA\u4F4D\u7F6E\u4E0D\u662F\u666E\u901A\u6587\u4EF6\u3002`,
32946
+ { path: target, stage }
32947
+ );
32948
+ }
32949
+ if (metadata.size > 0) {
32950
+ return "existing";
32951
+ }
32952
+ } catch (error) {
32953
+ if (error instanceof WorkflowCliError || !(typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT")) {
32954
+ throw error;
32955
+ }
32435
32956
  }
32957
+ await writePrivateBuffer(target, Buffer.from(stageScaffold(stage, location.manifest.title)));
32958
+ return "created";
32436
32959
  }
32437
- function selectedTitle(value) {
32438
- const title = value["selectedTitle"];
32439
- return typeof title === "string" && title.trim().length > 0 ? title.trim() : void 0;
32960
+ async function workflowSections(location, stage) {
32961
+ if (stage !== "visual-plan") {
32962
+ return void 0;
32963
+ }
32964
+ const article = await artifactAvailability(location, "article");
32965
+ if (article.availability === "missing") {
32966
+ return [];
32967
+ }
32968
+ return parseWorkflowArticleSections(await readFile4(article.path, "utf8"));
32969
+ }
32970
+ function workflowConstraints(stage) {
32971
+ if (stage === "article" || stage === "titles") {
32972
+ return {
32973
+ authorMaxCharacters: WECHAT_AUTHOR_CHARACTER_LIMIT,
32974
+ digestMaxCharacters: WECHAT_DIGEST_CHARACTER_LIMIT,
32975
+ titleMaxCharacters: WECHAT_TITLE_CHARACTER_LIMIT
32976
+ };
32977
+ }
32978
+ if (stage === "visual-plan" || stage === "delivery") {
32979
+ return {
32980
+ coverMaxBytes: PUBLISHING_COVER_MAXIMUM_BYTES,
32981
+ inlineImageMaxBytes: PUBLISHING_INLINE_IMAGE_MAXIMUM_BYTES,
32982
+ inlineImageMaxCount: PUBLISHING_INLINE_IMAGE_MAXIMUM_COUNT,
32983
+ supportedImageTypes: ["image/jpeg", "image/png"]
32984
+ };
32985
+ }
32986
+ return {};
32987
+ }
32988
+ async function namedResources(location, stage, scaffold) {
32989
+ const definition = CONTENT_STAGE_DEFINITIONS[stage];
32990
+ const inputs = {};
32991
+ for (const kind of [...definition.inputs.required, ...definition.inputs.optional]) {
32992
+ const availability = await artifactAvailability(location, kind);
32993
+ inputs[kind] = availability.availability === "missing" ? { availability: "missing" } : { availability: "present", path: availability.path };
32994
+ }
32995
+ return {
32996
+ inputs,
32997
+ output: { path: artifactPath(location, definition.output), scaffold }
32998
+ };
32999
+ }
33000
+ async function inputFingerprint(location, stage) {
33001
+ const definition = CONTENT_STAGE_DEFINITIONS[stage];
33002
+ const inputs = {};
33003
+ for (const kind of [...definition.inputs.required, ...definition.inputs.optional]) {
33004
+ const availability = await artifactAvailability(location, kind);
33005
+ inputs[kind] = availability.availability === "missing" ? { availability: "missing" } : { availability: "present", hash: availability.hash };
33006
+ }
33007
+ if (stage === "delivery") {
33008
+ inputs["visual-assets"] = await workflowVisualAssetsFingerprint(location.directory);
33009
+ }
33010
+ return sha2562(JSON.stringify(inputs));
33011
+ }
33012
+ function cursorFingerprint(state) {
33013
+ return sha2562(JSON.stringify(state));
32440
33014
  }
32441
- async function validateStageResult(step, source, location) {
32442
- if (source.trim().length === 0) {
32443
- throw new WorkflowCliError("DXC_WORKFLOW_ARTIFACT_INVALID", "\u5F53\u524D\u6B65\u9AA4\u6CA1\u6709\u53EF\u4FDD\u5B58\u7684\u5185\u5BB9\u3002");
33015
+ function encodeWorkItem(payload) {
33016
+ const encoded = Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
33017
+ return `wi1_${encoded}.${sha2562(encoded).slice(0, 32)}`;
33018
+ }
33019
+ function decodeWorkItem(workItemId) {
33020
+ const match = WORK_ITEM_PATTERN.exec(workItemId);
33021
+ if (match === null || match[1] === void 0 || match[2] === void 0) {
33022
+ throw new WorkflowCliError("DXC_WORK_ITEM_INVALID", "\u5DE5\u4F5C\u9879 ID \u683C\u5F0F\u65E0\u6548\u3002");
33023
+ }
33024
+ if (sha2562(match[1]).slice(0, 32) !== match[2]) {
33025
+ throw new WorkflowCliError("DXC_WORK_ITEM_INVALID", "\u5DE5\u4F5C\u9879 ID \u5B8C\u6574\u6027\u6821\u9A8C\u5931\u8D25\u3002");
33026
+ }
33027
+ try {
33028
+ const value = JSON.parse(Buffer.from(match[1], "base64url").toString("utf8"));
33029
+ if (!isRecord(value) || value["version"] !== 1 || typeof value["projectId"] !== "string" || typeof value["stage"] !== "string" || !CONTENT_ARTIFACT_KINDS.includes(value["stage"]) || typeof value["action"] !== "string" || ![
33030
+ "completed",
33031
+ "delivery-confirm",
33032
+ "delivery-preview",
33033
+ "stage",
33034
+ "stage-confirm",
33035
+ "visual-assets"
33036
+ ].includes(value["action"]) || typeof value["cursorFingerprint"] !== "string" || typeof value["inputFingerprint"] !== "string" || value["outputFingerprint"] !== void 0 && typeof value["outputFingerprint"] !== "string" || value["bindingFingerprint"] !== void 0 && typeof value["bindingFingerprint"] !== "string") {
33037
+ throw new TypeError("invalid work item payload");
33038
+ }
33039
+ return value;
33040
+ } catch (error) {
33041
+ if (error instanceof WorkflowCliError) {
33042
+ throw error;
33043
+ }
33044
+ throw new WorkflowCliError("DXC_WORK_ITEM_INVALID", "\u5DE5\u4F5C\u9879 ID \u8F7D\u8377\u65E0\u6548\u3002");
32444
33045
  }
32445
- if (step === "article") {
33046
+ }
33047
+ async function validateRequiredInputs(location, stage) {
33048
+ for (const kind of CONTENT_STAGE_DEFINITIONS[stage].inputs.required) {
33049
+ if ((await artifactAvailability(location, kind)).availability === "missing") {
33050
+ throw new WorkflowCliError(
33051
+ "DXC_WORKFLOW_INPUT_MISSING",
33052
+ `\u5F53\u524D ${stage} \u9636\u6BB5\u7F3A\u5C11\u547D\u540D\u8F93\u5165 ${kind}\u3002`,
33053
+ { input: kind, stage }
33054
+ );
33055
+ }
33056
+ }
33057
+ }
33058
+ async function validateStageResult(stage, source, location) {
33059
+ if (source.trim().length === 0 || source.includes(SCAFFOLD_MARKER)) {
33060
+ throw new WorkflowCliError(
33061
+ "DXC_WORKFLOW_ARTIFACT_INVALID",
33062
+ `${stage} \u9636\u6BB5\u4ECD\u662F\u672A\u586B\u5199\u7684 scaffold\uFF08\u811A\u624B\u67B6\uFF09\u3002`,
33063
+ { path: artifactPath(location, stage), stage }
33064
+ );
33065
+ }
33066
+ if (stage === "article") {
32446
33067
  let article;
32447
33068
  try {
32448
33069
  article = parseArticleDocument(source);
32449
33070
  } catch {
32450
33071
  throw new WorkflowCliError(
32451
33072
  "DXC_WORKFLOW_ARTIFACT_INVALID",
32452
- "\u6B63\u6587\u9700\u8981\u5305\u542B\u53EF\u53D1\u5E03\u7684\u6807\u9898\u3001\u4F5C\u8005\u548C Markdown \u6B63\u6587\u3002"
33073
+ "\u6B63\u6587\u9700\u8981\u5305\u542B\u53EF\u53D1\u5E03\u7684\u6807\u9898\u3001\u4F5C\u8005\u548C Markdown \u6B63\u6587\u3002",
33074
+ { path: artifactPath(location, stage), stage }
32453
33075
  );
32454
33076
  }
32455
33077
  const lengths = {
@@ -32461,62 +33083,41 @@ async function validateStageResult(step, source, location) {
32461
33083
  if (invalid !== void 0) {
32462
33084
  throw new WorkflowCliError(
32463
33085
  "DXC_WORKFLOW_ARTICLE_FIELD_INVALID",
32464
- `${invalid} \u4E2A Unicode \u5B57\u7B26\uFF1B\u8BF7\u5728\u6B63\u6587\u9636\u6BB5\u4FEE\u6B63\u540E\u518D\u7EE7\u7EED\u3002`
33086
+ `${invalid} \u4E2A Unicode \u5B57\u7B26\uFF1B\u8BF7\u5728\u6B63\u6587\u9636\u6BB5\u4FEE\u6B63\u540E\u518D\u7EE7\u7EED\u3002`,
33087
+ { path: artifactPath(location, stage), stage }
32465
33088
  );
32466
33089
  }
32467
33090
  }
32468
- if (step === "titles") {
33091
+ if (stage === "titles") {
32469
33092
  const title = selectedTitle(frontmatter(source));
32470
- if (title === void 0 || [...title].length > 32) {
33093
+ if (title === void 0 || [...title].length > WECHAT_TITLE_CHARACTER_LIMIT) {
32471
33094
  throw new WorkflowCliError(
32472
33095
  "DXC_WORKFLOW_ARTIFACT_INVALID",
32473
- "\u6807\u9898\u6B65\u9AA4\u9700\u8981\u5728 selectedTitle \u4E2D\u4FDD\u5B58\u4E00\u4E2A\u4E0D\u8D85\u8FC7 32 \u5B57\u7684\u6700\u7EC8\u6807\u9898\u3002"
33096
+ `\u6807\u9898\u6B65\u9AA4\u9700\u8981\u5728 selectedTitle \u4E2D\u4FDD\u5B58\u4E00\u4E2A\u4E0D\u8D85\u8FC7 ${WECHAT_TITLE_CHARACTER_LIMIT} \u5B57\u7684\u6700\u7EC8\u6807\u9898\u3002`,
33097
+ { path: artifactPath(location, stage), stage }
32474
33098
  );
32475
33099
  }
32476
33100
  }
32477
- if (step === "visual-plan") {
32478
- const value = frontmatter(source);
32479
- if (typeof value["coverAsset"] !== "string") {
32480
- throw new WorkflowCliError(
32481
- "DXC_WORKFLOW_ARTIFACT_INVALID",
32482
- "\u89C6\u89C9\u6B65\u9AA4\u9700\u8981\u4FDD\u5B58\u771F\u5B9E\u5C01\u9762\u8DEF\u5F84 coverAsset\u3002"
32483
- );
32484
- }
32485
- const articleSource = await readArtifact(
32486
- location.directory,
32487
- location.manifest.artifacts.article
32488
- );
32489
- try {
32490
- await validateWorkflowVisualPlan({
32491
- articleSource,
32492
- assetsDirectory: path8.join(location.directory, location.manifest.assetsDirectory),
32493
- projectDirectory: location.directory,
32494
- visualPlanFile: path8.join(location.directory, location.manifest.artifacts["visual-plan"]),
32495
- visualPlanSource: source
32496
- });
32497
- } catch (error) {
32498
- if (error instanceof WechatCliError) {
32499
- throw new WorkflowCliError(error.code, error.guidance ?? error.message);
32500
- }
32501
- throw error;
32502
- }
33101
+ if (stage === "visual-plan") {
33102
+ const articleSource = await readArtifact(location, "article");
33103
+ validateWorkflowSemanticVisualPlan({
33104
+ sections: parseWorkflowArticleSections(articleSource),
33105
+ source
33106
+ });
32503
33107
  }
32504
- if (step === "quality-review" && frontmatter(source)["verdict"] !== "pass") {
33108
+ if (stage === "quality-review" && frontmatter(source)["verdict"] !== "pass") {
32505
33109
  throw new WorkflowCliError(
32506
33110
  "DXC_WORKFLOW_ARTIFACT_INVALID",
32507
- "\u5BA1\u6821\u6B65\u9AA4\u53EA\u6709 verdict: pass \u65F6\u624D\u80FD\u7EE7\u7EED\u4EA4\u4ED8\u3002"
33111
+ "\u5BA1\u6821\u6B65\u9AA4\u53EA\u6709 verdict: pass \u65F6\u624D\u80FD\u7EE7\u7EED\u4EA4\u4ED8\u3002",
33112
+ { path: artifactPath(location, stage), stage }
32508
33113
  );
32509
33114
  }
32510
33115
  }
32511
33116
  var WorkflowCli = class {
32512
- #currentDirectory;
32513
- #homeDirectory;
32514
33117
  #now;
32515
33118
  #profile;
32516
33119
  #projects;
32517
33120
  constructor(input) {
32518
- this.#currentDirectory = input.currentDirectory;
32519
- this.#homeDirectory = input.homeDirectory;
32520
33121
  this.#now = input.now ?? (() => /* @__PURE__ */ new Date());
32521
33122
  this.#profile = new ProfileCli({
32522
33123
  currentDirectory: input.currentDirectory,
@@ -32533,15 +33134,6 @@ var WorkflowCli = class {
32533
33134
  const status = await this.#profile.status();
32534
33135
  return status.data.configured ? status.data.profile.user.confirmationPolicy : "guided";
32535
33136
  }
32536
- async #locate(directory, projectId) {
32537
- if (directory !== void 0 && projectId !== void 0) {
32538
- throw new WorkflowCliError(
32539
- "DXC_WORKFLOW_PROJECT_REFERENCE_INVALID",
32540
- "\u9879\u76EE\u76EE\u5F55\u548C\u9879\u76EE ID \u53EA\u80FD\u6307\u5B9A\u4E00\u4E2A\u3002"
32541
- );
32542
- }
32543
- return projectId === void 0 ? this.#projects.locate(directory) : this.#projects.locateByProjectId(projectId);
32544
- }
32545
33137
  async #loadState(location, persistMigration = true, migrationUpdatedAt = this.#now().toISOString()) {
32546
33138
  try {
32547
33139
  const raw = await readPrivateFile(
@@ -32596,79 +33188,328 @@ var WorkflowCli = class {
32596
33188
  }
32597
33189
  return migrated;
32598
33190
  }
33191
+ #nextTimestamp(previous) {
33192
+ const current = this.#now().getTime();
33193
+ const previousTime = previous === void 0 ? Number.NEGATIVE_INFINITY : Date.parse(previous);
33194
+ return new Date(Math.max(current, previousTime + 1)).toISOString();
33195
+ }
33196
+ async #basePayload(location, state, stage, action) {
33197
+ return {
33198
+ action,
33199
+ cursorFingerprint: cursorFingerprint(state),
33200
+ inputFingerprint: await inputFingerprint(location, stage),
33201
+ projectId: location.manifest.projectId,
33202
+ stage,
33203
+ version: 1
33204
+ };
33205
+ }
33206
+ async #stagePayload(location, state) {
33207
+ const stage = nextStep(state.lastCompletedStep);
33208
+ if (stage === null) {
33209
+ return this.#basePayload(location, state, "delivery", "completed");
33210
+ }
33211
+ return this.#basePayload(
33212
+ location,
33213
+ state,
33214
+ stage,
33215
+ stage === "delivery" ? "delivery-preview" : "stage"
33216
+ );
33217
+ }
33218
+ async #confirmationPayload(location, state, stage) {
33219
+ const source = await readArtifact(location, stage);
33220
+ await validateStageResult(stage, source, location);
33221
+ const base = await this.#basePayload(location, state, stage, "stage-confirm");
33222
+ let bindingFingerprint;
33223
+ if (stage === "visual-plan") {
33224
+ bindingFingerprint = await workflowVisualAssetsFingerprint(location.directory);
33225
+ }
33226
+ return {
33227
+ ...base,
33228
+ ...bindingFingerprint === void 0 ? {} : { bindingFingerprint },
33229
+ outputFingerprint: sha2562(source)
33230
+ };
33231
+ }
33232
+ async #visualAssetsPayload(location, state) {
33233
+ const source = await readArtifact(location, "visual-plan");
33234
+ await validateStageResult("visual-plan", source, location);
33235
+ return {
33236
+ ...await this.#basePayload(location, state, "visual-plan", "visual-assets"),
33237
+ outputFingerprint: sha2562(source)
33238
+ };
33239
+ }
33240
+ async #currentWorkItemId(location, state) {
33241
+ return encodeWorkItem(await this.#stagePayload(location, state));
33242
+ }
33243
+ async #stale(location, state) {
33244
+ throw new WorkflowCliError(
33245
+ "DXC_WORK_ITEM_STALE",
33246
+ "\u5DE5\u4F5C\u9879\u5DF2\u8FC7\u671F\uFF1B\u8BF7\u4F7F\u7528\u5F53\u524D\u5DE5\u4F5C\u9879\u7EE7\u7EED\uFF0C\u4E0D\u4F1A\u66FF\u4F60\u9759\u9ED8\u5B8C\u6210\u65E7\u52A8\u4F5C\u3002",
33247
+ { workItemId: await this.#currentWorkItemId(location, state) }
33248
+ );
33249
+ }
33250
+ async #resolve(workItemId) {
33251
+ const payload = decodeWorkItem(workItemId);
33252
+ let location;
33253
+ try {
33254
+ location = await this.#projects.locateByProjectId(payload.projectId);
33255
+ } catch {
33256
+ throw new WorkflowCliError("DXC_WORK_ITEM_INVALID", "\u5DE5\u4F5C\u9879\u6307\u5411\u7684\u9879\u76EE\u4E0D\u5B58\u5728\u6216\u65E0\u6548\u3002");
33257
+ }
33258
+ const state = await this.#loadState(location);
33259
+ const stage = nextStep(state.lastCompletedStep);
33260
+ if (stage === null) {
33261
+ const expected2 = encodeWorkItem(await this.#stagePayload(location, state));
33262
+ if (payload.action !== "completed" || expected2 !== workItemId) {
33263
+ return this.#stale(location, state);
33264
+ }
33265
+ return { location, payload, state };
33266
+ }
33267
+ if (payload.stage !== stage) {
33268
+ return this.#stale(location, state);
33269
+ }
33270
+ const currentBase = await this.#basePayload(location, state, stage, payload.action);
33271
+ if (payload.cursorFingerprint !== currentBase.cursorFingerprint || payload.inputFingerprint !== currentBase.inputFingerprint) {
33272
+ return this.#stale(location, state);
33273
+ }
33274
+ let expected;
33275
+ switch (payload.action) {
33276
+ case "stage":
33277
+ case "delivery-preview":
33278
+ expected = await this.#stagePayload(location, state);
33279
+ break;
33280
+ case "stage-confirm":
33281
+ expected = await this.#confirmationPayload(location, state, stage);
33282
+ break;
33283
+ case "visual-assets":
33284
+ if (stage !== "visual-plan") {
33285
+ return this.#stale(location, state);
33286
+ }
33287
+ expected = await this.#visualAssetsPayload(location, state);
33288
+ break;
33289
+ case "delivery-confirm": {
33290
+ if (stage !== "delivery") {
33291
+ return this.#stale(location, state);
33292
+ }
33293
+ expected = {
33294
+ ...await this.#basePayload(location, state, stage, "delivery-confirm"),
33295
+ ...payload.bindingFingerprint === void 0 ? {} : { bindingFingerprint: payload.bindingFingerprint }
33296
+ };
33297
+ break;
33298
+ }
33299
+ case "completed":
33300
+ return this.#stale(location, state);
33301
+ }
33302
+ if (encodeWorkItem(expected) !== workItemId) {
33303
+ return this.#stale(location, state);
33304
+ }
33305
+ return { location, payload, state };
33306
+ }
33307
+ async #workItem(resolved, rich, actionOverride) {
33308
+ const { location, payload } = resolved;
33309
+ const definition = CONTENT_STAGE_DEFINITIONS[payload.stage];
33310
+ const workItemId = encodeWorkItem(payload);
33311
+ let nextAction;
33312
+ switch (payload.action) {
33313
+ case "stage":
33314
+ nextAction = { type: "run-stage" };
33315
+ break;
33316
+ case "visual-assets": {
33317
+ const article = await readArtifact(location, "article");
33318
+ const visualPlan = await readArtifact(location, "visual-plan");
33319
+ const sections2 = parseWorkflowArticleSections(article);
33320
+ const assetsDirectory = path8.join(location.directory, location.manifest.assetsDirectory);
33321
+ await ensurePrivateDirectory(path8.join(assetsDirectory, "visuals"));
33322
+ nextAction = {
33323
+ assets: [
33324
+ ...workflowVisualAssetAssignments({
33325
+ assetsDirectory,
33326
+ projectDirectory: location.directory,
33327
+ sections: sections2,
33328
+ visualPlanSource: visualPlan
33329
+ })
33330
+ ],
33331
+ type: "generate-assets"
33332
+ };
33333
+ break;
33334
+ }
33335
+ case "stage-confirm":
33336
+ nextAction = {
33337
+ presentation: { path: artifactPath(location, payload.stage), type: "artifact" },
33338
+ type: "confirm-stage"
33339
+ };
33340
+ break;
33341
+ case "delivery-preview":
33342
+ nextAction = { type: "preview" };
33343
+ break;
33344
+ case "delivery-confirm":
33345
+ nextAction = actionOverride ?? {
33346
+ expiresAt: (/* @__PURE__ */ new Date(0)).toISOString(),
33347
+ type: "confirm-delivery"
33348
+ };
33349
+ break;
33350
+ case "completed":
33351
+ nextAction = { type: "completed" };
33352
+ break;
33353
+ }
33354
+ const base = {
33355
+ nextAction: actionOverride ?? nextAction,
33356
+ skill: definition.skill,
33357
+ stage: payload.stage,
33358
+ workItemId
33359
+ };
33360
+ if (!rich || payload.action === "completed") {
33361
+ return base;
33362
+ }
33363
+ const scaffold = payload.action === "stage" ? await ensureStageScaffold(location, payload.stage) : payload.stage === "delivery" ? "not-applicable" : "existing";
33364
+ const sections = await workflowSections(location, payload.stage);
33365
+ return {
33366
+ ...base,
33367
+ constraints: workflowConstraints(payload.stage),
33368
+ resources: await namedResources(location, payload.stage, scaffold),
33369
+ ...sections === void 0 ? {} : { sections: [...sections] }
33370
+ };
33371
+ }
33372
+ async #result(location, payload, rich = false, actionOverride) {
33373
+ return {
33374
+ command: "workflow.next",
33375
+ data: {
33376
+ project: projectSummary(location),
33377
+ workItem: await this.#workItem(
33378
+ { location, payload, state: await this.#loadState(location) },
33379
+ rich,
33380
+ actionOverride
33381
+ )
33382
+ },
33383
+ ok: true
33384
+ };
33385
+ }
32599
33386
  async start(options) {
32600
- if (options.directory !== void 0 && options.projectId !== void 0) {
33387
+ if (options.projectId !== void 0 && (options.directory !== void 0 || options.title !== void 0)) {
32601
33388
  throw new WorkflowCliError(
32602
33389
  "DXC_WORKFLOW_PROJECT_REFERENCE_INVALID",
32603
- "\u9879\u76EE\u76EE\u5F55\u548C\u9879\u76EE ID \u53EA\u80FD\u6307\u5B9A\u4E00\u4E2A\u3002"
33390
+ "\u5DF2\u6709\u9879\u76EE\u53EA\u80FD\u4F7F\u7528 --project\uFF0C\u4E0D\u63A5\u53D7\u6807\u9898\u6216\u76EE\u5F55\u3002"
32604
33391
  );
32605
33392
  }
32606
- const directory = options.directory === void 0 ? void 0 : resolveUserPath({
32607
- currentDirectory: this.#currentDirectory,
32608
- homeDirectory: this.#homeDirectory,
32609
- input: options.directory
32610
- });
32611
33393
  const requested = options.from === void 0 ? void 0 : requestedStep(options.from);
32612
33394
  let location;
33395
+ let existingState;
32613
33396
  if (options.projectId !== void 0) {
32614
33397
  location = await this.#projects.locateByProjectId(options.projectId);
32615
- } else if (directory !== void 0) {
32616
- try {
32617
- location = await this.#projects.locate(directory);
32618
- } catch (error) {
32619
- if (!(error instanceof ProjectCliError) || error.code !== "DXC_PROJECT_NOT_FOUND") {
32620
- throw error;
32621
- }
32622
- if (options.title === void 0 || options.title.trim().length === 0) {
32623
- throw new WorkflowCliError(
32624
- "DXC_WORKFLOW_TITLE_REQUIRED",
32625
- "\u521B\u5EFA\u65B0\u9879\u76EE\u65F6\u9700\u8981\u63D0\u4F9B\u6587\u7AE0\u4E3B\u9898\u3002"
32626
- );
32627
- }
32628
- const initialized = await this.#projects.initialize(directory, options.title);
32629
- location = { directory: initialized.data.directory, manifest: initialized.data.manifest };
33398
+ existingState = await this.#loadState(location);
33399
+ if (requested === void 0) {
33400
+ return this.#result(location, await this.#stagePayload(location, existingState));
32630
33401
  }
32631
33402
  } else {
32632
33403
  if (options.title === void 0 || options.title.trim().length === 0) {
32633
33404
  throw new WorkflowCliError("DXC_WORKFLOW_TITLE_REQUIRED", "\u521B\u5EFA\u65B0\u9879\u76EE\u65F6\u9700\u8981\u63D0\u4F9B\u6587\u7AE0\u4E3B\u9898\u3002");
32634
33405
  }
32635
- const initialized = await this.#projects.initialize(void 0, options.title);
33406
+ const initialized = await this.#projects.initialize(options.directory, options.title);
32636
33407
  location = { directory: initialized.data.directory, manifest: initialized.data.manifest };
32637
33408
  }
32638
- if (requested === void 0 && options.projectId !== void 0) {
32639
- const state2 = await this.#loadState(location);
32640
- const confirmationPolicy2 = await this.#confirmationPolicy();
32641
- return {
32642
- command: "workflow.next",
32643
- data: { match: "ready", work: workItem(location, state2, confirmationPolicy2) },
32644
- ok: true
32645
- };
32646
- }
32647
- if (requested === void 0 && directory !== void 0) {
32648
- const state2 = await this.#loadState(location);
32649
- if (state2.lastCompletedStep !== null) {
32650
- const confirmationPolicy2 = await this.#confirmationPolicy();
32651
- return {
32652
- command: "workflow.next",
32653
- data: { match: "ready", work: workItem(location, state2, confirmationPolicy2) },
32654
- ok: true
32655
- };
32656
- }
32657
- }
32658
- const step = requested ?? "research";
33409
+ const stage = requested ?? "research";
32659
33410
  const state = {
32660
- lastCompletedStep: previousStep(step),
33411
+ lastCompletedStep: previousStep(stage),
32661
33412
  projectId: location.manifest.projectId,
32662
- updatedAt: this.#now().toISOString(),
33413
+ updatedAt: this.#nextTimestamp(existingState?.updatedAt),
32663
33414
  version: 1
32664
33415
  };
32665
33416
  await writePrivateJson(statePath(location.directory), state);
32666
- const confirmationPolicy = await this.#confirmationPolicy();
32667
- return {
32668
- command: "workflow.next",
32669
- data: { match: "ready", work: workItem(location, state, confirmationPolicy) },
32670
- ok: true
33417
+ return this.#result(location, await this.#stagePayload(location, state));
33418
+ }
33419
+ async next(options) {
33420
+ if (options.projectId === void 0 === (options.workItemId === void 0)) {
33421
+ throw new WorkflowCliError(
33422
+ "DXC_WORKFLOW_PROJECT_REFERENCE_INVALID",
33423
+ "workflow next \u5FC5\u987B\u4E14\u53EA\u80FD\u6307\u5B9A --project \u6216 --work-item\u3002"
33424
+ );
33425
+ }
33426
+ if (options.workItemId !== void 0) {
33427
+ const resolved = await this.#resolve(options.workItemId);
33428
+ return this.#result(resolved.location, resolved.payload, true);
33429
+ }
33430
+ const location = await this.#projects.locateByProjectId(options.projectId);
33431
+ const state = await this.#loadState(location);
33432
+ return this.#result(location, await this.#stagePayload(location, state));
33433
+ }
33434
+ async complete(workItemId, confirmed = false) {
33435
+ const resolved = await this.#resolve(workItemId);
33436
+ const { location, payload, state } = resolved;
33437
+ if (payload.action === "completed") {
33438
+ return this.#result(location, payload);
33439
+ }
33440
+ if (payload.stage === "delivery" || payload.action.startsWith("delivery-")) {
33441
+ throw new WorkflowCliError(
33442
+ "DXC_WORKFLOW_DELIVERY_REQUIRED",
33443
+ "\u5185\u5BB9\u5DF2\u7ECF\u51C6\u5907\u5B8C\u6210\uFF1B\u8BF7\u751F\u6210\u5FAE\u4FE1\u9884\u89C8\u5E76\u786E\u8BA4\u4EA4\u4ED8\u3002"
33444
+ );
33445
+ }
33446
+ if (payload.action === "stage-confirm") {
33447
+ if (!confirmed) {
33448
+ throw new WorkflowCliError(
33449
+ "DXC_WORKFLOW_CONFIRMATION_REQUIRED",
33450
+ `\u8BF7\u660E\u786E\u786E\u8BA4\u91C7\u7528\u5F53\u524D${stageLabel(payload.stage)}\u3002`
33451
+ );
33452
+ }
33453
+ return this.#advance(location, state, payload.stage);
33454
+ }
33455
+ if (confirmed) {
33456
+ throw new WorkflowCliError(
33457
+ "DXC_WORKFLOW_CONFIRMATION_REQUIRED",
33458
+ "\u751F\u4EA7\u5DE5\u4F5C\u9879\u4E0D\u80FD\u76F4\u63A5\u786E\u8BA4\uFF1B\u8BF7\u5148\u5B8C\u6210\u6821\u9A8C\u5E76\u5C55\u793A CLI \u8FD4\u56DE\u7684\u786E\u8BA4\u5DE5\u4F5C\u9879\u3002"
33459
+ );
33460
+ }
33461
+ await validateRequiredInputs(location, payload.stage);
33462
+ const source = await readArtifact(location, payload.stage);
33463
+ await validateStageResult(payload.stage, source, location);
33464
+ if (payload.action === "stage" && payload.stage === "visual-plan") {
33465
+ const visualPayload = await this.#visualAssetsPayload(location, state);
33466
+ return this.#result(location, visualPayload, true);
33467
+ }
33468
+ if (payload.action === "visual-assets") {
33469
+ const articleSource = await readArtifact(location, "article");
33470
+ const sections = parseWorkflowArticleSections(articleSource);
33471
+ const assetsDirectory = path8.join(location.directory, location.manifest.assetsDirectory);
33472
+ const assignments = workflowVisualAssetAssignments({
33473
+ assetsDirectory,
33474
+ projectDirectory: location.directory,
33475
+ sections,
33476
+ visualPlanSource: source
33477
+ });
33478
+ try {
33479
+ await finalizeWorkflowVisualAssets({
33480
+ articleFile: artifactPath(location, "article"),
33481
+ articleSource,
33482
+ assetsDirectory,
33483
+ assignments,
33484
+ projectDirectory: location.directory,
33485
+ sections,
33486
+ visualPlanSource: source
33487
+ });
33488
+ } catch (error) {
33489
+ if (error instanceof WechatCliError) {
33490
+ throw new WorkflowCliError(error.code, error.guidance ?? error.message, {
33491
+ ...error.fields,
33492
+ stage: payload.stage
33493
+ });
33494
+ }
33495
+ throw error;
33496
+ }
33497
+ }
33498
+ const policy = await this.#confirmationPolicy();
33499
+ if (requiresStageConfirmation(policy, payload.stage)) {
33500
+ const confirmationPayload = await this.#confirmationPayload(location, state, payload.stage);
33501
+ return this.#result(location, confirmationPayload, true);
33502
+ }
33503
+ return this.#advance(location, state, payload.stage);
33504
+ }
33505
+ async #advance(location, state, stage) {
33506
+ const nextState = {
33507
+ ...state,
33508
+ lastCompletedStep: stage,
33509
+ updatedAt: this.#nextTimestamp(state.updatedAt)
32671
33510
  };
33511
+ await writePrivateJson(statePath(location.directory), nextState);
33512
+ return this.#result(location, await this.#stagePayload(location, nextState));
32672
33513
  }
32673
33514
  async listProjects() {
32674
33515
  const projects = [];
@@ -32690,6 +33531,7 @@ var WorkflowCli = class {
32690
33531
  projects.push({
32691
33532
  ...projectSummary(indexed.location),
32692
33533
  available: true,
33534
+ directory: indexed.location.directory,
32693
33535
  done: step === null,
32694
33536
  step,
32695
33537
  updatedAt: state.updatedAt
@@ -32698,96 +33540,82 @@ var WorkflowCli = class {
32698
33540
  projects.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
32699
33541
  return { command: "project.list", data: { projects }, ok: true };
32700
33542
  }
32701
- async next(query, directory) {
32702
- const projects = await this.#projects.find(query, directory);
32703
- if (projects.length === 0) {
32704
- return { command: "workflow.next", data: { match: "none", projects: [] }, ok: true };
33543
+ async deliveryInputs(workItemId) {
33544
+ const resolved = await this.#resolve(workItemId);
33545
+ if (resolved.payload.stage !== "delivery" || resolved.payload.action !== "delivery-preview" && resolved.payload.action !== "delivery-confirm") {
33546
+ throw new WorkflowCliError("DXC_WORK_ITEM_STALE", "\u5F53\u524D\u5DE5\u4F5C\u9879\u4E0D\u662F\u53EF\u4EA4\u4ED8\u5DE5\u4F5C\u9879\u3002");
32705
33547
  }
32706
- if (projects.length > 1) {
32707
- return {
32708
- command: "workflow.next",
32709
- data: { match: "multiple", projects: projects.map(projectSummary) },
32710
- ok: true
32711
- };
33548
+ await validateRequiredInputs(resolved.location, "delivery");
33549
+ const titleSource = await readArtifact(resolved.location, "titles");
33550
+ const title = selectedTitle(frontmatter(titleSource));
33551
+ if (title === void 0) {
33552
+ throw new WorkflowCliError("DXC_WORKFLOW_ARTIFACT_INVALID", "\u6807\u9898\u4EA7\u7269\u7F3A\u5C11\u6700\u7EC8\u6807\u9898\u3002");
32712
33553
  }
32713
- const location = projects[0];
32714
- if (location === void 0) {
32715
- throw new ProjectCliError("DXC_PROJECT_NOT_FOUND", "Content project was not found");
33554
+ let sidecar;
33555
+ try {
33556
+ sidecar = await readWorkflowVisualAssetsSidecar(resolved.location.directory);
33557
+ await validateWorkflowVisualAssets({
33558
+ articleSource: await readArtifact(resolved.location, "article"),
33559
+ assetsDirectory: path8.join(
33560
+ resolved.location.directory,
33561
+ resolved.location.manifest.assetsDirectory
33562
+ ),
33563
+ projectDirectory: resolved.location.directory,
33564
+ visualPlanSource: await readArtifact(resolved.location, "visual-plan")
33565
+ });
33566
+ } catch {
33567
+ throw new WorkflowCliError(
33568
+ "DXC_WORKFLOW_ARTIFACT_INVALID",
33569
+ "\u89C6\u89C9\u7D20\u6750 sidecar\uFF08\u65C1\u8F66\u5143\u6570\u636E\uFF09\u7F3A\u5931\uFF0C\u8BF7\u91CD\u65B0\u5B8C\u6210 visual-plan \u9636\u6BB5\u3002"
33570
+ );
32716
33571
  }
32717
- const state = await this.#loadState(location);
32718
- const confirmationPolicy = await this.#confirmationPolicy();
32719
33572
  return {
32720
- command: "workflow.next",
32721
- data: { match: "ready", work: workItem(location, state, confirmationPolicy) },
32722
- ok: true
33573
+ articleFile: artifactPath(resolved.location, "article"),
33574
+ assetsDirectory: path8.join(
33575
+ resolved.location.directory,
33576
+ resolved.location.manifest.assetsDirectory
33577
+ ),
33578
+ project: projectSummary(resolved.location),
33579
+ projectDirectory: resolved.location.directory,
33580
+ title,
33581
+ visualPlanFile: sidecar.publishingVisualPlanPath
32723
33582
  };
32724
33583
  }
32725
- async complete(directory, projectId, confirmed = false) {
32726
- const location = await this.#locate(directory, projectId);
32727
- const state = await this.#loadState(location);
32728
- const confirmationPolicy = await this.#confirmationPolicy();
32729
- const step = nextStep(state.lastCompletedStep);
32730
- if (step === null) {
32731
- return {
32732
- command: "workflow.next",
32733
- data: { match: "ready", work: workItem(location, state, confirmationPolicy) },
32734
- ok: true
32735
- };
32736
- }
32737
- if (step === "delivery") {
32738
- throw new WorkflowCliError(
32739
- "DXC_WORKFLOW_DELIVERY_REQUIRED",
32740
- "\u5185\u5BB9\u5DF2\u7ECF\u51C6\u5907\u5B8C\u6210\uFF1B\u8BF7\u751F\u6210\u5FAE\u4FE1\u9884\u89C8\u5E76\u786E\u8BA4\u4EA4\u4ED8\u3002"
32741
- );
33584
+ async issueDeliveryConfirmation(previewWorkItemId, bindingFingerprint, expiresAt) {
33585
+ const resolved = await this.#resolve(previewWorkItemId);
33586
+ if (resolved.payload.action !== "delivery-preview") {
33587
+ throw new WorkflowCliError("DXC_WORK_ITEM_STALE", "\u8BF7\u4ECE\u5F53\u524D delivery \u9884\u89C8\u5DE5\u4F5C\u9879\u5F00\u59CB\u3002");
32742
33588
  }
32743
- const source = await readArtifact(location.directory, location.manifest.artifacts[step]);
32744
- await validateStageResult(step, source, location);
32745
- if (requiresStageConfirmation(confirmationPolicy, step) && !confirmed) {
32746
- throw new WorkflowCliError(
32747
- "DXC_WORKFLOW_CONFIRMATION_REQUIRED",
32748
- `\u5F53\u524D\u786E\u8BA4\u7B56\u7565\u8981\u6C42\u7528\u6237\u786E\u8BA4${stageLabel(step)}\uFF1B\u786E\u8BA4\u91C7\u7528\u5F53\u524D\u4EA7\u7269\u540E\uFF0C\u8BF7\u4F7F\u7528 --confirm \u5B8C\u6210\u672C\u9636\u6BB5\u3002`
32749
- );
32750
- }
32751
- const nextState = {
32752
- ...state,
32753
- lastCompletedStep: step,
32754
- updatedAt: this.#now().toISOString()
32755
- };
32756
- await writePrivateJson(statePath(location.directory), nextState);
32757
- return {
32758
- command: "workflow.next",
32759
- data: { match: "ready", work: workItem(location, nextState, confirmationPolicy) },
32760
- ok: true
33589
+ const payload = {
33590
+ ...await this.#basePayload(
33591
+ resolved.location,
33592
+ resolved.state,
33593
+ "delivery",
33594
+ "delivery-confirm"
33595
+ ),
33596
+ bindingFingerprint
32761
33597
  };
33598
+ return this.#result(resolved.location, payload, true, {
33599
+ expiresAt,
33600
+ type: "confirm-delivery"
33601
+ });
32762
33602
  }
32763
- async deliveryInputs(directory, projectId) {
32764
- const location = await this.#locate(directory, projectId);
32765
- const state = await this.#loadState(location);
32766
- if (nextStep(state.lastCompletedStep) !== "delivery") {
33603
+ async resolveDeliveryConfirmation(workItemId, bindingFingerprint) {
33604
+ const resolved = await this.#resolve(workItemId);
33605
+ if (resolved.payload.action !== "delivery-confirm" || resolved.payload.bindingFingerprint !== bindingFingerprint) {
32767
33606
  throw new WorkflowCliError(
32768
- "DXC_WORKFLOW_NOT_READY",
32769
- "\u8BF7\u5148\u5B8C\u6210\u6B63\u6587\u3001\u6807\u9898\u3001\u89C6\u89C9\u548C\u5BA1\u6821\u6B65\u9AA4\uFF0C\u518D\u751F\u6210\u5FAE\u4FE1\u9884\u89C8\u3002"
33607
+ "DXC_WORK_ITEM_STALE",
33608
+ "\u6700\u7EC8\u9884\u89C8\u7ED1\u5B9A\u5DF2\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u5E76\u786E\u8BA4\u5F53\u524D\u9884\u89C8\u3002"
32770
33609
  );
32771
33610
  }
32772
- const titleSource = await readArtifact(location.directory, location.manifest.artifacts.titles);
32773
- const title = selectedTitle(frontmatter(titleSource));
32774
- return {
32775
- articleFile: path8.join(location.directory, location.manifest.artifacts.article),
32776
- assetsDirectory: path8.join(location.directory, location.manifest.assetsDirectory),
32777
- project: projectSummary(location),
32778
- projectDirectory: location.directory,
32779
- title,
32780
- visualPlanFile: path8.join(location.directory, location.manifest.artifacts["visual-plan"])
32781
- };
33611
+ return this.deliveryInputs(workItemId);
32782
33612
  }
32783
- async completeDelivery(directory, receipt) {
32784
- const location = await this.#projects.locate(directory);
32785
- const state = await this.#loadState(location);
32786
- const confirmationPolicy = await this.#confirmationPolicy();
32787
- if (nextStep(state.lastCompletedStep) !== "delivery") {
32788
- throw new WorkflowCliError("DXC_WORKFLOW_NOT_READY", "\u5F53\u524D\u6587\u7AE0\u4E0D\u5728\u5FAE\u4FE1\u4EA4\u4ED8\u6B65\u9AA4\u3002");
33613
+ async completeDelivery(workItemId, bindingFingerprint, receipt) {
33614
+ const resolved = await this.#resolve(workItemId);
33615
+ if (resolved.payload.action !== "delivery-confirm" || resolved.payload.bindingFingerprint !== bindingFingerprint) {
33616
+ throw new WorkflowCliError("DXC_WORK_ITEM_STALE", "\u4EA4\u4ED8\u786E\u8BA4\u4E0D\u5C5E\u4E8E\u5F53\u524D\u4E0D\u53EF\u53D8\u9884\u89C8\u3002");
32789
33617
  }
32790
- const deliveryFile = path8.join(location.directory, location.manifest.artifacts.delivery);
33618
+ const deliveryFile = artifactPath(resolved.location, "delivery");
32791
33619
  await writePrivateBuffer(
32792
33620
  deliveryFile,
32793
33621
  Buffer.from(
@@ -32795,22 +33623,12 @@ var WorkflowCli = class {
32795
33623
 
32796
33624
  - intentId: ${receipt.intentId}
32797
33625
  - mediaId: ${receipt.mediaId}
32798
- - completedAt: ${this.#now().toISOString()}
33626
+ - completedAt: ${this.#nextTimestamp(resolved.state.updatedAt)}
32799
33627
  `,
32800
33628
  "utf8"
32801
33629
  )
32802
33630
  );
32803
- const nextState = {
32804
- ...state,
32805
- lastCompletedStep: "delivery",
32806
- updatedAt: this.#now().toISOString()
32807
- };
32808
- await writePrivateJson(statePath(location.directory), nextState);
32809
- return {
32810
- command: "workflow.next",
32811
- data: { match: "ready", work: workItem(location, nextState, confirmationPolicy) },
32812
- ok: true
32813
- };
33631
+ return this.#advance(resolved.location, resolved.state, "delivery");
32814
33632
  }
32815
33633
  };
32816
33634
 
@@ -32818,15 +33636,21 @@ var WorkflowCli = class {
32818
33636
  var DELIVERY_STATE_FILE = ".dxc/delivery.json";
32819
33637
  var DELIVERY_STATE_MAXIMUM_BYTES = 32 * 1024;
32820
33638
  var MAXIMUM_POLL_ATTEMPTS = 150;
33639
+ var CHOICE_PATTERN = /^choice1_([A-Za-z0-9_-]+)\.([a-f0-9]{32})$/u;
33640
+ function sha2563(value) {
33641
+ return createHash8("sha256").update(value, "utf8").digest("hex");
33642
+ }
32821
33643
  function selectedDraftIdempotencyKey(projectId, rootSnapshotHash, selectedSnapshotHash, selectionRevision) {
32822
- const selectionDigest = createHash7("sha256").update(`${rootSnapshotHash}:${selectedSnapshotHash}:${selectionRevision}`, "utf8").digest("hex");
33644
+ const selectionDigest = sha2563(
33645
+ `${rootSnapshotHash}:${selectedSnapshotHash}:${selectionRevision}`
33646
+ );
32823
33647
  return `dxc:${projectId}:${selectionDigest}`;
32824
33648
  }
32825
33649
  function isRecord2(value) {
32826
33650
  return typeof value === "object" && value !== null && !Array.isArray(value);
32827
33651
  }
32828
33652
  function deliveryState(value) {
32829
- if (!isRecord2(value) || value["version"] !== 1) {
33653
+ if (!isRecord2(value) || value["version"] !== 1 && value["version"] !== 2) {
32830
33654
  return void 0;
32831
33655
  }
32832
33656
  const required = [
@@ -32846,26 +33670,81 @@ function deliveryState(value) {
32846
33670
  if (value["intentId"] !== void 0 && typeof value["intentId"] !== "string") {
32847
33671
  return void 0;
32848
33672
  }
33673
+ if (value["version"] === 2 && (typeof value["selectedSnapshotHash"] !== "string" || typeof value["selectedSnapshotId"] !== "string" || !Number.isInteger(value["selectionRevision"]) || typeof value["templateId"] !== "string")) {
33674
+ return void 0;
33675
+ }
32849
33676
  return value;
32850
33677
  }
32851
- function accountChoice(account) {
32852
- return { id: account.id, name: account.displayName };
32853
- }
32854
33678
  function availableAccounts(accounts) {
32855
33679
  return accounts.filter(
32856
33680
  (account) => account.status === "ACTIVE" && account.draftCapability === "available" && account.permissions.includes(11)
32857
33681
  );
32858
33682
  }
33683
+ function encodeChoice(workItemId, account) {
33684
+ const payload = Buffer.from(
33685
+ JSON.stringify({ accountId: account.id, workItemFingerprint: sha2563(workItemId) }),
33686
+ "utf8"
33687
+ ).toString("base64url");
33688
+ return `choice1_${payload}.${sha2563(payload).slice(0, 32)}`;
33689
+ }
33690
+ function decodeChoice(choiceId, workItemId) {
33691
+ const match = CHOICE_PATTERN.exec(choiceId);
33692
+ if (match === null || match[1] === void 0 || match[2] === void 0 || sha2563(match[1]).slice(0, 32) !== match[2]) {
33693
+ throw new WorkflowCliError("DXC_WORK_ITEM_INVALID", "\u516C\u4F17\u53F7 choiceId \u65E0\u6548\u3002");
33694
+ }
33695
+ try {
33696
+ const payload = JSON.parse(Buffer.from(match[1], "base64url").toString("utf8"));
33697
+ if (!isRecord2(payload) || typeof payload["accountId"] !== "string" || payload["workItemFingerprint"] !== sha2563(workItemId)) {
33698
+ throw new TypeError("choice binding mismatch");
33699
+ }
33700
+ return payload["accountId"];
33701
+ } catch {
33702
+ throw new WorkflowCliError(
33703
+ "DXC_WORK_ITEM_STALE",
33704
+ "\u516C\u4F17\u53F7 choiceId \u4E0D\u5C5E\u4E8E\u5F53\u524D delivery \u5DE5\u4F5C\u9879\uFF0C\u8BF7\u91CD\u65B0\u9009\u62E9\u3002"
33705
+ );
33706
+ }
33707
+ }
33708
+ function selectedState(state, preview) {
33709
+ return {
33710
+ ...state,
33711
+ idempotencyKey: selectedDraftIdempotencyKey(
33712
+ state.projectId,
33713
+ state.rootSnapshotHash,
33714
+ preview.selectedSnapshotHash,
33715
+ preview.selectionRevision
33716
+ ),
33717
+ selectedSnapshotHash: preview.selectedSnapshotHash,
33718
+ selectedSnapshotId: preview.selectedSnapshotId,
33719
+ selectionRevision: preview.selectionRevision,
33720
+ templateId: preview.templateId,
33721
+ version: 2
33722
+ };
33723
+ }
33724
+ function deliveryBindingFingerprint(state) {
33725
+ return sha2563(
33726
+ JSON.stringify({
33727
+ accountId: state.accountId,
33728
+ projectId: state.projectId,
33729
+ rootSnapshotHash: state.rootSnapshotHash,
33730
+ rootSnapshotId: state.rootSnapshotId,
33731
+ selectedSnapshotHash: state.selectedSnapshotHash,
33732
+ selectedSnapshotId: state.selectedSnapshotId,
33733
+ selectionRevision: state.selectionRevision,
33734
+ templateId: state.templateId
33735
+ })
33736
+ );
33737
+ }
33738
+ function completedVerification(verification) {
33739
+ return verification !== void 0 && verification.authorMatched === true && verification.contentMatched === true && verification.coverMatched === true && verification.digestMatched === true && verification.titleMatched === true && verification.imageMismatches.length === 0;
33740
+ }
32859
33741
  var WorkflowDeliveryCli = class {
32860
33742
  #output;
32861
33743
  #sleep;
32862
33744
  #wechat;
32863
33745
  #workflow;
32864
33746
  constructor(input) {
32865
- this.#output = {
32866
- writeStderr: input.output.writeStderr,
32867
- writeStdout: () => void 0
32868
- };
33747
+ this.#output = { writeStderr: input.output.writeStderr, writeStdout: () => void 0 };
32869
33748
  this.#sleep = input.sleep ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
32870
33749
  this.#wechat = input.wechat;
32871
33750
  this.#workflow = input.workflow;
@@ -32903,18 +33782,50 @@ var WorkflowDeliveryCli = class {
32903
33782
  async #writeState(projectDirectory, state) {
32904
33783
  await writePrivateJson(path9.join(projectDirectory, DELIVERY_STATE_FILE), state);
32905
33784
  }
33785
+ async #selectState(projectDirectory, state) {
33786
+ const preview = await this.#wechat.draftPreviewStatus(
33787
+ { snapshotHash: state.rootSnapshotHash, snapshotId: state.rootSnapshotId },
33788
+ this.#output
33789
+ );
33790
+ const selected = selectedState(state, preview);
33791
+ await this.#writeState(projectDirectory, selected);
33792
+ return selected;
33793
+ }
33794
+ #selectionWorkItem(workItemId, accounts) {
33795
+ const nextAction = {
33796
+ choices: accounts.map((account) => ({
33797
+ choiceId: encodeChoice(workItemId, account),
33798
+ label: account.displayName
33799
+ })),
33800
+ purpose: "wechat-account",
33801
+ type: "select-choice"
33802
+ };
33803
+ return {
33804
+ nextAction,
33805
+ skill: "dxc-wechat-publisher",
33806
+ stage: "delivery",
33807
+ workItemId
33808
+ };
33809
+ }
32906
33810
  async preview(options) {
32907
- const inputs = await this.#workflow.deliveryInputs(options.directory, options.projectId);
33811
+ const inputs = await this.#workflow.deliveryInputs(options.workItemId);
32908
33812
  const existingState = await this.#readStateIfPresent(inputs.projectDirectory);
32909
33813
  if (existingState?.projectId === inputs.project.projectId && existingState.intentId !== void 0) {
32910
- const existing = await this.#wechat.publishingDraftStatus(
32911
- existingState.intentId,
32912
- this.#output
32913
- );
32914
- throw new WorkflowCliError(
32915
- "DXC_WORKFLOW_DELIVERY_UNRESOLVED",
32916
- `\u5F53\u524D\u9879\u76EE\u4ECD\u7ED1\u5B9A\u8349\u7A3F\u610F\u56FE ${existing.intent.id}\uFF08${existing.intent.status}\uFF09\uFF1B\u5DF2\u4FDD\u7559\u539F\u4EA4\u4ED8\u72B6\u6001\u4E14\u672A\u751F\u6210\u65B0\u9884\u89C8\u3002\u8BF7\u7EE7\u7EED\u6267\u884C workflow deliver --confirm\uFF0C\u8BA9 CLI \u53EA\u8BFB\u56DE\u8BFB\u3001\u8BCA\u65AD\u6216\u5B8C\u6210\u539F\u610F\u56FE\u3002`
33814
+ await this.#wechat.publishingDraftStatus(existingState.intentId, this.#output);
33815
+ const selected = await this.#selectState(inputs.projectDirectory, existingState);
33816
+ const confirmation2 = await this.#workflow.issueDeliveryConfirmation(
33817
+ options.workItemId,
33818
+ deliveryBindingFingerprint(selected),
33819
+ selected.previewExpiresAt
32917
33820
  );
33821
+ return {
33822
+ command: "workflow.preview",
33823
+ data: {
33824
+ ...confirmation2.data,
33825
+ status: "needs-attention"
33826
+ },
33827
+ ok: true
33828
+ };
32918
33829
  }
32919
33830
  const accounts = availableAccounts(await this.#wechat.accounts(this.#output));
32920
33831
  if (accounts.length === 0) {
@@ -32923,9 +33834,10 @@ var WorkflowDeliveryCli = class {
32923
33834
  "\u5F53\u524D\u6CA1\u6709\u53EF\u521B\u5EFA\u8349\u7A3F\u7684\u516C\u4F17\u53F7\uFF0C\u8BF7\u5148\u5B8C\u6210\u516C\u4F17\u53F7\u7ED1\u5B9A\u3002"
32924
33835
  );
32925
33836
  }
32926
- const account = options.accountId === void 0 ? accounts.length === 1 ? accounts[0] : void 0 : accounts.find((candidate) => candidate.id === options.accountId);
33837
+ const accountId = options.choiceId === void 0 ? void 0 : decodeChoice(options.choiceId, options.workItemId);
33838
+ const account = accountId === void 0 ? accounts.length === 1 ? accounts[0] : void 0 : accounts.find((candidate) => candidate.id === accountId);
32927
33839
  if (account === void 0) {
32928
- if (options.accountId !== void 0) {
33840
+ if (accountId !== void 0) {
32929
33841
  throw new WechatCliError(
32930
33842
  "DXC_PUBLISHING_ACCOUNT_UNAVAILABLE",
32931
33843
  "\u9009\u62E9\u7684\u516C\u4F17\u53F7\u5F53\u524D\u4E0D\u53EF\u7528\u4E8E\u521B\u5EFA\u8349\u7A3F\u3002"
@@ -32934,17 +33846,15 @@ var WorkflowDeliveryCli = class {
32934
33846
  return {
32935
33847
  command: "workflow.preview",
32936
33848
  data: {
32937
- accounts: accounts.map(accountChoice),
32938
- status: "account-selection-required"
33849
+ project: inputs.project,
33850
+ status: "account-selection-required",
33851
+ workItem: this.#selectionWorkItem(options.workItemId, accounts)
32939
33852
  },
32940
33853
  ok: true
32941
33854
  };
32942
33855
  }
32943
33856
  await this.#wechat.optimizeDraftImages(
32944
- {
32945
- projectDirectory: inputs.projectDirectory,
32946
- visualPlanFile: inputs.visualPlanFile
32947
- },
33857
+ { projectDirectory: inputs.projectDirectory, visualPlanFile: inputs.visualPlanFile },
32948
33858
  this.#output
32949
33859
  );
32950
33860
  const preview = await this.#wechat.previewDraft(
@@ -32952,14 +33862,14 @@ var WorkflowDeliveryCli = class {
32952
33862
  accountId: account.id,
32953
33863
  articleFile: inputs.articleFile,
32954
33864
  assetsDirectory: inputs.assetsDirectory,
32955
- browserMode: options.browserMode ?? "system",
33865
+ browserMode: "agent",
32956
33866
  coverFile: "auto",
32957
33867
  title: inputs.title,
32958
33868
  visualPlanFile: inputs.visualPlanFile
32959
33869
  },
32960
33870
  this.#output
32961
33871
  );
32962
- await this.#writeState(inputs.projectDirectory, {
33872
+ const state = await this.#selectState(inputs.projectDirectory, {
32963
33873
  accountId: account.id,
32964
33874
  idempotencyKey: `dxc:${inputs.project.projectId}:${preview.snapshot.snapshotHash}`,
32965
33875
  previewExpiresAt: preview.preview.expiresAt,
@@ -32969,14 +33879,14 @@ var WorkflowDeliveryCli = class {
32969
33879
  rootSnapshotId: preview.snapshot.id,
32970
33880
  version: 1
32971
33881
  });
33882
+ const confirmation = await this.#workflow.issueDeliveryConfirmation(
33883
+ options.workItemId,
33884
+ deliveryBindingFingerprint(state),
33885
+ state.previewExpiresAt
33886
+ );
32972
33887
  return {
32973
33888
  command: "workflow.preview",
32974
- data: {
32975
- account: accountChoice(account),
32976
- expiresAt: preview.preview.expiresAt,
32977
- opened: true,
32978
- status: "ready"
32979
- },
33889
+ data: { ...confirmation.data, status: "ready" },
32980
33890
  ok: true
32981
33891
  };
32982
33892
  }
@@ -33022,29 +33932,33 @@ var WorkflowDeliveryCli = class {
33022
33932
  "\u8BF7\u5148\u67E5\u770B\u5FAE\u4FE1\u9884\u89C8\u5E76\u660E\u786E\u786E\u8BA4\u521B\u5EFA\u8349\u7A3F\u3002"
33023
33933
  );
33024
33934
  }
33025
- const inputs = await this.#workflow.deliveryInputs(options.directory, options.projectId);
33026
- let state = await this.#readState(inputs.projectDirectory);
33935
+ const unresolvedInputs = await this.#workflow.deliveryInputs(options.workItemId);
33936
+ let state = await this.#selectState(
33937
+ unresolvedInputs.projectDirectory,
33938
+ await this.#readState(unresolvedInputs.projectDirectory)
33939
+ );
33940
+ const bindingFingerprint = deliveryBindingFingerprint(state);
33941
+ const inputs = await this.#workflow.resolveDeliveryConfirmation(
33942
+ options.workItemId,
33943
+ bindingFingerprint
33944
+ );
33027
33945
  if (state.projectId !== inputs.project.projectId) {
33028
- throw new WorkflowCliError(
33029
- "DXC_WORKFLOW_DELIVERY_STATE_INVALID",
33030
- "\u9884\u89C8\u4E0D\u5C5E\u4E8E\u5F53\u524D\u6587\u7AE0\uFF1B\u8BF7\u91CD\u65B0\u751F\u6210\u5FAE\u4FE1\u9884\u89C8\u3002"
33031
- );
33946
+ throw new WorkflowCliError("DXC_WORK_ITEM_STALE", "\u9884\u89C8\u4E0D\u5C5E\u4E8E\u5F53\u524D\u6587\u7AE0\uFF1B\u8BF7\u91CD\u65B0\u751F\u6210\u5FAE\u4FE1\u9884\u89C8\u3002");
33032
33947
  }
33033
33948
  const preview = await this.#wechat.draftPreviewStatus(
33034
33949
  { snapshotHash: state.rootSnapshotHash, snapshotId: state.rootSnapshotId },
33035
33950
  this.#output
33036
33951
  );
33037
- const template = { id: preview.templateId, selectionRevision: preview.selectionRevision };
33038
- const selectedIdempotencyKey = selectedDraftIdempotencyKey(
33039
- state.projectId,
33040
- state.rootSnapshotHash,
33041
- preview.selectedSnapshotHash,
33042
- preview.selectionRevision
33043
- );
33044
- if (state.intentId === void 0 && state.idempotencyKey !== selectedIdempotencyKey) {
33045
- state = { ...state, idempotencyKey: selectedIdempotencyKey };
33046
- await this.#writeState(inputs.projectDirectory, state);
33952
+ const current = selectedState(state, preview);
33953
+ if (deliveryBindingFingerprint(current) !== bindingFingerprint) {
33954
+ await this.#writeState(inputs.projectDirectory, current);
33955
+ throw new WorkflowCliError(
33956
+ "DXC_WORK_ITEM_STALE",
33957
+ "\u6A21\u677F\u3001\u8D26\u53F7\u6216\u4E0D\u53EF\u53D8\u5FEB\u7167\u9009\u62E9\u5DF2\u7ECF\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u67E5\u770B\u6700\u7EC8\u9884\u89C8\u3002"
33958
+ );
33047
33959
  }
33960
+ state = current;
33961
+ const template = { id: state.templateId, selectionRevision: state.selectionRevision };
33048
33962
  if (!preview.viewed) {
33049
33963
  const refreshed = await this.#wechat.refreshDraftPreview(
33050
33964
  {
@@ -33056,9 +33970,19 @@ var WorkflowDeliveryCli = class {
33056
33970
  );
33057
33971
  state = { ...state, previewExpiresAt: refreshed.expiresAt };
33058
33972
  await this.#writeState(inputs.projectDirectory, state);
33973
+ const confirmation = await this.#workflow.issueDeliveryConfirmation(
33974
+ (await this.#workflow.next({ projectId: state.projectId })).data.workItem.workItemId,
33975
+ bindingFingerprint,
33976
+ refreshed.expiresAt
33977
+ );
33059
33978
  return {
33060
33979
  command: "workflow.deliver",
33061
- data: { expiresAt: refreshed.expiresAt, status: "preview-required", template },
33980
+ data: {
33981
+ expiresAt: refreshed.expiresAt,
33982
+ status: "preview-required",
33983
+ template,
33984
+ workItem: confirmation.data.workItem
33985
+ },
33062
33986
  ok: true
33063
33987
  };
33064
33988
  }
@@ -33102,19 +34026,30 @@ var WorkflowDeliveryCli = class {
33102
34026
  }
33103
34027
  if (result.intent.status === "SUCCEEDED" && result.intent.mediaId !== void 0) {
33104
34028
  const completedDiagnostics = await this.#diagnostics(result);
33105
- await this.#workflow.completeDelivery(inputs.projectDirectory, {
33106
- intentId: result.intent.id,
33107
- mediaId: result.intent.mediaId
33108
- });
33109
- return {
33110
- command: "workflow.deliver",
33111
- data: {
33112
- mediaId: result.intent.mediaId,
33113
- status: "completed",
33114
- template,
33115
- ...completedDiagnostics.verification === void 0 ? {} : { verification: completedDiagnostics.verification }
33116
- },
33117
- ok: true
34029
+ if (completedVerification(completedDiagnostics.verification)) {
34030
+ await this.#workflow.completeDelivery(options.workItemId, bindingFingerprint, {
34031
+ intentId: result.intent.id,
34032
+ mediaId: result.intent.mediaId
34033
+ });
34034
+ return {
34035
+ command: "workflow.deliver",
34036
+ data: {
34037
+ mediaId: result.intent.mediaId,
34038
+ status: "completed",
34039
+ template,
34040
+ verification: completedDiagnostics.verification
34041
+ },
34042
+ ok: true
34043
+ };
34044
+ }
34045
+ diagnostics = {
34046
+ ...completedDiagnostics,
34047
+ reason: {
34048
+ message: "\u8349\u7A3F\u5DF2\u521B\u5EFA\uFF0C\u4F46\u4E94\u5B57\u6BB5\u6216\u56FE\u7247\u8EAB\u4EFD\u56DE\u8BFB\u5C1A\u672A\u5168\u90E8\u901A\u8FC7\u3002",
34049
+ nextAction: "\u4FDD\u7559\u539F intent\uFF0C\u53EA\u8BFB\u6838\u9A8C\u540E\u7EE7\u7EED\u3002",
34050
+ phase: "draft-readback",
34051
+ retryAllowed: false
34052
+ }
33118
34053
  };
33119
34054
  }
33120
34055
  diagnostics ??= await this.#diagnostics(result);
@@ -33163,12 +34098,6 @@ function positiveInteger(value, name = "value") {
33163
34098
  }
33164
34099
  return parsed;
33165
34100
  }
33166
- function previewBrowserMode(value) {
33167
- if (value !== "agent" && value !== "system") {
33168
- throw new CommanderError(1, "DXC_OPTION_INVALID", "browser must be agent or system");
33169
- }
33170
- return value;
33171
- }
33172
34101
  function skillInstallTarget(value) {
33173
34102
  if (value !== "codex" && value !== "workbuddy") {
33174
34103
  throw new CommanderError(1, "DXC_OPTION_INVALID", "target must be codex or workbuddy");
@@ -33188,10 +34117,20 @@ function actionError(error) {
33188
34117
  };
33189
34118
  }
33190
34119
  if (error instanceof WorkflowCliError || error instanceof ProjectCliError || error instanceof ProfileCliError || error instanceof ContentMemoryCliError || error instanceof SkillInstallerError) {
33191
- return { code: error.code, message: error.message, retryable: false };
34120
+ return {
34121
+ code: error.code,
34122
+ ...error instanceof WorkflowCliError && error.details !== void 0 ? { details: error.details } : {},
34123
+ message: error.message,
34124
+ retryable: false
34125
+ };
33192
34126
  }
33193
34127
  if (error instanceof Error && error.name === "KnowledgeCliError" && "code" in error) {
33194
- return { code: String(error.code), message: error.message, retryable: false };
34128
+ return {
34129
+ code: String(error.code),
34130
+ ..."details" in error && error.details !== void 0 ? { details: error.details } : {},
34131
+ message: error.message,
34132
+ retryable: false
34133
+ };
33195
34134
  }
33196
34135
  return {
33197
34136
  code: "DXC_COMMAND_FAILED",
@@ -33226,7 +34165,7 @@ async function loadKnowledgeModule() {
33226
34165
  }
33227
34166
  };
33228
34167
  try {
33229
- return await import("./chunks/knowledge-LSRBVSCZ.js");
34168
+ return await import("./chunks/knowledge-JCYMCMSP.js");
33230
34169
  } finally {
33231
34170
  process.emitWarning = emitWarning;
33232
34171
  }
@@ -33317,24 +34256,27 @@ function createProgram(context) {
33317
34256
  })
33318
34257
  )
33319
34258
  );
33320
- workflow.command("next").description("\u8FD4\u56DE\u552F\u4E00\u9700\u8981\u6267\u884C\u7684\u4E0B\u4E00\u6B65").argument("[query]", "\u6587\u7AE0\u6807\u9898\u6216\u9879\u76EE ID").option("--directory <path>", "\u6307\u5B9A\u9879\u76EE\u76EE\u5F55").option("--json", "\u8F93\u51FA JSON").action(
33321
- async (query, options) => runAction(
34259
+ workflow.command("next").description("\u8FD4\u56DE\u552F\u4E00\u9700\u8981\u6267\u884C\u7684\u4E0B\u4E00\u6B65").option("--project <uuid>", "\u9879\u76EE ID").option("--work-item <id>", "\u5F53\u524D\u5DE5\u4F5C\u9879 ID").option("--json", "\u8F93\u51FA JSON").action(
34260
+ async (options) => runAction(
33322
34261
  context,
33323
34262
  options.json === true,
33324
34263
  () => new WorkflowCli({
33325
34264
  currentDirectory: context.currentDirectory,
33326
34265
  homeDirectory: context.homeDirectory
33327
- }).next(query, options.directory)
34266
+ }).next({
34267
+ ...options.project === void 0 ? {} : { projectId: options.project },
34268
+ ...options.workItem === void 0 ? {} : { workItemId: options.workItem }
34269
+ })
33328
34270
  )
33329
34271
  );
33330
- workflow.command("complete").description("\u4FDD\u5B58\u5F53\u524D\u6B65\u9AA4\u5E76\u8FD4\u56DE\u4E0B\u4E00\u6B65").option("--confirm", "\u786E\u8BA4\u91C7\u7528\u5F53\u524D\u9636\u6BB5\u4EA7\u7269").option("--directory <path>", "\u9879\u76EE\u76EE\u5F55").option("--project <uuid>", "\u9879\u76EE ID").option("--json", "\u8F93\u51FA JSON").action(
34272
+ workflow.command("complete").description("\u4FDD\u5B58\u5F53\u524D\u6B65\u9AA4\u5E76\u8FD4\u56DE\u4E0B\u4E00\u6B65").requiredOption("--work-item <id>", "\u5F53\u524D\u5DE5\u4F5C\u9879 ID").option("--confirm", "\u786E\u8BA4\u91C7\u7528\u5F53\u524D\u9636\u6BB5\u4EA7\u7269").option("--json", "\u8F93\u51FA JSON").action(
33331
34273
  async (options) => runAction(
33332
34274
  context,
33333
34275
  options.json === true,
33334
34276
  () => new WorkflowCli({
33335
34277
  currentDirectory: context.currentDirectory,
33336
34278
  homeDirectory: context.homeDirectory
33337
- }).complete(options.directory, options.project, options.confirm === true)
34279
+ }).complete(options.workItem, options.confirm === true)
33338
34280
  )
33339
34281
  );
33340
34282
  const deliveryAction = async (json, action) => runAction(context, json, () => {
@@ -33350,24 +34292,21 @@ function createProgram(context) {
33350
34292
  })
33351
34293
  );
33352
34294
  });
33353
- workflow.command("preview").description("\u51C6\u5907\u5E76\u6253\u5F00\u5F53\u524D\u6587\u7AE0\u7684\u5FAE\u4FE1\u9884\u89C8").option("--account <uuid>", "\u591A\u4E2A\u516C\u4F17\u53F7\u65F6\u9009\u62E9\u76EE\u6807\u8D26\u53F7").option("--directory <path>", "\u9879\u76EE\u76EE\u5F55").option("--project <uuid>", "\u9879\u76EE ID").option("--browser <agent|system>", "\u9884\u89C8\u6253\u5F00\u4F4D\u7F6E", "system").option("--json", "\u8F93\u51FA JSON").action(
34295
+ workflow.command("preview").description("\u51C6\u5907\u5E76\u6253\u5F00\u5F53\u524D\u6587\u7AE0\u7684\u5FAE\u4FE1\u9884\u89C8").requiredOption("--work-item <id>", "\u5F53\u524D delivery \u5DE5\u4F5C\u9879 ID").option("--choice <id>", "\u516C\u4F17\u53F7\u5019\u9009 choiceId").option("--json", "\u8F93\u51FA JSON").action(
33354
34296
  async (options) => deliveryAction(
33355
34297
  options.json === true,
33356
34298
  (delivery) => delivery.preview({
33357
- ...options.account === void 0 ? {} : { accountId: options.account },
33358
- browserMode: previewBrowserMode(options.browser),
33359
- ...options.directory === void 0 ? {} : { directory: options.directory },
33360
- ...options.project === void 0 ? {} : { projectId: options.project }
34299
+ ...options.choice === void 0 ? {} : { choiceId: options.choice },
34300
+ workItemId: options.workItem
33361
34301
  })
33362
34302
  )
33363
34303
  );
33364
- workflow.command("deliver").description("\u786E\u8BA4\u5F53\u524D\u9884\u89C8\u5E76\u521B\u5EFA\u5FAE\u4FE1\u516C\u4F17\u53F7\u8349\u7A3F").option("--directory <path>", "\u9879\u76EE\u76EE\u5F55").option("--project <uuid>", "\u9879\u76EE ID").option("--confirm", "\u786E\u8BA4\u5DF2\u7ECF\u67E5\u770B\u9884\u89C8\u5E76\u521B\u5EFA\u8349\u7A3F").option("--json", "\u8F93\u51FA JSON").action(
34304
+ workflow.command("deliver").description("\u786E\u8BA4\u5F53\u524D\u9884\u89C8\u5E76\u521B\u5EFA\u5FAE\u4FE1\u516C\u4F17\u53F7\u8349\u7A3F").requiredOption("--work-item <id>", "\u6700\u7EC8\u9884\u89C8\u7ED1\u5B9A\u5DE5\u4F5C\u9879 ID").option("--confirm", "\u786E\u8BA4\u5DF2\u7ECF\u67E5\u770B\u9884\u89C8\u5E76\u521B\u5EFA\u8349\u7A3F").option("--json", "\u8F93\u51FA JSON").action(
33365
34305
  async (options) => deliveryAction(
33366
34306
  options.json === true,
33367
34307
  (delivery) => delivery.deliver({
33368
34308
  confirmed: options.confirm === true,
33369
- ...options.directory === void 0 ? {} : { directory: options.directory },
33370
- ...options.project === void 0 ? {} : { projectId: options.project }
34309
+ workItemId: options.workItem
33371
34310
  })
33372
34311
  )
33373
34312
  );
@@ -33401,45 +34340,54 @@ function createProgram(context) {
33401
34340
  })
33402
34341
  );
33403
34342
  const profile = program2.command("profile").description("\u7BA1\u7406\u672C\u5730\u521B\u4F5C\u753B\u50CF");
33404
- profile.command("status").description("\u67E5\u770B\u521B\u4F5C\u753B\u50CF").option("--json", "\u8F93\u51FA JSON").action(
34343
+ profile.command("begin").description("\u5F00\u59CB\u753B\u50CF\u95EE\u7B54").option("--json", "\u8F93\u51FA JSON").action(
33405
34344
  async (options) => runAction(
33406
34345
  context,
33407
34346
  options.json === true,
33408
34347
  () => new ProfileCli({
33409
34348
  currentDirectory: context.currentDirectory,
33410
34349
  homeDirectory: context.homeDirectory
33411
- }).status()
34350
+ }).begin()
33412
34351
  )
33413
34352
  );
33414
- profile.command("save").description("\u4FDD\u5B58\u521B\u4F5C\u753B\u50CF").requiredOption("--input <path>", "\u753B\u50CF JSON \u6587\u4EF6").option("--json", "\u8F93\u51FA JSON").action(
34353
+ profile.command("answer").description("\u56DE\u7B54\u5F53\u524D\u753B\u50CF\u95EE\u9898").requiredOption("--session <uuid>", "\u753B\u50CF\u4F1A\u8BDD ID").option("--choice <id>", "\u9009\u62E9\u9879 choiceId\uFF0C\u53EF\u91CD\u590D", collectValues, []).option("--text <value>", "\u6587\u672C\u7B54\u6848").option("--note <value>", "\u8865\u5145\u8BF4\u660E").option("--json", "\u8F93\u51FA JSON").action(
33415
34354
  async (options) => runAction(
33416
34355
  context,
33417
34356
  options.json === true,
33418
34357
  () => new ProfileCli({
33419
34358
  currentDirectory: context.currentDirectory,
33420
34359
  homeDirectory: context.homeDirectory
33421
- }).save(options.input)
34360
+ }).answer(options.session, {
34361
+ choices: options.choice,
34362
+ ...options.note === void 0 ? {} : { note: options.note },
34363
+ ...options.text === void 0 ? {} : { text: options.text }
34364
+ })
33422
34365
  )
33423
34366
  );
33424
- profile.command("update").description("\u66F4\u65B0\u521B\u4F5C\u753B\u50CF").requiredOption("--input <path>", "\u753B\u50CF\u66F4\u65B0 JSON \u6587\u4EF6").option("--json", "\u8F93\u51FA JSON").action(
34367
+ profile.command("status").description("\u67E5\u770B\u521B\u4F5C\u753B\u50CF").option("--json", "\u8F93\u51FA JSON").action(
33425
34368
  async (options) => runAction(
33426
34369
  context,
33427
34370
  options.json === true,
33428
34371
  () => new ProfileCli({
33429
34372
  currentDirectory: context.currentDirectory,
33430
34373
  homeDirectory: context.homeDirectory
33431
- }).update(options.input)
34374
+ }).status()
33432
34375
  )
33433
34376
  );
33434
34377
  const memory = program2.command("memory").description("\u7BA1\u7406\u672C\u5730\u5185\u5BB9\u8BB0\u5FC6");
33435
- memory.command("add").description("\u4FDD\u5B58\u4E00\u6761\u7528\u6237\u786E\u8BA4\u7684\u5185\u5BB9\u8BB0\u5FC6").requiredOption("--input <path>", "\u8BB0\u5FC6 JSON \u6587\u4EF6").option("--json", "\u8F93\u51FA JSON").action(
34378
+ memory.command("add").description("\u4FDD\u5B58\u4E00\u6761\u7528\u6237\u786E\u8BA4\u7684\u5185\u5BB9\u8BB0\u5FC6").requiredOption("--content <text>", "\u8BB0\u5FC6\u6B63\u6587").requiredOption("--kind <kind>", "\u8BB0\u5FC6\u9886\u57DF\u7C7B\u578B").requiredOption("--scope <scope>", "global \u6216 project").option("--expires-at <datetime>", "\u8FC7\u671F\u65F6\u95F4").option("--json", "\u8F93\u51FA JSON").action(
33436
34379
  async (options) => runAction(
33437
34380
  context,
33438
34381
  options.json === true,
33439
34382
  () => new ContentMemoryCli({
33440
34383
  currentDirectory: context.currentDirectory,
33441
34384
  homeDirectory: context.homeDirectory
33442
- }).add(options.input)
34385
+ }).add({
34386
+ content: options.content,
34387
+ ...options.expiresAt === void 0 ? {} : { expiresAt: options.expiresAt },
34388
+ kind: options.kind,
34389
+ scope: options.scope
34390
+ })
33443
34391
  )
33444
34392
  );
33445
34393
  memory.command("list").description("\u67E5\u770B\u5185\u5BB9\u8BB0\u5FC6").option("--json", "\u8F93\u51FA JSON").action(
@@ -33452,42 +34400,49 @@ function createProgram(context) {
33452
34400
  }).list()
33453
34401
  )
33454
34402
  );
33455
- memory.command("archive").description("\u5F52\u6863\u4E00\u6761\u5185\u5BB9\u8BB0\u5FC6").argument("<memory-id>", "\u8BB0\u5FC6 ID").option("--json", "\u8F93\u51FA JSON").action(
33456
- async (memoryId, options) => runAction(
34403
+ memory.command("correct").description("\u539F\u5B50\u66F4\u6B63\u4E00\u6761\u5185\u5BB9\u8BB0\u5FC6").requiredOption("--memory <uuid>", "\u88AB\u66F4\u6B63\u7684\u8BB0\u5FC6 ID").requiredOption("--content <text>", "\u66F4\u6B63\u540E\u7684\u6B63\u6587").requiredOption("--kind <kind>", "\u8BB0\u5FC6\u9886\u57DF\u7C7B\u578B").requiredOption("--scope <scope>", "global \u6216 project").option("--expires-at <datetime>", "\u8FC7\u671F\u65F6\u95F4").option("--json", "\u8F93\u51FA JSON").action(
34404
+ async (options) => runAction(
33457
34405
  context,
33458
34406
  options.json === true,
33459
34407
  () => new ContentMemoryCli({
33460
34408
  currentDirectory: context.currentDirectory,
33461
34409
  homeDirectory: context.homeDirectory
33462
- }).archive(memoryId)
34410
+ }).correct(options.memory, {
34411
+ content: options.content,
34412
+ ...options.expiresAt === void 0 ? {} : { expiresAt: options.expiresAt },
34413
+ kind: options.kind,
34414
+ scope: options.scope
34415
+ })
33463
34416
  )
33464
34417
  );
33465
- const knowledge = program2.command("knowledge").description("\u7BA1\u7406\u672C\u5730\u5386\u53F2\u6587\u7AE0\u548C\u91D1\u53E5");
33466
- knowledge.command("status").description("\u67E5\u770B\u77E5\u8BC6\u5E93\u72B6\u6001").option("--json", "\u8F93\u51FA JSON").action(
33467
- async (options) => withKnowledge(context, "knowledge.status", options.json === true, (cli) => cli.status())
33468
- );
33469
- knowledge.command("import").description("\u5BFC\u5165\u7528\u6237\u660E\u786E\u9009\u62E9\u7684\u6587\u7AE0").argument("<files...>", "Markdown \u6216\u6587\u672C\u6587\u4EF6").option("--lexical-only", "\u53EA\u5EFA\u7ACB\u5173\u952E\u8BCD\u7D22\u5F15").option("--json", "\u8F93\u51FA JSON").action(
33470
- async (files, options) => withKnowledge(
34418
+ memory.command("archive").description("\u5F52\u6863\u4E00\u6761\u5185\u5BB9\u8BB0\u5FC6").requiredOption("--memory <uuid>", "\u8BB0\u5FC6 ID").option("--json", "\u8F93\u51FA JSON").action(
34419
+ async (options) => runAction(
33471
34420
  context,
33472
- "knowledge.import",
33473
34421
  options.json === true,
33474
- (cli) => cli.importArticles({ files, lexicalOnly: options.lexicalOnly === true })
34422
+ () => new ContentMemoryCli({
34423
+ currentDirectory: context.currentDirectory,
34424
+ homeDirectory: context.homeDirectory
34425
+ }).archive(options.memory)
33475
34426
  )
33476
34427
  );
33477
- knowledge.command("import-directory").description("\u5BFC\u5165\u7528\u6237\u660E\u786E\u9009\u62E9\u7684\u76EE\u5F55").requiredOption("--directory <path>", "\u6587\u7AE0\u76EE\u5F55").option("--lexical-only", "\u53EA\u5EFA\u7ACB\u5173\u952E\u8BCD\u7D22\u5F15").option("--json", "\u8F93\u51FA JSON").action(
34428
+ const knowledge = program2.command("knowledge").description("\u7BA1\u7406\u672C\u5730\u5386\u53F2\u6587\u7AE0\u548C\u91D1\u53E5");
34429
+ knowledge.command("status").description("\u67E5\u770B\u77E5\u8BC6\u5E93\u72B6\u6001").option("--json", "\u8F93\u51FA JSON").action(
34430
+ async (options) => withKnowledge(context, "knowledge.status", options.json === true, (cli) => cli.status())
34431
+ );
34432
+ knowledge.command("import").description("\u5BFC\u5165\u7528\u6237\u660E\u786E\u9009\u62E9\u7684\u6587\u7AE0").requiredOption("--source <path>", "\u7528\u6237\u660E\u786E\u9009\u62E9\u7684\u5355\u6587\u4EF6\u6216\u76EE\u5F55").option("--lexical-only", "\u53EA\u5EFA\u7ACB\u5173\u952E\u8BCD\u7D22\u5F15").option("--choice <id>", "\u6A21\u578B\u4E0D\u53EF\u7528\u65F6 CLI \u8FD4\u56DE\u7684 choiceId").option("--json", "\u8F93\u51FA JSON").action(
33478
34433
  async (options) => withKnowledge(
33479
34434
  context,
33480
34435
  "knowledge.import",
33481
34436
  options.json === true,
33482
- (cli) => cli.importDirectory(options.directory, options.lexicalOnly === true)
34437
+ (cli) => cli.importSource(options.source, options.lexicalOnly === true, options.choice)
33483
34438
  )
33484
34439
  );
33485
- knowledge.command("search").description("\u68C0\u7D22\u5386\u53F2\u6587\u7AE0\u548C\u91D1\u53E5").argument("<query>", "\u68C0\u7D22\u76EE\u6807").option("--mode <mode>", "hybrid / lexical / semantic", "hybrid").option("--limit <count>", "\u6700\u591A\u8FD4\u56DE\u6761\u6570", "8").option("--json", "\u8F93\u51FA JSON").action(
34440
+ knowledge.command("search").description("\u68C0\u7D22\u5386\u53F2\u6587\u7AE0\u548C\u91D1\u53E5").argument("<query>", "\u68C0\u7D22\u76EE\u6807").option("--mode <mode>", "hybrid / lexical / semantic", "hybrid").option("--limit <count>", "\u6700\u591A\u8FD4\u56DE\u6761\u6570", "8").option("--choice <id>", "\u6A21\u578B\u4E0D\u53EF\u7528\u65F6 CLI \u8FD4\u56DE\u7684 choiceId").option("--json", "\u8F93\u51FA JSON").action(
33486
34441
  async (query, options) => withKnowledge(
33487
34442
  context,
33488
34443
  "knowledge.search",
33489
34444
  options.json === true,
33490
- (cli) => cli.search(query, options.mode, positiveInteger(options.limit, "limit"))
34445
+ (cli) => cli.search(query, options.mode, positiveInteger(options.limit, "limit"), options.choice)
33491
34446
  )
33492
34447
  );
33493
34448
  knowledge.command("remove").description("\u5220\u9664\u4E00\u7BC7\u672C\u5730\u5386\u53F2\u6587\u7AE0").argument("<article-id>", "\u6587\u7AE0 ID").option("--json", "\u8F93\u51FA JSON").action(
@@ -33499,14 +34454,6 @@ function createProgram(context) {
33499
34454
  )
33500
34455
  );
33501
34456
  const quotes = knowledge.command("quotes").description("\u7BA1\u7406\u672C\u5730\u91D1\u53E5");
33502
- quotes.command("init").description("\u521D\u59CB\u5316\u91D1\u53E5\u5E93").option("--json", "\u8F93\u51FA JSON").action(
33503
- async (options) => withKnowledge(
33504
- context,
33505
- "knowledge.quotes.init",
33506
- options.json === true,
33507
- (cli) => cli.initQuotes()
33508
- )
33509
- );
33510
34457
  quotes.command("status").description("\u67E5\u770B\u91D1\u53E5\u5E93\u72B6\u6001").option("--json", "\u8F93\u51FA JSON").action(
33511
34458
  async (options) => withKnowledge(
33512
34459
  context,
@@ -33561,14 +34508,6 @@ function createProgram(context) {
33561
34508
  (cli) => cli.archiveQuote(quoteId)
33562
34509
  )
33563
34510
  );
33564
- quotes.command("sync").description("\u540C\u6B65\u91D1\u53E5\u5E93\u7D22\u5F15").option("--lexical-only", "\u53EA\u5EFA\u7ACB\u5173\u952E\u8BCD\u7D22\u5F15").option("--json", "\u8F93\u51FA JSON").action(
33565
- async (options) => withKnowledge(
33566
- context,
33567
- "knowledge.quotes.sync",
33568
- options.json === true,
33569
- (cli) => cli.syncQuotes(options.lexicalOnly === true)
33570
- )
33571
- );
33572
34511
  const auth = program2.command("auth").description("\u7BA1\u7406\u5F53\u524D\u8BBE\u5907\u767B\u5F55");
33573
34512
  const authAction = (options, action) => runAction(
33574
34513
  context,