@synova-cloud/sdk 1.0.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.
- package/LICENSE +21 -0
- package/README.md +475 -0
- package/dist/index.cjs +630 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +564 -0
- package/dist/index.d.ts +564 -0
- package/dist/index.js +620 -0
- package/dist/index.js.map +1 -0
- package/package.json +76 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Synova Cloud
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
# Synova Cloud SDK for Node.js
|
|
2
|
+
|
|
3
|
+
Official Node.js SDK for the [Synova Cloud](https://synova.cloud) API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @synova-cloud/sdk
|
|
9
|
+
# or
|
|
10
|
+
yarn add @synova-cloud/sdk
|
|
11
|
+
# or
|
|
12
|
+
pnpm add @synova-cloud/sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { SynovaCloudSdk } from '@synova-cloud/sdk';
|
|
19
|
+
|
|
20
|
+
const client = new SynovaCloudSdk('your-api-key');
|
|
21
|
+
|
|
22
|
+
// Execute a prompt
|
|
23
|
+
const response = await client.prompts.execute('prm_abc123', {
|
|
24
|
+
provider: 'openai',
|
|
25
|
+
model: 'gpt-4o',
|
|
26
|
+
variables: { name: 'World' },
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
console.log(response.content);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Configuration
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { SynovaCloudSdk } from '@synova-cloud/sdk';
|
|
36
|
+
|
|
37
|
+
const client = new SynovaCloudSdk('your-api-key', {
|
|
38
|
+
baseUrl: 'https://api.synova.cloud', // Custom API URL
|
|
39
|
+
timeout: 30000, // Request timeout in ms (default: 30000)
|
|
40
|
+
debug: true, // Enable debug logging
|
|
41
|
+
retry: {
|
|
42
|
+
maxRetries: 3, // Max retry attempts (default: 3)
|
|
43
|
+
strategy: 'exponential', // 'exponential' or 'linear' (default: 'exponential')
|
|
44
|
+
initialDelayMs: 1000, // Initial retry delay (default: 1000)
|
|
45
|
+
maxDelayMs: 30000, // Max retry delay (default: 30000)
|
|
46
|
+
backoffMultiplier: 2, // Multiplier for exponential backoff (default: 2)
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## API Reference
|
|
52
|
+
|
|
53
|
+
### Prompts
|
|
54
|
+
|
|
55
|
+
#### Get Prompt
|
|
56
|
+
|
|
57
|
+
Retrieve a prompt template by ID.
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// Get latest version (default)
|
|
61
|
+
const prompt = await client.prompts.get('prm_abc123');
|
|
62
|
+
|
|
63
|
+
// Get by tag
|
|
64
|
+
const prompt = await client.prompts.get('prm_abc123', { tag: 'production' });
|
|
65
|
+
|
|
66
|
+
// Get specific version
|
|
67
|
+
const prompt = await client.prompts.get('prm_abc123', { version: '1.2.0' });
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
#### Execute Prompt
|
|
71
|
+
|
|
72
|
+
Execute a prompt with an LLM provider.
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
const response = await client.prompts.execute('prm_abc123', {
|
|
76
|
+
provider: 'openai', // Required: 'openai', 'anthropic', 'google', etc.
|
|
77
|
+
model: 'gpt-4o', // Required: model ID
|
|
78
|
+
variables: { // Optional: template variables
|
|
79
|
+
userMessage: 'Hello!',
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
console.log(response.content); // LLM response text
|
|
84
|
+
console.log(response.usage); // { inputTokens, outputTokens, totalTokens }
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### Execute with Tag or Version
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// Execute specific tag
|
|
91
|
+
const response = await client.prompts.execute('prm_abc123', {
|
|
92
|
+
provider: 'anthropic',
|
|
93
|
+
model: 'claude-sonnet-4-20250514',
|
|
94
|
+
tag: 'production',
|
|
95
|
+
variables: { topic: 'AI' },
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Execute specific version
|
|
99
|
+
const response = await client.prompts.execute('prm_abc123', {
|
|
100
|
+
provider: 'openai',
|
|
101
|
+
model: 'gpt-4o',
|
|
102
|
+
version: '1.2.0',
|
|
103
|
+
variables: { topic: 'AI' },
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
#### With Model Parameters
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
const response = await client.prompts.execute('prm_abc123', {
|
|
111
|
+
provider: 'openai',
|
|
112
|
+
model: 'gpt-4o',
|
|
113
|
+
variables: { topic: 'TypeScript' },
|
|
114
|
+
parameters: {
|
|
115
|
+
temperature: 0.7,
|
|
116
|
+
maxTokens: 1000,
|
|
117
|
+
topP: 0.9,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### With Metadata (Analytics)
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const response = await client.prompts.execute('prm_abc123', {
|
|
126
|
+
provider: 'openai',
|
|
127
|
+
model: 'gpt-4o',
|
|
128
|
+
variables: { query: 'Hello' },
|
|
129
|
+
metadata: {
|
|
130
|
+
userId: 'user_123',
|
|
131
|
+
sessionId: 'sess_456',
|
|
132
|
+
environment: 'production',
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
#### With Conversation History
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
const response = await client.prompts.execute('prm_chat456', {
|
|
141
|
+
provider: 'anthropic',
|
|
142
|
+
model: 'claude-sonnet-4-20250514',
|
|
143
|
+
variables: { topic: 'TypeScript' },
|
|
144
|
+
messages: [
|
|
145
|
+
{ role: 'user', content: 'What is TypeScript?' },
|
|
146
|
+
{ role: 'assistant', content: 'TypeScript is a typed superset of JavaScript...' },
|
|
147
|
+
{ role: 'user', content: 'How do I use generics?' },
|
|
148
|
+
],
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
#### With Structured Output (JSON Schema)
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
const response = await client.prompts.execute('prm_extract789', {
|
|
156
|
+
provider: 'openai',
|
|
157
|
+
model: 'gpt-4o',
|
|
158
|
+
variables: { text: 'John Doe, age 30, from New York' },
|
|
159
|
+
responseSchema: {
|
|
160
|
+
type: 'object',
|
|
161
|
+
properties: {
|
|
162
|
+
name: { type: 'string' },
|
|
163
|
+
age: { type: 'number' },
|
|
164
|
+
city: { type: 'string' },
|
|
165
|
+
},
|
|
166
|
+
required: ['name', 'age', 'city'],
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Access parsed object directly
|
|
171
|
+
console.log(response.object); // { name: 'John Doe', age: 30, city: 'New York' }
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
#### Image Generation
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
const response = await client.prompts.execute('prm_image123', {
|
|
178
|
+
provider: 'openai',
|
|
179
|
+
model: 'dall-e-3',
|
|
180
|
+
variables: { description: 'A sunset over mountains' },
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
if (response.type === 'image') {
|
|
184
|
+
for (const file of response.files) {
|
|
185
|
+
console.log('Generated image:', file.url);
|
|
186
|
+
console.log('MIME type:', file.mimeType);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
#### With Tool Calls
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
const response = await client.prompts.execute('prm_assistant012', {
|
|
195
|
+
provider: 'openai',
|
|
196
|
+
model: 'gpt-4o',
|
|
197
|
+
variables: { query: 'What is the weather in Tokyo?' },
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
if (response.type === 'tool_calls') {
|
|
201
|
+
for (const toolCall of response.toolCalls) {
|
|
202
|
+
console.log(`Tool: ${toolCall.name}`);
|
|
203
|
+
console.log(`Arguments: ${JSON.stringify(toolCall.arguments)}`);
|
|
204
|
+
|
|
205
|
+
// Execute your tool
|
|
206
|
+
const result = await executeMyTool(toolCall.name, toolCall.arguments);
|
|
207
|
+
|
|
208
|
+
// Continue conversation with tool results
|
|
209
|
+
const finalResponse = await client.prompts.execute('prm_assistant012', {
|
|
210
|
+
provider: 'openai',
|
|
211
|
+
model: 'gpt-4o',
|
|
212
|
+
messages: [
|
|
213
|
+
{ role: 'user', content: 'What is the weather in Tokyo?' },
|
|
214
|
+
{ role: 'assistant', toolCalls: [toolCall] },
|
|
215
|
+
{ role: 'tool', toolResults: [{ toolCallId: toolCall.id, content: JSON.stringify(result) }] },
|
|
216
|
+
],
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
console.log(finalResponse.content);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Models
|
|
225
|
+
|
|
226
|
+
#### List All Models
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
const { providers } = await client.models.list();
|
|
230
|
+
|
|
231
|
+
for (const provider of providers) {
|
|
232
|
+
console.log(`${provider.displayName}:`);
|
|
233
|
+
for (const model of provider.models) {
|
|
234
|
+
console.log(` - ${model.displayName} (${model.id})`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
#### Filter Models
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
// Filter by type
|
|
243
|
+
const textModels = await client.models.list({ type: 'text' });
|
|
244
|
+
const imageModels = await client.models.list({ type: 'image' });
|
|
245
|
+
|
|
246
|
+
// Filter by capability
|
|
247
|
+
const visionModels = await client.models.list({ capability: 'vision' });
|
|
248
|
+
|
|
249
|
+
// Filter by provider
|
|
250
|
+
const openaiModels = await client.models.list({ provider: 'openai' });
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
#### Get Models by Provider
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
const models = await client.models.getByProvider('anthropic');
|
|
257
|
+
|
|
258
|
+
for (const model of models) {
|
|
259
|
+
console.log(`${model.displayName}: context=${model.limits.contextWindow}`);
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
#### Get Specific Model
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
const model = await client.models.get('openai', 'gpt-4o');
|
|
267
|
+
|
|
268
|
+
console.log('Capabilities:', model.capabilities);
|
|
269
|
+
console.log('Context window:', model.limits.contextWindow);
|
|
270
|
+
console.log('Pricing:', model.pricing);
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Files
|
|
274
|
+
|
|
275
|
+
#### Upload Files
|
|
276
|
+
|
|
277
|
+
Upload files for use in prompt execution (e.g., images for vision models).
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
const result = await client.files.upload(
|
|
281
|
+
[file1, file2], // File[] or Blob[]
|
|
282
|
+
{ projectId: 'prj_abc123' }
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
for (const file of result.data) {
|
|
286
|
+
console.log(`Uploaded: ${file.originalName}`);
|
|
287
|
+
console.log(` ID: ${file.id}`);
|
|
288
|
+
console.log(` URL: ${file.url}`);
|
|
289
|
+
console.log(` Size: ${file.size} bytes`);
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
#### Use Uploaded Files in Messages
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
// Upload an image
|
|
297
|
+
const uploadResult = await client.files.upload([imageFile], { projectId: 'prj_abc123' });
|
|
298
|
+
const fileId = uploadResult.data[0].id;
|
|
299
|
+
|
|
300
|
+
// Use in prompt execution with vision model
|
|
301
|
+
const response = await client.prompts.execute('prm_vision123', {
|
|
302
|
+
provider: 'openai',
|
|
303
|
+
model: 'gpt-4o',
|
|
304
|
+
messages: [
|
|
305
|
+
{
|
|
306
|
+
role: 'user',
|
|
307
|
+
content: 'What is in this image?',
|
|
308
|
+
files: [{ fileId }],
|
|
309
|
+
},
|
|
310
|
+
],
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
console.log(response.content);
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Error Handling
|
|
317
|
+
|
|
318
|
+
The SDK provides typed errors for different failure scenarios:
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
import {
|
|
322
|
+
SynovaCloudSdk,
|
|
323
|
+
SynovaError,
|
|
324
|
+
AuthSynovaError,
|
|
325
|
+
NotFoundSynovaError,
|
|
326
|
+
RateLimitSynovaError,
|
|
327
|
+
TimeoutSynovaError,
|
|
328
|
+
NetworkSynovaError,
|
|
329
|
+
ServerSynovaError,
|
|
330
|
+
ApiSynovaError,
|
|
331
|
+
} from '@synova-cloud/sdk';
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
const response = await client.prompts.execute('prm_abc123', {
|
|
335
|
+
provider: 'openai',
|
|
336
|
+
model: 'gpt-4o',
|
|
337
|
+
variables: { name: 'World' },
|
|
338
|
+
});
|
|
339
|
+
} catch (error) {
|
|
340
|
+
if (error instanceof AuthSynovaError) {
|
|
341
|
+
console.error('Invalid API key');
|
|
342
|
+
} else if (error instanceof NotFoundSynovaError) {
|
|
343
|
+
console.error(`Resource not found: ${error.resourceType} ${error.resourceId}`);
|
|
344
|
+
} else if (error instanceof RateLimitSynovaError) {
|
|
345
|
+
console.error(`Rate limited. Retry after: ${error.retryAfterMs}ms`);
|
|
346
|
+
} else if (error instanceof TimeoutSynovaError) {
|
|
347
|
+
console.error(`Request timed out after ${error.timeoutMs}ms`);
|
|
348
|
+
} else if (error instanceof NetworkSynovaError) {
|
|
349
|
+
console.error(`Network error: ${error.message}`);
|
|
350
|
+
} else if (error instanceof ServerSynovaError) {
|
|
351
|
+
console.error(`Server error ${error.httpCode}: ${error.message}`);
|
|
352
|
+
} else if (error instanceof ApiSynovaError) {
|
|
353
|
+
console.error(`API error [${error.code}]: ${error.message}`);
|
|
354
|
+
} else if (error instanceof SynovaError) {
|
|
355
|
+
console.error(`Synova error: ${error.message}`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Retry Behavior
|
|
361
|
+
|
|
362
|
+
The SDK automatically retries requests on:
|
|
363
|
+
- Rate limit errors (429) - uses `Retry-After` header if available
|
|
364
|
+
- Server errors (5xx)
|
|
365
|
+
- Network errors
|
|
366
|
+
- Timeout errors
|
|
367
|
+
|
|
368
|
+
Non-retryable errors (fail immediately):
|
|
369
|
+
- Authentication errors (401)
|
|
370
|
+
- Not found errors (404)
|
|
371
|
+
- Client errors (4xx)
|
|
372
|
+
|
|
373
|
+
### Retry Strategies
|
|
374
|
+
|
|
375
|
+
**Exponential Backoff** (default):
|
|
376
|
+
```
|
|
377
|
+
delay = initialDelayMs * backoffMultiplier^(attempt-1)
|
|
378
|
+
```
|
|
379
|
+
Example with defaults: 1000ms, 2000ms, 4000ms...
|
|
380
|
+
|
|
381
|
+
**Linear**:
|
|
382
|
+
```
|
|
383
|
+
delay = initialDelayMs * attempt
|
|
384
|
+
```
|
|
385
|
+
Example with defaults: 1000ms, 2000ms, 3000ms...
|
|
386
|
+
|
|
387
|
+
Both strategies add ±10% jitter to prevent thundering herd.
|
|
388
|
+
|
|
389
|
+
## Custom Logger
|
|
390
|
+
|
|
391
|
+
You can provide a custom logger that implements the `ISynovaLogger` interface:
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
import { SynovaCloudSdk, ISynovaLogger } from '@synova-cloud/sdk';
|
|
395
|
+
|
|
396
|
+
const customLogger: ISynovaLogger = {
|
|
397
|
+
debug: (message, ...args) => myLogger.debug(message, ...args),
|
|
398
|
+
info: (message, ...args) => myLogger.info(message, ...args),
|
|
399
|
+
warn: (message, ...args) => myLogger.warn(message, ...args),
|
|
400
|
+
error: (messageOrError, ...args) => {
|
|
401
|
+
if (messageOrError instanceof Error) {
|
|
402
|
+
myLogger.error(messageOrError.message, messageOrError, ...args);
|
|
403
|
+
} else {
|
|
404
|
+
myLogger.error(messageOrError, ...args);
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
const client = new SynovaCloudSdk('your-api-key', {
|
|
410
|
+
debug: true,
|
|
411
|
+
logger: customLogger,
|
|
412
|
+
});
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
## TypeScript
|
|
416
|
+
|
|
417
|
+
The SDK is written in TypeScript and provides full type definitions:
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
import type {
|
|
421
|
+
// Config
|
|
422
|
+
ISynovaConfig,
|
|
423
|
+
ISynovaRetryConfig,
|
|
424
|
+
TSynovaRetryStrategy,
|
|
425
|
+
ISynovaLogger,
|
|
426
|
+
// Prompts
|
|
427
|
+
ISynovaPrompt,
|
|
428
|
+
ISynovaPromptVariable,
|
|
429
|
+
ISynovaGetPromptOptions,
|
|
430
|
+
// Execution
|
|
431
|
+
ISynovaExecuteOptions,
|
|
432
|
+
ISynovaExecuteResponse,
|
|
433
|
+
ISynovaUsage,
|
|
434
|
+
ISynovaExecutionError,
|
|
435
|
+
// Messages
|
|
436
|
+
ISynovaMessage,
|
|
437
|
+
ISynovaToolCall,
|
|
438
|
+
ISynovaToolResult,
|
|
439
|
+
TSynovaMessageRole,
|
|
440
|
+
TSynovaResponseType,
|
|
441
|
+
// Files
|
|
442
|
+
ISynovaFileAttachment,
|
|
443
|
+
ISynovaFileThumbnails,
|
|
444
|
+
ISynovaUploadedFile,
|
|
445
|
+
ISynovaUploadResponse,
|
|
446
|
+
ISynovaUploadOptions,
|
|
447
|
+
// Models
|
|
448
|
+
ISynovaModel,
|
|
449
|
+
ISynovaModelCapabilities,
|
|
450
|
+
ISynovaModelLimits,
|
|
451
|
+
ISynovaModelPricing,
|
|
452
|
+
ISynovaProvider,
|
|
453
|
+
ISynovaModelsResponse,
|
|
454
|
+
ISynovaListModelsOptions,
|
|
455
|
+
TSynovaModelType,
|
|
456
|
+
} from '@synova-cloud/sdk';
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
## CommonJS
|
|
460
|
+
|
|
461
|
+
The SDK supports both ESM and CommonJS:
|
|
462
|
+
|
|
463
|
+
```javascript
|
|
464
|
+
const { SynovaCloudSdk } = require('@synova-cloud/sdk');
|
|
465
|
+
|
|
466
|
+
const client = new SynovaCloudSdk('your-api-key');
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
## Requirements
|
|
470
|
+
|
|
471
|
+
- Node.js 18+ (uses native `fetch`)
|
|
472
|
+
|
|
473
|
+
## License
|
|
474
|
+
|
|
475
|
+
MIT
|