@intlpullhq/cli 0.1.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,995 @@
1
+ import {
2
+ getAuthErrorMessage,
3
+ getGlobalConfig,
4
+ getProjectConfig,
5
+ getResolvedApiKey
6
+ } from "./chunk-2T7ERBDS.js";
7
+
8
+ // src/lib/api/base.ts
9
+ var DEFAULT_API_URL = process.env.INTLPULL_API_URL || "https://api.intlpull.com";
10
+ var DEFAULT_TIMEOUT_MS = 3e4;
11
+ var BaseApiClient = class {
12
+ baseUrl;
13
+ apiKey;
14
+ timeout;
15
+ constructor() {
16
+ const globalConfig = getGlobalConfig();
17
+ this.baseUrl = globalConfig.apiUrl || DEFAULT_API_URL;
18
+ const resolved = getResolvedApiKey();
19
+ this.apiKey = resolved?.key || null;
20
+ this.timeout = DEFAULT_TIMEOUT_MS;
21
+ }
22
+ async request(endpoint, options = {}) {
23
+ if (!this.apiKey) {
24
+ throw new Error(getAuthErrorMessage());
25
+ }
26
+ const url = `${this.baseUrl}${endpoint}`;
27
+ const headers = {
28
+ "Content-Type": "application/json",
29
+ "X-API-Key": this.apiKey,
30
+ ...options.headers || {}
31
+ };
32
+ const controller = new AbortController();
33
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
34
+ let response;
35
+ try {
36
+ response = await fetch(url, {
37
+ ...options,
38
+ headers,
39
+ signal: controller.signal
40
+ });
41
+ } catch (err) {
42
+ clearTimeout(timeoutId);
43
+ if (err instanceof Error && err.name === "AbortError") {
44
+ throw new Error(`Request timeout: Server did not respond within ${this.timeout / 1e3}s`);
45
+ }
46
+ if (err instanceof TypeError) {
47
+ throw new Error(`Network error: Unable to connect to ${this.baseUrl}. Check your internet connection.`);
48
+ }
49
+ throw new Error(`Request failed: ${err instanceof Error ? err.message : "Unknown network error"}`);
50
+ } finally {
51
+ clearTimeout(timeoutId);
52
+ }
53
+ if (!response.ok) {
54
+ const error = await response.json().catch(() => ({ error: `Request failed: ${response.status}` }));
55
+ throw new Error(error.error || `Request failed: ${response.status}`);
56
+ }
57
+ const text = await response.text();
58
+ if (!text) {
59
+ return {};
60
+ }
61
+ try {
62
+ return JSON.parse(text);
63
+ } catch {
64
+ throw new Error(`Invalid JSON response from API`);
65
+ }
66
+ }
67
+ async requestBlob(endpoint) {
68
+ if (!this.apiKey) {
69
+ throw new Error(getAuthErrorMessage());
70
+ }
71
+ const url = `${this.baseUrl}${endpoint}`;
72
+ const controller = new AbortController();
73
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
74
+ let response;
75
+ try {
76
+ response = await fetch(url, {
77
+ headers: {
78
+ "X-API-Key": this.apiKey
79
+ },
80
+ signal: controller.signal
81
+ });
82
+ } catch (err) {
83
+ clearTimeout(timeoutId);
84
+ if (err instanceof Error && err.name === "AbortError") {
85
+ throw new Error(`Request timeout: Server did not respond within ${this.timeout / 1e3}s`);
86
+ }
87
+ if (err instanceof TypeError) {
88
+ throw new Error(`Network error: Unable to connect to ${this.baseUrl}. Check your internet connection.`);
89
+ }
90
+ throw new Error(`Request failed: ${err instanceof Error ? err.message : "Unknown network error"}`);
91
+ } finally {
92
+ clearTimeout(timeoutId);
93
+ }
94
+ if (!response.ok) {
95
+ const error = await response.json().catch(() => ({ error: "Request failed" }));
96
+ throw new Error(error.error || "Request failed");
97
+ }
98
+ return response.blob();
99
+ }
100
+ async requestBlobWithJsonFallback(endpoint) {
101
+ if (!this.apiKey) {
102
+ throw new Error(getAuthErrorMessage());
103
+ }
104
+ const url = `${this.baseUrl}${endpoint}`;
105
+ const controller = new AbortController();
106
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
107
+ let response;
108
+ try {
109
+ response = await fetch(url, {
110
+ headers: {
111
+ "X-API-Key": this.apiKey
112
+ },
113
+ signal: controller.signal
114
+ });
115
+ } catch (err) {
116
+ clearTimeout(timeoutId);
117
+ if (err instanceof Error && err.name === "AbortError") {
118
+ throw new Error(`Request timeout: Server did not respond within ${this.timeout / 1e3}s`);
119
+ }
120
+ if (err instanceof TypeError) {
121
+ throw new Error(`Network error: Unable to connect to ${this.baseUrl}. Check your internet connection.`);
122
+ }
123
+ throw new Error(`Request failed: ${err instanceof Error ? err.message : "Unknown network error"}`);
124
+ } finally {
125
+ clearTimeout(timeoutId);
126
+ }
127
+ if (!response.ok) {
128
+ const error = await response.json().catch(() => ({ error: "Request failed" }));
129
+ throw new Error(error.error || "Request failed");
130
+ }
131
+ const contentType = response.headers.get("content-type") || "";
132
+ if (contentType.includes("application/json")) {
133
+ const jsonData = await response.json();
134
+ const jsonString = JSON.stringify(jsonData, null, 2);
135
+ return new Blob([jsonString], { type: "application/json" });
136
+ }
137
+ return response.blob();
138
+ }
139
+ };
140
+
141
+ // src/lib/api/projects.ts
142
+ var ProjectsApi = class extends BaseApiClient {
143
+ async listProjects() {
144
+ return this.request("/api/v1/projects");
145
+ }
146
+ async getProject(projectId) {
147
+ return this.request(`/api/v1/projects/${projectId}`);
148
+ }
149
+ async createProject(data) {
150
+ return this.request("/api/v1/projects", {
151
+ method: "POST",
152
+ body: JSON.stringify(data)
153
+ });
154
+ }
155
+ /**
156
+ * Find a project by name (searches through all projects)
157
+ */
158
+ async findProjectByName(name) {
159
+ const { projects } = await this.listProjects();
160
+ const normalizedName = name.toLowerCase().trim();
161
+ return projects.find(
162
+ (p) => p.name.toLowerCase() === normalizedName || p.slug.toLowerCase() === normalizedName
163
+ ) || null;
164
+ }
165
+ /**
166
+ * Get project languages/settings
167
+ */
168
+ async getProjectLanguages(projectId) {
169
+ return this.request(`/api/v1/projects/${projectId}/language-settings`);
170
+ }
171
+ /**
172
+ * Add multiple languages to a project with optional skip_translation flag
173
+ */
174
+ async addLanguagesBulk(projectId, languages, skipTranslation = false) {
175
+ return this.request(`/api/v1/projects/${projectId}/language-settings/bulk`, {
176
+ method: "POST",
177
+ body: JSON.stringify({
178
+ languages,
179
+ skip_translation: skipTranslation
180
+ })
181
+ });
182
+ }
183
+ // Versions
184
+ async listVersions(projectId) {
185
+ return this.request(`/api/v1/projects/${projectId}/versions`);
186
+ }
187
+ async createVersion(projectId, data) {
188
+ return this.request(`/api/v1/projects/${projectId}/versions`, {
189
+ method: "POST",
190
+ body: JSON.stringify(data)
191
+ });
192
+ }
193
+ async downloadVersion(projectId, version) {
194
+ return this.requestBlob(`/api/v1/projects/${projectId}/versions/${version}/download`);
195
+ }
196
+ };
197
+
198
+ // src/lib/api/translations.ts
199
+ var TranslationsApi = class extends BaseApiClient {
200
+ // Keys - uses import endpoint to create/update keys
201
+ async pushKeys(projectId, keys, language = "en", namespace, options) {
202
+ const content = {};
203
+ for (const k of keys) {
204
+ content[k.key] = k.value;
205
+ }
206
+ const fileName = namespace ? `${namespace}.json` : "push.json";
207
+ const result = await this.request(`/api/v1/projects/${projectId}/import`, {
208
+ method: "POST",
209
+ body: JSON.stringify({
210
+ content: JSON.stringify(content),
211
+ file_name: fileName,
212
+ language,
213
+ options: {
214
+ namespace_name: namespace || "common",
215
+ update_existing: true,
216
+ branch_id: options?.branchId,
217
+ platforms: options?.platforms
218
+ }
219
+ })
220
+ });
221
+ return {
222
+ keys_inserted: result.keys_inserted,
223
+ keys_updated: result.keys_updated,
224
+ keys_skipped: result.keys_skipped,
225
+ translations_inserted: result.translations_inserted,
226
+ translations_updated: result.translations_updated
227
+ };
228
+ }
229
+ async getKeys(projectId, namespace) {
230
+ const params = namespace ? `?namespace=${namespace}` : "";
231
+ return this.request(`/api/v1/projects/${projectId}/keys${params}`);
232
+ }
233
+ // Translations - uses export/download endpoint
234
+ async pullTranslations(projectId, options) {
235
+ const params = new URLSearchParams();
236
+ params.set("format", "json");
237
+ if (options.languages?.length) {
238
+ options.languages.forEach((lang) => params.append("languages", lang));
239
+ }
240
+ if (options.branch) {
241
+ params.set("branch", options.branch);
242
+ }
243
+ if (options.platform) {
244
+ params.set("platform", options.platform);
245
+ }
246
+ return this.request(`/api/v1/projects/${projectId}/export/download?${params.toString()}`);
247
+ }
248
+ // Glossary
249
+ async searchGlossary(query, options) {
250
+ const params = new URLSearchParams();
251
+ params.set("q", query);
252
+ if (options?.limit) params.set("limit", String(options.limit));
253
+ if (options?.language) params.set("language", options.language);
254
+ return this.request(`/api/v1/glossary/search?${params.toString()}`);
255
+ }
256
+ async addGlossaryTerm(data) {
257
+ const config = getProjectConfig();
258
+ if (!config?.projectId) {
259
+ throw new Error("No project configured. Run `npx @intlpullhq/cli init` first.");
260
+ }
261
+ return this.request(`/api/v1/projects/${config.projectId}/glossary`, {
262
+ method: "POST",
263
+ body: JSON.stringify(data)
264
+ });
265
+ }
266
+ async exportGlossary(glossaryId) {
267
+ const config = getProjectConfig();
268
+ if (!config?.projectId) {
269
+ throw new Error("No project configured. Run `npx @intlpullhq/cli init` first.");
270
+ }
271
+ const params = glossaryId ? `?glossary_id=${glossaryId}` : "";
272
+ return this.request(`/api/v1/projects/${config.projectId}/glossary/export${params}`);
273
+ }
274
+ // Translation Memory
275
+ async searchTM(data) {
276
+ const config = getProjectConfig();
277
+ if (!config?.projectId) {
278
+ throw new Error("No project configured. Run `npx @intlpullhq/cli init` first.");
279
+ }
280
+ return this.request(`/api/v1/projects/${config.projectId}/memory/search`, {
281
+ method: "POST",
282
+ body: JSON.stringify(data)
283
+ });
284
+ }
285
+ async addTMEntry(data) {
286
+ const config = getProjectConfig();
287
+ if (!config?.projectId) {
288
+ throw new Error("No project configured. Run `npx @intlpullhq/cli init` first.");
289
+ }
290
+ return this.request(`/api/v1/projects/${config.projectId}/memory`, {
291
+ method: "POST",
292
+ body: JSON.stringify(data)
293
+ });
294
+ }
295
+ async getTMStats() {
296
+ const config = getProjectConfig();
297
+ if (!config?.projectId) {
298
+ throw new Error("No project configured. Run `npx @intlpullhq/cli init` first.");
299
+ }
300
+ return this.request(`/api/v1/projects/${config.projectId}/memory/stats`);
301
+ }
302
+ };
303
+
304
+ // src/lib/api/import-export.ts
305
+ var ImportExportApi = class extends BaseApiClient {
306
+ // Export
307
+ async exportTranslations(projectId, options) {
308
+ const params = new URLSearchParams();
309
+ params.set("format", options.format);
310
+ if (options.languages?.length) {
311
+ params.set("languages", options.languages.join(","));
312
+ }
313
+ if (options.namespaces?.length) {
314
+ params.set("namespaces", options.namespaces.join(","));
315
+ }
316
+ if (options.branch) {
317
+ params.set("branch", options.branch);
318
+ }
319
+ if (options.platform) {
320
+ params.set("platform", options.platform);
321
+ }
322
+ return this.requestBlobWithJsonFallback(
323
+ `/api/v1/projects/${projectId}/export/download?${params.toString()}`
324
+ );
325
+ }
326
+ /**
327
+ * Import translations from file content
328
+ */
329
+ async importTranslations(projectId, content, fileName, language, options) {
330
+ return this.request(`/api/v1/projects/${projectId}/import`, {
331
+ method: "POST",
332
+ body: JSON.stringify({
333
+ content,
334
+ file_name: fileName,
335
+ language,
336
+ options: options || {}
337
+ })
338
+ });
339
+ }
340
+ };
341
+
342
+ // src/lib/api/workflows.ts
343
+ var WorkflowsApi = class extends BaseApiClient {
344
+ /**
345
+ * Get workflow settings for a project
346
+ */
347
+ async getWorkflowSettings(projectId) {
348
+ return this.request(`/api/v1/projects/${projectId}/workflows/settings`);
349
+ }
350
+ /**
351
+ * List all workflows for a project
352
+ */
353
+ async listWorkflows(projectId) {
354
+ return this.request(`/api/v1/projects/${projectId}/workflows`);
355
+ }
356
+ /**
357
+ * Get pending approvals for a project
358
+ */
359
+ async getPendingApprovals(projectId) {
360
+ return this.request(`/api/v1/projects/${projectId}/workflows/pending`);
361
+ }
362
+ /**
363
+ * Get approval history for a translation
364
+ */
365
+ async getApprovalHistory(projectId, translationId) {
366
+ return this.request(`/api/v1/projects/${projectId}/translations/${translationId}/approvals`);
367
+ }
368
+ /**
369
+ * Approve a translation at the current stage
370
+ */
371
+ async approveTranslation(projectId, translationId, comment) {
372
+ return this.request(`/api/v1/projects/${projectId}/translations/${translationId}/approve`, {
373
+ method: "POST",
374
+ body: JSON.stringify({ comment })
375
+ });
376
+ }
377
+ /**
378
+ * Reject a translation with feedback
379
+ */
380
+ async rejectTranslation(projectId, translationId, reason) {
381
+ return this.request(`/api/v1/projects/${projectId}/translations/${translationId}/reject`, {
382
+ method: "POST",
383
+ body: JSON.stringify({ reason })
384
+ });
385
+ }
386
+ /**
387
+ * Get lock status for a translation
388
+ */
389
+ async getLockStatus(projectId, translationId) {
390
+ return this.request(`/api/v1/projects/${projectId}/translations/${translationId}/lock`);
391
+ }
392
+ /**
393
+ * Lock a translation
394
+ */
395
+ async lockTranslation(projectId, translationId, reason) {
396
+ return this.request(`/api/v1/projects/${projectId}/translations/${translationId}/lock`, {
397
+ method: "POST",
398
+ body: JSON.stringify({ reason })
399
+ });
400
+ }
401
+ /**
402
+ * Unlock a translation
403
+ */
404
+ async unlockTranslation(projectId, translationId) {
405
+ return this.request(`/api/v1/projects/${projectId}/translations/${translationId}/lock`, {
406
+ method: "DELETE"
407
+ });
408
+ }
409
+ /**
410
+ * Get all locked translations for a project
411
+ */
412
+ async getLockedTranslations(projectId) {
413
+ return this.request(`/api/v1/projects/${projectId}/workflows/locked`);
414
+ }
415
+ };
416
+
417
+ // src/lib/api/api-keys.ts
418
+ var ApiKeysApi = class extends BaseApiClient {
419
+ /**
420
+ * List organization-level API keys (not project-scoped)
421
+ */
422
+ async listAPIKeys() {
423
+ return this.request("/api/v1/api-keys");
424
+ }
425
+ /**
426
+ * List all API keys (organization-level + project-scoped)
427
+ */
428
+ async listAllAPIKeys() {
429
+ return this.request("/api/v1/api-keys/all");
430
+ }
431
+ /**
432
+ * List API keys for a specific project
433
+ */
434
+ async listProjectAPIKeys(projectId) {
435
+ return this.request(`/api/v1/projects/${projectId}/api-keys`);
436
+ }
437
+ /**
438
+ * Create an organization-level API key
439
+ * If project_id is provided, creates a project-scoped key instead
440
+ */
441
+ async createAPIKey(options) {
442
+ if (options.project_id) {
443
+ return this.request(`/api/v1/projects/${options.project_id}/api-keys`, {
444
+ method: "POST",
445
+ body: JSON.stringify({
446
+ name: options.name,
447
+ description: options.description,
448
+ expires_in_days: options.expires_in_days
449
+ })
450
+ });
451
+ }
452
+ return this.request("/api/v1/api-keys", {
453
+ method: "POST",
454
+ body: JSON.stringify(options)
455
+ });
456
+ }
457
+ /**
458
+ * Create a project-scoped API key
459
+ */
460
+ async createProjectAPIKey(projectId, options) {
461
+ return this.request(`/api/v1/projects/${projectId}/api-keys`, {
462
+ method: "POST",
463
+ body: JSON.stringify(options)
464
+ });
465
+ }
466
+ /**
467
+ * Revoke an API key
468
+ */
469
+ async revokeAPIKey(keyId) {
470
+ return this.request(`/api/v1/api-keys/${keyId}`, {
471
+ method: "DELETE"
472
+ });
473
+ }
474
+ /**
475
+ * Revoke a project-scoped API key
476
+ */
477
+ async revokeProjectAPIKey(projectId, keyId) {
478
+ return this.request(`/api/v1/projects/${projectId}/api-keys/${keyId}`, {
479
+ method: "DELETE"
480
+ });
481
+ }
482
+ };
483
+
484
+ // src/lib/api/emails.ts
485
+ var EmailsApi = class extends BaseApiClient {
486
+ /**
487
+ * List email templates for a project
488
+ */
489
+ async listEmailTemplates(projectId) {
490
+ return this.request(`/api/v1/projects/${projectId}/email-templates`);
491
+ }
492
+ /**
493
+ * Get a single email template by slug with content
494
+ */
495
+ async getEmailTemplateBySlug(projectId, slug, language) {
496
+ const url = language ? `/api/v1/projects/${projectId}/email-templates/slug/${slug}?lang=${language}` : `/api/v1/projects/${projectId}/email-templates/slug/${slug}`;
497
+ const response = await this.request(url);
498
+ return response.template;
499
+ }
500
+ /**
501
+ * Get a single email template by ID with content
502
+ */
503
+ async getEmailTemplate(projectId, templateId, language) {
504
+ const url = language ? `/api/v1/projects/${projectId}/email-templates/${templateId}?lang=${language}` : `/api/v1/projects/${projectId}/email-templates/${templateId}`;
505
+ const response = await this.request(url);
506
+ return response.template;
507
+ }
508
+ /**
509
+ * Get HTML content for a specific language
510
+ */
511
+ async getHtmlContent(projectId, templateId, language) {
512
+ return this.request(
513
+ `/api/v1/projects/${projectId}/email-templates/${templateId}/content/${language}`
514
+ );
515
+ }
516
+ /**
517
+ * Create a new email template
518
+ */
519
+ async createEmailTemplate(projectId, data) {
520
+ return this.request(`/api/v1/projects/${projectId}/email-templates`, {
521
+ method: "POST",
522
+ body: JSON.stringify(data)
523
+ });
524
+ }
525
+ /**
526
+ * Update an email template
527
+ */
528
+ async updateEmailTemplate(projectId, templateId, data) {
529
+ return this.request(`/api/v1/projects/${projectId}/email-templates/${templateId}`, {
530
+ method: "PUT",
531
+ body: JSON.stringify(data)
532
+ });
533
+ }
534
+ /**
535
+ * Update HTML properties (source HTML/CSS)
536
+ */
537
+ async updateHtmlProperties(projectId, templateId, properties) {
538
+ const response = await this.request(
539
+ `/api/v1/projects/${projectId}/email-templates/${templateId}/html/properties`,
540
+ {
541
+ method: "PATCH",
542
+ body: JSON.stringify({ html_properties: properties })
543
+ }
544
+ );
545
+ return response.template;
546
+ }
547
+ /**
548
+ * Set HTML translation for a specific language
549
+ */
550
+ async setHtmlTranslation(projectId, templateId, data) {
551
+ return this.request(
552
+ `/api/v1/projects/${projectId}/email-templates/${templateId}/html/translation`,
553
+ {
554
+ method: "PUT",
555
+ body: JSON.stringify(data)
556
+ }
557
+ );
558
+ }
559
+ /**
560
+ * Delete an email template
561
+ */
562
+ async deleteEmailTemplate(projectId, templateId) {
563
+ return this.request(`/api/v1/projects/${projectId}/email-templates/${templateId}`, {
564
+ method: "DELETE"
565
+ });
566
+ }
567
+ /**
568
+ * Publish an email template
569
+ */
570
+ async publishEmailTemplate(projectId, templateId, notes) {
571
+ const response = await this.request(
572
+ `/api/v1/projects/${projectId}/email-templates/${templateId}/publish`,
573
+ {
574
+ method: "POST",
575
+ body: JSON.stringify({ notes })
576
+ }
577
+ );
578
+ return response.template;
579
+ }
580
+ /**
581
+ * Render an email template with variables
582
+ */
583
+ async renderEmailTemplate(projectId, slug, language, variables) {
584
+ return this.request(`/api/v1/projects/${projectId}/email-templates/${slug}/render`, {
585
+ method: "POST",
586
+ body: JSON.stringify({ language, variables })
587
+ });
588
+ }
589
+ /**
590
+ * Get email template GitHub integration status
591
+ */
592
+ async getEmailGitHubIntegration(projectId) {
593
+ return this.request(`/api/v1/projects/${projectId}/email-templates/github`);
594
+ }
595
+ /**
596
+ * Sync email templates with GitHub (pull or push)
597
+ */
598
+ async syncEmailTemplates(projectId, options) {
599
+ return this.request(`/api/v1/projects/${projectId}/email-templates/github/sync`, {
600
+ method: "POST",
601
+ body: JSON.stringify(options)
602
+ });
603
+ }
604
+ /**
605
+ * Get email template sync logs
606
+ */
607
+ async getEmailSyncLogs(projectId) {
608
+ return this.request(`/api/v1/projects/${projectId}/email-templates/github/logs`);
609
+ }
610
+ };
611
+
612
+ // src/lib/api/billing.ts
613
+ var PLAN_LIMITS = {
614
+ free: {
615
+ strings: 500,
616
+ teamMembers: 30,
617
+ price: 0,
618
+ otaEnabled: false,
619
+ branchesEnabled: false,
620
+ ssoEnabled: false
621
+ },
622
+ starter: {
623
+ strings: 5e3,
624
+ teamMembers: 30,
625
+ price: 12,
626
+ otaEnabled: false,
627
+ branchesEnabled: false,
628
+ ssoEnabled: false
629
+ },
630
+ growth: {
631
+ strings: 15e3,
632
+ teamMembers: 30,
633
+ price: 49,
634
+ otaEnabled: false,
635
+ branchesEnabled: true,
636
+ ssoEnabled: false
637
+ },
638
+ business: {
639
+ strings: 5e4,
640
+ teamMembers: 30,
641
+ price: 149,
642
+ otaEnabled: true,
643
+ branchesEnabled: true,
644
+ ssoEnabled: false
645
+ },
646
+ pro: {
647
+ strings: 1e5,
648
+ teamMembers: 30,
649
+ price: 399,
650
+ otaEnabled: true,
651
+ branchesEnabled: true,
652
+ ssoEnabled: true
653
+ },
654
+ professional: {
655
+ // Legacy alias
656
+ strings: 1e5,
657
+ teamMembers: 30,
658
+ price: 399,
659
+ otaEnabled: true,
660
+ branchesEnabled: true,
661
+ ssoEnabled: true
662
+ },
663
+ enterprise: {
664
+ strings: -1,
665
+ // unlimited
666
+ teamMembers: -1,
667
+ price: -1,
668
+ otaEnabled: true,
669
+ branchesEnabled: true,
670
+ ssoEnabled: true
671
+ }
672
+ };
673
+ var BillingApi = class extends BaseApiClient {
674
+ /**
675
+ * Get current billing info including plan and usage
676
+ */
677
+ async getBillingInfo() {
678
+ return this.request("/api/v1/billing");
679
+ }
680
+ /**
681
+ * Get plan limits for a given plan name
682
+ */
683
+ getPlanLimits(planName) {
684
+ return PLAN_LIMITS[planName.toLowerCase()] || PLAN_LIMITS.free;
685
+ }
686
+ /**
687
+ * Check if uploading a certain number of strings would exceed the plan limit
688
+ * This counts unique source language keys only (translations don't count toward limit)
689
+ */
690
+ async checkUsageLimit(requestedStrings) {
691
+ const billing = await this.getBillingInfo();
692
+ const limits = this.getPlanLimits(billing.plan.name);
693
+ const isUnlimited = limits.strings < 0;
694
+ const currentStrings = billing.usage.strings_used;
695
+ const maxStrings = limits.strings;
696
+ if (isUnlimited) {
697
+ return {
698
+ allowed: true,
699
+ plan: billing.plan.name,
700
+ current_strings: currentStrings,
701
+ max_strings: -1,
702
+ requested_strings: requestedStrings,
703
+ would_exceed_by: 0,
704
+ is_unlimited: true
705
+ };
706
+ }
707
+ const projectedTotal = Math.max(currentStrings, requestedStrings);
708
+ const wouldExceedBy = projectedTotal > maxStrings ? projectedTotal - maxStrings : 0;
709
+ return {
710
+ allowed: projectedTotal <= maxStrings,
711
+ plan: billing.plan.name,
712
+ current_strings: currentStrings,
713
+ max_strings: maxStrings,
714
+ requested_strings: requestedStrings,
715
+ would_exceed_by: wouldExceedBy,
716
+ is_unlimited: false
717
+ };
718
+ }
719
+ /**
720
+ * Format a number for display (with commas)
721
+ */
722
+ formatNumber(num) {
723
+ if (num < 0) return "Unlimited";
724
+ return num.toLocaleString();
725
+ }
726
+ };
727
+
728
+ // src/lib/api/documents.ts
729
+ var DocumentsApi = class extends BaseApiClient {
730
+ async listDocuments(projectId) {
731
+ return this.request(`/api/v1/projects/${projectId}/documents`);
732
+ }
733
+ async getDocument(projectId, documentId) {
734
+ return this.request(`/api/v1/projects/${projectId}/documents/${documentId}`);
735
+ }
736
+ async uploadDocument(projectId, file, filename, sourceLang, targetLangs) {
737
+ if (!this.apiKey) {
738
+ throw new Error(getAuthErrorMessage());
739
+ }
740
+ const formData = new FormData();
741
+ formData.append("file", file, filename);
742
+ if (sourceLang) formData.append("source_language", sourceLang);
743
+ if (targetLangs && targetLangs.length > 0) {
744
+ formData.append("target_languages", targetLangs.join(","));
745
+ }
746
+ const url = `${this.baseUrl}/api/v1/projects/${projectId}/documents`;
747
+ const controller = new AbortController();
748
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
749
+ try {
750
+ const response = await fetch(url, {
751
+ method: "POST",
752
+ headers: {
753
+ "X-API-Key": this.apiKey
754
+ // Do NOT set Content-Type, let browser/node set boundary
755
+ },
756
+ body: formData,
757
+ signal: controller.signal
758
+ });
759
+ clearTimeout(timeoutId);
760
+ if (!response.ok) {
761
+ const error = await response.json().catch(() => ({ error: `Request failed: ${response.status}` }));
762
+ throw new Error(error.error || `Request failed: ${response.status}`);
763
+ }
764
+ return response.json();
765
+ } catch (err) {
766
+ clearTimeout(timeoutId);
767
+ throw new Error(`Upload failed: ${err instanceof Error ? err.message : "Unknown error"}`);
768
+ }
769
+ }
770
+ async downloadDocument(projectId, documentId, language) {
771
+ const blob = await this.requestBlob(`/api/v1/projects/${projectId}/documents/${documentId}/export?language=${language}`);
772
+ return blob.arrayBuffer();
773
+ }
774
+ };
775
+
776
+ // src/lib/api/index.ts
777
+ var ApiClient = class {
778
+ projects;
779
+ translations;
780
+ importExport;
781
+ workflows;
782
+ apiKeys;
783
+ emails;
784
+ billing;
785
+ documents;
786
+ constructor() {
787
+ this.projects = new ProjectsApi();
788
+ this.translations = new TranslationsApi();
789
+ this.importExport = new ImportExportApi();
790
+ this.workflows = new WorkflowsApi();
791
+ this.apiKeys = new ApiKeysApi();
792
+ this.emails = new EmailsApi();
793
+ this.emails = new EmailsApi();
794
+ this.billing = new BillingApi();
795
+ this.documents = new DocumentsApi();
796
+ }
797
+ // Documents API
798
+ listDocuments(projectId) {
799
+ return this.documents.listDocuments(projectId);
800
+ }
801
+ getDocument(projectId, documentId) {
802
+ return this.documents.getDocument(projectId, documentId);
803
+ }
804
+ // Helper type for file upload since passing Blob from CLI can be tricky (Node.js doesn't have File/Blob in older versions, but we target Node 18+)
805
+ uploadDocument(projectId, file, filename, sourceLang, targetLangs) {
806
+ return this.documents.uploadDocument(projectId, file, filename, sourceLang, targetLangs);
807
+ }
808
+ downloadDocument(projectId, documentId, language) {
809
+ return this.documents.downloadDocument(projectId, documentId, language);
810
+ }
811
+ // Projects API
812
+ listProjects() {
813
+ return this.projects.listProjects();
814
+ }
815
+ getProject(projectId) {
816
+ return this.projects.getProject(projectId);
817
+ }
818
+ createProject(data) {
819
+ return this.projects.createProject(data);
820
+ }
821
+ findProjectByName(name) {
822
+ return this.projects.findProjectByName(name);
823
+ }
824
+ getProjectLanguages(projectId) {
825
+ return this.projects.getProjectLanguages(projectId);
826
+ }
827
+ addLanguagesBulk(projectId, languages, skipTranslation = false) {
828
+ return this.projects.addLanguagesBulk(projectId, languages, skipTranslation);
829
+ }
830
+ listVersions(projectId) {
831
+ return this.projects.listVersions(projectId);
832
+ }
833
+ createVersion(projectId, data) {
834
+ return this.projects.createVersion(projectId, data);
835
+ }
836
+ downloadVersion(projectId, version) {
837
+ return this.projects.downloadVersion(projectId, version);
838
+ }
839
+ // Translations API
840
+ pushKeys(projectId, keys, language, namespace, options) {
841
+ return this.translations.pushKeys(projectId, keys, language, namespace, options);
842
+ }
843
+ getKeys(projectId, namespace) {
844
+ return this.translations.getKeys(projectId, namespace);
845
+ }
846
+ pullTranslations(projectId, options) {
847
+ return this.translations.pullTranslations(projectId, options);
848
+ }
849
+ searchGlossary(query, options) {
850
+ return this.translations.searchGlossary(query, options);
851
+ }
852
+ addGlossaryTerm(data) {
853
+ return this.translations.addGlossaryTerm(data);
854
+ }
855
+ exportGlossary(glossaryId) {
856
+ return this.translations.exportGlossary(glossaryId);
857
+ }
858
+ searchTM(data) {
859
+ return this.translations.searchTM(data);
860
+ }
861
+ addTMEntry(data) {
862
+ return this.translations.addTMEntry(data);
863
+ }
864
+ getTMStats() {
865
+ return this.translations.getTMStats();
866
+ }
867
+ // Import/Export API
868
+ exportTranslations(projectId, options) {
869
+ return this.importExport.exportTranslations(projectId, options);
870
+ }
871
+ importTranslations(projectId, content, fileName, language, options) {
872
+ return this.importExport.importTranslations(projectId, content, fileName, language, options);
873
+ }
874
+ // Workflows API
875
+ getWorkflowSettings(projectId) {
876
+ return this.workflows.getWorkflowSettings(projectId);
877
+ }
878
+ listWorkflows(projectId) {
879
+ return this.workflows.listWorkflows(projectId);
880
+ }
881
+ getPendingApprovals(projectId) {
882
+ return this.workflows.getPendingApprovals(projectId);
883
+ }
884
+ getApprovalHistory(projectId, translationId) {
885
+ return this.workflows.getApprovalHistory(projectId, translationId);
886
+ }
887
+ approveTranslation(projectId, translationId, comment) {
888
+ return this.workflows.approveTranslation(projectId, translationId, comment);
889
+ }
890
+ rejectTranslation(projectId, translationId, reason) {
891
+ return this.workflows.rejectTranslation(projectId, translationId, reason);
892
+ }
893
+ getLockStatus(projectId, translationId) {
894
+ return this.workflows.getLockStatus(projectId, translationId);
895
+ }
896
+ lockTranslation(projectId, translationId, reason) {
897
+ return this.workflows.lockTranslation(projectId, translationId, reason);
898
+ }
899
+ unlockTranslation(projectId, translationId) {
900
+ return this.workflows.unlockTranslation(projectId, translationId);
901
+ }
902
+ getLockedTranslations(projectId) {
903
+ return this.workflows.getLockedTranslations(projectId);
904
+ }
905
+ // API Keys API
906
+ listAPIKeys() {
907
+ return this.apiKeys.listAPIKeys();
908
+ }
909
+ listAllAPIKeys() {
910
+ return this.apiKeys.listAllAPIKeys();
911
+ }
912
+ listProjectAPIKeys(projectId) {
913
+ return this.apiKeys.listProjectAPIKeys(projectId);
914
+ }
915
+ createAPIKey(options) {
916
+ return this.apiKeys.createAPIKey(options);
917
+ }
918
+ createProjectAPIKey(projectId, options) {
919
+ return this.apiKeys.createProjectAPIKey(projectId, options);
920
+ }
921
+ revokeAPIKey(keyId) {
922
+ return this.apiKeys.revokeAPIKey(keyId);
923
+ }
924
+ revokeProjectAPIKey(projectId, keyId) {
925
+ return this.apiKeys.revokeProjectAPIKey(projectId, keyId);
926
+ }
927
+ // Emails API
928
+ listEmailTemplates(projectId) {
929
+ return this.emails.listEmailTemplates(projectId);
930
+ }
931
+ getEmailTemplateBySlug(projectId, slug, language) {
932
+ return this.emails.getEmailTemplateBySlug(projectId, slug, language);
933
+ }
934
+ getEmailTemplate(projectId, templateId, language) {
935
+ return this.emails.getEmailTemplate(projectId, templateId, language);
936
+ }
937
+ getHtmlContent(projectId, templateId, language) {
938
+ return this.emails.getHtmlContent(projectId, templateId, language);
939
+ }
940
+ createEmailTemplate(projectId, data) {
941
+ return this.emails.createEmailTemplate(projectId, data);
942
+ }
943
+ updateEmailTemplate(projectId, templateId, data) {
944
+ return this.emails.updateEmailTemplate(projectId, templateId, data);
945
+ }
946
+ updateHtmlProperties(projectId, templateId, properties) {
947
+ return this.emails.updateHtmlProperties(projectId, templateId, properties);
948
+ }
949
+ setHtmlTranslation(projectId, templateId, data) {
950
+ return this.emails.setHtmlTranslation(projectId, templateId, data);
951
+ }
952
+ deleteEmailTemplate(projectId, templateId) {
953
+ return this.emails.deleteEmailTemplate(projectId, templateId);
954
+ }
955
+ publishEmailTemplate(projectId, templateId, notes) {
956
+ return this.emails.publishEmailTemplate(projectId, templateId, notes);
957
+ }
958
+ renderEmailTemplate(projectId, slug, language, variables) {
959
+ return this.emails.renderEmailTemplate(projectId, slug, language, variables);
960
+ }
961
+ getEmailGitHubIntegration(projectId) {
962
+ return this.emails.getEmailGitHubIntegration(projectId);
963
+ }
964
+ syncEmailTemplates(projectId, options) {
965
+ return this.emails.syncEmailTemplates(projectId, options);
966
+ }
967
+ getEmailSyncLogs(projectId) {
968
+ return this.emails.getEmailSyncLogs(projectId);
969
+ }
970
+ // Billing API
971
+ getBillingInfo() {
972
+ return this.billing.getBillingInfo();
973
+ }
974
+ checkUsageLimit(requestedStrings) {
975
+ return this.billing.checkUsageLimit(requestedStrings);
976
+ }
977
+ };
978
+ function createApiClient() {
979
+ return new ApiClient();
980
+ }
981
+
982
+ export {
983
+ BaseApiClient,
984
+ ProjectsApi,
985
+ TranslationsApi,
986
+ ImportExportApi,
987
+ WorkflowsApi,
988
+ ApiKeysApi,
989
+ EmailsApi,
990
+ PLAN_LIMITS,
991
+ BillingApi,
992
+ DocumentsApi,
993
+ ApiClient,
994
+ createApiClient
995
+ };