@braze/sanity-plugin-ai-translation 0.2.87 → 0.3.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.
@@ -0,0 +1,87 @@
1
+ declare interface GPT5Request {
2
+ messages: Array<{
3
+ role: "developer" | "user" | "assistant";
4
+ content: string;
5
+ }>;
6
+ model?: string;
7
+ temperature?: number;
8
+ reasoningEffort?: "low" | "medium" | "high";
9
+ }
10
+
11
+ declare interface GPT5Response {
12
+ content: string;
13
+ tokensUsed: {
14
+ prompt: number;
15
+ completion: number;
16
+ reasoning: number;
17
+ total: number;
18
+ cached: number;
19
+ };
20
+ }
21
+
22
+ /**
23
+ * Server-side configuration for `requestOpenAICompletion`
24
+ *
25
+ * @public
26
+ */
27
+ export declare interface OpenAICompletionOptions {
28
+ /** OpenAI API key, supplied by the caller (for example from the function environment) */
29
+ apiKey: string;
30
+ }
31
+
32
+ /**
33
+ * Request data assembled by Studio and sent to the server.
34
+ * This is client-supplied, so it is validated at runtime and must never carry credentials.
35
+ *
36
+ * @public
37
+ */
38
+ export declare interface OpenAICompletionPayload {
39
+ /** OpenAI model id, e.g. `gpt-5` or `gpt-4o` */
40
+ model: string;
41
+ /** Messages assembled by Studio (serialized document + prompts) */
42
+ messages: OpenAIMessage[];
43
+ reasoningEffort?: OpenAIReasoningEffort;
44
+ temperature?: number;
45
+ }
46
+
47
+ /**
48
+ * Output of `requestOpenAICompletion`
49
+ *
50
+ * @public
51
+ */
52
+ export declare type OpenAICompletionResult = GPT5Response;
53
+
54
+ /**
55
+ * A single message in an assembled OpenAI prompt
56
+ *
57
+ * @public
58
+ */
59
+ export declare type OpenAIMessage = GPT5Request["messages"][number];
60
+
61
+ /**
62
+ * Reasoning effort for GPT-5 models
63
+ *
64
+ * @public
65
+ */
66
+ export declare type OpenAIReasoningEffort = NonNullable<
67
+ GPT5Request["reasoningEffort"]
68
+ >;
69
+
70
+ /**
71
+ * Sends assembled translation messages to OpenAI and returns the translated text and token usage.
72
+ *
73
+ * Serialization, prompt assembly, structure validation, and the Content Release write stay in
74
+ * Studio; this only performs the completion call. Model allowlisting is the caller's responsibility.
75
+ *
76
+ * Payloads with more than {@link MAX_MESSAGES} messages or more than {@link MAX_TOTAL_CONTENT_LENGTH}
77
+ * characters of content are rejected before any request is sent. Only `role` and `content` of each
78
+ * message are forwarded to OpenAI.
79
+ *
80
+ * @public
81
+ */
82
+ export declare const requestOpenAICompletion: (
83
+ payload: OpenAICompletionPayload,
84
+ options: OpenAICompletionOptions,
85
+ ) => Promise<OpenAICompletionResult>;
86
+
87
+ export {};
package/dist/server.js ADDED
@@ -0,0 +1,144 @@
1
+ import { devLog, retryWithBackoff, isNetworkError } from "./_chunks-es/retry.js";
2
+ const describeMessages = (messages) => messages.map(({ role, content }) => ({ role, contentLength: content.length })), callGPT5 = async (apiKey, request) => (request.model || "gpt-5").startsWith("gpt-5") ? await callGPT5ResponsesAPI(apiKey, request) : await callChatCompletionsAPI(apiKey, request), callGPT5ResponsesAPI = async (apiKey, request) => {
3
+ const model = request.model || "gpt-5", requestBody = {
4
+ model: model === "gpt-5" ? "gpt-5.1" : model,
5
+ // Map gpt-5 to gpt-5.1, use others as-is
6
+ input: request.messages
7
+ // Uses 'input' instead of 'messages'
8
+ };
9
+ request.reasoningEffort && (requestBody.reasoning = {
10
+ effort: request.reasoningEffort,
11
+ summary: "auto"
12
+ // Enable automatic reasoning summaries
13
+ }), devLog.debug("OpenAI GPT-5 Request", {
14
+ model: requestBody.model,
15
+ reasoningEffort: request.reasoningEffort,
16
+ messages: describeMessages(request.messages)
17
+ });
18
+ const response = await retryWithBackoff(
19
+ async () => {
20
+ const fetchResponse = await fetch("https://api.openai.com/v1/responses", {
21
+ method: "POST",
22
+ headers: {
23
+ "Content-Type": "application/json",
24
+ Authorization: `Bearer ${apiKey}`
25
+ },
26
+ body: JSON.stringify(requestBody)
27
+ });
28
+ if (!fetchResponse.ok) {
29
+ const errorMessage = `GPT-5 API error: ${(await fetchResponse.json()).error?.message || "Unknown error"}`, errorObj = new Error(errorMessage);
30
+ throw errorObj.response = { status: fetchResponse.status }, errorObj;
31
+ }
32
+ return fetchResponse;
33
+ },
34
+ {
35
+ maxAttempts: 2,
36
+ initialDelayMs: 0,
37
+ retryableErrors: isNetworkError,
38
+ context: { model: requestBody.model, endpoint: "responses" }
39
+ }
40
+ );
41
+ if (!response.ok) {
42
+ const error = await response.json();
43
+ throw new Error(`GPT-5 API error: ${error.error?.message || "Unknown error"}`);
44
+ }
45
+ const result = await response.json(), tokensUsed = {
46
+ prompt: result.usage?.input_tokens || 0,
47
+ completion: result.usage?.output_tokens || 0,
48
+ reasoning: result.usage?.output_tokens_details?.reasoning_tokens || 0,
49
+ total: result.usage?.total_tokens || 0,
50
+ cached: result.usage?.input_tokens_details?.cached_tokens || 0
51
+ };
52
+ let content = "";
53
+ if (result.output && Array.isArray(result.output)) {
54
+ const messageItem = result.output.find((item) => item.type === "message");
55
+ messageItem?.content && Array.isArray(messageItem.content) && (content = messageItem.content.find((item) => item.type === "output_text")?.text || "");
56
+ }
57
+ return content || (content = result.output_text || result.text || ""), {
58
+ content,
59
+ tokensUsed
60
+ };
61
+ }, callChatCompletionsAPI = async (apiKey, request) => {
62
+ const model = request.model || "gpt-4o", requestBody = {
63
+ model,
64
+ messages: request.messages,
65
+ temperature: request.temperature ?? 0.3
66
+ };
67
+ devLog.debug("OpenAI Chat Completions Request", {
68
+ model,
69
+ temperature: request.temperature ?? 0.3,
70
+ messages: describeMessages(request.messages)
71
+ });
72
+ const response = await retryWithBackoff(
73
+ async () => {
74
+ const fetchResponse = await fetch("https://api.openai.com/v1/chat/completions", {
75
+ method: "POST",
76
+ headers: {
77
+ "Content-Type": "application/json",
78
+ Authorization: `Bearer ${apiKey}`
79
+ },
80
+ body: JSON.stringify(requestBody)
81
+ });
82
+ if (!fetchResponse.ok) {
83
+ const errorMessage = `OpenAI API error: ${(await fetchResponse.json()).error?.message || "Unknown error"}`, errorObj = new Error(errorMessage);
84
+ throw errorObj.response = { status: fetchResponse.status }, errorObj;
85
+ }
86
+ return fetchResponse;
87
+ },
88
+ {
89
+ maxAttempts: 2,
90
+ initialDelayMs: 0,
91
+ retryableErrors: isNetworkError,
92
+ context: { model, endpoint: "chat/completions" }
93
+ }
94
+ );
95
+ if (!response.ok) {
96
+ const error = await response.json();
97
+ throw new Error(`OpenAI API error: ${error.error?.message || "Unknown error"}`);
98
+ }
99
+ const result = await response.json(), usage = result.usage || {}, tokensUsed = {
100
+ prompt: usage.prompt_tokens || 0,
101
+ completion: usage.completion_tokens || 0,
102
+ reasoning: usage.reasoning_tokens || 0,
103
+ total: usage.total_tokens || 0,
104
+ cached: usage.prompt_tokens_details?.cached_tokens || 0
105
+ };
106
+ return {
107
+ content: result.choices[0].message.content || "",
108
+ tokensUsed
109
+ };
110
+ }, MESSAGE_ROLES = /* @__PURE__ */ new Set(["developer", "user", "assistant"]), REASONING_EFFORTS = /* @__PURE__ */ new Set(["low", "medium", "high"]), MAX_MESSAGES = 20, MAX_TOTAL_CONTENT_LENGTH = 1e6, assertValidPayload = (payload) => {
111
+ if (!payload || typeof payload != "object")
112
+ throw new Error("requestOpenAICompletion: payload must be an object");
113
+ if (typeof payload.model != "string" || payload.model.trim() === "")
114
+ throw new Error("requestOpenAICompletion: model is required");
115
+ if (!Array.isArray(payload.messages) || payload.messages.length === 0)
116
+ throw new Error("requestOpenAICompletion: messages must be a non-empty array");
117
+ if (payload.messages.length > MAX_MESSAGES)
118
+ throw new Error(`requestOpenAICompletion: messages must contain at most ${MAX_MESSAGES} items`);
119
+ let totalContentLength = 0;
120
+ if (payload.messages.forEach((message, index) => {
121
+ if (!message || !MESSAGE_ROLES.has(message.role) || typeof message.content != "string")
122
+ throw new Error(
123
+ `requestOpenAICompletion: messages[${index}] must have a role of developer, user, or assistant and string content`
124
+ );
125
+ totalContentLength += message.content.length;
126
+ }), totalContentLength > MAX_TOTAL_CONTENT_LENGTH)
127
+ throw new Error(
128
+ `requestOpenAICompletion: message content must total at most ${MAX_TOTAL_CONTENT_LENGTH} characters (got ${totalContentLength})`
129
+ );
130
+ if (payload.reasoningEffort !== void 0 && !REASONING_EFFORTS.has(payload.reasoningEffort))
131
+ throw new Error("requestOpenAICompletion: reasoningEffort must be low, medium, or high");
132
+ if (payload.temperature !== void 0 && !Number.isFinite(payload.temperature))
133
+ throw new Error("requestOpenAICompletion: temperature must be a finite number");
134
+ }, requestOpenAICompletion = async (payload, options) => {
135
+ if (typeof options?.apiKey != "string" || options.apiKey.trim() === "")
136
+ throw new Error("requestOpenAICompletion: apiKey is required");
137
+ assertValidPayload(payload);
138
+ const { model, reasoningEffort, temperature } = payload, messages = payload.messages.map(({ role, content }) => ({ role, content }));
139
+ return callGPT5(options.apiKey, { model, messages, reasoningEffort, temperature });
140
+ };
141
+ export {
142
+ requestOpenAICompletion
143
+ };
144
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sources":["../src/utils/openai/callGPT5.ts","../src/server/openai/requestOpenAICompletion.ts"],"sourcesContent":["import { devLog } from '../common/devLog'\nimport { isNetworkError, retryWithBackoff } from '../common/retry'\n\nexport interface GPT5Request {\n messages: Array<{ role: 'developer' | 'user' | 'assistant'; content: string }>\n model?: string\n temperature?: number\n reasoningEffort?: 'low' | 'medium' | 'high'\n}\n\nexport interface GPT5Response {\n content: string\n tokensUsed: {\n prompt: number\n completion: number\n reasoning: number\n total: number\n cached: number\n }\n}\n\n// This module also runs inside Sanity functions, so logs must never include prompt or document text.\nconst describeMessages = (messages: GPT5Request['messages']) =>\n messages.map(({ role, content }) => ({ role, contentLength: content.length }))\n\n/**\n * Calls the OpenAI API with the given request parameters.\n * All GPT-5 variants (gpt-5, gpt-5.1, gpt-5-mini, gpt-5-nano, gpt-5-pro) use the new /v1/responses endpoint.\n * GPT-4 models use the standard /v1/chat/completions endpoint.\n */\nexport const callGPT5 = async (apiKey: string, request: GPT5Request): Promise<GPT5Response> => {\n const model = request.model || 'gpt-5'\n const isGPT5Family = model.startsWith('gpt-5')\n\n // All GPT-5 variants require /v1/responses endpoint; GPT-4 uses /v1/chat/completions\n if (isGPT5Family) {\n return await callGPT5ResponsesAPI(apiKey, request)\n }\n\n // GPT-4 and other models use the standard chat completions endpoint\n return await callChatCompletionsAPI(apiKey, request)\n}\n\n/**\n * Calls all GPT-5 variants using the new /v1/responses endpoint.\n * This endpoint supports reasoning configuration for applicable models.\n */\nconst callGPT5ResponsesAPI = async (apiKey: string, request: GPT5Request): Promise<GPT5Response> => {\n const model = request.model || 'gpt-5'\n\n // Format request for OpenAI Responses API (different from chat completions API)\n const requestBody: Record<string, unknown> = {\n model: model === 'gpt-5' ? 'gpt-5.1' : model, // Map gpt-5 to gpt-5.1, use others as-is\n input: request.messages, // Uses 'input' instead of 'messages'\n }\n\n // Include reasoning effort setting if provided by caller\n if (request.reasoningEffort) {\n requestBody.reasoning = {\n effort: request.reasoningEffort,\n summary: 'auto', // Enable automatic reasoning summaries\n }\n }\n\n devLog.debug('OpenAI GPT-5 Request', {\n model: requestBody.model,\n reasoningEffort: request.reasoningEffort,\n messages: describeMessages(request.messages),\n })\n\n const response = await retryWithBackoff(\n async () => {\n const fetchResponse = await fetch('https://api.openai.com/v1/responses', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(requestBody),\n })\n\n if (!fetchResponse.ok) {\n const error = await fetchResponse.json()\n const errorMessage = `GPT-5 API error: ${error.error?.message || 'Unknown error'}`\n const errorObj = new Error(errorMessage)\n // Attach response status for error classification\n ;(errorObj as { response?: { status: number } }).response = { status: fetchResponse.status }\n throw errorObj\n }\n\n return fetchResponse\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: { model: requestBody.model, endpoint: 'responses' },\n },\n )\n\n if (!response.ok) {\n const error = await response.json()\n throw new Error(`GPT-5 API error: ${error.error?.message || 'Unknown error'}`)\n }\n\n const result = await response.json()\n\n // Parse token usage from Responses API response structure\n const tokensUsed = {\n prompt: result.usage?.input_tokens || 0,\n completion: result.usage?.output_tokens || 0,\n reasoning: result.usage?.output_tokens_details?.reasoning_tokens || 0,\n total: result.usage?.total_tokens || 0,\n cached: result.usage?.input_tokens_details?.cached_tokens || 0,\n }\n\n // Responses API wraps output in array with reasoning and message objects\n let content = ''\n\n if (result.output && Array.isArray(result.output)) {\n // Find the message item in the output array\n const messageItem = result.output.find((item: { type: string }) => item.type === 'message') as\n | { content?: Array<{ type: string; text?: string }> }\n | undefined\n\n if (messageItem?.content && Array.isArray(messageItem.content)) {\n // Find the output_text item in the content array\n const outputTextItem = messageItem.content.find((item) => item.type === 'output_text')\n content = outputTextItem?.text || ''\n }\n }\n\n // Fallback to other possible field names if output array approach didn't work\n if (!content) {\n content = result.output_text || result.text || ''\n }\n\n return {\n content,\n tokensUsed,\n }\n}\n\n/**\n * Calls GPT-4 and other models using the standard /v1/chat/completions endpoint.\n */\nconst callChatCompletionsAPI = async (apiKey: string, request: GPT5Request): Promise<GPT5Response> => {\n const model = request.model || 'gpt-4o'\n\n const requestBody: Record<string, unknown> = {\n model,\n messages: request.messages,\n temperature: request.temperature ?? 0.3,\n }\n\n devLog.debug('OpenAI Chat Completions Request', {\n model,\n temperature: request.temperature ?? 0.3,\n messages: describeMessages(request.messages),\n })\n\n const response = await retryWithBackoff(\n async () => {\n const fetchResponse = await fetch('https://api.openai.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(requestBody),\n })\n\n if (!fetchResponse.ok) {\n const error = await fetchResponse.json()\n const errorMessage = `OpenAI API error: ${error.error?.message || 'Unknown error'}`\n const errorObj = new Error(errorMessage)\n // Attach response status for error classification\n ;(errorObj as { response?: { status: number } }).response = { status: fetchResponse.status }\n throw errorObj\n }\n\n return fetchResponse\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: { model, endpoint: 'chat/completions' },\n },\n )\n\n if (!response.ok) {\n const error = await response.json()\n throw new Error(`OpenAI API error: ${error.error?.message || 'Unknown error'}`)\n }\n\n const result = await response.json()\n\n const usage = result.usage || {}\n const tokensUsed = {\n prompt: usage.prompt_tokens || 0,\n completion: usage.completion_tokens || 0,\n reasoning: usage.reasoning_tokens || 0,\n total: usage.total_tokens || 0,\n cached: usage.prompt_tokens_details?.cached_tokens || 0,\n }\n\n const message = result.choices[0].message\n const messageContent = message.content || ''\n\n return {\n content: messageContent,\n tokensUsed,\n }\n}\n","import { callGPT5 } from '../../utils/openai/callGPT5'\nimport type {\n OpenAICompletionOptions,\n OpenAICompletionPayload,\n OpenAICompletionResult,\n OpenAIMessage,\n OpenAIReasoningEffort,\n} from './types'\n\nconst MESSAGE_ROLES: ReadonlySet<string> = new Set<OpenAIMessage['role']>(['developer', 'user', 'assistant'])\nconst REASONING_EFFORTS: ReadonlySet<string> = new Set<OpenAIReasoningEffort>(['low', 'medium', 'high'])\n\n/** Studio sends a developer message and a user message; anything far beyond that is not a translation request. */\nexport const MAX_MESSAGES = 20\n/** Roughly the GPT-5 input limit (about 272k tokens); larger payloads would be rejected by OpenAI anyway. */\nexport const MAX_TOTAL_CONTENT_LENGTH = 1_000_000\n\nconst assertValidPayload = (payload: OpenAICompletionPayload): void => {\n if (!payload || typeof payload !== 'object') {\n throw new Error('requestOpenAICompletion: payload must be an object')\n }\n if (typeof payload.model !== 'string' || payload.model.trim() === '') {\n throw new Error('requestOpenAICompletion: model is required')\n }\n if (!Array.isArray(payload.messages) || payload.messages.length === 0) {\n throw new Error('requestOpenAICompletion: messages must be a non-empty array')\n }\n if (payload.messages.length > MAX_MESSAGES) {\n throw new Error(`requestOpenAICompletion: messages must contain at most ${MAX_MESSAGES} items`)\n }\n let totalContentLength = 0\n payload.messages.forEach((message, index) => {\n if (!message || !MESSAGE_ROLES.has(message.role) || typeof message.content !== 'string') {\n throw new Error(\n `requestOpenAICompletion: messages[${index}] must have a role of developer, user, or assistant and string content`,\n )\n }\n totalContentLength += message.content.length\n })\n if (totalContentLength > MAX_TOTAL_CONTENT_LENGTH) {\n throw new Error(\n `requestOpenAICompletion: message content must total at most ${MAX_TOTAL_CONTENT_LENGTH} characters (got ${totalContentLength})`,\n )\n }\n if (payload.reasoningEffort !== undefined && !REASONING_EFFORTS.has(payload.reasoningEffort)) {\n throw new Error('requestOpenAICompletion: reasoningEffort must be low, medium, or high')\n }\n if (payload.temperature !== undefined && !Number.isFinite(payload.temperature)) {\n throw new Error('requestOpenAICompletion: temperature must be a finite number')\n }\n}\n\n/**\n * Sends assembled translation messages to OpenAI and returns the translated text and token usage.\n *\n * Serialization, prompt assembly, structure validation, and the Content Release write stay in\n * Studio; this only performs the completion call. Model allowlisting is the caller's responsibility.\n *\n * Payloads with more than {@link MAX_MESSAGES} messages or more than {@link MAX_TOTAL_CONTENT_LENGTH}\n * characters of content are rejected before any request is sent. Only `role` and `content` of each\n * message are forwarded to OpenAI.\n *\n * @public\n */\nexport const requestOpenAICompletion = async (\n payload: OpenAICompletionPayload,\n options: OpenAICompletionOptions,\n): Promise<OpenAICompletionResult> => {\n if (typeof options?.apiKey !== 'string' || options.apiKey.trim() === '') {\n throw new Error('requestOpenAICompletion: apiKey is required')\n }\n assertValidPayload(payload)\n\n const { model, reasoningEffort, temperature } = payload\n const messages = payload.messages.map(({ role, content }) => ({ role, content }))\n return callGPT5(options.apiKey, { model, messages, reasoningEffort, temperature })\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@braze/sanity-plugin-ai-translation",
3
- "version": "0.2.87",
3
+ "version": "0.3.0",
4
4
  "description": "A Sanity Studio v3 plugin that provides AI translation for translations fields.",
