langctl 0.1.1 → 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,285 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { isAuthenticated, getApiKey } from '../auth.js';
4
+ const TEAM_MANAGEMENT_URL = 'https://bcgnmvkgkbhbxzzflwdb.supabase.co/functions/v1/manage-team-members';
5
+ /**
6
+ * List all team members
7
+ */
8
+ export async function listTeamCommand() {
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 team members...').start();
15
+ try {
16
+ const response = await fetch(TEAM_MANAGEMENT_URL, {
17
+ method: 'POST',
18
+ headers: {
19
+ 'Content-Type': 'application/json',
20
+ 'X-API-Key': apiKey
21
+ },
22
+ body: JSON.stringify({
23
+ action: 'list'
24
+ })
25
+ });
26
+ const data = await response.json();
27
+ if (!data.success) {
28
+ throw new Error(data.error);
29
+ }
30
+ spinner.stop();
31
+ if (!data.members || data.members.length === 0) {
32
+ console.log(chalk.yellow('\nNo team members found\n'));
33
+ return;
34
+ }
35
+ console.log(chalk.blue.bold(`\nšŸ‘„ Team Members (${data.members.length})\n`));
36
+ data.members.forEach((member) => {
37
+ console.log(chalk.white.bold(member.email || member.user_id));
38
+ console.log(chalk.gray(` Role: ${member.role}`));
39
+ if (member.joined_at) {
40
+ console.log(chalk.gray(` Joined: ${new Date(member.joined_at).toLocaleDateString()}`));
41
+ }
42
+ console.log('');
43
+ });
44
+ }
45
+ catch (error) {
46
+ spinner.fail(chalk.red('Failed to list team members'));
47
+ console.error(chalk.red(`Error: ${error.message}\n`));
48
+ }
49
+ }
50
+ /**
51
+ * Get team member details
52
+ */
53
+ export async function getTeamMemberCommand(email) {
54
+ if (!isAuthenticated()) {
55
+ console.log(chalk.red('āœ— Not authenticated. Please run "langctl auth <api-key>" first.\n'));
56
+ return;
57
+ }
58
+ const apiKey = getApiKey();
59
+ const spinner = ora('Fetching member details...').start();
60
+ try {
61
+ const response = await fetch(TEAM_MANAGEMENT_URL, {
62
+ method: 'POST',
63
+ headers: {
64
+ 'Content-Type': 'application/json',
65
+ 'X-API-Key': apiKey
66
+ },
67
+ body: JSON.stringify({
68
+ action: 'get',
69
+ email
70
+ })
71
+ });
72
+ const data = await response.json();
73
+ if (!data.success) {
74
+ throw new Error(data.error);
75
+ }
76
+ spinner.stop();
77
+ const member = data.member;
78
+ console.log(chalk.blue.bold('\nšŸ‘¤ Team Member Details\n'));
79
+ console.log(chalk.white.bold(`Email: ${member.email}`));
80
+ console.log(chalk.gray(`Role: ${member.role}`));
81
+ console.log(chalk.gray(`User ID: ${member.user_id}`));
82
+ if (member.joined_at) {
83
+ console.log(chalk.gray(`Joined: ${new Date(member.joined_at).toLocaleString()}`));
84
+ }
85
+ console.log('');
86
+ }
87
+ catch (error) {
88
+ spinner.fail(chalk.red('Failed to get member details'));
89
+ console.error(chalk.red(`Error: ${error.message}\n`));
90
+ }
91
+ }
92
+ /**
93
+ * Invite team member
94
+ */
95
+ export async function inviteTeamMemberCommand(email, options) {
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(`Inviting ${email}...`).start();
102
+ try {
103
+ const response = await fetch(TEAM_MANAGEMENT_URL, {
104
+ method: 'POST',
105
+ headers: {
106
+ 'Content-Type': 'application/json',
107
+ 'X-API-Key': apiKey
108
+ },
109
+ body: JSON.stringify({
110
+ action: 'invite',
111
+ email,
112
+ role: options.role || 'member'
113
+ })
114
+ });
115
+ const data = await response.json();
116
+ if (!data.success) {
117
+ throw new Error(data.error);
118
+ }
119
+ spinner.succeed(chalk.green(data.message));
120
+ console.log('');
121
+ }
122
+ catch (error) {
123
+ spinner.fail(chalk.red('Failed to invite member'));
124
+ console.error(chalk.red(`Error: ${error.message}\n`));
125
+ }
126
+ }
127
+ /**
128
+ * Remove team member
129
+ */
130
+ export async function removeTeamMemberCommand(email) {
131
+ if (!isAuthenticated()) {
132
+ console.log(chalk.red('āœ— Not authenticated. Please run "langctl auth <api-key>" first.\n'));
133
+ return;
134
+ }
135
+ const apiKey = getApiKey();
136
+ const spinner = ora(`Removing ${email}...`).start();
137
+ try {
138
+ const response = await fetch(TEAM_MANAGEMENT_URL, {
139
+ method: 'POST',
140
+ headers: {
141
+ 'Content-Type': 'application/json',
142
+ 'X-API-Key': apiKey
143
+ },
144
+ body: JSON.stringify({
145
+ action: 'remove',
146
+ email
147
+ })
148
+ });
149
+ const data = await response.json();
150
+ if (!data.success) {
151
+ throw new Error(data.error);
152
+ }
153
+ spinner.succeed(chalk.green(data.message));
154
+ console.log('');
155
+ }
156
+ catch (error) {
157
+ spinner.fail(chalk.red('Failed to remove member'));
158
+ console.error(chalk.red(`Error: ${error.message}\n`));
159
+ }
160
+ }
161
+ /**
162
+ * Update team member role
163
+ */
164
+ export async function updateTeamRoleCommand(email, role) {
165
+ if (!isAuthenticated()) {
166
+ console.log(chalk.red('āœ— Not authenticated. Please run "langctl auth <api-key>" first.\n'));
167
+ return;
168
+ }
169
+ const validRoles = ['viewer', 'member', 'admin', 'owner'];
170
+ if (!validRoles.includes(role)) {
171
+ console.log(chalk.red(`āœ— Invalid role. Must be one of: ${validRoles.join(', ')}\n`));
172
+ return;
173
+ }
174
+ const apiKey = getApiKey();
175
+ const spinner = ora(`Updating role for ${email}...`).start();
176
+ try {
177
+ const response = await fetch(TEAM_MANAGEMENT_URL, {
178
+ method: 'POST',
179
+ headers: {
180
+ 'Content-Type': 'application/json',
181
+ 'X-API-Key': apiKey
182
+ },
183
+ body: JSON.stringify({
184
+ action: 'update-role',
185
+ email,
186
+ role
187
+ })
188
+ });
189
+ const data = await response.json();
190
+ if (!data.success) {
191
+ throw new Error(data.error);
192
+ }
193
+ spinner.succeed(chalk.green(data.message));
194
+ console.log('');
195
+ }
196
+ catch (error) {
197
+ spinner.fail(chalk.red('Failed to update role'));
198
+ console.error(chalk.red(`Error: ${error.message}\n`));
199
+ }
200
+ }
201
+ /**
202
+ * List invitations
203
+ */
204
+ export async function listInvitationsCommand(options) {
205
+ if (!isAuthenticated()) {
206
+ console.log(chalk.red('āœ— Not authenticated. Please run "langctl auth <api-key>" first.\n'));
207
+ return;
208
+ }
209
+ const apiKey = getApiKey();
210
+ const spinner = ora('Fetching invitations...').start();
211
+ try {
212
+ const response = await fetch(TEAM_MANAGEMENT_URL, {
213
+ method: 'POST',
214
+ headers: {
215
+ 'Content-Type': 'application/json',
216
+ 'X-API-Key': apiKey
217
+ },
218
+ body: JSON.stringify({
219
+ action: 'list-invitations',
220
+ pending: options.pending || false
221
+ })
222
+ });
223
+ const data = await response.json();
224
+ if (!data.success) {
225
+ throw new Error(data.error);
226
+ }
227
+ spinner.stop();
228
+ if (!data.invitations || data.invitations.length === 0) {
229
+ console.log(chalk.yellow('\nNo invitations found\n'));
230
+ return;
231
+ }
232
+ console.log(chalk.blue.bold(`\nāœ‰ļø Invitations (${data.invitations.length})\n`));
233
+ data.invitations.forEach((invitation) => {
234
+ console.log(chalk.white.bold(invitation.email));
235
+ console.log(chalk.gray(` Role: ${invitation.role}`));
236
+ console.log(chalk.gray(` Status: ${invitation.status}`));
237
+ if (invitation.invited_at) {
238
+ console.log(chalk.gray(` Invited: ${new Date(invitation.invited_at).toLocaleDateString()}`));
239
+ }
240
+ if (invitation.expires_at) {
241
+ console.log(chalk.gray(` Expires: ${new Date(invitation.expires_at).toLocaleDateString()}`));
242
+ }
243
+ console.log('');
244
+ });
245
+ }
246
+ catch (error) {
247
+ spinner.fail(chalk.red('Failed to list invitations'));
248
+ console.error(chalk.red(`Error: ${error.message}\n`));
249
+ }
250
+ }
251
+ /**
252
+ * Revoke invitation
253
+ */
254
+ export async function revokeInvitationCommand(email) {
255
+ if (!isAuthenticated()) {
256
+ console.log(chalk.red('āœ— Not authenticated. Please run "langctl auth <api-key>" first.\n'));
257
+ return;
258
+ }
259
+ const apiKey = getApiKey();
260
+ const spinner = ora(`Revoking invitation for ${email}...`).start();
261
+ try {
262
+ const response = await fetch(TEAM_MANAGEMENT_URL, {
263
+ method: 'POST',
264
+ headers: {
265
+ 'Content-Type': 'application/json',
266
+ 'X-API-Key': apiKey
267
+ },
268
+ body: JSON.stringify({
269
+ action: 'revoke-invitation',
270
+ email
271
+ })
272
+ });
273
+ const data = await response.json();
274
+ if (!data.success) {
275
+ throw new Error(data.error);
276
+ }
277
+ spinner.succeed(chalk.green(data.message));
278
+ console.log('');
279
+ }
280
+ catch (error) {
281
+ spinner.fail(chalk.red('Failed to revoke invitation'));
282
+ console.error(chalk.red(`Error: ${error.message}\n`));
283
+ }
284
+ }
285
+ //# sourceMappingURL=team.js.map
package/dist/index.js CHANGED
@@ -4,8 +4,13 @@ import chalk from 'chalk';
4
4
  import { initCommand } from './commands/init.js';
