langctl 0.1.0 → 0.1.2

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,396 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { isAuthenticated, getApiKey } from '../auth.js';
4
+ const EDGE_FUNCTION_URL = 'https://bcgnmvkgkbhbxzzflwdb.supabase.co/functions/v1/manage-translation-keys';
5
+ const LIST_PROJECTS_URL = 'https://bcgnmvkgkbhbxzzflwdb.supabase.co/functions/v1/list-projects';
6
+ /**
7
+ * Get project by slug
8
+ */
9
+ async function getProjectBySlug(apiKey, slug) {
10
+ try {
11
+ const response = await fetch(LIST_PROJECTS_URL, {
12
+ method: 'POST',
13
+ headers: {
14
+ 'Content-Type': 'application/json',
15
+ 'X-API-Key': apiKey
16
+ }
17
+ });
18
+ if (!response.ok)
19
+ return null;
20
+ const data = await response.json();
21
+ if (!data.success || !data.projects)
22
+ return null;
23
+ return data.projects.find((p) => p.slug === slug) || null;
24
+ }
25
+ catch (error) {
26
+ return null;
27
+ }
28
+ }
29
+ /**
30
+ * List translation keys
31
+ */
32
+ export async function listKeysCommand(projectSlug, options) {
33
+ if (!isAuthenticated()) {
34
+ console.log(chalk.red('āœ— Not authenticated. Please run "langctl auth <api-key>" first.\n'));
35
+ return;
36
+ }
37
+ const apiKey = getApiKey();
38
+ const spinner = ora('Fetching keys...').start();
39
+ try {
40
+ const project = await getProjectBySlug(apiKey, projectSlug);
41
+ if (!project) {
42
+ spinner.fail(chalk.red(`Project "${projectSlug}" not found`));
43
+ return;
44
+ }
45
+ const response = await fetch(EDGE_FUNCTION_URL, {
46
+ method: 'POST',
47
+ headers: {
48
+ 'Content-Type': 'application/json',
49
+ 'X-API-Key': apiKey
50
+ },
51
+ body: JSON.stringify({
52
+ action: 'list',
53
+ projectId: project.id,
54
+ module: options.module,
55
+ published: options.published,
56
+ search: options.search,
57
+ limit: options.limit || 100,
58
+ offset: options.offset || 0
59
+ })
60
+ });
61
+ const data = await response.json();
62
+ if (!data.success) {
63
+ throw new Error(data.error);
64
+ }
65
+ spinner.stop();
66
+ if (!data.keys || data.keys.length === 0) {
67
+ console.log(chalk.yellow('\nNo keys found\n'));
68
+ return;
69
+ }
70
+ console.log(chalk.blue.bold(`\nšŸ”‘ Translation Keys (${data.keys.length}${data.total ? ` of ${data.total}` : ''})\n`));
71
+ data.keys.forEach((key) => {
72
+ console.log(chalk.white.bold(key.key));
73
+ if (key.description) {
74
+ console.log(chalk.gray(` Description: ${key.description}`));
75
+ }
76
+ if (key.module) {
77
+ console.log(chalk.gray(` Module: ${key.module}`));
78
+ }
79
+ console.log(chalk.gray(` Published: ${key.published ? chalk.green('Yes') : chalk.yellow('No')}`));
80
+ const langs = Object.keys(key.translations);
81
+ if (langs.length > 0) {
82
+ console.log(chalk.gray(` Languages: ${langs.join(', ')}`));
83
+ }
84
+ console.log('');
85
+ });
86
+ }
87
+ catch (error) {
88
+ spinner.fail(chalk.red('Failed to list keys'));
89
+ console.error(chalk.red(`Error: ${error.message}\n`));
90
+ }
91
+ }
92
+ /**
93
+ * Get single key
94
+ */
95
+ export async function getKeyCommand(projectSlug, keyName) {
96
+ if (!isAuthenticated()) {
97
+ console.log(chalk.red('āœ— Not authenticated. Please run "langctl auth <api-key>" first.\n'));
98
+ return;
99
+ }
100
+ const apiKey = getApiKey();
101
+ const spinner = ora('Fetching key...').start();
102
+ try {
103
+ const project = await getProjectBySlug(apiKey, projectSlug);
104
+ if (!project) {
105
+ spinner.fail(chalk.red(`Project "${projectSlug}" not found`));
106
+ return;
107
+ }
108
+ // First list to find the key ID
109
+ const listResponse = await fetch(EDGE_FUNCTION_URL, {
110
+ method: 'POST',
111
+ headers: {
112
+ 'Content-Type': 'application/json',
113
+ 'X-API-Key': apiKey
114
+ },
115
+ body: JSON.stringify({
116
+ action: 'list',
117
+ projectId: project.id,
118
+ search: keyName,
119
+ limit: 1
120
+ })
121
+ });
122
+ const listData = await listResponse.json();
123
+ if (!listData.success || !listData.keys || listData.keys.length === 0) {
124
+ spinner.fail(chalk.red(`Key "${keyName}" not found`));
125
+ return;
126
+ }
127
+ const key = listData.keys.find((k) => k.key === keyName);
128
+ if (!key) {
129
+ spinner.fail(chalk.red(`Key "${keyName}" not found`));
130
+ return;
131
+ }
132
+ spinner.stop();
133
+ console.log(chalk.blue.bold('\nšŸ”‘ Translation Key Details\n'));
134
+ console.log(chalk.white.bold(`Key: ${key.key}`));
135
+ console.log(chalk.gray(`ID: ${key.id}`));
136
+ if (key.description) {
137
+ console.log(chalk.gray(`Description: ${key.description}`));
138
+ }
139
+ if (key.module) {
140
+ console.log(chalk.gray(`Module: ${key.module}`));
141
+ }
142
+ console.log(chalk.gray(`Published: ${key.published ? chalk.green('Yes') : chalk.yellow('No')}`));
143
+ console.log(chalk.blue.bold('\nTranslations:'));
144
+ Object.entries(key.translations).forEach(([lang, value]) => {
145
+ console.log(chalk.white(` ${lang}: ${value}`));
146
+ });
147
+ console.log('');
148
+ }
149
+ catch (error) {
150
+ spinner.fail(chalk.red('Failed to get key'));
151
+ console.error(chalk.red(`Error: ${error.message}\n`));
152
+ }
153
+ }
154
+ /**
155
+ * Create translation key
156
+ */
157
+ export async function createKeyCommand(projectSlug, keyName, options) {
158
+ if (!isAuthenticated()) {
159
+ console.log(chalk.red('āœ— Not authenticated. Please run "langctl auth <api-key>" first.\n'));
160
+ return;
161
+ }
162
+ const apiKey = getApiKey();
163
+ const spinner = ora('Creating key...').start();
164
+ try {
165
+ const project = await getProjectBySlug(apiKey, projectSlug);
166
+ if (!project) {
167
+ spinner.fail(chalk.red(`Project "${projectSlug}" not found`));
168
+ return;
169
+ }
170
+ // Build translations object from --value-* options
171
+ const translations = {};
172
+ Object.keys(options).forEach(opt => {
173
+ if (opt.startsWith('value')) {
174
+ const lang = opt.replace('value', '').toLowerCase();
175
+ if (lang) {
176
+ translations[lang] = options[opt];
177
+ }
178
+ }
179
+ });
180
+ const response = await fetch(EDGE_FUNCTION_URL, {
181
+ method: 'POST',
182
+ headers: {
183
+ 'Content-Type': 'application/json',
184
+ 'X-API-Key': apiKey
185
+ },
186
+ body: JSON.stringify({
187
+ action: 'create',
188
+ projectId: project.id,
189
+ key: keyName,
190
+ translations,
191
+ description: options.description,
192
+ module: options.module,
193
+ tags: options.tags ? options.tags.split(',') : []
194
+ })
195
+ });
196
+ const data = await response.json();
197
+ if (!data.success) {
198
+ throw new Error(data.error);
199
+ }
200
+ spinner.succeed(chalk.green(`Created key: ${keyName}`));
201
+ console.log('');
202
+ }
203
+ catch (error) {
204
+ spinner.fail(chalk.red('Failed to create key'));
205
+ console.error(chalk.red(`Error: ${error.message}\n`));
206
+ }
207
+ }
208
+ /**
209
+ * Delete translation key
210
+ */
211
+ export async function deleteKeyCommand(projectSlug, keyName) {
212
+ if (!isAuthenticated()) {
213
+ console.log(chalk.red('āœ— Not authenticated. Please run "langctl auth <api-key>" first.\n'));
214
+ return;
215
+ }
216
+ const apiKey = getApiKey();
217
+ const spinner = ora('Deleting key...').start();
218
+ try {
219
+ const project = await getProjectBySlug(apiKey, projectSlug);
220
+ if (!project) {
221
+ spinner.fail(chalk.red(`Project "${projectSlug}" not found`));
222
+ return;
223
+ }
224
+ // Find key ID
225
+ const listResponse = await fetch(EDGE_FUNCTION_URL, {
226
+ method: 'POST',
227
+ headers: {
228
+ 'Content-Type': 'application/json',
229
+ 'X-API-Key': apiKey
230
+ },
231
+ body: JSON.stringify({
232
+ action: 'list',
233
+ projectId: project.id,
234
+ search: keyName,
235
+ limit: 1
236
+ })
237
+ });
238
+ const listData = await listResponse.json();
239
+ const key = listData.keys?.find((k) => k.key === keyName);
240
+ if (!key) {
241
+ spinner.fail(chalk.red(`Key "${keyName}" not found`));
242
+ return;
243
+ }
244
+ // Delete key
245
+ const response = await fetch(EDGE_FUNCTION_URL, {
246
+ method: 'POST',
247
+ headers: {
248
+ 'Content-Type': 'application/json',
249
+ 'X-API-Key': apiKey
250
+ },
251
+ body: JSON.stringify({
252
+ action: 'delete',
253
+ projectId: project.id,
254
+ keyId: key.id
255
+ })
256
+ });
257
+ const data = await response.json();
258
+ if (!data.success) {
259
+ throw new Error(data.error);
260
+ }
261
+ spinner.succeed(chalk.green(`Deleted key: ${keyName}`));
262
+ console.log('');
263
+ }
264
+ catch (error) {
265
+ spinner.fail(chalk.red('Failed to delete key'));
266
+ console.error(chalk.red(`Error: ${error.message}\n`));
267
+ }
268
+ }
269
+ /**
270
+ * Update single translation
271
+ */
272
+ export async function translateKeyCommand(projectSlug, keyName, options) {
273
+ if (!isAuthenticated()) {
274
+ console.log(chalk.red('āœ— Not authenticated. Please run "langctl auth <api-key>" first.\n'));
275
+ return;
276
+ }
277
+ const apiKey = getApiKey();
278
+ const spinner = ora('Updating translation...').start();
279
+ try {
280
+ const project = await getProjectBySlug(apiKey, projectSlug);
281
+ if (!project) {
282
+ spinner.fail(chalk.red(`Project "${projectSlug}" not found`));
283
+ return;
284
+ }
285
+ // Find key ID
286
+ const listResponse = await fetch(EDGE_FUNCTION_URL, {
287
+ method: 'POST',
288
+ headers: {
289
+ 'Content-Type': 'application/json',
290
+ 'X-API-Key': apiKey
291
+ },
292
+ body: JSON.stringify({
293
+ action: 'list',
294
+ projectId: project.id,
295
+ search: keyName,
296
+ limit: 1
297
+ })
298
+ });
299
+ const listData = await listResponse.json();
300
+ const key = listData.keys?.find((k) => k.key === keyName);
301
+ if (!key) {
302
+ spinner.fail(chalk.red(`Key "${keyName}" not found`));
303
+ return;
304
+ }
305
+ // Update translation
306
+ const response = await fetch(EDGE_FUNCTION_URL, {
307
+ method: 'POST',
308
+ headers: {
309
+ 'Content-Type': 'application/json',
310
+ 'X-API-Key': apiKey
311
+ },
312
+ body: JSON.stringify({
313
+ action: 'translate',
314
+ projectId: project.id,
315
+ keyId: key.id,
316
+ language: options.language,
317
+ value: options.value
318
+ })
319
+ });
320
+ const data = await response.json();
321
+ if (!data.success) {
322
+ throw new Error(data.error);
323
+ }
324
+ spinner.succeed(chalk.green(`Updated ${options.language} translation for ${keyName}`));
325
+ console.log('');
326
+ }
327
+ catch (error) {
328
+ spinner.fail(chalk.red('Failed to update translation'));
329
+ console.error(chalk.red(`Error: ${error.message}\n`));
330
+ }
331
+ }
332
+ /**
333
+ * Publish/unpublish keys
334
+ */
335
+ export async function publishKeysCommand(projectSlug, keys, options) {
336
+ if (!isAuthenticated()) {
337
+ console.log(chalk.red('āœ— Not authenticated. Please run "langctl auth <api-key>" first.\n'));
338
+ return;
339
+ }
340
+ const apiKey = getApiKey();
341
+ const action = options.unpublish ? 'unpublish' : 'publish';
342
+ const spinner = ora(`${action === 'publish' ? 'Publishing' : 'Unpublishing'} keys...`).start();
343
+ try {
344
+ const project = await getProjectBySlug(apiKey, projectSlug);
345
+ if (!project) {
346
+ spinner.fail(chalk.red(`Project "${projectSlug}" not found`));
347
+ return;
348
+ }
349
+ // Get key IDs
350
+ const listResponse = await fetch(EDGE_FUNCTION_URL, {
351
+ method: 'POST',
352
+ headers: {
353
+ 'Content-Type': 'application/json',
354
+ 'X-API-Key': apiKey
355
+ },
356
+ body: JSON.stringify({
357
+ action: 'list',
358
+ projectId: project.id,
359
+ limit: 1000
360
+ })
361
+ });
362
+ const listData = await listResponse.json();
363
+ const keyIds = listData.keys
364
+ ?.filter((k) => keys.includes(k.key))
365
+ .map((k) => k.id) || [];
366
+ if (keyIds.length === 0) {
367
+ spinner.fail(chalk.red('No matching keys found'));
368
+ return;
369
+ }
370
+ // Publish/unpublish keys
371
+ const response = await fetch(EDGE_FUNCTION_URL, {
372
+ method: 'POST',
373
+ headers: {
374
+ 'Content-Type': 'application/json',
375
+ 'X-API-Key': apiKey
376
+ },
377
+ body: JSON.stringify({
378
+ action: 'publish',
379
+ projectId: project.id,
380
+ keyIds,
381
+ published: !options.unpublish
382
+ })
383
+ });
384
+ const data = await response.json();
385
+ if (!data.success) {
386
+ throw new Error(data.error);
387
+ }
388
+ spinner.succeed(chalk.green(data.message));
389
+ console.log('');
390
+ }
391
+ catch (error) {
392
+ spinner.fail(chalk.red(`Failed to ${action} keys`));
393
+ console.error(chalk.red(`Error: ${error.message}\n`));
394
+ }
395
+ }
396
+ //# sourceMappingURL=keys.js.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Get organization information
3
+ */
4
+ export declare function orgInfoCommand(): Promise<void>;
5
+ /**
6
+ * Get organization statistics
7
+ */
8
+ export declare function orgStatsCommand(): Promise<void>;
9
+ /**
10
+ * Get organization plan and limits
11
+ */
12
+ export declare function orgPlanCommand(): Promise<void>;
13
+ //# sourceMappingURL=org.d.ts.map
@@ -0,0 +1,147 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { isAuthenticated, getApiKey } from '../auth.js';
4
+ const ORG_INFO_URL = 'https://bcgnmvkgkbhbxzzflwdb.supabase.co/functions/v1/get-organization-info';
5
+ /**
6
+ * Get organization information
7
+ */
8
+ export async function orgInfoCommand() {
9
+ if (!isAuthenticated()) {
10
+ console.log(chalk.red('āœ— Not authenticated. Please run "langctl auth <api-key>" first.\n'));
11
+ return;
12
+ }
13
+ const apiKey = getApiKey();
14
+ const spinner = ora('Fetching organization info...').start();
15
+ try {
16
+ const response = await fetch(ORG_INFO_URL, {
17
+ method: 'POST',
18
+ headers: {
19
+ 'Content-Type': 'application/json',
20
+ 'X-API-Key': apiKey
21
+ },
22
+ body: JSON.stringify({
23
+ action: 'info'
24
+ })
25
+ });
26
+ const data = await response.json();
27
+ if (!data.success) {
28
+ throw new Error(data.error);
29
+ }
30
+ spinner.stop();
31
+ const org = data.organization;
32
+ console.log(chalk.blue.bold('\nšŸ¢ Organization Information\n'));
33
+ console.log(chalk.white.bold(org.name));
34
+ console.log(chalk.gray(`ID: ${org.id}`));
35
+ console.log(chalk.gray(`Slug: ${org.slug}`));
36
+ if (org.description) {
37
+ console.log(chalk.gray(`Description: ${org.description}`));
38
+ }
39
+ console.log(chalk.gray(`Plan: ${org.plan}`));
40
+ console.log(chalk.gray(`Created: ${new Date(org.created_at).toLocaleDateString()}`));
41
+ console.log('');
42
+ }
43
+ catch (error) {
44
+ spinner.fail(chalk.red('Failed to fetch organization info'));
45
+ console.error(chalk.red(`Error: ${error.message}\n`));
46
+ }
47
+ }
48
+ /**
49
+ * Get organization statistics
50
+ */
51
+ export async function orgStatsCommand() {
52
+ if (!isAuthenticated()) {
53
+ console.log(chalk.red('āœ— Not authenticated. Please run "langctl auth <api-key>" first.\n'));
54
+ return;
55
+ }
56
+ const apiKey = getApiKey();
57
+ const spinner = ora('Fetching organization statistics...').start();
58
+ try {
59
+ const response = await fetch(ORG_INFO_URL, {
60
+ method: 'POST',
61
+ headers: {
62
+ 'Content-Type': 'application/json',
63
+ 'X-API-Key': apiKey
64
+ },
65
+ body: JSON.stringify({
66
+ action: 'stats'
67
+ })
68
+ });
69
+ const data = await response.json();
70
+ if (!data.success) {
71
+ throw new Error(data.error);
72
+ }
73
+ spinner.stop();
74
+ const stats = data.stats;
75
+ console.log(chalk.blue.bold('\nšŸ“Š Organization Statistics\n'));
76
+ console.log(chalk.white.bold('Team'));
77
+ console.log(chalk.gray(` Members: ${stats.members}`));
78
+ console.log('');
79
+ console.log(chalk.white.bold('Projects'));
80
+ console.log(chalk.gray(` Total Projects: ${stats.projects}`));
81
+ console.log('');
82
+ console.log(chalk.white.bold('Translation Keys'));
83
+ console.log(chalk.gray(` Total Keys: ${stats.total_keys}`));
84
+ console.log(chalk.green(` Published: ${stats.published_keys}`));
85
+ console.log(chalk.yellow(` Unpublished: ${stats.unpublished_keys}`));
86
+ console.log('');
87
+ console.log(chalk.white.bold('Languages'));
88
+ console.log(chalk.gray(` Total Languages: ${stats.languages}`));
89
+ if (stats.language_codes && stats.language_codes.length > 0) {
90
+ console.log(chalk.gray(` Codes: ${stats.language_codes.join(', ')}`));
91
+ }
92
+ console.log('');
93
+ console.log(chalk.white.bold('Resources'));
94
+ console.log(chalk.gray(` API Keys: ${stats.api_keys}`));
95
+ console.log(chalk.gray(` Webhooks: ${stats.webhooks}`));
96
+ console.log('');
97
+ }
98
+ catch (error) {
99
+ spinner.fail(chalk.red('Failed to fetch statistics'));
100
+ console.error(chalk.red(`Error: ${error.message}\n`));
101
+ }
102
+ }
103
+ /**
104
+ * Get organization plan and limits
105
+ */
106
+ export async function orgPlanCommand() {
107
+ if (!isAuthenticated()) {
108
+ console.log(chalk.red('āœ— Not authenticated. Please run "langctl auth <api-key>" first.\n'));
109
+ return;
110
+ }
111
+ const apiKey = getApiKey();
112
+ const spinner = ora('Fetching plan information...').start();
113
+ try {
114
+ const response = await fetch(ORG_INFO_URL, {
115
+ method: 'POST',
116
+ headers: {
117
+ 'Content-Type': 'application/json',
118
+ 'X-API-Key': apiKey
119
+ },
120
+ body: JSON.stringify({
121
+ action: 'plan'
122
+ })
123
+ });
124
+ const data = await response.json();
125
+ if (!data.success) {
126
+ throw new Error(data.error);
127
+ }
128
+ spinner.stop();
129
+ const limits = data.limits;
130
+ console.log(chalk.blue.bold('\nšŸ’Ž Subscription Plan\n'));
131
+ console.log(chalk.white.bold(`Current Plan: ${data.plan.toUpperCase()}`));
132
+ console.log('');
133
+ console.log(chalk.white.bold('Plan Limits'));
134
+ // Display limits with "Unlimited" for -1 or null values
135
+ const formatLimit = (value) => (value === -1 || value === null) ? chalk.green('Unlimited') : chalk.gray(value.toString());
136
+ console.log(chalk.gray(` Max Members: ${formatLimit(limits.max_members)}`));
137
+ console.log(chalk.gray(` Max Projects: ${formatLimit(limits.max_projects)}`));
138
+ console.log(chalk.gray(` Max Keys per Project: ${formatLimit(limits.max_keys_per_project)}`));
139
+ console.log(chalk.gray(` Max API Keys: ${formatLimit(limits.max_api_keys)}`));
140
+ console.log('');
141
+ }
142
+ catch (error) {
143
+ spinner.fail(chalk.red('Failed to fetch plan information'));
144
+ console.error(chalk.red(`Error: ${error.message}\n`));
145
+ }
146
+ }
147
+ //# sourceMappingURL=org.js.map
@@ -1,2 +1,30 @@
1
1
  export declare function projectsListCommand(): Promise<void>;
2
+ /**
3
+ * Create new project
4
+ */
5
+ export declare function projectsCreateCommand(name: string, options: any): Promise<void>;
6
+ /**
7
+ * Get project details
8
+ */
9
+ export declare function projectsGetCommand(slug: string): Promise<void>;
10
+ /**
11
+ * Update project
12
+ */
13
+ export declare function projectsUpdateCommand(slug: string, options: any): Promise<void>;
14
+ /**
15
+ * Delete project
16
+ */
17
+ export declare function projectsDeleteCommand(slug: string): Promise<void>;
18
+ /**
19
+ * Add language to project
20
+ */
21
+ export declare function projectsAddLanguageCommand(slug: string, language: string): Promise<void>;
22
+ /**
23
+ * Remove language from project
24
+ */
25
+ export declare function projectsRemoveLanguageCommand(slug: string, language: string): Promise<void>;
26
+ /**
27
+ * Get project statistics
28
+ */
29
+ export declare function projectsStatsCommand(slug: string): Promise<void>;
2
30
  //# sourceMappingURL=projects.d.ts.map