somark-js 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/LICENSE +156 -0
  2. package/README.md +32 -0
  3. package/README_CN.md +31 -0
  4. package/dist/chunk-RWOUZTFQ.mjs +236 -0
  5. package/dist/cli.mjs +1503 -0
  6. package/dist/config-GOZJZCRT.mjs +26 -0
  7. package/dist/index.cjs +1304 -0
  8. package/dist/index.d.cts +258 -0
  9. package/dist/index.d.ts +258 -0
  10. package/dist/index.mjs +1245 -0
  11. package/package.json +66 -0
  12. package/vendor/somarkdown-viewer/LICENSE +21 -0
  13. package/vendor/somarkdown-viewer/README.md +63 -0
  14. package/vendor/somarkdown-viewer/README_CN.md +63 -0
  15. package/vendor/somarkdown-viewer/index.html +112 -0
  16. package/vendor/somarkdown-viewer/lib/bi-direction-jump.js +161 -0
  17. package/vendor/somarkdown-viewer/lib/deps/highlight.js.default.min.css +11 -0
  18. package/vendor/somarkdown-viewer/lib/deps/katex.min.css +6 -0
  19. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_AMS-Regular.ttf +0 -0
  20. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_AMS-Regular.woff +0 -0
  21. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_AMS-Regular.woff2 +0 -0
  22. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  23. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  24. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  25. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  26. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  27. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  28. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  29. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Fraktur-Bold.woff +0 -0
  30. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  31. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  32. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Fraktur-Regular.woff +0 -0
  33. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  34. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Main-Bold.ttf +0 -0
  35. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Main-Bold.woff +0 -0
  36. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Main-Bold.woff2 +0 -0
  37. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  38. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Main-BoldItalic.woff +0 -0
  39. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  40. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Main-Italic.ttf +0 -0
  41. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Main-Italic.woff +0 -0
  42. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Main-Italic.woff2 +0 -0
  43. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Main-Regular.ttf +0 -0
  44. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Main-Regular.woff +0 -0
  45. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Main-Regular.woff2 +0 -0
  46. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  47. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Math-BoldItalic.woff +0 -0
  48. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  49. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Math-Italic.ttf +0 -0
  50. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Math-Italic.woff +0 -0
  51. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Math-Italic.woff2 +0 -0
  52. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  53. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_SansSerif-Bold.woff +0 -0
  54. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  55. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  56. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_SansSerif-Italic.woff +0 -0
  57. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  58. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  59. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_SansSerif-Regular.woff +0 -0
  60. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  61. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Script-Regular.ttf +0 -0
  62. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Script-Regular.woff +0 -0
  63. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Script-Regular.woff2 +0 -0
  64. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Size1-Regular.ttf +0 -0
  65. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Size1-Regular.woff +0 -0
  66. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Size1-Regular.woff2 +0 -0
  67. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Size2-Regular.ttf +0 -0
  68. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Size2-Regular.woff +0 -0
  69. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Size2-Regular.woff2 +0 -0
  70. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Size3-Regular.ttf +0 -0
  71. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Size3-Regular.woff +0 -0
  72. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Size3-Regular.woff2 +0 -0
  73. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Size4-Regular.ttf +0 -0
  74. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Size4-Regular.woff +0 -0
  75. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Size4-Regular.woff2 +0 -0
  76. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  77. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Typewriter-Regular.woff +0 -0
  78. package/vendor/somarkdown-viewer/lib/deps/katex_fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  79. package/vendor/somarkdown-viewer/lib/i18n.js +81 -0
  80. package/vendor/somarkdown-viewer/lib/index.js +299 -0
  81. package/vendor/somarkdown-viewer/lib/somarkdown/VERSION +1 -0
  82. package/vendor/somarkdown-viewer/lib/somarkdown/somarkdown.css +591 -0
  83. package/vendor/somarkdown-viewer/lib/somarkdown/somarkdown.umd.min.js +59 -0
  84. package/vendor/somarkdown-viewer/lib/sync-scroll.js +147 -0
  85. package/vendor/somarkdown-viewer/style/index.css +527 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,1304 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ APIError: () => APIError,