5
5
  "private": false,
6
6
  "keywords": [
@@ -29,6 +29,11 @@
29
29
  "import": "./dist/index.js",
30
30
  "default": "./dist/index.js"
31
31
  },
32
+ "./server": {
33
+ "source": "./src/server/index.ts",
34
+ "import": "./dist/server.js",
35
+ "default": "./dist/server.js"
36
+ },
32
37
  "./package.json": "./package.json"
33
38
  },
34
39
  "main": "./dist/index.js",
@@ -40,6 +45,20 @@
40
45
  "v2-incompatible.js"
41
46
  ],
42
47
  "browserslist": "extends @sanity/browserslist-config",
48
+ "scripts": {
49
+ "build": "bash copy-readme.sh && plugin-kit verify-package --silent && pkg-utils build --strict --check --clean",
50
+ "check:openai-server": "pnpm build && node scripts/check-openai-server.mjs",
51
+ "dev": "plugin-kit link-watch",
52
+ "demo:serialization": "tsx scripts/demo-serialization.ts",
53
+ "prepublishOnly": "pnpm run build",
54
+ "test:pricing": "tsx scripts/test-pricing.ts",
55
+ "test": "vitest run",
56
+ "test:watch": "vitest",
57
+ "watch": "pkg-utils watch --strict",
58
+ "link-watch": "plugin-kit link-watch",
59
+ "lint": "biome check .",
60
+ "typecheck": "tsc -p tsconfig.settings.json --noEmit"
61
+ },
43
62
  "dependencies": {
44
63
  "@portabletext/react": "^4.0.3",
45
64
  "@sanity/diff-patch": "^6.0.0",
@@ -70,6 +89,7 @@
70
89
  "react": "^18.2.0",
71
90
  "sanity": "^4.18.0",
72
91
  "sanity-plugin-internationalized-array": "^3.1.5",
92
+
73
93
  "typescript": "^5.8.3",
74
94
  "vitest": "^3.2.4"
75
95
  },
@@ -89,6 +109,7 @@
89
109
  "registry": "https://registry.npmjs.org/",
90
110
  "access": "restricted"
91
111
  },
112
+ "packageManager": "pnpm@10.12.4",
92
113
  "sanityPlugin": {
93
114
  "verifyPackage": {
94
115
  "eslintImports": false,
@@ -97,17 +118,5 @@
97
118
  "srcIndex": false,
98
119
  "tsconfig": false
99
120
  }
100
- },
101
- "scripts": {
102
- "build": "bash copy-readme.sh && plugin-kit verify-package --silent && pkg-utils build --strict --check --clean",
103
- "dev": "plugin-kit link-watch",
104
- "demo:serialization": "tsx scripts/demo-serialization.ts",
105
- "test:pricing": "tsx scripts/test-pricing.ts",
106
- "test": "vitest run",
107
- "test:watch": "vitest",
108
- "watch": "pkg-utils watch --strict",
109
- "link-watch": "plugin-kit link-watch",
110
- "lint": "biome check .",
111
- "typecheck": "tsc -p tsconfig.settings.json --noEmit"
112
121
  }
113
- }
122
+ }