@musnows/scriverse 0.7.2 → 0.7.4

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 (82) hide show
  1. package/README.en.md +3 -0
  2. package/README.md +8 -0
  3. package/dist/ai-connectivity-test.js +109 -0
  4. package/dist/ai-connectivity-test.js.map +1 -0
  5. package/dist/ai-conversation-export.js +70 -0
  6. package/dist/ai-conversation-export.js.map +1 -0
  7. package/dist/ai-stream-timeout.js +18 -0
  8. package/dist/ai-stream-timeout.js.map +1 -0
  9. package/dist/ai.js +926 -354
  10. package/dist/ai.js.map +1 -1
  11. package/dist/app.js +498 -80
  12. package/dist/app.js.map +1 -1
  13. package/dist/attachment-storage.js +28 -12
  14. package/dist/attachment-storage.js.map +1 -1
  15. package/dist/backup-encryption.js +129 -0
  16. package/dist/backup-encryption.js.map +1 -0
  17. package/dist/character-extraction.js +133 -0
  18. package/dist/character-extraction.js.map +1 -0
  19. package/dist/cli-core.js +7 -6
  20. package/dist/cli-core.js.map +1 -1
  21. package/dist/collaboration-presence.js +200 -18
  22. package/dist/collaboration-presence.js.map +1 -1
  23. package/dist/database.js +335 -3
  24. package/dist/database.js.map +1 -1
  25. package/dist/docx-export.js +1 -1
  26. package/dist/docx-export.js.map +1 -1
  27. package/dist/domain.js +18 -0
  28. package/dist/domain.js.map +1 -1
  29. package/dist/epub-export.js +319 -0
  30. package/dist/epub-export.js.map +1 -0
  31. package/dist/hybrid-search.js +8 -0
  32. package/dist/hybrid-search.js.map +1 -1
  33. package/dist/image-metadata.js +17 -2
  34. package/dist/image-metadata.js.map +1 -1
  35. package/dist/presence-store.js +160 -0
  36. package/dist/presence-store.js.map +1 -0
  37. package/dist/public/ai-connectivity-test.d.ts +7 -0
  38. package/dist/public/ai-connectivity-test.js +82 -0
  39. package/dist/public/ai-context-meter.js +27 -0
  40. package/dist/public/ai-mentions.js +14 -0
  41. package/dist/public/ai-request-manager.js +99 -0
  42. package/dist/public/ai-stream-protocol.js +51 -0
  43. package/dist/public/app.js +3587 -559
  44. package/dist/public/background-task-center.js +1 -1
  45. package/dist/public/chapter-editor-virtualization.js +52 -0
  46. package/dist/public/chapter-version-diff.d.ts +20 -0
  47. package/dist/public/chapter-version-diff.js +116 -0
  48. package/dist/public/foreshadow-reminder.d.ts +32 -0
  49. package/dist/public/foreshadow-reminder.js +73 -0
  50. package/dist/public/global-replace-refresh.js +60 -0
  51. package/dist/public/index.html +161 -31
  52. package/dist/public/outline-board.d.ts +61 -0
  53. package/dist/public/outline-board.js +137 -0
  54. package/dist/public/page-route.d.ts +1 -0
  55. package/dist/public/page-route.js +8 -0
  56. package/dist/public/presence-client-id.d.ts +10 -0
  57. package/dist/public/presence-client-id.js +31 -0
  58. package/dist/public/reading-preview.d.ts +32 -0
  59. package/dist/public/reading-preview.js +136 -0
  60. package/dist/public/s3-backup-ui.d.ts +10 -0
  61. package/dist/public/s3-backup-ui.js +29 -0
  62. package/dist/public/setting-filters.d.ts +16 -0
  63. package/dist/public/setting-filters.js +19 -0
  64. package/dist/public/styles.css +522 -11
  65. package/dist/public/upload-progress.d.ts +2 -0
  66. package/dist/public/upload-progress.js +10 -0
  67. package/dist/s3-backup.js +197 -12
  68. package/dist/s3-backup.js.map +1 -1
  69. package/dist/security.js +86 -21
  70. package/dist/security.js.map +1 -1
  71. package/dist/server-runtime.js +38 -18
  72. package/dist/server-runtime.js.map +1 -1
  73. package/dist/store.js +899 -104
  74. package/dist/store.js.map +1 -1
  75. package/dist/upload-limits.js +35 -0
  76. package/dist/upload-limits.js.map +1 -0
  77. package/dist/user-auth.js +56 -10
  78. package/dist/user-auth.js.map +1 -1
  79. package/dist/utils.js +3 -0
  80. package/dist/utils.js.map +1 -1
  81. package/dist/version.js +1 -1
  82. package/package.json +4 -2