5
5
  import { authCommand, logoutCommand } from './commands/auth.js';
6
6
  import { configCommand } from './commands/config.js';
7
- import { projectsListCommand } from './commands/projects.js';
7
+ import { projectsListCommand, projectsCreateCommand, projectsGetCommand, projectsUpdateCommand, projectsDeleteCommand, projectsAddLanguageCommand, projectsRemoveLanguageCommand, projectsStatsCommand } from './commands/projects.js';
8
+ import { listKeysCommand, getKeyCommand, createKeyCommand, deleteKeyCommand, translateKeyCommand, publishKeysCommand } from './commands/keys.js';
9
+ import { listTeamCommand, getTeamMemberCommand, inviteTeamMemberCommand, removeTeamMemberCommand, updateTeamRoleCommand, listInvitationsCommand, revokeInvitationCommand } from './commands/team.js';
10
+ import { orgInfoCommand, orgStatsCommand, orgPlanCommand } from './commands/org.js';
8
11
  import { pullCommand } from './commands/pull.js';
12
+ import { exportCommand } from './commands/export.js';
13
+ import { importCommand } from './commands/import.js';
9
14
  import { debugListApiKeys, debugListOrganizations } from './commands/debug.js';
10
15
  import { showLogo } from './utils/banner.js';
11
16
  const program = new Command();
