langctl 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +2 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +26 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
- package/CONTRIBUTING.md +33 -0
- package/LICENSE +1 -1
- package/README.md +1018 -113
- package/dist/api.d.ts +20 -0
- package/dist/api.js +75 -0
- package/dist/auth.d.ts +7 -2
- package/dist/auth.js +27 -27
- package/dist/commands/auth.js +2 -10
- package/dist/commands/config.js +1 -0
- package/dist/commands/debug.js +45 -41
- package/dist/commands/export.d.ts +13 -0
- package/dist/commands/export.js +125 -0
- package/dist/commands/import.d.ts +12 -0
- package/dist/commands/import.js +150 -0
- package/dist/commands/init.js +0 -10
- package/dist/commands/keys.d.ts +25 -0
- package/dist/commands/keys.js +294 -0
- package/dist/commands/org.d.ts +13 -0
- package/dist/commands/org.js +118 -0
- package/dist/commands/projects.d.ts +31 -0
- package/dist/commands/projects.js +295 -24
- package/dist/commands/pull.js +40 -95
- package/dist/commands/team.d.ts +29 -0
- package/dist/commands/team.js +270 -0
- package/dist/config.d.ts +1 -0
- package/dist/index.js +357 -6
- package/package.json +53 -7
- package/NPM_PUBLISH_GUIDE.md +0 -293
- package/QUICK_PUBLISH.md +0 -64
- package/SECURITY_FIX.md +0 -167
- package/dist/supabase.d.ts +0 -14
- package/dist/supabase.js +0 -30
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { isAuthenticated } from '../auth.js';
|
|
4
|
+
import { getApiClient } from '../api.js';
|
|
5
|
+
import { config } from '../config.js';
|
|
6
|
+
/**
|
|
7
|
+
* List all team members
|
|
8
|
+
*/
|
|
9
|
+
export async function listTeamCommand() {
|
|
10
|
+
if (!isAuthenticated()) {
|
|
11
|
+
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
12
|
+
process.exitCode = 1;
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const orgId = config.get('organizationId');
|
|
16
|
+
if (!orgId) {
|
|
17
|
+
console.log(chalk.red('✗ No organization configured. Please run "langctl init" first.\n'));
|
|
18
|
+
process.exitCode = 1;
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const spinner = ora('Fetching team members...').start();
|
|
22
|
+
try {
|
|
23
|
+
const api = getApiClient();
|
|
24
|
+
const members = await api.get(`/orgs/${orgId}/members`);
|
|
25
|
+
spinner.stop();
|
|
26
|
+
if (!members || members.length === 0) {
|
|
27
|
+
console.log(chalk.yellow('\nNo team members found\n'));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
console.log(chalk.blue.bold(`\n👥 Team Members (${members.length})\n`));
|
|
31
|
+
members.forEach((member) => {
|
|
32
|
+
console.log(chalk.white.bold(member.email));
|
|
33
|
+
console.log(chalk.gray(` Role: ${member.role}`));
|
|
34
|
+
if (member.joinedAt) {
|
|
35
|
+
console.log(chalk.gray(` Joined: ${new Date(member.joinedAt).toLocaleDateString()}`));
|
|
36
|
+
}
|
|
37
|
+
console.log('');
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
spinner.fail(chalk.red('Failed to list team members'));
|
|
42
|
+
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
43
|
+
process.exitCode = 1;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get team member details
|
|
48
|
+
*/
|
|
49
|
+
export async function getTeamMemberCommand(email) {
|
|
50
|
+
if (!isAuthenticated()) {
|
|
51
|
+
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
52
|
+
process.exitCode = 1;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const orgId = config.get('organizationId');
|
|
56
|
+
if (!orgId) {
|
|
57
|
+
console.log(chalk.red('✗ No organization configured. Please run "langctl init" first.\n'));
|
|
58
|
+
process.exitCode = 1;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const spinner = ora('Fetching member details...').start();
|
|
62
|
+
try {
|
|
63
|
+
const api = getApiClient();
|
|
64
|
+
const members = await api.get(`/orgs/${orgId}/members`);
|
|
65
|
+
const member = members.find((m) => m.email === email);
|
|
66
|
+
if (!member) {
|
|
67
|
+
throw new Error(`Member "${email}" not found`);
|
|
68
|
+
}
|
|
69
|
+
spinner.stop();
|
|
70
|
+
console.log(chalk.blue.bold('\n👤 Team Member Details\n'));
|
|
71
|
+
console.log(chalk.white.bold(`Email: ${member.email}`));
|
|
72
|
+
console.log(chalk.gray(`Role: ${member.role}`));
|
|
73
|
+
console.log(chalk.gray(`User ID: ${member.userId}`));
|
|
74
|
+
if (member.joinedAt) {
|
|
75
|
+
console.log(chalk.gray(`Joined: ${new Date(member.joinedAt).toLocaleString()}`));
|
|
76
|
+
}
|
|
77
|
+
console.log('');
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
spinner.fail(chalk.red('Failed to get member details'));
|
|
81
|
+
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
82
|
+
process.exitCode = 1;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Invite team member
|
|
87
|
+
*/
|
|
88
|
+
export async function inviteTeamMemberCommand(email, options) {
|
|
89
|
+
if (!isAuthenticated()) {
|
|
90
|
+
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
91
|
+
process.exitCode = 1;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const orgId = config.get('organizationId');
|
|
95
|
+
if (!orgId) {
|
|
96
|
+
console.log(chalk.red('✗ No organization configured. Please run "langctl init" first.\n'));
|
|
97
|
+
process.exitCode = 1;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const spinner = ora(`Inviting ${email}...`).start();
|
|
101
|
+
try {
|
|
102
|
+
const api = getApiClient();
|
|
103
|
+
await api.post(`/orgs/${orgId}/invitations`, {
|
|
104
|
+
email,
|
|
105
|
+
role: options.role || 'member'
|
|
106
|
+
});
|
|
107
|
+
spinner.succeed(chalk.green(`Invitation sent to ${email}`));
|
|
108
|
+
console.log('');
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
spinner.fail(chalk.red('Failed to invite member'));
|
|
112
|
+
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
113
|
+
process.exitCode = 1;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Remove team member
|
|
118
|
+
*/
|
|
119
|
+
export async function removeTeamMemberCommand(email) {
|
|
120
|
+
if (!isAuthenticated()) {
|
|
121
|
+
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
122
|
+
process.exitCode = 1;
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const orgId = config.get('organizationId');
|
|
126
|
+
if (!orgId) {
|
|
127
|
+
console.log(chalk.red('✗ No organization configured. Please run "langctl init" first.\n'));
|
|
128
|
+
process.exitCode = 1;
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const spinner = ora(`Removing ${email}...`).start();
|
|
132
|
+
try {
|
|
133
|
+
const api = getApiClient();
|
|
134
|
+
const members = await api.get(`/orgs/${orgId}/members`);
|
|
135
|
+
const member = members.find((m) => m.email === email);
|
|
136
|
+
if (!member) {
|
|
137
|
+
throw new Error(`Member "${email}" not found`);
|
|
138
|
+
}
|
|
139
|
+
await api.delete(`/orgs/${orgId}/members/${member.id}`);
|
|
140
|
+
spinner.succeed(chalk.green(`Removed ${email} from organization`));
|
|
141
|
+
console.log('');
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
spinner.fail(chalk.red('Failed to remove member'));
|
|
145
|
+
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
146
|
+
process.exitCode = 1;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Update team member role
|
|
151
|
+
*/
|
|
152
|
+
export async function updateTeamRoleCommand(email, role) {
|
|
153
|
+
if (!isAuthenticated()) {
|
|
154
|
+
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
155
|
+
process.exitCode = 1;
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const validRoles = ['viewer', 'member', 'admin', 'owner'];
|
|
159
|
+
if (!validRoles.includes(role)) {
|
|
160
|
+
console.log(chalk.red(`✗ Invalid role. Must be one of: ${validRoles.join(', ')}\n`));
|
|
161
|
+
process.exitCode = 1;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const orgId = config.get('organizationId');
|
|
165
|
+
if (!orgId) {
|
|
166
|
+
console.log(chalk.red('✗ No organization configured. Please run "langctl init" first.\n'));
|
|
167
|
+
process.exitCode = 1;
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const spinner = ora(`Updating role for ${email}...`).start();
|
|
171
|
+
try {
|
|
172
|
+
const api = getApiClient();
|
|
173
|
+
const members = await api.get(`/orgs/${orgId}/members`);
|
|
174
|
+
const member = members.find((m) => m.email === email);
|
|
175
|
+
if (!member) {
|
|
176
|
+
throw new Error(`Member "${email}" not found`);
|
|
177
|
+
}
|
|
178
|
+
await api.patch(`/orgs/${orgId}/members/${member.id}`, { role });
|
|
179
|
+
spinner.succeed(chalk.green(`Updated ${email} role to ${role}`));
|
|
180
|
+
console.log('');
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
spinner.fail(chalk.red('Failed to update role'));
|
|
184
|
+
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
185
|
+
process.exitCode = 1;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* List invitations
|
|
190
|
+
*/
|
|
191
|
+
export async function listInvitationsCommand(options) {
|
|
192
|
+
if (!isAuthenticated()) {
|
|
193
|
+
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
194
|
+
process.exitCode = 1;
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const orgId = config.get('organizationId');
|
|
198
|
+
if (!orgId) {
|
|
199
|
+
console.log(chalk.red('✗ No organization configured. Please run "langctl init" first.\n'));
|
|
200
|
+
process.exitCode = 1;
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const spinner = ora('Fetching invitations...').start();
|
|
204
|
+
try {
|
|
205
|
+
const api = getApiClient();
|
|
206
|
+
const params = {};
|
|
207
|
+
if (options?.pending) {
|
|
208
|
+
params.status = 'pending';
|
|
209
|
+
}
|
|
210
|
+
const result = await api.get(`/orgs/${orgId}/invitations`, params);
|
|
211
|
+
const invitations = result.invitations || [];
|
|
212
|
+
spinner.stop();
|
|
213
|
+
if (invitations.length === 0) {
|
|
214
|
+
console.log(chalk.yellow('\nNo invitations found\n'));
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
console.log(chalk.blue.bold(`\n✉️ Invitations (${invitations.length})\n`));
|
|
218
|
+
invitations.forEach((invitation) => {
|
|
219
|
+
console.log(chalk.white.bold(invitation.email));
|
|
220
|
+
console.log(chalk.gray(` Role: ${invitation.role}`));
|
|
221
|
+
console.log(chalk.gray(` Status: ${invitation.status}`));
|
|
222
|
+
if (invitation.createdAt) {
|
|
223
|
+
console.log(chalk.gray(` Invited: ${new Date(invitation.createdAt).toLocaleDateString()}`));
|
|
224
|
+
}
|
|
225
|
+
if (invitation.expiresAt) {
|
|
226
|
+
console.log(chalk.gray(` Expires: ${new Date(invitation.expiresAt).toLocaleDateString()}`));
|
|
227
|
+
}
|
|
228
|
+
console.log('');
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
spinner.fail(chalk.red('Failed to list invitations'));
|
|
233
|
+
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
234
|
+
process.exitCode = 1;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Revoke invitation
|
|
239
|
+
*/
|
|
240
|
+
export async function revokeInvitationCommand(email) {
|
|
241
|
+
if (!isAuthenticated()) {
|
|
242
|
+
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
243
|
+
process.exitCode = 1;
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const orgId = config.get('organizationId');
|
|
247
|
+
if (!orgId) {
|
|
248
|
+
console.log(chalk.red('✗ No organization configured. Please run "langctl init" first.\n'));
|
|
249
|
+
process.exitCode = 1;
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const spinner = ora(`Revoking invitation for ${email}...`).start();
|
|
253
|
+
try {
|
|
254
|
+
const api = getApiClient();
|
|
255
|
+
const result = await api.get(`/orgs/${orgId}/invitations`);
|
|
256
|
+
const invitation = (result.invitations || []).find((inv) => inv.email === email && inv.status === 'pending');
|
|
257
|
+
if (!invitation) {
|
|
258
|
+
throw new Error(`No pending invitation found for "${email}"`);
|
|
259
|
+
}
|
|
260
|
+
await api.post(`/orgs/${orgId}/invitations/${invitation.id}/revoke`);
|
|
261
|
+
spinner.succeed(chalk.green(`Invitation for ${email} revoked`));
|
|
262
|
+
console.log('');
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
spinner.fail(chalk.red('Failed to revoke invitation'));
|
|
266
|
+
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
267
|
+
process.exitCode = 1;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
//# sourceMappingURL=team.js.map
|
package/dist/config.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -4,15 +4,20 @@ 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();
|
|
12
17
|
// Package info
|
|
13
18
|
const packageJson = {
|
|
14
19
|
name: 'langctl',
|
|
15
|
-
version: '0.
|
|
20
|
+
version: '0.2.0',
|
|
16
21
|
description: 'CLI-first translation management for developers'
|
|
17
22
|
};
|
|
18
23
|
program
|
|
@@ -87,10 +92,322 @@ projects
|
|
|
87
92
|
process.exit(1);
|
|
88
93
|
}
|
|
89
94
|
});
|
|
90
|
-
|
|
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 (
|
|
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,13 +422,47 @@ 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')
|
|
111
462
|
.description('Debug helpers');
|
|
112
463
|
debug
|
|
113
464
|
.command('list-api-keys')
|
|
114
|
-
.description('
|
|
465
|
+
.description('Test API connection and show debug info')
|
|
115
466
|
.action(async () => {
|
|
116
467
|
try {
|
|
117
468
|
await debugListApiKeys();
|
|
@@ -123,7 +474,7 @@ debug
|
|
|
123
474
|
});
|
|
124
475
|
debug
|
|
125
476
|
.command('list-organizations')
|
|
126
|
-
.description('
|
|
477
|
+
.description('Show current organization details')
|
|
127
478
|
.action(async () => {
|
|
128
479
|
try {
|
|
129
480
|
await debugListOrganizations();
|