langctl 0.2.0 → 0.4.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +158 -1188
  3. package/dist/commands/auth.js +85 -19
  4. package/dist/commands/config.js +36 -26
  5. package/dist/commands/export.js +11 -122
  6. package/dist/commands/import.js +32 -143
  7. package/dist/commands/init.js +61 -72
  8. package/dist/commands/keys.js +145 -279
  9. package/dist/commands/org.js +45 -111
  10. package/dist/commands/projects.js +124 -312
  11. package/dist/commands/pull.js +86 -129
  12. package/dist/commands/push.js +125 -0
  13. package/dist/commands/review.js +65 -0
  14. package/dist/commands/team.js +68 -257
  15. package/dist/commands/translate.js +99 -0
  16. package/dist/core/cache.js +48 -0
  17. package/dist/core/config.js +130 -0
  18. package/dist/core/errors.js +67 -0
  19. package/dist/core/files.js +38 -0
  20. package/dist/core/http.js +156 -0
  21. package/dist/core/output.js +96 -0
  22. package/dist/core/project.js +58 -0
  23. package/dist/core/prompts.js +56 -0
  24. package/dist/formats/index.js +285 -0
  25. package/dist/index.js +277 -508
  26. package/dist/version.js +4 -0
  27. package/package.json +22 -15
  28. package/.gitattributes +0 -2
  29. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -26
  30. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
  31. package/CONTRIBUTING.md +0 -33
  32. package/dist/api.d.ts +0 -20
  33. package/dist/api.js +0 -75
  34. package/dist/auth.d.ts +0 -38
  35. package/dist/auth.js +0 -106
  36. package/dist/commands/auth.d.ts +0 -3
  37. package/dist/commands/config.d.ts +0 -2
  38. package/dist/commands/debug.d.ts +0 -3
  39. package/dist/commands/debug.js +0 -61
  40. package/dist/commands/export.d.ts +0 -13
  41. package/dist/commands/import.d.ts +0 -12
  42. package/dist/commands/init.d.ts +0 -2
  43. package/dist/commands/keys.d.ts +0 -25
  44. package/dist/commands/org.d.ts +0 -13
  45. package/dist/commands/projects.d.ts +0 -33
  46. package/dist/commands/pull.d.ts +0 -9
  47. package/dist/commands/team.d.ts +0 -29
  48. package/dist/config.d.ts +0 -48
  49. package/dist/config.js +0 -69
  50. package/dist/exporters/index.d.ts +0 -36
  51. package/dist/exporters/index.js +0 -214
  52. package/dist/index.d.ts +0 -3
  53. package/dist/utils/banner.d.ts +0 -9
  54. package/dist/utils/banner.js +0 -24
  55. package/translations/en.json +0 -5
  56. package/translations/en.xml +0 -9
  57. package/translations/fr.json +0 -4
  58. package/translations/fr.xml +0 -7
  59. package/translations/hi.json +0 -4
  60. package/translations/hi.xml +0 -7
  61. package/translations/ios/en.strings +0 -9
  62. package/translations/ios/fr.strings +0 -6
  63. package/translations/ios/hi.strings +0 -6
  64. package/translations/ios/ja.strings +0 -6
  65. package/translations/ja.json +0 -4
  66. package/translations/ja.xml +0 -7