@@ -87,10 +92,322 @@ projects
87
92
  process.exit(1);
88
93
  }
89
94
  });
90
- // Pull command
95
+ projects
96
+ .command('create <name>')
97
+ .description('Create a new project')
98
+ .option('-d, --description <text>', 'Project description')
99
+ .option('-l, --languages <langs>', 'Comma-separated language codes (e.g., en,es,fr)', 'en')
100
+ .option('--default-language <code>', 'Default language code')
101
+ .action(async (name, options) => {
102
+ try {
103
+ await projectsCreateCommand(name, options);
104
+ }
105
+ catch (error) {
106
+ console.error(chalk.red(`Error: ${error.message}\n`));
107
+ process.exit(1);
108
+ }
109
+ });
110
+ projects
111
+ .command('get <slug>')
112
+ .description('Get project details')
113
+ .action(async (slug) => {
114
+ try {
115
+ await projectsGetCommand(slug);
116
+ }
117
+ catch (error) {
118
+ console.error(chalk.red(`Error: ${error.message}\n`));
119
+ process.exit(1);
120
+ }
121
+ });
122
+ projects
123
+ .command('update <slug>')
124
+ .description('Update project')
125
+ .option('-n, --name <name>', 'New project name')
126
+ .option('-d, --description <text>', 'New description')
127
+ .option('-l, --languages <langs>', 'Comma-separated language codes')
128
+ .option('--default-language <code>', 'Default language code')
129
+ .action(async (slug, options) => {
130
+ try {
131
+ await projectsUpdateCommand(slug, options);
132
+ }
133
+ catch (error) {
134
+ console.error(chalk.red(`Error: ${error.message}\n`));
135
+ process.exit(1);
136
+ }
137
+ });
138
+ projects
139
+ .command('delete <slug>')
140
+ .description('Delete project')
141
+ .action(async (slug) => {
142
+ try {
143
+ await projectsDeleteCommand(slug);
144
+ }
145
+ catch (error) {
146
+ console.error(chalk.red(`Error: ${error.message}\n`));
147
+ process.exit(1);
148
+ }
149
+ });
150
+ projects
151
+ .command('add-language <slug> <language>')
152
+ .description('Add language to project')
153
+ .action(async (slug, language) => {
154
+ try {
155
+ await projectsAddLanguageCommand(slug, language);
156
+ }
157
+ catch (error) {
158
+ console.error(chalk.red(`Error: ${error.message}\n`));
159
+ process.exit(1);
160
+ }
161
+ });
162
+ projects
163
+ .command('remove-language <slug> <language>')
164
+ .description('Remove language from project')
165
+ .action(async (slug, language) => {
166
+ try {
167
+ await projectsRemoveLanguageCommand(slug, language);
168
+ }
169
+ catch (error) {
170
+ console.error(chalk.red(`Error: ${error.message}\n`));
171
+ process.exit(1);
172
+ }
173
+ });
174
+ projects
175
+ .command('stats <slug>')
176
+ .description('Get project statistics')
177
+ .action(async (slug) => {
178
+ try {
179
+ await projectsStatsCommand(slug);
180
+ }
181
+ catch (error) {
182
+ console.error(chalk.red(`Error: ${error.message}\n`));
183
+ process.exit(1);
184
+ }
185
+ });
186
+ // Keys command
187
+ const keys = program
188
+ .command('keys')
189
+ .description('Manage translation keys');
190
+ keys
191
+ .command('list <project>')
192
+ .description('List translation keys')
193
+ .option('-m, --module <name>', 'Filter by module')
194
+ .option('-p, --published', 'Show only published keys')
195
+ .option('-s, --search <term>', 'Search keys')
196
+ .option('--limit <number>', 'Limit results', '100')
197
+ .option('--offset <number>', 'Offset results', '0')
198
+ .action(async (project, options) => {
199
+ try {
200
+ await listKeysCommand(project, options);
201
+ }
202
+ catch (error) {
203
+ console.error(chalk.red(`Error: ${error.message}\n`));
204
+ process.exit(1);
205
+ }
206
+ });
207
+ keys
208
+ .command('get <project> <key>')
209
+ .description('Get key details')
210
+ .action(async (project, key) => {
211
+ try {
212
+ await getKeyCommand(project, key);
213
+ }
214
+ catch (error) {
215
+ console.error(chalk.red(`Error: ${error.message}\n`));
216
+ process.exit(1);
217
+ }
218
+ });
219
+ keys
220
+ .command('create <project> <key>')
221
+ .description('Create translation key')
222
+ .option('-d, --description <text>', 'Key description')
223
+ .option('-m, --module <name>', 'Module name')
224
+ .option('--value-en <value>', 'English value')
225
+ .option('--value-es <value>', 'Spanish value')
226
+ .option('--value-fr <value>', 'French value')
227
+ .option('--value-de <value>', 'German value')
228
+ .option('--tags <tags>', 'Comma-separated tags')
229
+ .action(async (project, key, options) => {
230
+ try {
231
+ await createKeyCommand(project, key, options);
232
+ }
233
+ catch (error) {
234
+ console.error(chalk.red(`Error: ${error.message}\n`));
235
+ process.exit(1);
236
+ }
237
+ });
238
+ keys
239
+ .command('delete <project> <key>')
240
+ .description('Delete translation key')
241
+ .action(async (project, key) => {
242
+ try {
243
+ await deleteKeyCommand(project, key);
244
+ }
245
+ catch (error) {
246
+ console.error(chalk.red(`Error: ${error.message}\n`));
247
+ process.exit(1);
248
+ }
249
+ });
250
+ keys
251
+ .command('translate <project> <key>')
252
+ .description('Update translation for a key')
253
+ .requiredOption('-l, --language <code>', 'Language code')
254
+ .requiredOption('-v, --value <text>', 'Translation value')
255
+ .action(async (project, key, options) => {
256
+ try {
257
+ await translateKeyCommand(project, key, options);
258
+ }
259
+ catch (error) {
260
+ console.error(chalk.red(`Error: ${error.message}\n`));
261
+ process.exit(1);
262
+ }
263
+ });
264
+ keys
265
+ .command('publish <project> <keys...>')
266
+ .description('Publish translation keys')
267
+ .option('--unpublish', 'Unpublish instead of publish')
268
+ .action(async (project, keysList, options) => {
269
+ try {
270
+ await publishKeysCommand(project, keysList, options);
271
+ }
272
+ catch (error) {
273
+ console.error(chalk.red(`Error: ${error.message}\n`));
274
+ process.exit(1);
275
+ }
276
+ });
277
+ // Team command
278
+ const team = program
279
+ .command('team')
280
+ .description('Manage team members');
281
+ team
282
+ .command('list')
283
+ .description('List team members')
284
+ .action(async () => {
285
+ try {
286
+ await listTeamCommand();
287
+ }
288
+ catch (error) {
289
+ console.error(chalk.red(`Error: ${error.message}\n`));
290
+ process.exit(1);
291
+ }
292
+ });
293
+ team
294
+ .command('get <email>')
295
+ .description('Get team member details')
296
+ .action(async (email) => {
297
+ try {
298
+ await getTeamMemberCommand(email);
299
+ }
300
+ catch (error) {
301
+ console.error(chalk.red(`Error: ${error.message}\n`));
302
+ process.exit(1);
303
+ }
304
+ });
305
+ team
306
+ .command('invite <email>')
307
+ .description('Invite team member')
308
+ .option('-r, --role <role>', 'Member role: viewer, member, admin', 'member')
309
+ .action(async (email, options) => {
310
+ try {
311
+ await inviteTeamMemberCommand(email, options);
312
+ }
313
+ catch (error) {
314
+ console.error(chalk.red(`Error: ${error.message}\n`));
315
+ process.exit(1);
316
+ }
317
+ });
318
+ team
319
+ .command('remove <email>')
320
+ .description('Remove team member')
321
+ .action(async (email) => {
322
+ try {
323
+ await removeTeamMemberCommand(email);
324
+ }
325
+ catch (error) {
326
+ console.error(chalk.red(`Error: ${error.message}\n`));
327
+ process.exit(1);
328
+ }
329
+ });
330
+ team
331
+ .command('update-role <email> <role>')
332
+ .description('Update member role (viewer, member, admin)')
333
+ .action(async (email, role) => {
334
+ try {
335
+ await updateTeamRoleCommand(email, role);
336
+ }
337
+ catch (error) {
338
+ console.error(chalk.red(`Error: ${error.message}\n`));
339
+ process.exit(1);
340
+ }
341
+ });
342
+ team
343
+ .command('invitations')
344
+ .description('List invitations')
345
+ .option('-p, --pending', 'Show only pending invitations')
346
+ .action(async (options) => {
347
+ try {
348
+ await listInvitationsCommand(options);
349
+ }
350
+ catch (error) {
351
+ console.error(chalk.red(`Error: ${error.message}\n`));
352
+ process.exit(1);
353
+ }
354
+ });
355
+ team
356
+ .command('revoke-invitation <email>')
357
+ .description('Revoke pending invitation')
358
+ .action(async (email) => {
359
+ try {
360
+ await revokeInvitationCommand(email);
361
+ }
362
+ catch (error) {
363
+ console.error(chalk.red(`Error: ${error.message}\n`));
364
+ process.exit(1);
365
+ }
366
+ });
367
+ // Organization command
368
+ const org = program
369
+ .command('org')
370
+ .description('Manage organization');
371
+ org
372
+ .command('info')
373
+ .description('Get organization information')
374
+ .action(async () => {
375
+ try {
376
+ await orgInfoCommand();
377
+ }
378
+ catch (error) {
379
+ console.error(chalk.red(`Error: ${error.message}\n`));
380
+ process.exit(1);
381
+ }
382
+ });
383
+ org
384
+ .command('stats')
385
+ .description('Get organization statistics')
386
+ .action(async () => {
387
+ try {
388
+ await orgStatsCommand();
389
+ }
390
+ catch (error) {
391
+ console.error(chalk.red(`Error: ${error.message}\n`));
392
+ process.exit(1);
393
+ }
394
+ });
395
+ org
396
+ .command('plan')
397
+ .description('Get subscription plan and limits')
398
+ .action(async () => {
399
+ try {
400
+ await orgPlanCommand();
401
+ }
402
+ catch (error) {
403
+ console.error(chalk.red(`Error: ${error.message}\n`));
404
+ process.exit(1);
405
+ }
406
+ });
407
+ // Pull command (deprecated, use export)
91
408
  program
