@lovable.dev/sdk 0.1.3 → 0.1.5

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/dist/index.js CHANGED
@@ -1,3 +1,16 @@
1
+ // src/types.ts
2
+ var ApiError = class extends Error {
3
+ status;
4
+ type;
5
+ detail;
6
+ constructor(status, message, type, detail) {
7
+ super(message);
8
+ this.status = status;
9
+ this.type = type;
10
+ this.detail = detail;
11
+ }
12
+ };
13
+
1
14
  // src/client.ts
2
15
  var DEFAULT_BASE_URL = "https://api.lovable.dev";
3
16
  function normalizeBaseUrl(url) {
@@ -9,25 +22,36 @@ function normalizeBaseUrl(url) {
9
22
  return normalized;
10
23
  }
11
24
  var LovableClient = class {
12
- apiKey;
25
+ authHeaders;
13
26
  baseUrl;
27
+ extraHeaders;
14
28
  constructor(options) {
15
- if (!options.apiKey) {
16
- throw new Error("API key is required");
29
+ const hasApiKey = !!options.apiKey;
30
+ const hasBearerToken = !!options.bearerToken;
31
+ if (!hasApiKey && !hasBearerToken) {
32
+ throw new Error("Either apiKey or bearerToken is required");
33
+ }
34
+ if (hasApiKey && hasBearerToken) {
35
+ throw new Error("Provide either apiKey or bearerToken, not both");
17
36
  }
18
- this.apiKey = options.apiKey;
37
+ this.authHeaders = hasApiKey ? { "Lovable-API-Key": options.apiKey } : { Authorization: `Bearer ${options.bearerToken}` };
19
38
  this.baseUrl = normalizeBaseUrl(options.baseUrl);
39
+ this.extraHeaders = options.headers ?? {};
20
40
  }
21
- async request(method, path, body) {
41
+ async rawRequest(method, path, body) {
22
42
  const url = `${this.baseUrl}${path}`;
23
43
  const headers = {
24
- "Lovable-API-Key": this.apiKey,
25
- "Content-Type": "application/json"
44
+ ...this.authHeaders,
45
+ ...this.extraHeaders,
46
+ Accept: "application/json"
26
47
  };
48
+ if (body !== void 0) {
49
+ headers["Content-Type"] = "application/json";
50
+ }
27
51
  const response = await fetch(url, {
28
52
  method,
29
53
  headers,
30
- body: body ? JSON.stringify(body) : void 0
54
+ body: body !== void 0 ? JSON.stringify(body) : void 0
31
55
  });
32
56
  if (!response.ok) {
33
57
  let errorBody;
@@ -35,17 +59,22 @@ var LovableClient = class {
35
59
  errorBody = await response.json();
36
60
  } catch {
37
61
  }
38
- const error = new Error(errorBody?.title ?? `HTTP ${response.status}: ${response.statusText}`);
39
- error.status = response.status;
40
- error.type = errorBody?.title;
41
- error.detail = errorBody?.detail;
42
- throw error;
62
+ const message = errorBody?.message ?? errorBody?.title ?? `HTTP ${response.status}: ${response.statusText}`;
63
+ throw new ApiError(response.status, message, errorBody?.title, errorBody?.detail);
43
64
  }
65
+ return response;
66
+ }
67
+ async request(method, path, body) {
68
+ const response = await this.rawRequest(method, path, body);
44
69
  if (response.status === 204) {
45
70
  return void 0;
46
71
  }
47
72
  return response.json();
48
73
  }
74
+ async requestText(method, path) {
75
+ const response = await this.rawRequest(method, path);
76
+ return response.text();
77
+ }
49
78
  /**
50
79
  * Get the current authenticated user and their workspaces.
51
80
  * Useful for validating an API key and discovering workspace IDs.
@@ -68,25 +97,36 @@ var LovableClient = class {
68
97
  return response.workspace;
69
98
  }
70
99
  /**
71
- * List projects in a workspace
100
+ * List projects in a workspace.
101
+ * Supports full-text search, filtering by visibility/publish status/folder/creator,
102
+ * and pagination via offset or cursor.
72
103
  */
73
104
  async listProjects(workspaceId, options) {
74
105
  const params = new URLSearchParams();
75
- if (options?.limit) params.set("limit", options.limit.toString());
76
- if (options?.cursor) params.set("cursor", options.cursor);
106
+ if (options?.query) params.set("q", options.query);
77
107
  if (options?.visibility) params.set("visibility", options.visibility);
108
+ if (options?.publish_status) params.set("publish_status", options.publish_status);
109
+ if (options?.folder_id) params.set("folder_id", options.folder_id);
110
+ if (options?.user_id) params.set("user_id", options.user_id);
111
+ if (options?.sort_by) params.set("sort_by", options.sort_by);
112
+ if (options?.sort_order) params.set("sort_order", options.sort_order);
113
+ if (options?.viewed_by_me) params.set("viewed_by_me", "true");
114
+ if (options?.limit !== void 0) params.set("limit", String(options.limit));
115
+ if (options?.offset !== void 0) params.set("offset", String(options.offset));
116
+ if (options?.cursor) params.set("cursor", options.cursor);
78
117
  const query = params.toString();
79
118
  const path = `/v1/workspaces/${workspaceId}/projects${query ? `?${query}` : ""}`;
80
- const response = await this.request("GET", path);
81
- return response.projects ?? [];
119
+ return this.request("GET", path);
82
120
  }
83
121
  /**
84
122
  * Create a new project in a workspace
85
123
  */
86
124
  async createProject(workspaceId, options) {
87
- let uploadedFiles;
88
- if (options.files?.length) {
89
- uploadedFiles = await this.uploadFiles(options.files);
125
+ let fileRefs;
126
+ if (options.uploadedFiles?.length) {
127
+ fileRefs = options.uploadedFiles;
128
+ } else if (options.files?.length) {
129
+ fileRefs = await this.uploadFiles(options.files);
90
130
  }
91
131
  const body = {
92
132
  description: options.description,
@@ -96,49 +136,53 @@ var LovableClient = class {
96
136
  if (options.techStack) {
97
137
  body.tech_stack = options.techStack;
98
138
  }
99
- const hasFiles = uploadedFiles && uploadedFiles.length > 0;
100
- if (options.initialMessage && !hasFiles) {
139
+ if (options.selectedLibraries?.length) {
140
+ body.selected_libraries = options.selectedLibraries;
141
+ }
142
+ if (options.initialMessage) {
101
143
  body.initial_message = options.initialMessage;
102
144
  }
103
- const project = await this.request("POST", `/v1/workspaces/${workspaceId}/projects`, body);
104
- if (hasFiles && options.initialMessage) {
105
- const chatBody = {
106
- message: options.initialMessage,
107
- files: uploadedFiles
108
- };
109
- await this.request("POST", `/v1/projects/${project.id}/messages`, chatBody);
145
+ if (fileRefs?.length) {
146
+ body.files = fileRefs;
110
147
  }
148
+ const project = await this.request("POST", `/v1/workspaces/${workspaceId}/projects`, body);
111
149
  return project;
112
150
  }
113
151
  /**
114
- * Send a chat message to a project
152
+ * Send a chat message to a project.
115
153
  *
116
- * Note: This sends a message to the project's AI agent. The response is
117
- * asynchronous - the API accepts the message and processes it in the background.
154
+ * The API accepts the message and processes it in the background.
155
+ * Returns the message ID and status. Use `waitForMessageCompletion()`
156
+ * to poll for the AI response, or `waitForResponse()` for SSE streaming.
118
157
  */
119
158
  async chat(projectId, options) {
120
- let uploadedFiles;
121
- if (options.files?.length) {
122
- uploadedFiles = await this.uploadFiles(options.files);
159
+ let fileRefs;
160
+ if (options.uploadedFiles?.length) {
161
+ fileRefs = options.uploadedFiles;
162
+ } else if (options.files?.length) {
163
+ fileRefs = await this.uploadFiles(options.files);
123
164
  }
124
165
  const body = {
125
166
  message: options.message
126
167
  };
127
- if (uploadedFiles) {
128
- body.files = uploadedFiles;
168
+ if (fileRefs) {
169
+ body.files = fileRefs;
129
170
  }
130
- if (options.chatOnly) {
131
- body.chat_only = true;
171
+ if (options.planMode) {
172
+ body.plan_mode = true;
132
173
  }
133
174
  if (options.customModel) {
134
175
  body.custom_model_endpoint = options.customModel.endpoint;
135
176
  body.custom_model_api_key = options.customModel.apiKey;
136
177
  body.custom_model_name = options.customModel.modelName;
137
178
  }
179
+ if (options.customModelDisableRace) {
180
+ body.custom_model_disable_race = true;
181
+ }
138
182
  if (options.continuation) {
139
183
  body.continuation = options.continuation;
140
184
  }
141
- await this.request("POST", `/v1/projects/${projectId}/messages`, body);
185
+ return this.request("POST", `/v1/projects/${projectId}/messages`, body);
142
186
  }
143
187
  /**
144
188
  * Invite a user to a workspace as a collaborator
@@ -240,6 +284,257 @@ var LovableClient = class {
240
284
  async getDatabaseConnectionInfo(projectId) {
241
285
  return this.request("GET", `/v1/projects/${projectId}/database/connection-info`);
242
286
  }
287
+ // ---------------------------------------------------------------------------
288
+ // Messages
289
+ // ---------------------------------------------------------------------------
290
+ /**
291
+ * Get a message by ID. Returns the message content, status, and (for user messages)
292
+ * the AI response if available. Use to poll for completion after `chat()`.
293
+ */
294
+ async getMessage(projectId, messageId) {
295
+ return this.request("GET", `/v1/projects/${projectId}/messages/${messageId}`);
296
+ }
297
+ /**
298
+ * Poll for message completion. Waits until the AI response reaches a terminal
299
+ * status (completed, stopped) or the timeout expires.
300
+ *
301
+ * Handles edge cases: 404 grace period for race conditions between queue
302
+ * dequeue and event creation, and queue pause detection.
303
+ */
304
+ async waitForMessageCompletion(projectId, messageId, options) {
305
+ const pollInterval = options?.pollInterval ?? 3e3;
306
+ const timeout = options?.timeout ?? 6e5;
307
+ const deadline = Date.now() + timeout;
308
+ let notFoundSince = null;
309
+ const notFoundGraceMs = 15e3;
310
+ while (Date.now() < deadline) {
311
+ try {
312
+ const msg = await this.getMessage(projectId, messageId);
313
+ notFoundSince = null;
314
+ if (msg.status === "queued") {
315
+ if (msg.queue_paused && msg.queue_pause_reason !== "hitl_tool") {
316
+ return {
317
+ status: "error",
318
+ message_id: messageId,
319
+ content: "",
320
+ error: `Message is queued (position ${msg.queue_position ?? "unknown"}) but the queue is paused` + (msg.queue_pause_reason ? ` (reason: ${msg.queue_pause_reason})` : "") + `. Unpause the queue in the Lovable editor, or use wait=false to return immediately.`
321
+ };
322
+ }
323
+ await sleep(pollInterval);
324
+ continue;
325
+ }
326
+ const ai = msg.response;
327
+ if (ai) {
328
+ if (ai.status === "completed" || ai.status === "stopped") {
329
+ return {
330
+ status: ai.status === "completed" ? "completed" : "error",
331
+ message_id: ai.message_id,
332
+ content: ai.content,
333
+ edit_id: ai.edit_id,
334
+ commit_sha: ai.commit_sha,
335
+ summary: ai.summary,
336
+ cost_credits: ai.cost_credits
337
+ };
338
+ }
339
+ }
340
+ if (msg.role === "assistant") {
341
+ if (msg.status === "completed" || msg.status === "stopped") {
342
+ return {
343
+ status: msg.status === "completed" ? "completed" : "error",
344
+ message_id: msg.message_id,
345
+ content: msg.content,
346
+ edit_id: msg.edit_id,
347
+ commit_sha: msg.commit_sha,
348
+ summary: msg.summary,
349
+ cost_credits: msg.cost_credits
350
+ };
351
+ }
352
+ }
353
+ await sleep(pollInterval);
354
+ } catch (err) {
355
+ if (err instanceof ApiError && err.status === 404) {
356
+ if (notFoundSince === null) {
357
+ notFoundSince = Date.now();
358
+ }
359
+ if (Date.now() - notFoundSince < notFoundGraceMs) {
360
+ await sleep(pollInterval);
361
+ continue;
362
+ }
363
+ return {
364
+ status: "error",
365
+ message_id: messageId,
366
+ content: "",
367
+ error: "Message not found. It may have been deleted from the queue."
368
+ };
369
+ }
370
+ await sleep(pollInterval);
371
+ }
372
+ }
373
+ return {
374
+ status: "timeout",
375
+ message_id: messageId,
376
+ content: "",
377
+ error: `Agent did not finish within ${timeout / 1e3}s`
378
+ };
379
+ }
380
+ // ---------------------------------------------------------------------------
381
+ // Knowledge
382
+ // ---------------------------------------------------------------------------
383
+ /** Get workspace knowledge (custom instructions for the AI agent). */
384
+ async getWorkspaceKnowledge(workspaceId) {
385
+ return this.request("GET", `/v1/workspaces/${workspaceId}/knowledge`);
386
+ }
387
+ /** Set workspace knowledge. Max 10,000 characters. */
388
+ async setWorkspaceKnowledge(workspaceId, content) {
389
+ return this.request("PUT", `/v1/workspaces/${workspaceId}/knowledge`, { content });
390
+ }
391
+ /** Get project knowledge (custom instructions for the AI agent). */
392
+ async getProjectKnowledge(projectId) {
393
+ return this.request("GET", `/v1/projects/${projectId}/knowledge`);
394
+ }
395
+ /** Set project knowledge. Max 10,000 characters. */
396
+ async setProjectKnowledge(projectId, content) {
397
+ return this.request("PUT", `/v1/projects/${projectId}/knowledge`, { content });
398
+ }
399
+ // ---------------------------------------------------------------------------
400
+ // Git operations
401
+ // ---------------------------------------------------------------------------
402
+ /**
403
+ * Get the structured diff for a message or commit.
404
+ * Pass `messageId` to get the diff for a specific AI message,
405
+ * or `sha` for a specific commit.
406
+ */
407
+ async getDiff(projectId, params) {
408
+ const qs = new URLSearchParams();
409
+ if (params.messageId) qs.set("message_id", params.messageId);
410
+ if (params.sha) qs.set("sha", params.sha);
411
+ if (params.baseSha) qs.set("base_sha", params.baseSha);
412
+ return this.request("GET", `/v1/projects/${projectId}/git/diff?${qs.toString()}`);
413
+ }
414
+ /** List all files in a project at a specific git ref. */
415
+ async listFiles(projectId, ref) {
416
+ const qs = new URLSearchParams({ ref });
417
+ return this.request("GET", `/v1/projects/${projectId}/git/files?${qs.toString()}`);
418
+ }
419
+ /** Read the raw content of a single file at a specific git ref. Returns text. */
420
+ async readFile(projectId, path, ref) {
421
+ const qs = new URLSearchParams({ path, ref });
422
+ return this.requestText("GET", `/v1/projects/${projectId}/git/file?${qs.toString()}`);
423
+ }
424
+ // ---------------------------------------------------------------------------
425
+ // Edits
426
+ // ---------------------------------------------------------------------------
427
+ /** List the edit history of a project. */
428
+ async listEdits(projectId, params) {
429
+ const qs = new URLSearchParams();
430
+ if (params?.limit !== void 0) qs.set("limit", String(params.limit));
431
+ if (params?.before) qs.set("before", params.before);
432
+ const suffix = qs.toString() ? `?${qs.toString()}` : "";
433
+ return this.request("GET", `/v1/projects/${projectId}/edits${suffix}`);
434
+ }
435
+ // ---------------------------------------------------------------------------
436
+ // File upload
437
+ // ---------------------------------------------------------------------------
438
+ /** Get a presigned URL for uploading a file. Returns the upload URL and file ID. */
439
+ async getFileUploadUrl(params) {
440
+ return this.request("POST", "/v1/files/upload-url", params);
441
+ }
442
+ // ---------------------------------------------------------------------------
443
+ // Visibility
444
+ // ---------------------------------------------------------------------------
445
+ /** Set a project's visibility (draft, private, or public). */
446
+ async setProjectVisibility(projectId, visibility) {
447
+ return this.request("PUT", `/v1/projects/${projectId}/visibility`, { visibility });
448
+ }
449
+ /** Set a folder's visibility (personal or workspace). */
450
+ async setFolderVisibility(workspaceId, folderId, visibility) {
451
+ return this.request("PUT", `/v1/workspaces/${workspaceId}/folders/${folderId}/visibility`, {
452
+ visibility
453
+ });
454
+ }
455
+ // ---------------------------------------------------------------------------
456
+ // Library & template projects
457
+ // ---------------------------------------------------------------------------
458
+ /** List available design system library projects in a workspace. */
459
+ async listLibraryProjects(workspaceId) {
460
+ return this.request(
461
+ "GET",
462
+ `/v1/workspaces/${workspaceId}/available-library-projects`
463
+ );
464
+ }
465
+ /** List available template projects in a workspace. */
466
+ async listTemplateProjects(workspaceId) {
467
+ return this.request(
468
+ "GET",
469
+ `/v1/workspaces/${workspaceId}/available-template-projects`
470
+ );
471
+ }
472
+ // ---------------------------------------------------------------------------
473
+ // MCP servers
474
+ // ---------------------------------------------------------------------------
475
+ /** List all MCP servers connected to a workspace. */
476
+ async listMCPServers(workspaceId) {
477
+ return this.request("GET", `/v1/workspaces/${workspaceId}/mcp-servers`);
478
+ }
479
+ /** Add an MCP server to a workspace. The server URL is tested before saving. */
480
+ async addMCPServer(workspaceId, body) {
481
+ return this.request("POST", `/v1/workspaces/${workspaceId}/mcp-servers`, body);
482
+ }
483
+ /** Remove an MCP server from a workspace. */
484
+ async removeMCPServer(workspaceId, serverId) {
485
+ return this.request("DELETE", `/v1/workspaces/${workspaceId}/mcp-servers/${serverId}`);
486
+ }
487
+ /** Browse available MCP server templates (catalog). */
488
+ async listMCPCatalog(workspaceId) {
489
+ return this.request("GET", `/v1/workspaces/${workspaceId}/mcp-catalog`);
490
+ }
491
+ // ---------------------------------------------------------------------------
492
+ // Connectors
493
+ // ---------------------------------------------------------------------------
494
+ /** List standard (OAuth-based) connectors in a workspace. */
495
+ async listStandardConnectors(workspaceId) {
496
+ return this.request(
497
+ "GET",
498
+ `/v1/workspaces/${workspaceId}/connectors/standard`
499
+ );
500
+ }
501
+ /** List seamless (zero-config) connectors in a workspace. */
502
+ async listSeamlessConnectors(workspaceId) {
503
+ return this.request(
504
+ "GET",
505
+ `/v1/workspaces/${workspaceId}/connectors/seamless`
506
+ );
507
+ }
508
+ /** List MCP connectors in a workspace. */
509
+ async listMCPConnectors(workspaceId) {
510
+ return this.request(
511
+ "GET",
512
+ `/v1/workspaces/${workspaceId}/connectors/mcp`
513
+ );
514
+ }
515
+ /** List authenticated connections (accounts) in a workspace. */
516
+ async listConnections(workspaceId, params) {
517
+ const qs = new URLSearchParams();
518
+ if (params?.connector_id) qs.set("connector_id", params.connector_id);
519
+ const suffix = qs.toString() ? `?${qs.toString()}` : "";
520
+ return this.request("GET", `/v1/workspaces/${workspaceId}/connections${suffix}`);
521
+ }
522
+ // ---------------------------------------------------------------------------
523
+ // Analytics
524
+ // ---------------------------------------------------------------------------
525
+ /** Get historical analytics for a published project. */
526
+ async getProjectAnalytics(projectId, params) {
527
+ const qs = new URLSearchParams({
528
+ startDate: params.startDate,
529
+ endDate: params.endDate
530
+ });
531
+ if (params.granularity) qs.set("granularity", params.granularity);
532
+ return this.request("GET", `/v1/projects/${projectId}/analytics?${qs.toString()}`);
533
+ }
534
+ /** Get real-time visitor trend for a published project. */
535
+ async getProjectAnalyticsTrend(projectId) {
536
+ return this.request("GET", `/v1/projects/${projectId}/analytics/trend`);
537
+ }
243
538
  /**
244
539
  * Publish a project.
245
540
  *
@@ -277,6 +572,7 @@ var LovableClient = class {
277
572
  workspace_id: options.workspaceId,
278
573
  include_history: options.includeHistory,
279
574
  include_custom_knowledge: options.includeCustomKnowledge,
575
+ project_name: options.projectName,
280
576
  skip_initial_remix_message: options.skipInitialRemixMessage,
281
577
  skip_integrations: options.skipIntegrations,
282
578
  include_agent_state: options.includeAgentState
@@ -386,14 +682,13 @@ var LovableClient = class {
386
682
  try {
387
683
  const response = await fetch(url, {
388
684
  headers: {
389
- "Lovable-API-Key": this.apiKey
685
+ ...this.authHeaders,
686
+ ...this.extraHeaders
390
687
  },
391
688
  signal: controller.signal
392
689
  });
393
690
  if (!response.ok) {
394
- const error = new Error(`Failed to connect to message stream: HTTP ${response.status}`);
395
- error.status = response.status;
396
- throw error;
691
+ throw new ApiError(response.status, `Failed to connect to message stream: HTTP ${response.status}`);
397
692
  }
398
693
  if (!response.body) {
399
694
  throw new Error("Response body is not readable");
@@ -606,6 +901,7 @@ function sleep(ms) {
606
901
  return new Promise((resolve) => setTimeout(resolve, ms));
607
902
  }
608
903
  export {
904
+ ApiError,
609
905
  LovableClient
610
906
  };
611
907
  //# sourceMappingURL=index.js.map