@@ -1,294 +1,160 @@
1
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
- * Resolve a project slug to a full project object
8
- */
9
- async function resolveProject(orgId, slug) {
10
- const api = getApiClient();
11
- return api.get(`/orgs/${orgId}/projects/by-slug/${slug}`);
12
- }
13
- /**
14
- * Find a key by exact name within a project (search + exact match)
15
- */
16
- async function findKeyByName(orgId, projectId, keyName) {
17
- const api = getApiClient();
18
- const result = await api.get(`/orgs/${orgId}/projects/${projectId}/keys`, {
19
- search: keyName,
20
- pageSize: '100'
21
- });
22
- const key = result.data?.find((k) => k.key === keyName);
23
- return key || null;
24
- }
25
- /**
26
- * List translation keys
27
- */
28
- export async function listKeysCommand(projectSlug, options) {
29
- if (!isAuthenticated()) {
30
- console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
31
- process.exitCode = 1;
32
- return;
33
- }
34
- const orgId = config.get('organizationId');
35
- if (!orgId) {
36
- console.log(chalk.red('✗ Organization ID not found. Please run "langctl auth <api-key>" again.\n'));
37
- process.exitCode = 1;
38
- return;
39
- }
40
- const spinner = ora('Fetching keys...').start();
2
+ import { CliError, ExitCode, usageError } from '../core/errors.js';
3
+ import { getSession } from '../core/http.js';
4
+ import { log, printJson, runtime, spinner, table } from '../core/output.js';
5
+ import { confirm } from '../core/prompts.js';
6
+ import { assertLanguages, getProject, listAllKeys, projectSlugFrom, requireKey } from '../core/project.js';
7
+ export async function listKeysCommand(projectArg, opts) {
8
+ const session = await getSession();
9
+ const project = await getProject(session, projectSlugFrom(projectArg));
10
+ const spin = spinner('Fetching keys…');
11
+ let keys;
41
12
  try {
42
- const project = await resolveProject(orgId, projectSlug);
43
- const limit = Math.max(1, parseInt(options.limit || '100', 10) || 100);
44
- const offset = Math.max(0, parseInt(options.offset || '0', 10) || 0);
45
- const skipWithinPage = offset % limit;
46
- const page = Math.floor(offset / limit) + 1;
47
- const pageSize = Math.min(100, limit + skipWithinPage);
48
- const api = getApiClient();
49
- const params = {
50
- page: String(page),
51
- pageSize: String(pageSize)
52
- };
53
- if (options.module)
54
- params.module = options.module;
55
- if (options.search)
56
- params.search = options.search;
57
- if (options.published)
58
- params.published = 'true';
59
- const result = await api.get(`/orgs/${orgId}/projects/${project.id}/keys`, params);
60
- spinner.stop();
61
- let keys = result.data || [];
62
- if (skipWithinPage > 0) {
63
- keys = keys.slice(skipWithinPage);
64
- }
65
- keys = keys.slice(0, limit);
66
- const total = result.pagination?.total;
67
- if (!keys || keys.length === 0) {
68
- console.log(chalk.yellow('\nNo keys found\n'));
69
- return;
70
- }
71
- console.log(chalk.blue.bold(`\n🔑 Translation Keys (${keys.length}${total ? ` of ${total}` : ''})\n`));
72
- keys.forEach((key) => {
73
- console.log(chalk.white.bold(key.key));
74
- if (key.description) {
75
- console.log(chalk.gray(` Description: ${key.description}`));
76
- }
77
- if (key.module) {
78
- console.log(chalk.gray(` Module: ${key.module}`));
79
- }
80
- console.log(chalk.gray(` Published: ${key.published ? chalk.green('Yes') : chalk.yellow('No')}`));
81
- const langs = Object.keys(key.translations || {});
82
- if (langs.length > 0) {
83
- console.log(chalk.gray(` Languages: ${langs.join(', ')}`));
84
- }
85
- console.log('');
13
+ keys = await listAllKeys(session, project, {
14
+ module: opts.module,
15
+ search: opts.search,
16
+ published: opts.published ? true : opts.drafts ? false : undefined,
86
17
  });
87
18
  }
88
- catch (error) {
89
- spinner.fail(chalk.red('Failed to list keys'));
90
- console.error(chalk.red(`Error: ${error.message}\n`));
91
- process.exitCode = 1;
19
+ finally {
20
+ spin.stop();
92
21
  }
93
- }
94
- /**
95
- * Get single key
96
- */
97
- export async function getKeyCommand(projectSlug, keyName) {
98
- if (!isAuthenticated()) {
99
- console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
100
- process.exitCode = 1;
101
- return;
22
+ const limit = opts.limit ? Number(opts.limit) : undefined;
23
+ if (limit !== undefined && (!Number.isInteger(limit) || limit < 1))
24
+ throw usageError('--limit must be a positive integer');
25
+ const shown = limit ? keys.slice(0, limit) : keys;
26
+ if (runtime.json) {
27
+ return printJson(shown.map(k => ({
28
+ key: k.key, published: k.published, module: k.module ?? null, description: k.description ?? null, translations: k.translations,
29
+ })));
102
30
  }
103
- const orgId = config.get('organizationId');
104
- if (!orgId) {
105
- console.log(chalk.red('✗ Organization ID not found. Please run "langctl auth <api-key>" again.\n'));
106
- process.exitCode = 1;
31
+ if (shown.length === 0) {
32
+ log.info('No keys match.');
107
33
  return;
108
34
  }
109
- const spinner = ora('Fetching key...').start();
110
- try {
111
- const project = await resolveProject(orgId, projectSlug);
112
- const key = await findKeyByName(orgId, project.id, keyName);
113
- if (!key) {
114
- spinner.fail(chalk.red(`Key "${keyName}" not found`));
115
- return;
116
- }
117
- spinner.stop();
118
- console.log(chalk.blue.bold('\n🔑 Translation Key Details\n'));
119
- console.log(chalk.white.bold(`Key: ${key.key}`));
120
- console.log(chalk.gray(`ID: ${key.id}`));
121
- if (key.description) {
122
- console.log(chalk.gray(`Description: ${key.description}`));
123
- }
124
- if (key.module) {
125
- console.log(chalk.gray(`Module: ${key.module}`));
126
- }
127
- console.log(chalk.gray(`Published: ${key.published ? chalk.green('Yes') : chalk.yellow('No')}`));
128
- console.log(chalk.blue.bold('\nTranslations:'));
129
- Object.entries(key.translations || {}).forEach(([lang, value]) => {
130
- console.log(chalk.white(` ${lang}: ${value}`));
131
- });
132
- console.log('');
133
- }
134
- catch (error) {
135
- spinner.fail(chalk.red('Failed to get key'));
136
- console.error(chalk.red(`Error: ${error.message}\n`));
137
- process.exitCode = 1;
138
- }
35
+ table(shown.map(k => [
36
+ k.key,
37
+ k.published ? chalk.green('published') : chalk.yellow('draft'),
38
+ k.module ?? '',
39
+ `${project.languages.filter(l => k.translations?.[l]).length}/${project.languages.length}`,
40
+ ]), ['KEY', 'STATUS', 'MODULE', 'LANGS']);
41
+ if (limit && keys.length > limit)
42
+ log.info(chalk.dim(`Showing ${limit} of ${keys.length}. Use --json for scripting.`));
139
43
  }
140
- /**
141
- * Create translation key
142
- */
143
- export async function createKeyCommand(projectSlug, keyName, options) {
144
- if (!isAuthenticated()) {
145
- console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
146
- process.exitCode = 1;
147
- return;
148
- }
149
- const orgId = config.get('organizationId');
150
- if (!orgId) {
151
- console.log(chalk.red('✗ Organization ID not found. Please run "langctl auth <api-key>" again.\n'));
152
- process.exitCode = 1;
153
- return;
154
- }
155
- const spinner = ora('Creating key...').start();
156
- try {
157
- const project = await resolveProject(orgId, projectSlug);
158
- // Build translations object from --value-* options
159
- const translations = {};
160
- Object.keys(options).forEach(opt => {
161
- if (opt.startsWith('value')) {
162
- const lang = opt.replace('value', '').toLowerCase();
163
- if (lang) {
164
- translations[lang] = options[opt];
165
- }
166
- }
167
- });
168
- const api = getApiClient();
169
- await api.post(`/orgs/${orgId}/projects/${project.id}/keys`, {
170
- key: keyName,
171
- translations: Object.keys(translations).length > 0 ? translations : undefined,
172
- description: options.description,
173
- module: options.module,
174
- tags: options.tags ? options.tags.split(',') : undefined
175
- });
176
- spinner.succeed(chalk.green(`Created key: ${keyName}`));
177
- console.log('');
178
- }
179
- catch (error) {
180
- spinner.fail(chalk.red('Failed to create key'));
181
- console.error(chalk.red(`Error: ${error.message}\n`));
182
- process.exitCode = 1;
44
+ export async function getKeyCommand(projectArg, keyName) {
45
+ const session = await getSession();
46
+ const project = await getProject(session, projectArg);
47
+ const key = await requireKey(session, project, keyName);
48
+ if (runtime.json)
49
+ return printJson(key);
50
+ log.out(chalk.bold(key.key) + ' ' + (key.published ? chalk.green('published') : chalk.yellow('draft')));
51
+ if (key.description)
52
+ log.out(chalk.dim(key.description));
53
+ if (key.module)
54
+ log.out(chalk.dim(`module: ${key.module}`));
55
+ for (const lang of project.languages) {
56
+ const v = key.translations?.[lang];
57
+ log.out(` ${lang.padEnd(6)} ${v === undefined || v === '' ? chalk.dim('— missing') : v}`);
183
58
  }
184
59
  }
185
- /**
186
- * Delete translation key
187
- */
188
- export async function deleteKeyCommand(projectSlug, keyName) {
189
- if (!isAuthenticated()) {
190
- console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
191
- process.exitCode = 1;
192
- return;
193
- }
194
- const orgId = config.get('organizationId');
195
- if (!orgId) {
196
- console.log(chalk.red('✗ Organization ID not found. Please run "langctl auth <api-key>" again.\n'));
197
- process.exitCode = 1;
198
- return;
199
- }
200
- const spinner = ora('Deleting key...').start();
201
- try {
202
- const project = await resolveProject(orgId, projectSlug);
203
- const key = await findKeyByName(orgId, project.id, keyName);
204
- if (!key) {
205
- spinner.fail(chalk.red(`Key "${keyName}" not found`));
206
- return;
207
- }
208
- const api = getApiClient();
209
- await api.delete(`/orgs/${orgId}/projects/${project.id}/keys/${key.id}`);
210
- spinner.succeed(chalk.green(`Deleted key: ${keyName}`));
211
- console.log('');
212
- }
213
- catch (error) {
214
- spinner.fail(chalk.red('Failed to delete key'));
215
- console.error(chalk.red(`Error: ${error.message}\n`));
216
- process.exitCode = 1;
217
- }
60
+ /** Collect `--value en=Hello --value es=Hola` plus the legacy `--value-en` style flags. */
61
+ function collectValues(opts) {
62
+ const out = {};
63
+ for (const pair of opts.value ?? []) {
64
+ const i = pair.indexOf('=');
65
+ if (i < 1)
66
+ throw usageError(`--value expects LANG=TEXT, got "${pair}"`, 'Example: --value en="Welcome" --value es="Bienvenido"');
67
+ out[pair.slice(0, i).trim()] = pair.slice(i + 1);
68
+ }
69
+ for (const lang of ['en', 'es', 'fr', 'de']) {
70
+ const legacy = opts[`value${lang[0].toUpperCase()}${lang[1]}`];
71
+ if (typeof legacy === 'string')
72
+ out[lang] = legacy;
73
+ }
74
+ return out;
218
75
  }
219
- /**
220
- * Update single translation
221
- */
222
- export async function translateKeyCommand(projectSlug, keyName, options) {
223
- if (!isAuthenticated()) {
224
- console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
225
- process.exitCode = 1;
226
- return;
227
- }
228
- const orgId = config.get('organizationId');
229
- if (!orgId) {
230
- console.log(chalk.red('✗ Organization ID not found. Please run "langctl auth <api-key>" again.\n'));
231
- process.exitCode = 1;
232
- return;
233
- }
234
- const spinner = ora('Updating translation...').start();
235
- try {
236
- const project = await resolveProject(orgId, projectSlug);
237
- const key = await findKeyByName(orgId, project.id, keyName);
238
- if (!key) {
239
- spinner.fail(chalk.red(`Key "${keyName}" not found`));
240
- return;
241
- }
242
- const api = getApiClient();
243
- await api.patch(`/orgs/${orgId}/projects/${project.id}/keys/${key.id}/translations/${options.language}`, { value: options.value });
244
- spinner.succeed(chalk.green(`Updated ${options.language} translation for ${keyName}`));
245
- console.log('');
246
- }
247
- catch (error) {
248
- spinner.fail(chalk.red('Failed to update translation'));
249
- console.error(chalk.red(`Error: ${error.message}\n`));
250
- process.exitCode = 1;
251
- }
76
+ export async function createKeyCommand(projectArg, keyName, opts) {
77
+ const session = await getSession();
78
+ const project = await getProject(session, projectArg);
79
+ const translations = collectValues(opts);
80
+ assertLanguages(project, Object.keys(translations));
81
+ const key = await session.api.post(`/orgs/${session.orgId}/projects/${project.id}/keys`, {
82
+ key: keyName,
83
+ translations,
84
+ description: opts.description,
85
+ module: opts.module,
86
+ tags: typeof opts.tags === 'string' ? opts.tags.split(',').map(t => t.trim()).filter(Boolean) : undefined,
87
+ published: Boolean(opts.publish),
88
+ });
89
+ if (runtime.json)
90
+ return printJson(key);
91
+ log.success(`Created ${chalk.bold(keyName)}${opts.publish ? ' (published)' : ' (draft — publish with "langctl keys publish")'}`);
252
92
  }
253
- /**
254
- * Publish/unpublish keys
255
- */
256
- export async function publishKeysCommand(projectSlug, keys, options) {
257
- if (!isAuthenticated()) {
258
- console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
259
- process.exitCode = 1;
260
- return;
261
- }
262
- const orgId = config.get('organizationId');
263
- if (!orgId) {
264
- console.log(chalk.red('✗ Organization ID not found. Please run "langctl auth <api-key>" again.\n'));
265
- process.exitCode = 1;
266
- return;
267
- }
268
- const action = options.unpublish ? 'unpublish' : 'publish';
269
- const spinner = ora(`${action === 'publish' ? 'Publishing' : 'Unpublishing'} keys...`).start();
270
- try {
271
- const project = await resolveProject(orgId, projectSlug);
272
- // List all keys to match names to IDs
273
- const api = getApiClient();
274
- const result = await api.get(`/orgs/${orgId}/projects/${project.id}/keys`, {
275
- pageSize: '1000'
276
- });
277
- const keyIds = result.data
278
- ?.filter((k) => keys.includes(k.key))
279
- .map((k) => k.id) || [];
280
- if (keyIds.length === 0) {
281
- spinner.fail(chalk.red('No matching keys found'));
282
- return;
283
- }
284
- const response = await api.post(`/orgs/${orgId}/projects/${project.id}/keys/bulk-publish`, { keyIds, published: !options.unpublish });
285
- spinner.succeed(chalk.green(response.message || `${action === 'publish' ? 'Published' : 'Unpublished'} ${response.count} key(s)`));
286
- console.log('');
287
- }
288
- catch (error) {
289
- spinner.fail(chalk.red(`Failed to ${action} keys`));
290
- console.error(chalk.red(`Error: ${error.message}\n`));
291
- process.exitCode = 1;
93
+ export async function updateKeyCommand(projectArg, keyName, opts) {
94
+ const session = await getSession();
95
+ const project = await getProject(session, projectArg);
96
+ const key = await requireKey(session, project, keyName);
97
+ const translations = collectValues(opts);
98
+ // legacy: keys translate <project> <key> -l es -v "Hola"
99
+ if (typeof opts.language === 'string') {
100
+ if (typeof opts.text !== 'string')
101
+ throw usageError('--language needs --text (or use --value LANG=TEXT)');
102
+ translations[opts.language] = opts.text;
103
+ }
104
+ assertLanguages(project, Object.keys(translations));
105
+ const body = {};
106
+ if (Object.keys(translations).length)
107
+ body.translations = { ...key.translations, ...translations };
108
+ if (opts.description !== undefined)
109
+ body.description = opts.description;
110
+ if (opts.module !== undefined)
111
+ body.module = opts.module;
112
+ if (Object.keys(body).length === 0)
113
+ throw usageError('Nothing to update.', 'Pass --value LANG=TEXT, --description or --module.');
114
+ const updated = await session.api.patch(`/orgs/${session.orgId}/projects/${project.id}/keys/${key.id}`, body);
115
+ if (runtime.json)
116
+ return printJson(updated);
117
+ log.success(`Updated ${chalk.bold(keyName)}${Object.keys(translations).length ? ` (${Object.keys(translations).join(', ')})` : ''}`);
118
+ }
119
+ export async function deleteKeyCommand(projectArg, keyNames) {
120
+ const session = await getSession();
121
+ const project = await getProject(session, projectArg);
122
+ const keys = [];
123
+ for (const name of keyNames)
124
+ keys.push(await requireKey(session, project, name));
125
+ if (!(await confirm(`Delete ${keys.length === 1 ? `key "${keys[0].key}"` : `${keys.length} keys`} from ${project.slug}?`))) {
126
+ throw new CliError('Cancelled.', ExitCode.Error);
127
+ }
128
+ for (const k of keys)
129
+ await session.api.delete(`/orgs/${session.orgId}/projects/${project.id}/keys/${k.id}`);
130
+ if (runtime.json)
131
+ return printJson({ deleted: keys.map(k => k.key) });
132
+ log.success(`Deleted ${keys.map(k => chalk.bold(k.key)).join(', ')}`);
133
+ }
134
+ export async function publishKeysCommand(projectArg, keyNames, opts, publish = true) {
135
+ const published = publish && !opts.unpublish;
136
+ const session = await getSession();
137
+ const project = await getProject(session, projectSlugFrom(projectArg));
138
+ let names = keyNames;
139
+ if (opts.all || opts.module) {
140
+ if (names.length)
141
+ throw usageError('Pass key names or --all/--module, not both.');
142
+ names = (await listAllKeys(session, project, { module: opts.module, published: !published })).map(k => k.key);
143
+ }
144
+ else if (names.length === 0) {
145
+ throw usageError('No keys given.', `Example: langctl keys ${published ? 'publish' : 'unpublish'} ${project.slug} home.title home.subtitle (or --all / --module <name>)`);
146
+ }
147
+ if (names.length === 0) {
148
+ if (runtime.json)
149
+ return printJson({ count: 0, notFound: [] });
150
+ return log.info(`Nothing to ${published ? 'publish' : 'unpublish'}.`);
151
+ }
152
+ const res = await session.api.post(`/orgs/${session.orgId}/projects/${project.id}/keys/bulk-publish`, { keys: names, published }, true);
153
+ if (runtime.json)
154
+ printJson(res);
155
+ else
156
+ log.success(`${published ? 'Published' : 'Unpublished'} ${res.count} key(s)`);
157
+ if (res.notFound?.length) {
158
+ throw new CliError(`Not found: ${res.notFound.join(', ')}`, ExitCode.NotFound);
292
159
  }
293
160
  }
294
- //# sourceMappingURL=keys.js.map
@@ -1,118 +1,52 @@
1
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
- * Get organization information
8
- */
2
+ import { getSession } from '../core/http.js';
3
+ import { log, printJson, runtime, table } from '../core/output.js';
4
+ const limit = (n) => (n === null || n === undefined ? 'unlimited' : String(n));
9
5
  export async function orgInfoCommand() {
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 organization info...').start();
22
- try {
23
- const api = getApiClient();
24
- const org = await api.get(`/orgs/${orgId}`);
25
- spinner.stop();
26
- console.log(chalk.blue.bold('\n🏢 Organization Information\n'));
27
- console.log(chalk.white.bold(org.name));
28
- console.log(chalk.gray(`ID: ${org.id}`));
29
- console.log(chalk.gray(`Slug: ${org.slug}`));
30
- console.log(chalk.gray(`Plan: ${org.plan}`));
31
- console.log(chalk.gray(`Created: ${new Date(org.createdAt).toLocaleDateString()}`));
32
- console.log('');
33
- }
34
- catch (error) {
35
- spinner.fail(chalk.red('Failed to fetch organization info'));
36
- console.error(chalk.red(`Error: ${error.message}\n`));
37
- process.exitCode = 1;
38
- }
6
+ const session = await getSession();
7
+ const org = await session.api.get(`/orgs/${session.orgId}`);
8
+ const { id, name, slug, plan, createdAt } = org;
9
+ if (runtime.json)
10
+ return printJson({ id, name, slug, plan, createdAt });
11
+ log.out(`${chalk.bold(name)} ${chalk.dim(`(${slug})`)} ${plan} plan`);
12
+ log.out(chalk.dim(`id ${id} · created ${createdAt.slice(0, 10)}`));
39
13
  }
40
- /**
41
- * Get organization statistics
42
- */
43
14
  export async function orgStatsCommand() {
44
- if (!isAuthenticated()) {
45
- console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
46
- process.exitCode = 1;
47
- return;
48
- }
49
- const orgId = config.get('organizationId');
50
- if (!orgId) {
51
- console.log(chalk.red('✗ No organization configured. Please run "langctl init" first.\n'));
52
- process.exitCode = 1;
53
- return;
54
- }
55
- const spinner = ora('Fetching organization statistics...').start();
56
- try {
57
- const api = getApiClient();
58
- const stats = await api.get(`/orgs/${orgId}/stats`);
59
- spinner.stop();
60
- console.log(chalk.blue.bold('\n📊 Organization Statistics\n'));
61
- console.log(chalk.white.bold('Team'));
62
- console.log(chalk.gray(` Members: ${stats.memberCount}`));
63
- console.log('');
64
- console.log(chalk.white.bold('Projects'));
65
- console.log(chalk.gray(` Total Projects: ${stats.projectCount}`));
66
- console.log('');
67
- console.log(chalk.white.bold('Translation Keys'));
68
- console.log(chalk.gray(` Total Keys: ${stats.totalKeys}`));
69
- console.log('');
70
- console.log(chalk.white.bold('Resources'));
71
- console.log(chalk.gray(` API Keys: ${stats.apiKeyCount}`));
72
- console.log('');
73
- }
74
- catch (error) {
75
- spinner.fail(chalk.red('Failed to fetch statistics'));
76
- console.error(chalk.red(`Error: ${error.message}\n`));
77
- process.exitCode = 1;
78
- }
15
+ const session = await getSession();
16
+ const stats = await session.api.get(`/orgs/${session.orgId}/stats`);
17
+ if (runtime.json)
18
+ return printJson(stats);
19
+ table([
20
+ ['Members', String(stats.memberCount)],
21
+ ['Projects', String(stats.projectCount)],
22
+ ['Translation keys', String(stats.totalKeys)],
23
+ ['Active API keys', String(stats.apiKeyCount)],
24
+ ]);
79
25
  }
80
- /**
81
- * Get organization plan and limits
82
- */
83
26
  export async function orgPlanCommand() {
84
- if (!isAuthenticated()) {
85
- console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
86
- process.exitCode = 1;
87
- return;
88
- }
89
- const orgId = config.get('organizationId');
90
- if (!orgId) {
91
- console.log(chalk.red('✗ No organization configured. Please run "langctl init" first.\n'));
92
- process.exitCode = 1;
93
- return;
94
- }
95
- const spinner = ora('Fetching plan information...').start();
96
- try {
97
- const api = getApiClient();
98
- const org = await api.get(`/orgs/${orgId}`);
99
- spinner.stop();
100
- console.log(chalk.blue.bold('\n💎 Subscription Plan\n'));
101
- console.log(chalk.white.bold(`Current Plan: ${org.plan.toUpperCase()}`));
102
- console.log('');
103
- console.log(chalk.white.bold('Plan Limits'));
104
- // Display limits with "Unlimited" for -1 or null values
105
- const formatLimit = (value) => (value === -1 || value === null) ? chalk.green('Unlimited') : chalk.gray(value.toString());
106
- console.log(chalk.gray(` Max Members: ${formatLimit(org.maxMembers)}`));
107
- console.log(chalk.gray(` Max Projects: ${formatLimit(org.maxProjects)}`));
108
- console.log(chalk.gray(` Max Keys per Project: ${formatLimit(org.maxKeysPerProject)}`));
109
- console.log(chalk.gray(` Max API Keys: ${formatLimit(org.maxApiKeys)}`));
110
- console.log('');
111
- }
112
- catch (error) {
113
- spinner.fail(chalk.red('Failed to fetch plan information'));
114
- console.error(chalk.red(`Error: ${error.message}\n`));
115
- process.exitCode = 1;
116
- }
27
+ const session = await getSession();
28
+ const [org, stats] = await Promise.all([
29
+ session.api.get(`/orgs/${session.orgId}`),
30
+ session.api.get(`/orgs/${session.orgId}/stats`),
31
+ ]);
32
+ const rows = {
33
+ plan: org.plan,
34
+ members: { used: stats.memberCount, limit: org.maxMembers ?? null },
35
+ projects: { used: stats.projectCount, limit: org.maxProjects ?? null },
36
+ apiKeys: { used: stats.apiKeyCount, limit: org.maxApiKeys ?? null },
37
+ keysPerProject: { limit: org.maxKeysPerProject ?? null },
38
+ languagesPerProject: { limit: org.maxLanguagesPerProject ?? null },
39
+ aiTranslationsPerMonth: { used: org.aiTranslationsUsedThisMonth ?? 0, limit: org.aiTranslationsLimit ?? null },
40
+ };
41
+ if (runtime.json)
42
+ return printJson(rows);
43
+ log.out(`${chalk.bold(org.plan.toUpperCase())} plan`);
44
+ table([
45
+ ['Members', `${stats.memberCount} / ${limit(org.maxMembers)}`],
46
+ ['Projects', `${stats.projectCount} / ${limit(org.maxProjects)}`],
47
+ ['API keys', `${stats.apiKeyCount} / ${limit(org.maxApiKeys)}`],
48
+ ['Keys per project', limit(org.maxKeysPerProject)],
49
+ ['Languages per project', limit(org.maxLanguagesPerProject)],
50
+ ['AI translations / month', `${org.aiTranslationsUsedThisMonth ?? 0} / ${limit(org.aiTranslationsLimit)}`],
51
+ ]);
117
52
  }
118
- //# sourceMappingURL=org.js.map