34
+ AuthenticationError: () => AuthenticationError,
35
+ ConnectionError: () => ConnectionError,
36
+ DocumentParser: () => DocumentParser,
37
+ FileTooLargeError: () => FileTooLargeError,
38
+ InsufficientBalanceError: () => InsufficientBalanceError,
39
+ InvalidParamError: () => InvalidParamError,
40
+ PDFProcessor: () => PDFProcessor,
41
+ ParseFailedError: () => ParseFailedError,
42
+ ParseResponse: () => ParseResponse,
43
+ ParseTimeoutError: () => ParseTimeoutError,
44
+ RateLimitError: () => RateLimitError,
45
+ RequestTimeoutError: () => RequestTimeoutError,
46
+ SoMark: () => SoMark,
47
+ SoMarkDown: () => SoMarkDown,
48
+ SoMarkDownPreview: () => SoMarkDownPreview,
49
+ SoMarkError: () => SoMarkError,
50
+ Task: () => Task,
51
+ UnsupportedFileError: () => UnsupportedFileError,
52
+ Usage: () => Usage,
53
+ UsageClient: () => UsageClient
54
+ });
55
+ module.exports = __toCommonJS(src_exports);
56
+
57
+ // src/config.ts
58
+ var fs = __toESM(require("fs"), 1);
59
+ var path = __toESM(require("path"), 1);
60
+ var os = __toESM(require("os"), 1);
61
+ var DEFAULT_BASE_URL = "https://somark.tech/api/v1";
62
+ var DEFAULT_TIMEOUT = 60;
63
+ var DEFAULT_MAX_RETRIES = 2;
64
+ var CONFIG_FILE = path.join(os.homedir(), ".somark", "config.toml");
65
+ var VALID_CONFIG_KEYS = ["api_key", "base_url", "timeout", "max_retries"];
66
+ function parseOptionalString(value) {
67
+ return value === void 0 || value === null ? void 0 : String(value);
68
+ }
69
+ function parseString(value) {
70
+ return String(value);
71
+ }
72
+ function parseNumber(value) {
73
+ return Number(value);
74
+ }
75
+ function parseInteger(value) {
76
+ return Number.parseInt(String(value), 10);
77
+ }
78
+ function validateAny(_key, _value) {
79
+ }
80
+ function validateBaseUrl(key, value) {
81
+ if (!/^https?:\/\//i.test(String(value))) {
82
+ throw new Error(`Invalid ${key} "${value}". Must start with http:// or https://`);
83
+ }
84
+ }
85
+ function maskIdentity(value) {
86
+ return value;
87
+ }
88
+ function maskApiKey(value) {
89
+ return value.length > 8 ? `${value.slice(0, 4)}...${value.slice(-4)}` : "***";
90
+ }
91
+ function formatValue(value) {
92
+ return String(value);
93
+ }
94
+ var CONFIG_SCHEMA = [
95
+ {
96
+ key: "api_key",
97
+ envVar: "SOMARK_API_KEY",
98
+ optionKey: "apiKey",
99
+ defaultValue: void 0,
100
+ parse: parseOptionalString,
101
+ validate: validateAny,
102
+ mask: maskApiKey,
103
+ format: formatValue,
104
+ showWhenUnset: false
105
+ },
106
+ {
107
+ key: "base_url",
108
+ envVar: "SOMARK_BASE_URL",
109
+ optionKey: "baseUrl",
110
+ defaultValue: DEFAULT_BASE_URL,
111
+ parse: parseString,
112
+ validate: validateBaseUrl,
113
+ mask: maskIdentity,
114
+ format: formatValue,
115
+ showWhenUnset: true
116
+ },
117
+ {
118
+ key: "timeout",
119
+ envVar: "SOMARK_TIMEOUT",
120
+ optionKey: "timeout",
121
+ defaultValue: DEFAULT_TIMEOUT,
122
+ parse: parseNumber,
123
+ validate: validateAny,
124
+ mask: maskIdentity,
125
+ format: formatValue,
126
+ showWhenUnset: true
127
+ },
128
+ {
129
+ key: "max_retries",
130
+ envVar: "SOMARK_MAX_RETRIES",
131
+ optionKey: "maxRetries",
132
+ defaultValue: DEFAULT_MAX_RETRIES,
133
+ parse: parseInteger,
134
+ validate: validateAny,
135
+ mask: maskIdentity,
136
+ format: formatValue,
137
+ showWhenUnset: true
138
+ }
139
+ ];
140
+ var CONFIG_SCHEMA_BY_KEY = new Map(
141
+ CONFIG_SCHEMA.map((entry) => [entry.key, entry])
142
+ );
143
+ function stripTomlComment(line) {
144
+ let quote;
145
+ let escaping = false;
146
+ for (let i = 0; i < line.length; i += 1) {
147
+ const char = line[i];
148
+ if (quote === '"') {
149
+ if (escaping) {
150
+ escaping = false;
151
+ } else if (char === "\\") {
152
+ escaping = true;
153
+ } else if (char === quote) {
154
+ quote = void 0;
155
+ }
156
+ } else if (quote === "'") {
157
+ if (char === quote) quote = void 0;
158
+ } else if (char === '"' || char === "'") {
159
+ quote = char;
160
+ } else if (char === "#") {
161
+ return line.slice(0, i);
162
+ }
163
+ }
164
+ return line;
165
+ }
166
+ function parseTomlValue(rawValue) {
167
+ const value = stripTomlComment(rawValue).trim();
168
+ if (value.startsWith('"') && value.endsWith('"')) {
169
+ return JSON.parse(value);
170
+ }
171
+ if (value.startsWith("'") && value.endsWith("'")) {
172
+ return value.slice(1, -1);
173
+ }
174
+ if (/^[+-]?(?:\d+\.?\d*|\.\d+)$/.test(value)) {
175
+ return Number(value);
176
+ }
177
+ return value;
178
+ }
179
+ function loadTomlConfig() {
180
+ if (!fs.existsSync(CONFIG_FILE)) return {};
181
+ try {
182
+ const content = fs.readFileSync(CONFIG_FILE, "utf-8");
183
+ const result = {};
184
+ for (const line of content.split("\n")) {
185
+ const trimmed = stripTomlComment(line).trim();
186
+ if (!trimmed || trimmed.startsWith("[")) continue;
187
+ const separatorIndex = trimmed.indexOf("=");
188
+ if (separatorIndex === -1) continue;
189
+ const key = trimmed.slice(0, separatorIndex).trim();
190
+ if (!VALID_CONFIG_KEYS.includes(key)) continue;
191
+ result[key] = parseTomlValue(trimmed.slice(separatorIndex + 1));
192
+ }
193
+ return result;
194
+ } catch {
195
+ return {};
196
+ }
197
+ }
198
+ function hasFileValue(file, key) {
199
+ const value = file[key];
200
+ return value !== void 0 && value !== null && value !== "";
201
+ }
202
+ function resolveSchemaEntry(entry, opts, file) {
203
+ let rawValue;
204
+ let source;
205
+ if (opts[entry.optionKey] !== void 0) {
206
+ rawValue = opts[entry.optionKey];
207
+ source = "explicit";
208
+ } else if (process.env[entry.envVar]) {
209
+ rawValue = process.env[entry.envVar];
210
+ source = "env";
211
+ } else if (hasFileValue(file, entry.key)) {
212
+ rawValue = file[entry.key];
213
+ source = "file";
214
+ } else {
215
+ rawValue = entry.defaultValue;
216
+ source = "default";
217
+ }
218
+ const value = entry.parse(rawValue);
219
+ entry.validate(entry.key, value);
220
+ return { value, source };
221
+ }
222
+ function resolveSchema(opts = {}) {
223
+ const file = loadTomlConfig();
224
+ return Object.fromEntries(
225
+ CONFIG_SCHEMA.map((entry) => [entry.key, resolveSchemaEntry(entry, opts, file)])
226
+ );
227
+ }
228
+ function resolveConfig(opts = {}) {
229
+ const resolved = resolveSchema(opts);
230
+ return {
231
+ apiKey: resolved.api_key.value,
232
+ baseUrl: resolved.base_url.value,
233
+ timeout: resolved.timeout.value,
234
+ maxRetries: resolved.max_retries.value
235
+ };
236
+ }
237
+
238
+ // src/errors.ts
239
+ var SoMarkError = class extends Error {
240
+ code;
241
+ constructor(message = "", code = -1) {
242
+ super(message);
243
+ this.name = "SoMarkError";
244
+ this.code = code;
245
+ }
246
+ };
247
+ var AuthenticationError = class extends SoMarkError {
248
+ constructor(message = "Authentication failed", code = -1) {
249
+ super(message, code);
250
+ this.name = "AuthenticationError";
251
+ }
252
+ };
253
+ var InsufficientBalanceError = class extends SoMarkError {
254
+ constructor(message = "Insufficient balance", code = -1) {
255
+ super(message, code);
256
+ this.name = "InsufficientBalanceError";
257
+ }
258
+ };
259
+ var RateLimitError = class extends SoMarkError {
260
+ constructor(message = "Rate limit exceeded", code = -1) {
261
+ super(message, code);
262
+ this.name = "RateLimitError";
263
+ }
264
+ };
265
+ var UnsupportedFileError = class extends SoMarkError {
266
+ constructor(message = "Unsupported file type", code = -1) {
267
+ super(message, code);
268
+ this.name = "UnsupportedFileError";
269
+ }
270
+ };
271
+ var FileTooLargeError = class extends SoMarkError {
272
+ constructor(message = "File too large", code = -1) {
273
+ super(message, code);
274
+ this.name = "FileTooLargeError";
275
+ }
276
+ };
277
+ var ParseTimeoutError = class extends SoMarkError {
278
+ constructor(message = "Parse timed out", code = -1) {
279
+ super(message, code);
280
+ this.name = "ParseTimeoutError";
281
+ }
282
+ };
283
+ var ParseFailedError = class extends SoMarkError {
284
+ constructor(message = "Parse failed", code = -1) {
285
+ super(message, code);
286
+ this.name = "ParseFailedError";
287
+ }
288
+ };
289
+ var InvalidParamError = class extends SoMarkError {
290
+ constructor(message = "Invalid parameter", code = -1) {
291
+ super(message, code);
292
+ this.name = "InvalidParamError";
293
+ }
294
+ };
295
+ var APIError = class extends SoMarkError {
296
+ constructor(message = "API error", code = -1) {
297
+ super(message, code);
298
+ this.name = "APIError";
299
+ }
300
+ };
301
+ var ConnectionError = class extends SoMarkError {
302
+ constructor(message = "Connection failed", code = -1) {
303
+ super(message, code);
304
+ this.name = "ConnectionError";
305
+ }
306
+ };
307
+ var RequestTimeoutError = class extends SoMarkError {
308
+ constructor(message = "Request timed out", code = -1) {
309
+ super(message, code);
310
+ this.name = "RequestTimeoutError";
311
+ }
312
+ };
313
+ var CODE_TO_ERROR = {
314
+ 1101: RateLimitError,
315
+ 1102: ParseTimeoutError,
316
+ 1104: UnsupportedFileError,
317
+ 1105: InvalidParamError,
318
+ 1106: InvalidParamError,
319
+ 1107: AuthenticationError,
320
+ 1108: InsufficientBalanceError,
321
+ 1111: APIError,
322
+ 1118: FileTooLargeError,
323
+ 1120: APIError,
324
+ 1123: InsufficientBalanceError,
325
+ 1124: RateLimitError,
326
+ 1132: FileTooLargeError,
327
+ 1133: InvalidParamError,
328
+ 1137: ParseFailedError,
329
+ 1138: APIError
330
+ };
331
+ function raiseForCode(code, message = "") {
332
+ const ErrorClass = CODE_TO_ERROR[code] ?? APIError;
333
+ throw new ErrorClass(message || `API error (code=${code})`, code);
334
+ }
335
+
336
+ // src/http.ts
337
+ var RETRY_CODES = /* @__PURE__ */ new Set([1101, 1124]);
338
+ var RETRY_HTTP_STATUSES = /* @__PURE__ */ new Set([500, 502, 503, 504]);
339
+ var HttpClient = class {
340
+ apiKey;
341
+ baseUrl;
342
+ timeoutMs;
343
+ maxRetries;
344
+ constructor(opts) {
345
+ this.apiKey = opts.apiKey;
346
+ this.baseUrl = opts.baseUrl.replace(/\/$/, "");
347
+ this.timeoutMs = opts.timeout * 1e3;
348
+ this.maxRetries = opts.maxRetries;
349
+ }
350
+ getApiKey() {
351
+ return this.apiKey;
352
+ }
353
+ buildFormData(data) {
354
+ const form = new FormData();
355
+ if (this.apiKey) form.append("api_key", this.apiKey);
356
+ for (const [k, v] of Object.entries(data)) {
357
+ if (v === void 0) continue;
358
+ if (Array.isArray(v)) {
359
+ for (const item of v) form.append(k, item);
360
+ } else {
361
+ form.append(k, v);
362
+ }
363
+ }
364
+ return form;
365
+ }
366
+ async post(path8, data = {}, file) {
367
+ const url = this.baseUrl + path8;
368
+ let lastError;
369
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
370
+ try {
371
+ const form = this.buildFormData(data);
372
+ if (file) {
373
+ const blob = new Blob([file.buffer], { type: file.mimeType });
374
+ form.append("file", blob, file.name);
375
+ }
376
+ const controller = new AbortController();
377
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
378
+ let response;
379
+ try {
380
+ response = await fetch(url, { method: "POST", body: form, signal: controller.signal });
381
+ } finally {
382
+ clearTimeout(timer);
383
+ }
384
+ if (RETRY_HTTP_STATUSES.has(response.status) && attempt < this.maxRetries) {
385
+ await sleep(Math.pow(2, attempt) * 1e3);
386
+ continue;
387
+ }
388
+ if (!response.ok) {
389
+ throw new APIError(`HTTP ${response.status}: ${response.statusText}`);
390
+ }
391
+ const result = await response.json();
392
+ const code = result.code ?? -1;
393
+ if (code !== 0) {
394
+ if (RETRY_CODES.has(code) && attempt < this.maxRetries) {
395
+ await sleep(Math.pow(2, attempt) * 1e3);
396
+ continue;
397
+ }
398
+ raiseForCode(code, (result.message ?? result.msg) || "");
399
+ }
400
+ return result;
401
+ } catch (err) {
402
+ if (err instanceof Error && err.name === "AbortError") {
403
+ lastError = new RequestTimeoutError("Request timed out");
404
+ } else if (err instanceof TypeError && err.message.includes("fetch")) {
405
+ lastError = new ConnectionError(err.message);
406
+ } else if (err instanceof Error && err.code === "ECONNREFUSED") {
407
+ lastError = new ConnectionError(err.message);
408
+ } else {
409
+ throw err;
410
+ }
411
+ if (attempt < this.maxRetries) await sleep(Math.pow(2, attempt) * 1e3);
412
+ }
413
+ }
414
+ throw lastError ?? new APIError("Request failed after retries");
415
+ }
416
+ };
417
+ function sleep(ms) {
418
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
419
+ }
420
+
421
+ // src/resources/parser.ts
422
+ var fs4 = __toESM(require("fs"), 1);
423
+ var path4 = __toESM(require("path"), 1);
424
+
425
+ // src/models.ts
426
+ var fs2 = __toESM(require("fs"), 1);
427
+ var path2 = __toESM(require("path"), 1);
428
+
429
+ // src/warnings.ts
430
+ var warningsSuppressed = false;
431
+ function dedupe(messages) {
432
+ const seen = /* @__PURE__ */ new Set();
433
+ const result = [];
434
+ for (const message of messages) {
435
+ if (seen.has(message)) continue;
436
+ seen.add(message);
437
+ result.push(message);
438
+ }
439
+ return result;
440
+ }
441
+ function collectApiWarnings(data) {
442
+ const raw = data.warnings;
443
+ if (!Array.isArray(raw)) return [];
444
+ return dedupe(raw.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean));
445
+ }
446
+ function emitWarnings(warnings) {
447
+ if (warningsSuppressed) return;
448
+ for (const warning of dedupe(warnings)) {
449
+ process.emitWarning(warning, { type: "SoMarkWarning" });
450
+ }
451
+ }
452
+ function parseMaxConcurrency(raw) {
453
+ const value = raw?.trim() ?? "";
454
+ if (!value) return { value: 1, warnings: [] };
455
+ const parsed = Number.parseInt(value, 10);
456
+ if (!Number.isFinite(parsed) || parsed < 1) {
457
+ return {
458
+ value: 1,
459
+ warnings: [`Invalid SOMARK_PARSE_MAX_CONCURRENCY="${value}"; using 1.`]
460
+ };
461
+ }
462
+ if (parsed >= 2) {
463
+ return {
464
+ value: parsed,
465
+ warnings: [
466
+ `SOMARK_PARSE_MAX_CONCURRENCY is set to ${parsed}. The official default quota is 1 for everyone; only users explicitly approved for higher concurrency should use 2 or above.`
467
+ ]
468
+ };
469
+ }
470
+ return { value: parsed, warnings: [] };
471
+ }
472
+
473
+ // src/models.ts
474
+ var ParseResponse = class _ParseResponse {
475
+ ok;
476
+ code;
477
+ message;
478
+ warnings;
479
+ taskId;
480
+ pages;
481
+ fileType;
482
+ imgs;
483
+ md;
484
+ jsonOutput;
485
+ zipUrl;
486
+ sourceFileName;
487
+ constructor(data) {
488
+ this.ok = data.ok;
489
+ this.code = data.code;
490
+ this.message = data.message;
491
+ this.warnings = data.warnings ?? [];
492
+ this.taskId = data.taskId;
493
+ this.pages = data.pages;
494
+ this.fileType = data.fileType;
495
+ this.imgs = data.imgs;
496
+ this.md = data.md;
497
+ this.jsonOutput = data.jsonOutput;
498
+ this.zipUrl = data.zipUrl;
499
+ this.sourceFileName = data.sourceFileName;
500
+ }
501
+ async save(targetPath, format) {
502
+ const plan = planOutput(this.sourceFileName, targetPath, format, { allowStdout: false });
503
+ for (const target of plan.targets) {
504
+ if (target.stdout || !target.path) throw new Error("save path is required");
505
+ await this.saveFormat(target.path, target.format);
506
+ }
507
+ }
508
+ contentForFormat(format) {
509
+ const fmt = normalizeOutputFormat(format);
510
+ if (fmt === "json") return JSON.stringify(this.jsonOutput ?? {}, null, 2);
511
+ if (fmt === "md") return this.md ?? "";
512
+ if (fmt === "zip") {
513
+ if (!this.zipUrl) throw new Error("ZIP output URL was not returned by the API.");
514
+ return this.zipUrl;
515
+ }
516
+ throw new Error(`Unsupported output format: ${fmt}. Valid formats: md, markdown, json, zip.`);
517
+ }
518
+ async saveFormat(targetPath, format) {
519
+ const fmt = normalizeOutputFormat(format);
520
+ if (fmt === "zip") {
521
+ if (!this.zipUrl) throw new Error("ZIP output URL was not returned by the API.");
522
+ await downloadOutputFile(targetPath, this.zipUrl);
523
+ return;
524
+ }
525
+ fs2.writeFileSync(targetPath, this.contentForFormat(fmt), "utf-8");
526
+ }
527
+ toJSON() {
528
+ return {
529
+ ok: this.ok,
530
+ code: this.code,
531
+ message: this.message,
532
+ warnings: this.warnings,
533
+ taskId: this.taskId,
534
+ pages: this.pages,
535
+ fileType: this.fileType,
536
+ imgs: this.imgs,
537
+ md: this.md,
538
+ jsonOutput: this.jsonOutput,
539
+ zipUrl: this.zipUrl,
540
+ sourceFileName: this.sourceFileName
541
+ };
542
+ }
543
+ static fromApi(data) {
544
+ const apiData = data.data ?? {};
545
+ const metadata = apiData.metadata ?? {};
546
+ const result = apiData.result ?? {};
547
+ const outputs = result.outputs ?? {};
548
+ const warnings = collectApiWarnings(data);
549
+ return new _ParseResponse({
550
+ ok: data.code === 0,
551
+ code: data.code ?? -1,
552
+ message: data.message ?? data.msg ?? "",
553
+ warnings,
554
+ taskId: apiData.task_id ?? "",
555
+ pages: metadata.page_num ?? 0,
556
+ fileType: metadata.file_type ?? "",
557
+ imgs: result.imgs ?? [],
558
+ md: outputs.markdown,
559
+ jsonOutput: outputs.json,
560
+ zipUrl: outputs.zip,
561
+ sourceFileName: result.file_name ?? apiData.file_name
562
+ });
563
+ }
564
+ };
565
+ function normalizeOutputFormat(format) {
566
+ const fmt = format.trim().toLowerCase();
567
+ return { markdown: "md" }[fmt] ?? fmt;
568
+ }
569
+ function normalizeSaveFormats(format, targetPath) {
570
+ const rawFormats = Array.isArray(format) ? format : typeof format === "string" ? format.split(",").map((item) => item.trim()) : [path2.extname(targetPath ?? "").replace(/^\./, "") || "md"];
571
+ const formats = rawFormats.map(normalizeOutputFormat).filter(Boolean);
572
+ const normalized = formats.length ? formats : ["md"];
573
+ const invalid = normalized.filter((fmt) => !["md", "json", "zip"].includes(fmt));
574
+ if (invalid.length) {
575
+ throw new Error(`Unsupported output format: ${invalid.join(", ")}. Valid formats: md, markdown, json, zip.`);
576
+ }
577
+ return normalized;
578
+ }
579
+ function planOutput(sourceFileName, out, format, opts = {}) {
580
+ const formats = normalizeSaveFormats(format, out);
581
+ if (opts.batch) {
582
+ if (!out || !isExistingDirectory(out)) {
583
+ throw new Error("batch parse requires --out to be an existing directory");
584
+ }
585
+ return directoryOutputPlan(sourceFileName, out, formats);
586
+ }
587
+ if (!out) {
588
+ if (formats.length > 1) throw new Error("multiple formats require --out to be an existing directory");
589
+ if (!opts.allowStdout) throw new Error("output path is required");
590
+ return { formats, targets: [{ format: formats[0], stdout: true }], stdout: true };
591
+ }
592
+ if (isExistingDirectory(out)) return directoryOutputPlan(sourceFileName, out, formats);
593
+ if (formats.length > 1) throw new Error("multiple formats require --out to be an existing directory");
594
+ if (looksLikeDirectoryPath(out)) throw new Error(`output directory does not exist: ${out}`);
595
+ const parent = path2.dirname(path2.resolve(out));
596
+ if (!isExistingDirectory(parent)) throw new Error(`output parent directory does not exist: ${parent}`);
597
+ return { formats, targets: [{ format: formats[0], path: out, stdout: false }], stdout: false };
598
+ }
599
+ function directoryOutputPlan(sourceFileName, directory, formats) {
600
+ const stem = sourceStem(sourceFileName);
601
+ if (!stem) throw new Error("saving to a directory requires a source file name");
602
+ return {
603
+ formats,
604
+ targets: formats.map((fmt) => ({
605
+ format: fmt,
606
+ path: path2.join(directory, `${stem}.${formatExt(fmt)}`),
607
+ stdout: false
608
+ })),
609
+ stdout: false
610
+ };
611
+ }
612
+ function formatExt(format) {
613
+ return normalizeOutputFormat(format) === "md" ? "md" : normalizeOutputFormat(format);
614
+ }
615
+ function sourceStem(fileName) {
616
+ if (!fileName) return void 0;
617
+ return path2.basename(fileName, path2.extname(fileName));
618
+ }
619
+ function isExistingDirectory(target) {
620
+ return fs2.existsSync(target) && fs2.statSync(target).isDirectory();
621
+ }
622
+ function looksLikeDirectoryPath(target) {
623
+ return target.endsWith(path2.sep) || target.endsWith("/");
624
+ }
625
+ async function downloadOutputFile(targetPath, url) {
626
+ let response;
627
+ try {
628
+ response = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(6e4) });
629
+ } catch (err) {
630
+ throw new Error(`failed to download ZIP output: ${err.message}`);
631
+ }
632
+ if (!response.ok) {
633
+ throw new Error(`failed to download ZIP output: HTTP ${response.status}: ${response.statusText}`);
634
+ }
635
+ const bytes = Buffer.from(await response.arrayBuffer());
636
+ fs2.writeFileSync(targetPath, bytes);
637
+ }
638
+ var Usage = class _Usage {
639
+ ok;
640
+ code;
641
+ message;
642
+ warnings;
643
+ remainingPaidPages;
644
+ remainingFreePagesToday;
645
+ remainingFreePagesThisMonth;
646
+ dashboardUrl;
647
+ constructor(data) {
648
+ this.ok = data.ok;
649
+ this.code = data.code;
650
+ this.message = data.message;
651
+ this.warnings = data.warnings ?? [];
652
+ this.remainingPaidPages = data.remainingPaidPages;
653
+ this.remainingFreePagesToday = data.remainingFreePagesToday;
654
+ this.remainingFreePagesThisMonth = data.remainingFreePagesThisMonth;
655
+ this.dashboardUrl = data.dashboardUrl;
656
+ }
657
+ toJSON() {
658
+ return {
659
+ ok: this.ok,
660
+ code: this.code,
661
+ message: this.message,
662
+ warnings: this.warnings,
663
+ remainingPaidPages: this.remainingPaidPages,
664
+ remainingFreePagesToday: this.remainingFreePagesToday,
665
+ remainingFreePagesThisMonth: this.remainingFreePagesThisMonth,
666
+ dashboardUrl: this.dashboardUrl
667
+ };
668
+ }
669
+ static fromApi(data) {
670
+ const apiData = data.data ?? {};
671
+ const warnings = collectApiWarnings(data);
672
+ return new _Usage({
673
+ ok: data.code === 0,
674
+ code: data.code ?? -1,
675
+ message: data.message ?? data.msg ?? "",
676
+ warnings,
677
+ remainingPaidPages: apiData.remaining_paid_pages ?? 0,
678
+ remainingFreePagesToday: apiData.remaining_free_pages_today ?? 0,
679
+ remainingFreePagesThisMonth: apiData.remaining_free_pages_this_month ?? 0,
680
+ dashboardUrl: apiData.dashboard_url ?? "https://somark.tech/workbench"
681
+ });
682
+ }
683
+ };
684
+ var Task = class {
685
+ id;
686
+ completed = false;
687
+ status = "queuing";
688
+ recordId;
689
+ fileName;
690
+ pages;
691
+ warnings;
692
+ refreshHandler;
693
+ _result;
694
+ constructor(taskId, refreshHandler, warnings = []) {
695
+ this.id = taskId;
696
+ this.refreshHandler = refreshHandler;
697
+ this.warnings = warnings;
698
+ }
699
+ async refresh() {
700
+ if (!this.refreshHandler) {
701
+ throw new Error("Task cannot refresh without a parser resource. Use client.parser.task(taskId).");
702
+ }
703
+ const data = await this.refreshHandler(this.id);
704
+ this.applyApi(data);
705
+ }
706
+ applyApi(data) {
707
+ this.warnings = collectApiWarnings(data);
708
+ const apiData = data.data ?? {};
709
+ const rawStatus = apiData.status ?? this.status;
710
+ this.status = rawStatus.toLowerCase();
711
+ this.recordId = apiData.record_id;
712
+ this.fileName = apiData.file_name;
713
+ const metadata = apiData.metadata ?? {};
714
+ this.pages = metadata.page_num;
715
+ if (this.status === "success" || this.status === "failed") {
716
+ this.completed = true;
717
+ if (this.status === "success") {
718
+ this._result = ParseResponse.fromApi(data);
719
+ }
720
+ }
721
+ }
722
+ async wait(opts = {}) {
723
+ const pollInterval = (opts.pollInterval ?? 3) * 1e3;
724
+ const timeout = (opts.timeout ?? 120) * 1e3;
725
+ const start = Date.now();
726
+ while (!this.completed) {
727
+ if (Date.now() - start >= timeout) {
728
+ throw new ParseTimeoutError(`Task ${this.id} timed out`);
729
+ }
730
+ await sleep2(pollInterval);
731
+ await this.refresh();
732
+ }
733
+ if (this.status === "failed") throw new ParseFailedError(`Task ${this.id} failed`);
734
+ return this._result;
735
+ }
736
+ result() {
737
+ if (!this._result) throw new Error("Task not completed. Call wait() first.");
738
+ return this._result;
739
+ }
740
+ };
741
+ function sleep2(ms) {
742
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
743
+ }
744
+
745
+ // src/resources/parse-core.ts
746
+ var fs3 = __toESM(require("fs"), 1);
747
+ var path3 = __toESM(require("path"), 1);
748
+ var FMT_MAP = { md: "markdown", zip: "zip", json: "json" };
749
+ var VALID_API_FORMATS = /* @__PURE__ */ new Set(["markdown", "json", "zip"]);
750
+ var VALID_FORMAT_HINT = "md, markdown, json, zip";
751
+ function buildParseData(opts) {
752
+ const data = {};
753
+ if (opts.formats?.length) {
754
+ const mapped = opts.formats.map((f) => {
755
+ const normalized = normalizeOutputFormat(f);
756
+ return FMT_MAP[normalized] ?? normalized;
757
+ });
758
+ const invalid = mapped.filter((f) => !VALID_API_FORMATS.has(f));
759
+ if (invalid.length) {
760
+ throw new InvalidParamError(`Unsupported output format: ${invalid.join(", ")}. Valid formats: ${VALID_FORMAT_HINT}.`);
761
+ }
762
+ data.output_formats = mapped;
763
+ }
764
+ if (opts.elementFormats) {
765
+ const ef = opts.elementFormats;
766
+ const efObj = {};
767
+ if (ef.image) efObj.image = ef.image;
768
+ if (ef.formula) efObj.formula = ef.formula;
769
+ if (ef.table) efObj.table = ef.table;
770
+ if (ef.cs) efObj.cs = ef.cs;
771
+ if (Object.keys(efObj).length) data.element_formats = JSON.stringify(efObj);
772
+ }
773
+ if (opts.extractConfig) {
774
+ const ec = opts.extractConfig;
775
+ const fcObj = {};
776
+ if (ec.enableTextCrossPage !== void 0) fcObj.enable_text_cross_page = ec.enableTextCrossPage;
777
+ if (ec.enableTableCrossPage !== void 0) fcObj.enable_table_cross_page = ec.enableTableCrossPage;
778
+ if (ec.enableTitleLevelRecognition !== void 0) fcObj.enable_title_level_recognition = ec.enableTitleLevelRecognition;
779
+ if (ec.enableInlineImage !== void 0) fcObj.enable_inline_image = ec.enableInlineImage;
780
+ if (ec.enableTableImage !== void 0) fcObj.enable_table_image = ec.enableTableImage;
781
+ if (ec.enableImageUnderstanding !== void 0) fcObj.enable_image_understanding = ec.enableImageUnderstanding;
782
+ if (ec.keepHeaderFooter !== void 0) fcObj.keep_header_footer = ec.keepHeaderFooter;
783
+ if (Object.keys(fcObj).length) data.feature_config = JSON.stringify(fcObj);
784
+ }
785
+ return data;
786
+ }
787
+ function readFileList(file) {
788
+ if (!fs3.existsSync(file)) throw new InvalidParamError(`File list not found: ${file}`);
789
+ return fs3.readFileSync(file, "utf-8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((item) => path3.isAbsolute(item) ? item : path3.resolve(item));
790
+ }
791
+ function resolveFileInputs(file, fileList = false) {
792
+ if (fileList) {
793
+ const manifests = Array.isArray(file) ? file : [file];
794
+ return { files: manifests.flatMap(readFileList), isBatch: true };
795
+ }
796
+ if (Array.isArray(file)) return { files: file, isBatch: true };
797
+ return { files: [file], isBatch: false };
798
+ }
799
+ function ensureFilesExist(files) {
800
+ const missing = files.filter((file) => !fs3.existsSync(file) || !fs3.statSync(file).isFile());
801
+ if (missing.length) throw new InvalidParamError(`File not found: ${missing.join(", ")}`);
802
+ }
803
+ function parseBatchSettings(itemCount) {
804
+ const { value, warnings } = parseMaxConcurrency(process.env.SOMARK_PARSE_MAX_CONCURRENCY);
805
+ return { concurrency: Math.min(value, itemCount) || 1, warnings };
806
+ }
807
+ async function runLimited(files, worker, onResult) {
808
+ const { concurrency, warnings } = parseBatchSettings(files.length);
809
+ const results = new Array(files.length);
810
+ let next = 0;
811
+ async function runWorker() {
812
+ while (next < files.length) {
813
+ const index = next++;
814
+ const result = await worker(files[index]);
815
+ results[index] = result;
816
+ onResult?.(index, result);
817
+ }
818
+ }
819
+ await Promise.all(Array.from({ length: concurrency }, () => runWorker()));
820
+ return { results, warnings, concurrency };
821
+ }
822
+ function attachWarnings(items, warnings) {
823
+ if (!warnings.length) return;
824
+ for (const item of items) {
825
+ item.warnings = [...item.warnings ?? [], ...warnings];
826
+ }
827
+ }
828
+
829
+ // src/resources/parser.ts
830
+ var DocumentParser = class {
831
+ _http;
832
+ constructor(http2) {
833
+ this._http = http2;
834
+ }
835
+ checkApiKey() {
836
+ if (!this._http.getApiKey()) {
837
+ throw new AuthenticationError("apiKey is required for parser operations.");
838
+ }
839
+ }
840
+ async pollTask(taskId) {
841
+ this.checkApiKey();
842
+ const data = await this._http.post("/parse/async_check", { task_id: taskId });
843
+ emitWarnings(collectApiWarnings(data));
844
+ return data;
845
+ }
846
+ task(taskId) {
847
+ return new Task(taskId, (id) => this.pollTask(id));
848
+ }
849
+ async parseOne(opts) {
850
+ this.checkApiKey();
851
+ const data = buildParseData({ formats: opts.formats ?? ["md"], elementFormats: opts.elementFormats, extractConfig: opts.extractConfig });
852
+ const buffer = fs4.readFileSync(opts.file);
853
+ const fileName = path4.basename(opts.file);
854
+ const result = await this._http.post("/parse/sync", data, { name: fileName, buffer, mimeType: "application/octet-stream" });
855
+ const response = ParseResponse.fromApi(result);
856
+ emitWarnings(response.warnings);
857
+ response.sourceFileName = fileName;
858
+ return response;
859
+ }
860
+ async parse(opts) {
861
+ this.checkApiKey();
862
+ const { files, isBatch } = resolveFileInputs(opts.file, opts.fileList);
863
+ ensureFilesExist(files);
864
+ const { results: responses, warnings } = await runLimited(files, (file) => this.parseOne({ ...opts, file }));
865
+ if (isBatch) {
866
+ emitWarnings(warnings);
867
+ attachWarnings(responses, warnings);
868
+ }
869
+ return isBatch ? responses : responses[0];
870
+ }
871
+ async createOne(opts) {
872
+ this.checkApiKey();
873
+ const data = buildParseData({ formats: opts.formats ?? ["md"], elementFormats: opts.elementFormats, extractConfig: opts.extractConfig });
874
+ const buffer = fs4.readFileSync(opts.file);
875
+ const fileName = path4.basename(opts.file);
876
+ const result = await this._http.post("/parse/async", data, { name: fileName, buffer, mimeType: "application/octet-stream" });
877
+ const taskId = result.data?.task_id ?? "";
878
+ const warnings = collectApiWarnings(result);
879
+ emitWarnings(warnings);
880
+ return new Task(taskId, (id) => this.pollTask(id), warnings);
881
+ }
882
+ async create(opts) {
883
+ this.checkApiKey();
884
+ const { files, isBatch } = resolveFileInputs(opts.file, opts.fileList);
885
+ ensureFilesExist(files);
886
+ const { results: tasks, warnings } = await runLimited(files, (file) => this.createOne({ ...opts, file }));
887
+ if (isBatch) {
888
+ emitWarnings(warnings);
889
+ attachWarnings(tasks, warnings);
890
+ }
891
+ return isBatch ? tasks : tasks[0];
892
+ }
893
+ };
894
+
895
+ // src/resources/usage.ts
896
+ var UsageClient = class {
897
+ _http;
898
+ constructor(http2) {
899
+ this._http = http2;
900
+ }
901
+ async get() {
902
+ if (!this._http.getApiKey()) {
903
+ throw new AuthenticationError("apiKey is required for usage queries.");
904
+ }
905
+ const result = await this._http.post("/usage", {});
906
+ const usage = Usage.fromApi(result);
907
+ emitWarnings(usage.warnings);
908
+ return usage;
909
+ }
910
+ };
911
+
912
+ // src/resources/pdf.ts
913
+ var fs5 = __toESM(require("fs"), 1);
914
+ var path5 = __toESM(require("path"), 1);
915
+ var PDFProcessor = class {
916
+ async toImages(opts) {
917
+ let pdfiumMod = null;
918
+ try {
919
+ pdfiumMod = await import("@hyzyla/pdfium");
920
+ } catch {
921
+ return { ok: false, files: [], error: "@hyzyla/pdfium is not installed. Run: npm install somark-js" };
922
+ }
923
+ let PNG = null;
924
+ try {
925
+ const pngjsMod = await import("pngjs");
926
+ PNG = pngjsMod.PNG;
927
+ } catch {
928
+ return { ok: false, files: [], error: "pngjs is not installed. Run: npm install pngjs" };
929
+ }
930
+ try {
931
+ const outDir = opts.out ?? "./";
932
+ if (!fs5.existsSync(outDir)) fs5.mkdirSync(outDir, { recursive: true });
933
+ const baseName = path5.basename(opts.file, path5.extname(opts.file));
934
+ const fmt = opts.filenameFormat ?? "{name}.page-{n}.png";
935
+ const dpi = opts.dpi ?? 150;
936
+ const scale = dpi / 72;
937
+ const pdfBuffer = fs5.readFileSync(opts.file);
938
+ const library = await pdfiumMod.PDFiumLibrary.init();
939
+ const document = await library.loadDocument(pdfBuffer);
940
+ const files = [];
941
+ const pageCount = document.getPageCount();
942
+ for (let i = 0; i < pageCount; i++) {
943
+ const page = document.getPage(i);
944
+ const image = await page.render({
945
+ scale,
946
+ render: async (renderOpts) => {
947
+ const png = new PNG({ width: renderOpts.width, height: renderOpts.height });
948
+ const src = renderOpts.data;
949
+ for (let j = 0; j < src.length; j += 4) {
950
+ png.data[j] = src[j + 2];
951
+ png.data[j + 1] = src[j + 1];
952
+ png.data[j + 2] = src[j];
953
+ png.data[j + 3] = src[j + 3];
954
+ }
955
+ return Buffer.from(PNG.sync.write(png));
956
+ }
957
+ });
958
+ const outName = fmt.replace("{name}", baseName).replace("{n}", String(i + 1));
959
+ const outPath = path5.join(outDir, outName);
960
+ fs5.writeFileSync(outPath, Buffer.from(image.data));
961
+ files.push(outPath);
962
+ }
963
+ document.destroy();
964
+ library.destroy();
965
+ return { ok: true, files };
966
+ } catch (err) {
967
+ return { ok: false, files: [], error: String(err) };
968
+ }
969
+ }
970
+ };
971
+
972
+ // src/resources/preview.ts
973
+ var fs6 = __toESM(require("fs"), 1);
974
+ var http = __toESM(require("http"), 1);
975
+ var path6 = __toESM(require("path"), 1);
976
+ var import_url = require("url");
977
+ var import_meta = {};
978
+ var SUPPORTED_PREVIEW_EXTS = /* @__PURE__ */ new Set([".md", ".smd"]);
979
+ var VENDOR_FIX_HINT = "Run: git submodule update --init --recursive or somark doctor fix";
980
+ var previewHttp = {
981
+ createServer: http.createServer
982
+ };
983
+ var previewFs = {
984
+ createReadStream: fs6.createReadStream,
985
+ existsSync: fs6.existsSync,
986
+ statSync: fs6.statSync
987
+ };
988
+ var MIME_TYPES = {
989
+ ".css": "text/css; charset=utf-8",
990
+ ".gif": "image/gif",
991
+ ".html": "text/html; charset=utf-8",
992
+ ".jpeg": "image/jpeg",
993
+ ".jpg": "image/jpeg",
994
+ ".js": "application/javascript; charset=utf-8",
995
+ ".json": "application/json; charset=utf-8",
996
+ ".map": "application/json; charset=utf-8",
997
+ ".md": "text/markdown; charset=utf-8",
998
+ ".png": "image/png",
999
+ ".smd": "text/plain; charset=utf-8",
1000
+ ".svg": "image/svg+xml",
1001
+ ".txt": "text/plain; charset=utf-8",
1002
+ ".wasm": "application/wasm",
1003
+ ".webp": "image/webp"
1004
+ };
1005
+ function currentModuleDir() {
1006
+ if (typeof __dirname !== "undefined") return __dirname;
1007
+ return path6.dirname((0, import_url.fileURLToPath)(import_meta.url));
1008
+ }
1009
+ var previewVendorPaths = {
1010
+ packageVendorDir() {
1011
+ return path6.resolve(currentModuleDir(), "../vendor/somarkdown-viewer");
1012
+ },
1013
+ developmentVendorDir() {
1014
+ return path6.resolve(process.cwd(), "vendor/somarkdown-viewer");
1015
+ }
1016
+ };
1017
+ function vendorCandidates() {
1018
+ const candidates = [
1019
+ { dir: previewVendorPaths.packageVendorDir(), source: "package" },
1020
+ { dir: previewVendorPaths.developmentVendorDir(), source: "development" }
1021
+ ];
1022
+ const seen = /* @__PURE__ */ new Set();
1023
+ return candidates.map((candidate) => ({ ...candidate, dir: path6.resolve(candidate.dir) })).filter((candidate) => {
1024
+ if (seen.has(candidate.dir)) return false;
1025
+ seen.add(candidate.dir);
1026
+ return true;
1027
+ });
1028
+ }
1029
+ function findVendorLocation() {
1030
+ for (const candidate of vendorCandidates()) {
1031
+ if (previewFs.existsSync(candidate.dir) && previewFs.statSync(candidate.dir).isDirectory()) return candidate;
1032
+ }
1033
+ return null;
1034
+ }
1035
+ function missingVendorEntries(vendorDir) {
1036
+ const required = [
1037
+ path6.join(vendorDir, "index.html"),
1038
+ path6.join(vendorDir, "lib")
1039
+ ];
1040
+ return required.filter((candidate) => !previewFs.existsSync(candidate)).map((item) => path6.basename(item));
1041
+ }
1042
+ function validateVendorLocation(vendorLocation) {
1043
+ if (!vendorLocation) {
1044
+ throw new Error(`vendor/somarkdown-viewer not found. ${VENDOR_FIX_HINT}`);
1045
+ }
1046
+ const missing = missingVendorEntries(vendorLocation.dir);
1047
+ if (missing.length > 0) {
1048
+ if (vendorLocation.source === "package") {
1049
+ throw new Error(`Installed preview assets are incomplete. Missing ${missing.join(", ")}. Reinstall the package or rebuild it with bundled viewer assets.`);
1050
+ }
1051
+ throw new Error(`vendor/somarkdown-viewer is incomplete. Missing ${missing.join(", ")}. ${VENDOR_FIX_HINT}`);
1052
+ }
1053
+ return vendorLocation;
1054
+ }
1055
+ function validatePreviewFile(file) {
1056
+ const absolutePath = path6.resolve(file);
1057
+ if (!previewFs.existsSync(absolutePath) || !previewFs.statSync(absolutePath).isFile()) {
1058
+ throw new Error(`Preview file not found: ${absolutePath}`);
1059
+ }
1060
+ const ext = path6.extname(absolutePath).toLowerCase();
1061
+ if (!SUPPORTED_PREVIEW_EXTS.has(ext)) {
1062
+ throw new Error(`Unsupported preview file type: ${ext || "(no extension)"}. Use .md or .smd.`);
1063
+ }
1064
+ return {
1065
+ absolutePath,
1066
+ rootDir: path6.dirname(absolutePath),
1067
+ routePath: `/_preview/file/${encodeURIComponent(path6.basename(absolutePath))}`
1068
+ };
1069
+ }
1070
+ function normalizeRelativeRoute(routePathname) {
1071
+ const prefix = "/_preview/file/";
1072
+ if (!routePathname.startsWith(prefix)) return null;
1073
+ const raw = routePathname.slice(prefix.length);
1074
+ if (!raw) return null;
1075
+ const decodedSegments = raw.split("/").filter(Boolean).map((segment) => {
1076
+ try {
1077
+ return decodeURIComponent(segment);
1078
+ } catch {
1079
+ return null;
1080
+ }
1081
+ });
1082
+ if (decodedSegments.some((segment) => !segment || segment === "." || segment === "..")) {
1083
+ return null;
1084
+ }
1085
+ return decodedSegments.join(path6.sep);
1086
+ }
1087
+ function isPathInside(rootDir, candidatePath) {
1088
+ const relativePath = path6.relative(rootDir, candidatePath);
1089
+ return relativePath !== "" && !relativePath.startsWith("..") && !path6.isAbsolute(relativePath);
1090
+ }
1091
+ function sendFile(res, filePath) {
1092
+ const ext = path6.extname(filePath).toLowerCase();
1093
+ res.writeHead(200, { "Content-Type": MIME_TYPES[ext] ?? "application/octet-stream" });
1094
+ previewFs.createReadStream(filePath).pipe(res);
1095
+ }
1096
+ function sendText(res, status, body) {
1097
+ res.writeHead(status, { "Content-Type": "text/plain; charset=utf-8" });
1098
+ res.end(body);
1099
+ }
1100
+ var SoMarkDownPreview = class {
1101
+ async start(opts = {}) {
1102
+ const host = opts.host ?? "127.0.0.1";
1103
+ const port = opts.port ?? 7878;
1104
+ const openBrowser = opts.openBrowser ?? true;
1105
+ const vendorLocation = validateVendorLocation(findVendorLocation());
1106
+ const vendorDir = vendorLocation.dir;
1107
+ const previewFile = opts.file ? validatePreviewFile(opts.file) : null;
1108
+ const server = previewHttp.createServer((req, res) => {
1109
+ const pathname = (req.url ?? "/").split("?")[0] || "/";
1110
+ if (pathname === "/") {
1111
+ sendFile(res, path6.join(vendorDir, "index.html"));
1112
+ return;
1113
+ }
1114
+ const relativePreviewPath = normalizeRelativeRoute(pathname);
1115
+ if (pathname.startsWith("/_preview/file/")) {
1116
+ if (!previewFile) {
1117
+ sendText(res, 404, "No preview file loaded.");
1118
+ return;
1119
+ }
1120
+ if (!relativePreviewPath) {
1121
+ sendText(res, 403, "Preview access denied.");
1122
+ return;
1123
+ }
1124
+ const candidatePath = path6.resolve(previewFile.rootDir, relativePreviewPath);
1125
+ if (candidatePath !== previewFile.absolutePath && !isPathInside(previewFile.rootDir, candidatePath)) {
1126
+ sendText(res, 403, "Preview access denied.");
1127
+ return;
1128
+ }
1129
+ if (!previewFs.existsSync(candidatePath) || !previewFs.statSync(candidatePath).isFile()) {
1130
+ sendText(res, 404, "Preview file not found.");
1131
+ return;
1132
+ }
1133
+ sendFile(res, candidatePath);
1134
+ return;
1135
+ }
1136
+ const vendorPath = path6.resolve(vendorDir, `.${pathname}`);
1137
+ const relativeVendorPath = path6.relative(vendorDir, vendorPath);
1138
+ if (relativeVendorPath.startsWith("..") || path6.isAbsolute(relativeVendorPath) || !previewFs.existsSync(vendorPath) || !previewFs.statSync(vendorPath).isFile()) {
1139
+ sendText(res, 404, "Not found");
1140
+ return;
1141
+ }
1142
+ sendFile(res, vendorPath);
1143
+ });
1144
+ server.on("clientError", (_err, socket) => {
1145
+ socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
1146
+ });
1147
+ await new Promise((resolve5, reject) => {
1148
+ server.listen(port, host, () => resolve5());
1149
+ server.once("error", (err) => {
1150
+ if (err.code === "EADDRINUSE") {
1151
+ reject(new Error(`Port ${port} is already in use on ${host}.`));
1152
+ return;
1153
+ }
1154
+ reject(err);
1155
+ });
1156
+ });
1157
+ const boundAddress = server.address();
1158
+ const boundPort = typeof boundAddress === "object" && boundAddress ? boundAddress.port : port;
1159
+ const serverUrl = `http://${host}:${boundPort}`;
1160
+ const fileUrl = previewFile ? `${serverUrl}?file=${encodeURIComponent(`${serverUrl}${previewFile.routePath}`)}` : serverUrl;
1161
+ if (openBrowser) {
1162
+ const open = await import("open").catch(() => null);
1163
+ if (open) await open.default(fileUrl);
1164
+ }
1165
+ let stopped = false;
1166
+ return {
1167
+ url: serverUrl,
1168
+ fileUrl,
1169
+ host,
1170
+ port: boundPort,
1171
+ stop: () => new Promise((resolve5, reject) => {
1172
+ if (stopped) {
1173
+ resolve5();
1174
+ return;
1175
+ }
1176
+ stopped = true;
1177
+ server.close((err) => err ? reject(err) : resolve5());
1178
+ })
1179
+ };
1180
+ }
1181
+ };
1182
+
1183
+ // src/client.ts
1184
+ var SoMark = class {
1185
+ parser;
1186
+ usage;
1187
+ pdf;
1188
+ preview;
1189
+ _http;
1190
+ constructor(opts = {}) {
1191
+ const cfg = resolveConfig(opts);
1192
+ this._http = new HttpClient({
1193
+ apiKey: cfg.apiKey,
1194
+ baseUrl: cfg.baseUrl,
1195
+ timeout: cfg.timeout,
1196
+ maxRetries: cfg.maxRetries
1197
+ });
1198
+ this.parser = new DocumentParser(this._http);
1199
+ this.usage = new UsageClient(this._http);
1200
+ this.pdf = new PDFProcessor();
1201
+ this.preview = new SoMarkDownPreview();
1202
+ }
1203
+ toString() {
1204
+ const key = this._http.getApiKey();
1205
+ const masked = key && key.length > 8 ? `${key.slice(0, 4)}...${key.slice(-4)}` : key ? "set" : "not set";
1206
+ return `SoMark(apiKey=${masked})`;
1207
+ }
1208
+ };
1209
+
1210
+ // src/somarkdown.ts
1211
+ var path7 = __toESM(require("path"), 1);
1212
+ var fs7 = __toESM(require("fs"), 1);
1213
+ var import_module = require("module");
1214
+ var import_url2 = require("url");
1215
+ var import_meta2 = {};
1216
+ var somarkdownFs = {
1217
+ existsSync: fs7.existsSync
1218
+ };
1219
+ var _SoMarkDown = null;
1220
+ function unique(values) {
1221
+ return [...new Set(values)];
1222
+ }
1223
+ function currentModuleDir2() {
1224
+ if (typeof __dirname !== "undefined") return __dirname;
1225
+ return path7.dirname((0, import_url2.fileURLToPath)(import_meta2.url));
1226
+ }
1227
+ function resolveSoMarkDownExport(mod) {
1228
+ const candidates = [
1229
+ mod,
1230
+ mod?.default,
1231
+ mod?.SoMarkDown,
1232
+ mod?.default?.SoMarkDown
1233
+ ];
1234
+ for (const candidate of candidates) {
1235
+ if (typeof candidate === "function") {
1236
+ const proto = candidate.prototype;
1237
+ if (typeof proto?.render === "function") return candidate;
1238
+ }
1239
+ }
1240
+ throw new Error("SoMarkDown UMD export is not a constructable renderer.");
1241
+ }
1242
+ function loadCommonJsFile(filePath) {
1243
+ const code = fs7.readFileSync(filePath, "utf-8");
1244
+ const module2 = { exports: {} };
1245
+ const localRequire = (0, import_module.createRequire)(filePath);
1246
+ const fn = new Function("module", "exports", "require", "__filename", "__dirname", code);
1247
+ fn.call(globalThis, module2, module2.exports, localRequire, filePath, path7.dirname(filePath));
1248
+ return module2.exports;
1249
+ }
1250
+ function loadSoMarkDown() {
1251
+ if (_SoMarkDown) return _SoMarkDown;
1252
+ const moduleDir = currentModuleDir2();
1253
+ const candidates = unique([
1254
+ path7.resolve(process.cwd(), "vendor/somarkdown-viewer/lib/somarkdown/somarkdown.umd.min.js"),
1255
+ path7.resolve(moduleDir, "vendor/somarkdown-viewer/lib/somarkdown/somarkdown.umd.min.js"),
1256
+ path7.resolve(moduleDir, "../vendor/somarkdown-viewer/lib/somarkdown/somarkdown.umd.min.js"),
1257
+ path7.resolve(moduleDir, "../../vendor/somarkdown-viewer/lib/somarkdown/somarkdown.umd.min.js"),
1258
+ path7.resolve(moduleDir, "../../../vendor/somarkdown-viewer/lib/somarkdown/somarkdown.umd.min.js")
1259
+ ]);
1260
+ for (const p of candidates) {
1261
+ if (somarkdownFs.existsSync(p)) {
1262
+ const mod = loadCommonJsFile(p);
1263
+ _SoMarkDown = resolveSoMarkDownExport(mod);
1264
+ return _SoMarkDown;
1265
+ }
1266
+ }
1267
+ throw new Error(
1268
+ "SoMarkDown UMD not found. Run: git submodule update --init --recursive && vendor/somarkdown-viewer/get_latest_smd.sh"
1269
+ );
1270
+ }
1271
+ var SoMarkDown = class {
1272
+ renderer;
1273
+ constructor(...args) {
1274
+ const Renderer = loadSoMarkDown();
1275
+ this.renderer = new Renderer(...args);
1276
+ }
1277
+ render(markdown, ...args) {
1278
+ return this.renderer.render(markdown, ...args);
1279
+ }
1280
+ };
1281
+ // Annotate the CommonJS export names for ESM import in node:
1282
+ 0 && (module.exports = {
1283
+ APIError,
1284
+ AuthenticationError,
1285
+ ConnectionError,
1286
+ DocumentParser,
1287
+ FileTooLargeError,
1288
+ InsufficientBalanceError,
1289
+ InvalidParamError,
1290
+ PDFProcessor,
1291
+ ParseFailedError,
1292
+ ParseResponse,
1293
+ ParseTimeoutError,
1294
+ RateLimitError,
1295
+ RequestTimeoutError,
1296
+ SoMark,
1297
+ SoMarkDown,
1298
+ SoMarkDownPreview,
1299
+ SoMarkError,
1300
+ Task,
1301
+ UnsupportedFileError,
1302
+ Usage,
1303
+ UsageClient
1304
+ });