@coreviz/sdk 1.0.13 → 1.0.15

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.
package/README.md CHANGED
@@ -51,6 +51,17 @@ Check out [coreviz.io/tools](https://coreviz.io/tools) to explore these features
51
51
  npm install @coreviz/sdk
52
52
  ```
53
53
 
54
+ ### React Native / Expo
55
+
56
+ When using this SDK in Expo / React Native, install the Expo image utilities (used for `resize`):
57
+
58
+ ```bash
59
+ npx expo install expo-image-manipulator expo-file-system
60
+ ```
61
+
62
+ Notes:
63
+ - **Local mode** (`mode: 'local'`) for `tag()` / `embed()` is **not supported** on React Native / Expo.
64
+
54
65
  ## Configuration
55
66
 
56
67
  To use the AI features, you need to instantiate the `CoreViz` class with your API key.
@@ -133,6 +144,30 @@ const editedImage = await coreviz.edit('https://example.com/photo.jpg', {
133
144
  });
134
145
  ```
135
146
 
147
+ ### `coreviz.batchGenerate(prompt, options)`
148
+
149
+ Generates multiple images based on a text prompt, optionally using reference images for style/structure guidance.
150
+
151
+ **Parameters:**
152
+ - `prompt` (string): The text description of the image(s) to generate.
153
+ - `options` (object, optional):
154
+ - `referenceImages` (string[], optional): Array of reference images (URL/base64) to guide generation.
155
+ - `count` (number, optional): Number of images to generate (default: 1).
156
+ - `aspectRatio` (string, optional): Target aspect ratio (e.g., `'1:1'`, `'16:9'`, `'4:3'`).
157
+ - `model` (string, optional): The model to use (default: `'google/nano-banana-pro'`).
158
+
159
+ **Returns:**
160
+ - `Promise<string[]>`: An array of generated images as URLs.
161
+
162
+ **Example:**
163
+
164
+ ```typescript
165
+ const images = await coreviz.batchGenerate("A futuristic city skyline", {
166
+ count: 4,
167
+ aspectRatio: "16:9"
168
+ });
169
+ ```
170
+
136
171
  ### `coreviz.embed(input, options?)`
137
172
 
138
173
  Generates embeddings for image or text inputs, enabling semantic search and similarity comparison. Use with `coreviz.similarity(embeddingA, embeddingB)` to compare two images or an image and a text.
@@ -187,4 +222,4 @@ Utility function to resize images client-side or server-side before processing.
187
222
  ```typescript
188
223
  const resized = await coreviz.resize(myFileObject, 800, 600);
189
224
  // or import { resize } from '@coreviz/sdk';
190
- ```
225
+ ```
package/dist/coreviz.d.ts CHANGED
@@ -27,6 +27,12 @@ export interface EmbedOptions {
27
27
  export interface EmbedResponse {
28
28
  embedding: number[];
29
29
  }
30
+ export interface BatchGenerateOptions {
31
+ referenceImages?: string[];
32
+ count?: number;
33
+ aspectRatio?: '1:1' | '2:3' | '3:2' | '3:4' | '4:3' | '4:5' | '5:4' | '9:16' | '16:9' | '21:9';
34
+ model?: 'google/nano-banana' | 'google/nano-banana-pro';
35
+ }
30
36
  export declare class CoreViz {
31
37
  private apiKey?;
32
38
  private token?;
@@ -35,6 +41,7 @@ export declare class CoreViz {
35
41
  private handleResponse;
36
42
  describe(image: string, options?: DescribeOptions): Promise<string>;
37
43
  edit(image: string, options: EditOptions): Promise<string>;
44
+ batchGenerate(prompt: string, options?: BatchGenerateOptions): Promise<string[]>;
38
45
  tag(image: string, options: TagOptions): Promise<TagResponse>;
39
46
  private tagLocal;
40
47
  embed(input: string, options?: EmbedOptions): Promise<EmbedResponse>;
package/dist/coreviz.js CHANGED
@@ -103,6 +103,31 @@ class CoreViz {
103
103
  throw err instanceof Error ? err : new Error("An unexpected error occurred.");
104
104
  }
105
105
  }
106
+ async batchGenerate(prompt, options = {}) {
107
+ try {
108
+ const headers = this.getHeaders();
109
+ let resizedImages = [];
110
+ if (options.referenceImages && options.referenceImages.length > 0) {
111
+ resizedImages = await Promise.all(options.referenceImages.map(img => (0, resize_1.resize)(img, 1024, 1024)));
112
+ }
113
+ const response = await fetch(`https://lab.coreviz.io/api/ai/batch-generate`, {
114
+ method: 'POST',
115
+ headers,
116
+ body: JSON.stringify({
117
+ prompt,
118
+ image: resizedImages.length > 0 ? resizedImages : undefined,
119
+ count: options.count || 1,
120
+ aspectRatio: options.aspectRatio,
121
+ model: options.model || 'google/nano-banana-pro',
122
+ }),
123
+ });
124
+ const data = await this.handleResponse(response);
125
+ return data.results || [];
126
+ }
127
+ catch (err) {
128
+ throw err instanceof Error ? err : new Error("An unexpected error occurred.");
129
+ }
130
+ }
106
131
  async tag(image, options) {
107
132
  const mode = options?.mode || 'api';
108
133
  if (mode === 'local') {
@@ -0,0 +1,49 @@
1
+ export interface CoreVizConfig {
2
+ apiKey?: string;
3
+ token?: string;
4
+ }
5
+ export interface DescribeOptions {
6
+ }
7
+ export interface EditOptions {
8
+ prompt: string;
9
+ aspectRatio?: 'match_input_image' | '1:1' | '16:9' | '9:16' | '4:3' | '3:4';
10
+ outputFormat?: 'jpg' | 'png';
11
+ model?: 'flux-kontext-max' | 'google/nano-banana' | 'seedream-4';
12
+ }
13
+ export interface TagOptions {
14
+ prompt: string;
15
+ options?: string[];
16
+ multiple?: boolean;
17
+ mode?: 'api' | 'local';
18
+ }
19
+ export interface TagResponse {
20
+ tags: string[];
21
+ raw?: unknown;
22
+ }
23
+ export interface EmbedOptions {
24
+ type?: 'image' | 'text';
25
+ mode?: 'api' | 'local';
26
+ }
27
+ export interface EmbedResponse {
28
+ embedding: number[];
29
+ }
30
+ export interface BatchGenerateOptions {
31
+ referenceImages?: string[];
32
+ count?: number;
33
+ aspectRatio?: '1:1' | '2:3' | '3:2' | '3:4' | '4:3' | '4:5' | '5:4' | '9:16' | '16:9' | '21:9';
34
+ model?: 'google/nano-banana' | 'google/nano-banana-pro';
35
+ }
36
+ export declare class CoreViz {
37
+ private apiKey?;
38
+ private token?;
39
+ constructor(config?: CoreVizConfig);
40
+ private getHeaders;
41
+ private handleResponse;
42
+ describe(image: string, _options?: DescribeOptions): Promise<string>;
43
+ edit(image: string, options: EditOptions): Promise<string>;
44
+ batchGenerate(prompt: string, options?: BatchGenerateOptions): Promise<string[]>;
45
+ tag(image: string, options: TagOptions): Promise<TagResponse>;
46
+ embed(input: string, options?: EmbedOptions): Promise<EmbedResponse>;
47
+ resize(input: string | File, maxWidth?: number, maxHeight?: number): Promise<string>;
48
+ similarity(vecA: number[], vecB: number[]): number;
49
+ }
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CoreViz = void 0;
4
+ const resize_native_1 = require("./resize.native");
5
+ class CoreViz {
6
+ constructor(config = {}) {
7
+ // React Native / Expo doesn't provide `process.env` in the same way; keep config explicit.
8
+ this.apiKey = config.apiKey;
9
+ this.token = config.token;
10
+ }
11
+ getHeaders() {
12
+ const headers = {
13
+ 'Content-Type': 'application/json',
14
+ };
15
+ if (this.token) {
16
+ headers['Authorization'] = `Bearer ${this.token}`;
17
+ }
18
+ else {
19
+ headers['x-api-key'] = this.apiKey || '';
20
+ }
21
+ return headers;
22
+ }
23
+ async handleResponse(response) {
24
+ if (response.status === 402) {
25
+ throw new Error('Insufficient credits');
26
+ }
27
+ if (!response.ok) {
28
+ throw new Error(`Request failed (${response.status})`);
29
+ }
30
+ const data = (await response.json());
31
+ if (data.error) {
32
+ throw new Error(data.error);
33
+ }
34
+ return data;
35
+ }
36
+ async describe(image, _options) {
37
+ const resizedImage = await (0, resize_native_1.resize)(image, 512, 512);
38
+ const headers = this.getHeaders();
39
+ const response = await fetch(`https://lab.coreviz.io/api/ai/describe`, {
40
+ method: 'POST',
41
+ headers,
42
+ body: JSON.stringify({ image: resizedImage }),
43
+ });
44
+ const data = await this.handleResponse(response);
45
+ return data.description;
46
+ }
47
+ async edit(image, options) {
48
+ const resizedImage = await (0, resize_native_1.resize)(image, 1024, 1024);
49
+ const headers = this.getHeaders();
50
+ const response = await fetch(`https://lab.coreviz.io/api/ai/edit`, {
51
+ method: 'POST',
52
+ headers,
53
+ body: JSON.stringify({
54
+ image: resizedImage,
55
+ prompt: options.prompt,
56
+ aspectRatio: options.aspectRatio || 'match_input_image',
57
+ outputFormat: options.outputFormat || 'jpg',
58
+ model: options.model || 'flux-kontext-max',
59
+ }),
60
+ });
61
+ const data = await this.handleResponse(response);
62
+ return data.result;
63
+ }
64
+ async batchGenerate(prompt, options = {}) {
65
+ const headers = this.getHeaders();
66
+ let resizedImages = [];
67
+ if (options.referenceImages && options.referenceImages.length > 0) {
68
+ resizedImages = await Promise.all(options.referenceImages.map((img) => (0, resize_native_1.resize)(img, 1024, 1024)));
69
+ }
70
+ const response = await fetch(`https://lab.coreviz.io/api/ai/batch-generate`, {
71
+ method: 'POST',
72
+ headers,
73
+ body: JSON.stringify({
74
+ prompt,
75
+ image: resizedImages.length > 0 ? resizedImages : undefined,
76
+ count: options.count || 1,
77
+ aspectRatio: options.aspectRatio,
78
+ model: options.model || 'google/nano-banana-pro',
79
+ }),
80
+ });
81
+ const data = await this.handleResponse(response);
82
+ return data.results || [];
83
+ }
84
+ async tag(image, options) {
85
+ const mode = options?.mode || 'api';
86
+ if (mode === 'local') {
87
+ throw new Error("Local tagging is not supported on React Native/Expo. Use `mode: 'api'`.");
88
+ }
89
+ const resizedImage = await (0, resize_native_1.resize)(image, 512, 512);
90
+ const headers = this.getHeaders();
91
+ const response = await fetch('https://lab.coreviz.io/api/ai/tag', {
92
+ method: 'POST',
93
+ headers,
94
+ body: JSON.stringify({
95
+ image: resizedImage,
96
+ prompt: options.prompt,
97
+ options: options.options && options.options.length > 0 ? options.options : undefined,
98
+ multiple: options.multiple ?? true,
99
+ }),
100
+ });
101
+ const data = await this.handleResponse(response);
102
+ const tags = Array.isArray(data.tags)
103
+ ? data.tags
104
+ : Array.isArray(data.result)
105
+ ? data.result
106
+ : typeof data.tag === 'string'
107
+ ? [data.tag]
108
+ : [];
109
+ return {
110
+ tags,
111
+ raw: data,
112
+ };
113
+ }
114
+ async embed(input, options) {
115
+ const mode = options?.mode || 'api';
116
+ if (mode === 'local') {
117
+ throw new Error("Local embedding is not supported on React Native/Expo. Use `mode: 'api'`.");
118
+ }
119
+ const headers = this.getHeaders();
120
+ let body = {};
121
+ // Determine type
122
+ let isImage = false;
123
+ if (options?.type) {
124
+ isImage = options.type === 'image';
125
+ }
126
+ else {
127
+ // Heuristic: assume URL/data:image => image
128
+ isImage = input.startsWith('data:image') || input.startsWith('http://') || input.startsWith('https://') || input.startsWith('file://');
129
+ }
130
+ if (isImage) {
131
+ const resizedImage = await (0, resize_native_1.resize)(input, 512, 512);
132
+ body.image = resizedImage;
133
+ }
134
+ else {
135
+ body.text = input;
136
+ }
137
+ const response = await fetch('https://lab.coreviz.io/api/ai/embed', {
138
+ method: 'POST',
139
+ headers,
140
+ body: JSON.stringify(body),
141
+ });
142
+ const data = await this.handleResponse(response);
143
+ return data;
144
+ }
145
+ async resize(input, maxWidth, maxHeight) {
146
+ return (0, resize_native_1.resize)(input, maxWidth, maxHeight);
147
+ }
148
+ similarity(vecA, vecB) {
149
+ if (vecA.length !== vecB.length)
150
+ return 0;
151
+ let dotProduct = 0;
152
+ let normA = 0;
153
+ let normB = 0;
154
+ for (let i = 0; i < vecA.length; i++) {
155
+ dotProduct += vecA[i] * vecB[i];
156
+ normA += vecA[i] * vecA[i];
157
+ normB += vecB[i] * vecB[i];
158
+ }
159
+ if (normA === 0 || normB === 0)
160
+ return 0;
161
+ return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
162
+ }
163
+ }
164
+ exports.CoreViz = CoreViz;
@@ -0,0 +1,4 @@
1
+ import { CoreViz, CoreVizConfig, DescribeOptions, EditOptions, TagOptions, TagResponse, EmbedOptions, EmbedResponse } from './coreviz.native';
2
+ import { resize } from './resize.native';
3
+ export { CoreViz, resize };
4
+ export type { CoreVizConfig, DescribeOptions, EditOptions, TagOptions, TagResponse, EmbedOptions, EmbedResponse };
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resize = exports.CoreViz = void 0;
4
+ const coreviz_native_1 = require("./coreviz.native");
5
+ Object.defineProperty(exports, "CoreViz", { enumerable: true, get: function () { return coreviz_native_1.CoreViz; } });
6
+ const resize_native_1 = require("./resize.native");
7
+ Object.defineProperty(exports, "resize", { enumerable: true, get: function () { return resize_native_1.resize; } });
package/dist/resize.js CHANGED
@@ -112,6 +112,7 @@ async function serverResize(inputStr, maxWidth, maxHeight) {
112
112
  }
113
113
  try {
114
114
  // Dynamic import to prevent bundling sharp on the client
115
+ // Note: `sharp` is an optional dependency (for RN/Expo compatibility). If missing, we gracefully fall back.
115
116
  const sharpModule = await Promise.resolve().then(() => __importStar(require('sharp')));
116
117
  const sharp = sharpModule.default;
117
118
  let buffer;
@@ -155,7 +156,7 @@ async function serverResize(inputStr, maxWidth, maxHeight) {
155
156
  return inputStr;
156
157
  }
157
158
  catch (error) {
158
- console.warn("Failed to resize image on server:", error);
159
+ console.warn("Failed to resize image on server. If you need server-side resizing, ensure `sharp` is installed. Falling back to original image.", error);
159
160
  return inputStr; // Fallback to original
160
161
  }
161
162
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * React Native / Expo image resize implementation.
3
+ *
4
+ * - Uses `expo-image-manipulator` to resize while preserving aspect ratio (fit: inside).
5
+ * - Supports inputs:
6
+ * - `file://...` URIs (typical from Expo ImagePicker/Camera)
7
+ * - `data:image/...;base64,...` data URLs
8
+ * - `http(s)://...` URLs (downloaded to cache first)
9
+ *
10
+ * Dependencies (expected to be installed by Expo apps):
11
+ * - expo-image-manipulator
12
+ * - expo-file-system
13
+ */
14
+ type ResizeInput = string | File;
15
+ export declare function resize(input: ResizeInput, maxWidth?: number, maxHeight?: number): Promise<string>;
16
+ export {};
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ /**
3
+ * React Native / Expo image resize implementation.
4
+ *
5
+ * - Uses `expo-image-manipulator` to resize while preserving aspect ratio (fit: inside).
6
+ * - Supports inputs:
7
+ * - `file://...` URIs (typical from Expo ImagePicker/Camera)
8
+ * - `data:image/...;base64,...` data URLs
9
+ * - `http(s)://...` URLs (downloaded to cache first)
10
+ *
11
+ * Dependencies (expected to be installed by Expo apps):
12
+ * - expo-image-manipulator
13
+ * - expo-file-system
14
+ */
15
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
+ desc = { enumerable: true, get: function() { return m[k]; } };
20
+ }
21
+ Object.defineProperty(o, k2, desc);
22
+ }) : (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ o[k2] = m[k];
25
+ }));
26
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
28
+ }) : function(o, v) {
29
+ o["default"] = v;
30
+ });
31
+ var __importStar = (this && this.__importStar) || (function () {
32
+ var ownKeys = function(o) {
33
+ ownKeys = Object.getOwnPropertyNames || function (o) {
34
+ var ar = [];
35
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
36
+ return ar;
37
+ };
38
+ return ownKeys(o);
39
+ };
40
+ return function (mod) {
41
+ if (mod && mod.__esModule) return mod;
42
+ var result = {};
43
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
44
+ __setModuleDefault(result, mod);
45
+ return result;
46
+ };
47
+ })();
48
+ Object.defineProperty(exports, "__esModule", { value: true });
49
+ exports.resize = resize;
50
+ function isDataUrl(s) {
51
+ return /^data:image\/\w+;base64,/.test(s);
52
+ }
53
+ function isHttpUrl(s) {
54
+ return /^https?:\/\//.test(s);
55
+ }
56
+ function getExtFromDataUrl(dataUrl) {
57
+ const m = dataUrl.match(/^data:(image\/\w+);base64,/);
58
+ const mime = m?.[1] || 'image/jpeg';
59
+ return mime.includes('png') ? 'png' : 'jpg';
60
+ }
61
+ function fitInside(width, height, maxWidth, maxHeight) {
62
+ if (width <= 0 || height <= 0)
63
+ return { width, height };
64
+ const wRatio = maxWidth / width;
65
+ const hRatio = maxHeight / height;
66
+ const ratio = Math.min(wRatio, hRatio, 1); // never enlarge
67
+ return { width: Math.round(width * ratio), height: Math.round(height * ratio) };
68
+ }
69
+ async function getImageSize(uri) {
70
+ // Importing from 'react-native' is safe for RN builds; this file is never used in web/node builds.
71
+ const { Image } = await Promise.resolve().then(() => __importStar(require('react-native')));
72
+ return await new Promise((resolve, reject) => {
73
+ Image.getSize(uri, (width, height) => resolve({ width, height }), (error) => reject(error));
74
+ });
75
+ }
76
+ async function ensureLocalFileUri(input) {
77
+ const FileSystem = await Promise.resolve().then(() => __importStar(require('expo-file-system')));
78
+ const cacheDir = FileSystem.cacheDirectory;
79
+ if (!cacheDir) {
80
+ throw new Error("expo-file-system cacheDirectory is unavailable. Ensure you're running in an Expo environment with expo-file-system installed.");
81
+ }
82
+ // data URL -> write to cache
83
+ if (isDataUrl(input)) {
84
+ const format = getExtFromDataUrl(input);
85
+ const base64Data = input.split(',')[1] || '';
86
+ const fileUri = `${cacheDir}coreviz_${Date.now()}.${format}`;
87
+ await FileSystem.writeAsStringAsync(fileUri, base64Data, { encoding: FileSystem.EncodingType.Base64 });
88
+ return { uri: fileUri, format };
89
+ }
90
+ // http(s) URL -> download to cache
91
+ if (isHttpUrl(input)) {
92
+ const format = input.toLowerCase().includes('.png') ? 'png' : 'jpg';
93
+ const fileUri = `${cacheDir}coreviz_${Date.now()}.${format}`;
94
+ const res = await FileSystem.downloadAsync(input, fileUri);
95
+ return { uri: res.uri, format };
96
+ }
97
+ // assume it's already a local file URI (file://...) or a content URI
98
+ return { uri: input, format: 'jpg' };
99
+ }
100
+ async function resize(input, maxWidth = 1920, maxHeight = 1080) {
101
+ if (typeof input !== 'string') {
102
+ throw new Error('React Native resizing only supports string inputs (file URI, data URL, or http(s) URL).');
103
+ }
104
+ // Lazy import to keep this module Expo-safe and avoid forcing deps in web/node builds.
105
+ let ImageManipulator;
106
+ try {
107
+ ImageManipulator = await Promise.resolve().then(() => __importStar(require('expo-image-manipulator')));
108
+ }
109
+ catch (e) {
110
+ throw new Error("Missing optional dependency 'expo-image-manipulator'. Install it in your Expo app: `npx expo install expo-image-manipulator`");
111
+ }
112
+ const { uri, format } = await ensureLocalFileUri(input);
113
+ const { width, height } = await getImageSize(uri);
114
+ const target = fitInside(width, height, maxWidth, maxHeight);
115
+ // If already within constraints, still normalize to a data URL (so downstream API calls always have base64 available).
116
+ const actions = target.width === width && target.height === height ? [] : [{ resize: { width: target.width, height: target.height } }];
117
+ const saveFormat = format === 'png' ? ImageManipulator.SaveFormat.PNG : ImageManipulator.SaveFormat.JPEG;
118
+ const result = await ImageManipulator.manipulateAsync(uri, actions, {
119
+ base64: true,
120
+ compress: saveFormat === ImageManipulator.SaveFormat.JPEG ? 0.92 : 1,
121
+ format: saveFormat,
122
+ });
123
+ if (!result.base64) {
124
+ throw new Error('Failed to produce base64 output during resize.');
125
+ }
126
+ const mime = saveFormat === ImageManipulator.SaveFormat.PNG ? 'image/png' : 'image/jpeg';
127
+ return `data:${mime};base64,${result.base64}`;
128
+ }
package/package.json CHANGED
@@ -1,9 +1,22 @@
1
1
  {
2
2
  "name": "@coreviz/sdk",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "description": "CoreViz SDK",
5
5
  "main": "dist/index.js",
6
+ "react-native": "dist/index.native.js",
6
7
  "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "react-native": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.native.js"
13
+ },
14
+ "default": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
17
+ }
18
+ }
19
+ },
7
20
  "files": [
8
21
  "dist",
9
22
  "README.md"
@@ -31,8 +44,21 @@
31
44
  "@types/node": "^24.10.1",
32
45
  "typescript": "^5.9.3"
33
46
  },
34
- "dependencies": {
47
+ "dependencies": {},
48
+ "optionalDependencies": {
35
49
  "sharp": "^0.34.5",
36
50
  "@huggingface/transformers": "^3.8.0"
51
+ },
52
+ "peerDependencies": {
53
+ "expo-file-system": "*",
54
+ "expo-image-manipulator": "*"
55
+ },
56
+ "peerDependenciesMeta": {
57
+ "expo-file-system": {
58
+ "optional": true
59
+ },
60
+ "expo-image-manipulator": {
61
+ "optional": true
62
+ }
37
63
  }
38
64
  }