92
409
  .command('pull <project>')
93
- .description('Pull translations from a project (all languages by default)')
410
+ .description('Pull translations from a project (deprecated, use export)')
94
411
  .option('-l, --language <code>', 'Pull specific language only (default: all)')
95
412
  .option('-f, --format <type>', 'Export format: json, json-nested, ios, android, flutter (default: json)')
96
413
  .option('-d, --dir <path>', 'Output directory (default: ./translations)')
@@ -105,6 +422,40 @@ program
105
422
  process.exit(1);
106
423
  }
107
424
  });
425
+ // Export command
426
+ program
427
+ .command('export <project>')
428
+ .description('Export translations from a project')
429
+ .option('-l, --language <code>', 'Export specific language (default: all)')
430
+ .option('-f, --format <type>', 'Export format: flat-json, nested-json, i18n-json, android-xml, ios-strings, flutter-arb (default: flat-json)')
431
+ .option('-o, --output <path>', 'Output file path')
432
+ .option('-m, --module <name>', 'Export only keys from specific module')
433
+ .option('--include-unpublished', 'Include unpublished translations')
434
+ .action(async (project, options) => {
435
+ try {
436
+ await exportCommand(project, options);
437
+ }
438
+ catch (error) {
439
+ console.error(chalk.red(`Error: ${error.message}\n`));
440
+ process.exit(1);
441
+ }
442
+ });
443
+ // Import command
444
+ program
445
+ .command('import <project> <file>')
446
+ .description('Import translations from a file')
447
+ .requiredOption('-l, --language <code>', 'Target language code')
448
+ .option('--overwrite', 'Overwrite existing translations')
449
+ .option('--publish', 'Auto-publish after import')
450
+ .action(async (project, file, options) => {
451
+ try {
452
+ await importCommand(project, file, options);
453
+ }
454
+ catch (error) {
455
+ console.error(chalk.red(`Error: ${error.message}\n`));
456
+ process.exit(1);
457
+ }
458
+ });
108
459
  // Debug command
109
460
  const debug = program
110
461
  .command('debug')