outrank-cli 0.8.0 → 0.9.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.
@@ -27,6 +27,8 @@ export const MCP_STARTUP_RESOLVE_TIMEOUT_MS = 3_000;
27
27
 
28
28
  export const MAX_IMAGE_UPLOAD_MEGABYTES = 5;
29
29
  export const MAX_IMAGE_UPLOAD_BYTES = MAX_IMAGE_UPLOAD_MEGABYTES * BYTES_PER_MB;
30
+ export const MAX_TEXT_FILE_MEGABYTES = 1;
31
+ export const MAX_TEXT_FILE_BYTES = MAX_TEXT_FILE_MEGABYTES * BYTES_PER_MB;
30
32
  export const MAX_UPSTREAM_ERROR_BODY_CHARS = 500;
31
33
  export const MAX_RATE_LIMIT_HEADER_LENGTH = 64;
32
34
  export const MAX_TOOL_NAME_LENGTH = 46;
@@ -50,6 +52,7 @@ export const BLOG_TEMPLATES = Object.freeze(['classic', 'editorial', 'journal',
50
52
  export const BLOG_ANALYTICS_RANGES = Object.freeze(['7d', '30d', '90d']);
51
53
  export const BLOG_DOMAIN_MODES = Object.freeze(['subdomain', 'apex', 'subfolder']);
52
54
  export const BLOG_POST_LIST_STATUSES = Object.freeze(['all', 'published', 'scheduled', 'draft']);
55
+ export const BLOG_POST_WRITE_STATUSES = Object.freeze(['draft', 'published', 'scheduled']);
53
56
  export const BLOG_SUBSCRIBER_STATUSES = Object.freeze(['subscribed', 'unsubscribed']);
54
57
  export const BLOG_PREVIEW_DESIGN_SCOPE = 'design';
55
58
  export const BLOG_PREVIEW_SCOPES = Object.freeze([BLOG_PREVIEW_DESIGN_SCOPE, 'post', 'page']);
package/src/core/files.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
 
4
- import { MAX_IMAGE_UPLOAD_BYTES } from './constants.js';
4
+ import { MAX_IMAGE_UPLOAD_BYTES, MAX_TEXT_FILE_BYTES } from './constants.js';
5
5
  import { usageError } from './errors.js';
6
6
 
7
7
  const ImageMime = Object.freeze({
@@ -184,3 +184,39 @@ export const readImageUpload = async ({ filePath, allowedDir }) => {
184
184
 
185
185
  return toUpload(buffer, fileName);
186
186
  };
187
+
188
+ const UTF8_ENCODING = 'utf-8';
189
+ const BYTE_ORDER_MARK = '\uFEFF';
190
+ const LINE_BREAK_PATTERN = /\r\n?/g;
191
+ const LINE_FEED = '\n';
192
+ const TEXT_OVERSIZE_GUIDANCE = 'Split the content or shorten it below the limit, then retry.';
193
+ const TEXT_ENCODING_GUIDANCE = 'Save the file as UTF-8 text, then retry.';
194
+
195
+ export const readTextFile = async ({ filePath, option }) => {
196
+ if (typeof filePath !== 'string' || filePath === '') {
197
+ throw usageError(`Missing required option: ${option} <path>.`, `Pass a local file path with \`${option} <path>\`.`);
198
+ }
199
+ let buffer;
200
+ try {
201
+ const stats = await fs.stat(filePath);
202
+ if (!stats.isFile()) throw usageError(`Not a file: ${filePath}`, readGuidance(filePath));
203
+ if (stats.size > MAX_TEXT_FILE_BYTES) {
204
+ throw usageError(
205
+ `${filePath} is ${stats.size} bytes; the maximum is ${MAX_TEXT_FILE_BYTES} bytes.`,
206
+ TEXT_OVERSIZE_GUIDANCE,
207
+ );
208
+ }
209
+ buffer = await fs.readFile(filePath);
210
+ } catch (error) {
211
+ if (error?.cliCode) throw error;
212
+ throw usageError(`Failed to read file: ${error.message}`, readGuidance(filePath));
213
+ }
214
+ let text;
215
+ try {
216
+ text = new TextDecoder(UTF8_ENCODING, { fatal: true }).decode(buffer);
217
+ } catch {
218
+ throw usageError(`${filePath} is not valid UTF-8 text.`, TEXT_ENCODING_GUIDANCE);
219
+ }
220
+ const withoutBom = text.startsWith(BYTE_ORDER_MARK) ? text.slice(BYTE_ORDER_MARK.length) : text;
221
+ return withoutBom.replace(LINE_BREAK_PATTERN, LINE_FEED);
222
+ };
@@ -11,6 +11,7 @@ export const FieldKind = Object.freeze({
11
11
  OBJECT: 'object',
12
12
  JSON: 'json',
13
13
  IMAGE: 'image',
14
+ TEXT_FILE: 'text-file',
14
15
  });
15
16
 
16
17
  export const BoolSerialize = Object.freeze({
@@ -63,6 +64,7 @@ export const list = ({ split = DEFAULT_LIST_SEPARATOR, serialize = ListSerialize
63
64
  export const object = () => createField(FieldKind.OBJECT, { properties: undefined });
64
65
  export const json = () => createField(FieldKind.JSON);
65
66
  export const image = () => createField(FieldKind.IMAGE);
67
+ export const textFile = () => createField(FieldKind.TEXT_FILE);
66
68
 
67
69
  export const isField = (value) => Boolean(value) && Object.values(FieldKind).includes(value.kind);
68
70
 
@@ -124,6 +126,10 @@ const parseScalar = (field, value, label) => {
124
126
  if (!valid) throw usageError(`${label} must be an image upload (file_base64, file_name, file_type).`);
125
127
  return value;
126
128
  }
129
+ if (field.kind === FieldKind.TEXT_FILE) {
130
+ if (typeof value !== 'string') throw usageError(`${label} must be the path of a text file.`);
131
+ return value;
132
+ }
127
133
  throw new Error(`Unknown field kind: ${field.kind}`);
128
134
  };
129
135
 
@@ -254,6 +260,7 @@ const zodScalar = (z, field, options = {}) => {
254
260
  if (field.kind === FieldKind.IMAGE) {
255
261
  return z.object({ file_base64: z.string().min(1), file_name: z.string().min(1), file_type: z.string().min(1) });
256
262
  }
263
+ if (field.kind === FieldKind.TEXT_FILE) return z.string();
257
264
  throw new Error(`Unknown field kind: ${field.kind}`);
258
265
  };
259
266