@@ -4,6 +4,7 @@ import { copyFile, mkdir, open, readFile, rm, stat } from "node:fs/promises";
4
4
  import { dirname, join, resolve, sep } from "node:path";
5
5
  import sharp from "sharp";
6
6
  import { AppError } from "./errors.js";
7
+ import { DEFAULT_ATTACHMENT_IMAGE_MAX_BYTES, formatUploadLimit } from "./upload-limits.js";
7
8
  const maximumPixels = 25_000_000;
8
9
  const maximumAnimationFrames = 100;
9
10
  const maximumAnimationPixels = 50_000_000;
@@ -41,11 +42,27 @@ async function sha256File(path) {
41
42
  }
42
43
  return hash.digest("hex");
43
44
  }
45
+ async function hasGifSignature(path) {
46
+ const handle = await open(path, "r");
47
+ const header = Buffer.alloc(6);
48
+ try {
49
+ const { bytesRead } = await handle.read(header, 0, header.length, 0);
50
+ if (bytesRead !== header.length)
51
+ return false;
52
+ const signature = header.toString("ascii");
53
+ return signature === "GIF87a" || signature === "GIF89a";
54
+ }
55
+ finally {
56
+ await handle.close();
57
+ }
58
+ }
44
59
  export class AttachmentStorage {
45
60
  rootDirectory;
61
+ maximumUploadBytes;
46
62
  temporaryDirectory;
47
- constructor(rootDirectory) {
63
+ constructor(rootDirectory, maximumUploadBytes = DEFAULT_ATTACHMENT_IMAGE_MAX_BYTES) {
48
64
  this.rootDirectory = rootDirectory;
65
+ this.maximumUploadBytes = maximumUploadBytes;
49
66
  this.temporaryDirectory = join(rootDirectory, ".tmp");
50
67
  }
51
68
  async prepare() {
@@ -82,10 +99,14 @@ export class AttachmentStorage {
82
99
  async ingest(sourcePath) {
83
100
  await this.prepare();
84
101
  const originalStats = await stat(sourcePath);
102
+ if (originalStats.size > this.maximumUploadBytes) {
103
+ throw new AppError(413, "ATTACHMENT_TOO_LARGE", `图片附件不能超过 ${formatUploadLimit(this.maximumUploadBytes)}`);
104
+ }
85
105
  const originalSha256 = await sha256File(sourcePath);
106
+ const isGif = await hasGifSignature(sourcePath);
86
107
  let metadata;
87
108
  try {
88
- metadata = await sharp(sourcePath, { animated: true, limitInputPixels: maximumPixels, sequentialRead: true }).metadata();
109
+ metadata = await sharp(sourcePath, { animated: true, limitInputPixels: isGif ? false : maximumPixels, sequentialRead: true }).metadata();
89
110
  }
90
111
  catch {
91
112
  throw new AppError(415, "INVALID_ATTACHMENT_IMAGE", "附件不是有效的图片文件");
@@ -99,19 +120,19 @@ export class AttachmentStorage {
99
120
  if (!Number.isInteger(width) || width <= 0 || !Number.isInteger(pageHeight) || pageHeight <= 0) {
100
121
  throw new AppError(415, "INVALID_ATTACHMENT_IMAGE", "无法读取附件图片尺寸");
101
122
  }
102
- if (width * pageHeight > maximumPixels)
123
+ if (!isGif && width * pageHeight > maximumPixels)
103
124
  throw new AppError(413, "ATTACHMENT_IMAGE_TOO_LARGE", "附件图片像素尺寸过大");
104
- if (!Number.isInteger(pageCount) || pageCount > maximumAnimationFrames) {
125
+ if (!isGif && (!Number.isInteger(pageCount) || pageCount > maximumAnimationFrames)) {
105
126
  throw new AppError(413, "ATTACHMENT_ANIMATION_TOO_LARGE", "附件动画帧数过多");
106
127
  }
107
- if (width * pageHeight * pageCount > maximumAnimationPixels) {
128
+ if (!isGif && width * pageHeight * pageCount > maximumAnimationPixels) {
108
129
  throw new AppError(413, "ATTACHMENT_ANIMATION_TOO_LARGE", "附件动画总像素量过大");
109
130
  }
110
131
  const originalMimeType = mimeType(format);
111
132
  const candidatePath = join(this.temporaryDirectory, `${originalSha256}-${Date.now()}.webp`);
112
133
  let selectedPath = sourcePath;
113
134
  let storedMimeType = originalMimeType;
114
- if (format !== "webp") {
135
+ if (format !== "webp" && !isGif) {
115
136
  try {
116
137
  await sharp(sourcePath, { animated: true, limitInputPixels: maximumPixels, sequentialRead: true })
117
138
  .webp({ lossless: true, effort: 6 })
@@ -126,13 +147,8 @@ export class AttachmentStorage {
126
147
  storedMimeType = "image/webp";
127
148
  }
128
149
  }
129
- catch (error) {
150
+ catch {
130
151
  await rm(candidatePath, { force: true });
131
- if (format === "gif") {
132
- if (error instanceof AppError)
133
- throw error;
134
- throw new AppError(422, "ATTACHMENT_ANIMATION_CONVERSION_FAILED", "GIF 附件无法安全转换为动画 WebP");
135
- }
136
152
  }
137
153
  }
138
154
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"attachment-storage.js","sourceRoot":"","sources":["../src/attachment-storage.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"attachment-storage.js","sourceRoot":"","sources":["../src/attachment-storage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,129 @@
1
+ import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
2
+ import { AppError } from "./errors.js";
3
+ export const BACKUP_ENCRYPTION_MAGIC = "SCRIVERSE-ENC1";
4
+ const magic = Buffer.from(BACKUP_ENCRYPTION_MAGIC, "ascii");
5
+ const envelopeVersion = 1;
6
+ const aes256GcmAlgorithm = 1;
7
+ const dekLength = 32;
8
+ const ivLength = 12;
9
+ const authTagLength = 16;
10
+ const headerLength = magic.byteLength + 16;
11
+ function parseKek(value) {
12
+ if (!/^[A-Za-z0-9_-]{43}$/u.test(value)) {
13
+ throw new AppError(400, "BACKUP_KEK_INVALID", "S3 备份加密密钥格式无效");
14
+ }
15
+ const key = Buffer.from(value, "base64url");
16
+ if (key.byteLength !== 32 || key.toString("base64url") !== value) {
17
+ throw new AppError(400, "BACKUP_KEK_INVALID", "S3 备份加密密钥格式无效");
18
+ }
19
+ return key;
20
+ }
21
+ function envelopeHeader(ciphertextLength) {
22
+ const header = Buffer.alloc(headerLength);
23
+ magic.copy(header, 0);
24
+ let offset = magic.byteLength;
25
+ header.writeUInt8(envelopeVersion, offset++);
26
+ header.writeUInt8(aes256GcmAlgorithm, offset++);
27
+ header.writeUInt8(ivLength, offset++);
28
+ header.writeUInt8(authTagLength, offset++);
29
+ header.writeUInt8(ivLength, offset++);
30
+ header.writeUInt8(authTagLength, offset++);
31
+ header.writeUInt16BE(dekLength, offset);
32
+ offset += 2;
33
+ header.writeBigUInt64BE(BigInt(ciphertextLength), offset);
34
+ return header;
35
+ }
36
+ export function generateKek() {
37
+ return randomBytes(32).toString("base64url");
38
+ }
39
+ export function isEncryptedEnvelope(value) {
40
+ return value.byteLength >= magic.byteLength && value.subarray(0, magic.byteLength).equals(magic);
41
+ }
42
+ export function encryptObject(plaintext, kek) {
43
+ const key = parseKek(kek);
44
+ const dek = randomBytes(dekLength);
45
+ const wrappingIv = randomBytes(ivLength);
46
+ const payloadIv = randomBytes(ivLength);
47
+ const header = envelopeHeader(plaintext.byteLength);
48
+ const keyCipher = createCipheriv("aes-256-gcm", key, wrappingIv);
49
+ keyCipher.setAAD(header);
50
+ const wrappedDek = Buffer.concat([keyCipher.update(dek), keyCipher.final()]);
51
+ const keyTag = keyCipher.getAuthTag();
52
+ const payloadCipher = createCipheriv("aes-256-gcm", dek, payloadIv);
53
+ payloadCipher.setAAD(header);
54
+ const ciphertext = Buffer.concat([payloadCipher.update(plaintext), payloadCipher.final()]);
55
+ const payloadTag = payloadCipher.getAuthTag();
56
+ return Buffer.concat([
57
+ header,
58
+ wrappingIv,
59
+ keyTag,
60
+ payloadIv,
61
+ payloadTag,
62
+ wrappedDek,
63
+ ciphertext
64
+ ]);
65
+ }
66
+ export function decryptObject(envelope, kek) {
67
+ if (envelope.byteLength < headerLength || !isEncryptedEnvelope(envelope)) {
68
+ throw new AppError(400, "BACKUP_ENVELOPE_INVALID", "备份对象不是 Scriverse 加密信封");
69
+ }
70
+ const header = envelope.subarray(0, headerLength);
71
+ let offset = magic.byteLength;
72
+ const version = header.readUInt8(offset++);
73
+ const algorithm = header.readUInt8(offset++);
74
+ const wrappingIvLength = header.readUInt8(offset++);
75
+ const wrappingTagLength = header.readUInt8(offset++);
76
+ const payloadIvLength = header.readUInt8(offset++);
77
+ const payloadTagLength = header.readUInt8(offset++);
78
+ const wrappedDekLength = header.readUInt16BE(offset);
79
+ offset += 2;
80
+ const ciphertextLengthBigInt = header.readBigUInt64BE(offset);
81
+ if (ciphertextLengthBigInt > BigInt(Number.MAX_SAFE_INTEGER)) {
82
+ throw new AppError(400, "BACKUP_ENVELOPE_INVALID", "备份加密信封长度无效");
83
+ }
84
+ const ciphertextLength = Number(ciphertextLengthBigInt);
85
+ if (version !== envelopeVersion
86
+ || algorithm !== aes256GcmAlgorithm
87
+ || wrappingIvLength !== ivLength
88
+ || wrappingTagLength !== authTagLength
89
+ || payloadIvLength !== ivLength
90
+ || payloadTagLength !== authTagLength
91
+ || wrappedDekLength !== dekLength) {
92
+ throw new AppError(400, "BACKUP_ENVELOPE_UNSUPPORTED", "备份加密信封版本或算法不受支持");
93
+ }
94
+ const expectedLength = headerLength
95
+ + wrappingIvLength
96
+ + wrappingTagLength
97
+ + payloadIvLength
98
+ + payloadTagLength
99
+ + wrappedDekLength
100
+ + ciphertextLength;
101
+ if (expectedLength !== envelope.byteLength) {
102
+ throw new AppError(400, "BACKUP_ENVELOPE_INVALID", "备份加密信封长度无效");
103
+ }
104
+ let bodyOffset = headerLength;
105
+ const wrappingIv = envelope.subarray(bodyOffset, bodyOffset += wrappingIvLength);
106
+ const wrappingTag = envelope.subarray(bodyOffset, bodyOffset += wrappingTagLength);
107
+ const payloadIv = envelope.subarray(bodyOffset, bodyOffset += payloadIvLength);
108
+ const payloadTag = envelope.subarray(bodyOffset, bodyOffset += payloadTagLength);
109
+ const wrappedDek = envelope.subarray(bodyOffset, bodyOffset += wrappedDekLength);
110
+ const ciphertext = envelope.subarray(bodyOffset);
111
+ try {
112
+ const keyDecipher = createDecipheriv("aes-256-gcm", parseKek(kek), wrappingIv);
113
+ keyDecipher.setAAD(header);
114
+ keyDecipher.setAuthTag(wrappingTag);
115
+ const dek = Buffer.concat([keyDecipher.update(wrappedDek), keyDecipher.final()]);
116
+ if (dek.byteLength !== dekLength)
117
+ throw new Error("Invalid DEK length");
118
+ const payloadDecipher = createDecipheriv("aes-256-gcm", dek, payloadIv);
119
+ payloadDecipher.setAAD(header);
120
+ payloadDecipher.setAuthTag(payloadTag);
121
+ return Buffer.concat([payloadDecipher.update(ciphertext), payloadDecipher.final()]);
122
+ }
123
+ catch (error) {
124
+ if (error instanceof AppError)
125
+ throw error;
126
+ throw new AppError(400, "BACKUP_DECRYPTION_FAILED", "备份对象解密失败,密钥错误或对象已损坏");
127
+ }
128
+ }
129
+ //# sourceMappingURL=backup-encryption.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backup-encryption.js","sourceRoot":"","sources":["../src/backup-encryption.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,133 @@
1
+ import { createHash } from "node:crypto";
2
+ import { AppError } from "./errors.js";
3
+ import { normalizeCharacterName } from "./store.js";
4
+ export const CHARACTER_EXTRACTION_MAX_CANDIDATES = 500;
5
+ export const CHARACTER_EXTRACTION_MAX_NAME_LENGTH = 200;
6
+ export const CHARACTER_EXTRACTION_MAX_ALIASES = 100;
7
+ export const CHARACTER_EXTRACTION_MAX_IDENTITY_LENGTH = 20_000;
8
+ export const CHARACTER_EXTRACTION_MAX_SPECIES_LENGTH = 200;
9
+ function recordValue(value) {
10
+ return value && typeof value === "object" && !Array.isArray(value)
11
+ ? value
12
+ : null;
13
+ }
14
+ export function normalizeCharacterExtractionText(value) {
15
+ return typeof value === "string" ? value.normalize("NFKC").trim().replace(/\s+/gu, " ") : "";
16
+ }
17
+ export function normalizeCharacterExtractionAliases(name, value) {
18
+ if (!Array.isArray(value))
19
+ return [];
20
+ const primary = normalizeCharacterName(name);
21
+ const aliases = new Map();
22
+ for (const item of value.slice(0, CHARACTER_EXTRACTION_MAX_ALIASES)) {
23
+ const alias = normalizeCharacterExtractionText(item);
24
+ if (!alias || alias.length > CHARACTER_EXTRACTION_MAX_NAME_LENGTH)
25
+ continue;
26
+ const normalized = normalizeCharacterName(alias);
27
+ if (!normalized || normalized === primary || aliases.has(normalized))
28
+ continue;
29
+ aliases.set(normalized, alias);
30
+ }
31
+ return [...aliases.values()];
32
+ }
33
+ export function normalizeCharacterExtractionCandidate(value, index) {
34
+ const candidate = recordValue(value);
35
+ if (!candidate)
36
+ return null;
37
+ const name = normalizeCharacterExtractionText(candidate.name);
38
+ if (!name || name.length > CHARACTER_EXTRACTION_MAX_NAME_LENGTH)
39
+ return null;
40
+ const identity = normalizeCharacterExtractionText(candidate.identity);
41
+ const species = normalizeCharacterExtractionText(candidate.species);
42
+ const evidence = recordValue(candidate.firstEvidence);
43
+ const chapterId = normalizeCharacterExtractionText(evidence?.chapterId);
44
+ const chapterTitle = normalizeCharacterExtractionText(evidence?.chapterTitle);
45
+ const quote = normalizeCharacterExtractionText(evidence?.quote);
46
+ const stableCharacterId = normalizeCharacterExtractionText(candidate.stableCharacterId);
47
+ const candidateId = normalizeCharacterExtractionText(candidate.candidateId) || `candidate-${index + 1}`;
48
+ if (candidateId.length > 100 || !/^[A-Za-z0-9_-]+$/u.test(candidateId))
49
+ return null;
50
+ if (stableCharacterId.length > 200)
51
+ return null;
52
+ return {
53
+ candidateId,
54
+ name,
55
+ aliases: normalizeCharacterExtractionAliases(name, candidate.aliases),
56
+ species: species.length <= CHARACTER_EXTRACTION_MAX_SPECIES_LENGTH ? species : "",
57
+ identity: identity.length <= CHARACTER_EXTRACTION_MAX_IDENTITY_LENGTH ? identity : "",
58
+ firstChapterId: normalizeCharacterExtractionText(candidate.firstChapterId) || null,
59
+ firstEvidence: chapterId && quote && quote.length <= 80
60
+ ? { chapterId, chapterTitle: chapterTitle.slice(0, 300), quote }
61
+ : null,
62
+ stableCharacterId: stableCharacterId || null
63
+ };
64
+ }
65
+ export function parseStoredCharacterExtractionCandidates(value) {
66
+ if (!Array.isArray(value) || value.length > CHARACTER_EXTRACTION_MAX_CANDIDATES) {
67
+ throw new AppError(409, "CHARACTER_EXTRACTION_PREVIEW_INVALID", "角色抽取预览数据无效,请重新运行任务");
68
+ }
69
+ const candidates = value.map((item, index) => normalizeCharacterExtractionCandidate(item, index));
70
+ if (candidates.some((candidate) => candidate === null)) {
71
+ throw new AppError(409, "CHARACTER_EXTRACTION_PREVIEW_INVALID", "角色抽取预览包含无效字段,请重新运行任务");
72
+ }
73
+ const parsed = candidates;
74
+ if (new Set(parsed.map((candidate) => candidate.candidateId)).size !== parsed.length) {
75
+ throw new AppError(409, "CHARACTER_EXTRACTION_PREVIEW_INVALID", "角色抽取预览候选标识重复,请重新运行任务");
76
+ }
77
+ return parsed;
78
+ }
79
+ export function editableCharacterExtractionCandidate(candidate, selection) {
80
+ const name = selection.name === undefined
81
+ ? candidate.name
82
+ : normalizeCharacterExtractionText(selection.name);
83
+ if (!name || name.length > CHARACTER_EXTRACTION_MAX_NAME_LENGTH) {
84
+ throw new AppError(400, "CHARACTER_EXTRACTION_NAME_INVALID", "角色标准名不能为空且不能超过 200 个字符", {
85
+ candidateId: candidate.candidateId
86
+ });
87
+ }
88
+ const aliases = selection.aliases === undefined
89
+ ? candidate.aliases
90
+ : normalizeCharacterExtractionAliases(name, selection.aliases);
91
+ const rawAliases = selection.aliases ?? candidate.aliases;
92
+ const normalizedAliases = rawAliases.map((alias) => normalizeCharacterName(normalizeCharacterExtractionText(alias)));
93
+ if (rawAliases.length > CHARACTER_EXTRACTION_MAX_ALIASES
94
+ || aliases.length !== rawAliases.length
95
+ || normalizedAliases.some((alias) => !alias || alias === normalizeCharacterName(name))
96
+ || new Set(normalizedAliases).size !== normalizedAliases.length) {
97
+ throw new AppError(400, "CHARACTER_EXTRACTION_ALIASES_INVALID", "角色别名不能为空、重复、等同标准名或超过长度限制", {
98
+ candidateId: candidate.candidateId
99
+ });
100
+ }
101
+ const species = selection.species === undefined
102
+ ? candidate.species
103
+ : normalizeCharacterExtractionText(selection.species);
104
+ const identity = selection.attributes?.identity === undefined
105
+ ? candidate.identity
106
+ : normalizeCharacterExtractionText(selection.attributes.identity);
107
+ if (species.length > CHARACTER_EXTRACTION_MAX_SPECIES_LENGTH || identity.length > CHARACTER_EXTRACTION_MAX_IDENTITY_LENGTH) {
108
+ throw new AppError(400, "CHARACTER_EXTRACTION_ATTRIBUTES_INVALID", "角色种族或身份字段超过长度限制", {
109
+ candidateId: candidate.candidateId
110
+ });
111
+ }
112
+ return { name, aliases, species, identity };
113
+ }
114
+ function canonicalValue(value) {
115
+ if (Array.isArray(value))
116
+ return value.map(canonicalValue);
117
+ const record = recordValue(value);
118
+ if (!record)
119
+ return value;
120
+ return Object.fromEntries(Object.keys(record).sort().map((key) => [key, canonicalValue(record[key])]));
121
+ }
122
+ export function characterExtractionHash(value) {
123
+ return createHash("sha256").update(JSON.stringify(canonicalValue(value))).digest("hex");
124
+ }
125
+ export function characterExtractionSelectionFingerprint(selections) {
126
+ return characterExtractionHash([...selections]
127
+ .map((selection) => ({
128
+ ...selection,
129
+ aliases: selection.aliases ? [...selection.aliases] : undefined
130
+ }))
131
+ .sort((left, right) => left.candidateId.localeCompare(right.candidateId)));
132
+ }
133
+ //# sourceMappingURL=character-extraction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"character-extraction.js","sourceRoot":"","sources":["../src/character-extraction.ts"],"names":[],"mappings":"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"}
package/dist/cli-core.js CHANGED
@@ -450,7 +450,7 @@ function helpText() {
450
450
  scriverse work get <workId>
451
451
  scriverse work history <workId>
452
452
  scriverse work restore <workId> --version <number>
453
- scriverse manuscript get <workId> [--format json|markdown|txt|docx] [--output <path>]
453
+ scriverse manuscript get <workId> [--format json|markdown|txt|docx|epub] [--output <path>]
454
454
  scriverse search <workId> --query <text> [--type <type>] [--limit <number>]
455
455
  scriverse audit <workId>
456
456
  scriverse writing progress <workId>
@@ -486,7 +486,7 @@ AI 编辑辅助:
486
486
  }
487
487
  function schemaList() {
488
488
  return {
489
- output: "除 manuscript 的 markdown/txt/docx 外,所有成功结果都输出 JSON;错误输出到 stderr 并返回非零状态码。",
489
+ output: "除 manuscript 的 markdown/txt/docx/epub 外,所有成功结果都输出 JSON;错误输出到 stderr 并返回非零状态码。",
490
490
  input: {
491
491
  json: "create/update 使用 --input file.json 或 --input - 从标准输入读取 JSON 对象",
492
492
  longText: "使用可重复的 --field-file field=path 注入长文本字段",
@@ -715,15 +715,16 @@ async function execute(parsed, dependencies) {
715
715
  const workId = requiredPosition(parsed.positionals, 2, "workId");
716
716
  assertPositionCount(parsed.positionals, 3);
717
717
  const format = option(parsed, "format") ?? "json";
718
- if (!["json", "markdown", "txt", "docx"].includes(format))
719
- throw new CliError("CLI_FORMAT_INVALID", "format 必须是 json、markdown、txt 或 docx");
718
+ if (!["json", "markdown", "txt", "docx", "epub"].includes(format))
719
+ throw new CliError("CLI_FORMAT_INVALID", "format 必须是 json、markdown、txt、docxepub");
720
720
  if (format === "json") {
721
721
  if (option(parsed, "output"))
722
722
  throw new CliError("CLI_OUTPUT_UNSUPPORTED", "JSON 格式直接输出到标准输出,不支持 --output");
723
723
  emitJson(dependencies.stdout, await apiRequest(dependencies.fetchImpl, config, `/api/works/${encoded(workId)}`), compact);
724
724
  }
725
- else if (format === "markdown" || format === "docx") {
726
- const outputPath = option(parsed, "output") ?? (format === "docx" ? `novel-${workId}.docx` : `novel-${workId}.zip`);
725
+ else if (format === "markdown" || format === "docx" || format === "epub") {
726
+ const defaultOutput = format === "docx" ? `novel-${workId}.docx` : format === "epub" ? `novel-${workId}.epub` : `novel-${workId}.zip`;
727
+ const outputPath = option(parsed, "output") ?? defaultOutput;
727
728
  emitJson(dependencies.stdout, {
728
729
  format,
729
730
  ...(await downloadToFile(dependencies.fetchImpl, config, `/api/works/${encoded(workId)}/export?format=${format}`, outputPath, dependencies.cwd))