@newpeak/barista-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/.eslintrc.json +23 -0
  2. package/.prettierrc +9 -0
  3. package/.sisyphus/notepads/liberica-employees/learnings.md +73 -0
  4. package/AGENTS.md +270 -0
  5. package/CONTRIBUTING.md +291 -0
  6. package/README.md +707 -0
  7. package/bin/barista +6 -0
  8. package/bin/barista.js +3 -0
  9. package/docs/ARCHITECTURE.md +184 -0
  10. package/docs/COMMANDS.md +352 -0
  11. package/docs/COMMAND_DESIGN_SPEC.md +811 -0
  12. package/docs/INTEGRATION_NOTES.md +270 -0
  13. package/docs/commands/REFERENCE.md +297 -0
  14. package/docs/commands/arabica/auth/index.md +296 -0
  15. package/docs/commands/liberica/auth/index.md +133 -0
  16. package/docs/commands/liberica/context/index.md +60 -0
  17. package/docs/commands/liberica/employees/create.md +185 -0
  18. package/docs/commands/liberica/employees/disable.md +138 -0
  19. package/docs/commands/liberica/employees/enable.md +137 -0
  20. package/docs/commands/liberica/employees/get.md +153 -0
  21. package/docs/commands/liberica/employees/list.md +168 -0
  22. package/docs/commands/liberica/employees/update.md +180 -0
  23. package/docs/commands/liberica/orgs/list.md +62 -0
  24. package/docs/commands/liberica/positions/list.md +61 -0
  25. package/docs/commands/liberica/roles/list.md +67 -0
  26. package/docs/commands/liberica/users/create.md +170 -0
  27. package/docs/commands/liberica/users/get.md +151 -0
  28. package/docs/commands/liberica/users/list.md +175 -0
  29. package/package.json +37 -0
  30. package/src/commands/arabica/auth/index.ts +277 -0
  31. package/src/commands/arabica/auth/login.ts +5 -0
  32. package/src/commands/arabica/auth/logout.ts +5 -0
  33. package/src/commands/arabica/auth/register.ts +5 -0
  34. package/src/commands/arabica/auth/status.ts +5 -0
  35. package/src/commands/arabica/index.ts +23 -0
  36. package/src/commands/auth.ts +107 -0
  37. package/src/commands/context.ts +60 -0
  38. package/src/commands/liberica/auth/index.ts +170 -0
  39. package/src/commands/liberica/context/index.ts +43 -0
  40. package/src/commands/liberica/employees/create.ts +275 -0
  41. package/src/commands/liberica/employees/delete.ts +122 -0
  42. package/src/commands/liberica/employees/disable.ts +97 -0
  43. package/src/commands/liberica/employees/enable.ts +97 -0
  44. package/src/commands/liberica/employees/get.ts +115 -0
  45. package/src/commands/liberica/employees/index.ts +23 -0
  46. package/src/commands/liberica/employees/list.ts +131 -0
  47. package/src/commands/liberica/employees/update.ts +157 -0
  48. package/src/commands/liberica/index.ts +36 -0
  49. package/src/commands/liberica/orgs/index.ts +35 -0
  50. package/src/commands/liberica/positions/index.ts +30 -0
  51. package/src/commands/liberica/roles/index.ts +59 -0
  52. package/src/commands/liberica/users/create.ts +132 -0
  53. package/src/commands/liberica/users/delete.ts +49 -0
  54. package/src/commands/liberica/users/disable.ts +41 -0
  55. package/src/commands/liberica/users/enable.ts +30 -0
  56. package/src/commands/liberica/users/get.ts +46 -0
  57. package/src/commands/liberica/users/index.ts +27 -0
  58. package/src/commands/liberica/users/list.ts +68 -0
  59. package/src/commands/liberica/users/me.ts +42 -0
  60. package/src/commands/liberica/users/reset-password.ts +42 -0
  61. package/src/commands/liberica/users/update.ts +48 -0
  62. package/src/core/api/client.ts +825 -0
  63. package/src/core/auth/token-manager.ts +183 -0
  64. package/src/core/config/manager.ts +164 -0
  65. package/src/index.ts +37 -0
  66. package/src/types/employee.ts +102 -0
  67. package/src/types/index.ts +75 -0
  68. package/src/types/org.ts +25 -0
  69. package/src/types/position.ts +24 -0
  70. package/src/types/user.ts +64 -0
  71. package/tests/unit/commands/arabica/auth.test.ts +230 -0
  72. package/tests/unit/commands/liberica/auth.test.ts +175 -0
  73. package/tests/unit/commands/liberica/context.test.ts +98 -0
  74. package/tests/unit/commands/liberica/employees/create.test.ts +463 -0
  75. package/tests/unit/commands/liberica/employees/disable.test.ts +82 -0
  76. package/tests/unit/commands/liberica/employees/enable.test.ts +82 -0
  77. package/tests/unit/commands/liberica/employees/get.test.ts +111 -0
  78. package/tests/unit/commands/liberica/employees/list.test.ts +294 -0
  79. package/tests/unit/commands/liberica/employees/update.test.ts +210 -0
  80. package/tests/unit/config.test.ts +141 -0
  81. package/tests/unit/types.test.ts +195 -0
  82. package/tsconfig.json +20 -0
@@ -0,0 +1,60 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { configManager } from '../core/config/manager.js';
4
+ import { tokenManager } from '../core/auth/token-manager.js';
5
+ import { Environment } from '../types/index.js';
6
+
7
+ export function createContextCommand(): Command {
8
+ const contextCommand = new Command('context');
9
+ contextCommand.description('Manage CLI context (environment, service, tenant)');
10
+
11
+ contextCommand.action(async () => {
12
+ await contextCommand.help();
13
+ });
14
+
15
+ contextCommand
16
+ .command('show')
17
+ .description('Show current context')
18
+ .action(async () => {
19
+ const context = configManager.getCurrentContext();
20
+ const token = await tokenManager.getToken({
21
+ service: context.service,
22
+ environment: context.environment,
23
+ tenant: context.tenant,
24
+ });
25
+
26
+ console.log(chalk.bold('\n📋 Current Context\n'));
27
+ console.log(` ${chalk.gray('Environment:')} ${chalk.green(context.environment)}`);
28
+ console.log(` ${chalk.gray('Service:')} ${chalk.green(context.service)}`);
29
+ console.log(` ${chalk.gray('Tenant:')} ${chalk.green(context.tenant)}`);
30
+ console.log(
31
+ ` ${chalk.gray('Token Status:')} ${token ? chalk.green('✓ Logged in') : chalk.red('✗ Not logged in')}`
32
+ );
33
+ console.log();
34
+ });
35
+
36
+ contextCommand
37
+ .command('use-env <environment>')
38
+ .description('Switch environment (dev|test|prod-cn|prod-jp)')
39
+ .action(async (env: string) => {
40
+ const validEnvs: Environment[] = ['dev', 'test', 'prod-cn', 'prod-jp'];
41
+ if (!validEnvs.includes(env as Environment)) {
42
+ console.error(chalk.red(`Invalid environment: ${env}`));
43
+ console.log(chalk.gray(`Valid environments: ${validEnvs.join(', ')}`));
44
+ process.exit(1);
45
+ }
46
+
47
+ await configManager.setEnvironment(env as Environment);
48
+ console.log(chalk.green(`✓ Environment set to ${env}`));
49
+ });
50
+
51
+ contextCommand
52
+ .command('use-tenant <tenant>')
53
+ .description('Switch tenant')
54
+ .action(async (tenant: string) => {
55
+ await configManager.setTenant(tenant);
56
+ console.log(chalk.green(`✓ Tenant set to ${tenant}`));
57
+ });
58
+
59
+ return contextCommand;
60
+ }
@@ -0,0 +1,170 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import inquirer from 'inquirer';
4
+ import { configManager } from '../../../core/config/manager.js';
5
+ import { tokenManager } from '../../../core/auth/token-manager.js';
6
+ import { apiClient } from '../../../core/api/client.js';
7
+ import { Environment } from '../../../types/index.js';
8
+
9
+ export function createLibericaAuthCommand(): Command {
10
+ const authCommand = new Command('auth');
11
+ authCommand.description('Manage Liberica authentication');
12
+
13
+ authCommand.action(async () => {
14
+ await authCommand.help();
15
+ });
16
+
17
+ authCommand
18
+ .command('login')
19
+ .description('Login to Liberica')
20
+ .arguments('<env> [tenant] [username] [password]')
21
+ .option('--env <environment>', 'Environment (dev|test|prod-cn|prod-jp)')
22
+ .option('--tenant <tenant>', 'Tenant name')
23
+ .option('--username <username>', 'Username')
24
+ .option('--password <password>', 'Password')
25
+ .action(async (env: string, tenant: string, username: string, password: string, options: Record<string, any>) => {
26
+ const context = configManager.getCurrentContext();
27
+ const environment = (options.env as Environment) || env || context.environment;
28
+ let resolvedTenant = options.tenant || tenant || context.tenant;
29
+
30
+ console.log(chalk.bold(`\n🔐 Login to Liberica (${environment})\n`));
31
+
32
+ if (!resolvedTenant) {
33
+ const { tenant: inputTenant } = await inquirer.prompt([
34
+ {
35
+ type: 'input',
36
+ name: 'tenant',
37
+ message: 'Enter tenant name:',
38
+ validate: (input: string) => input.length > 0 || 'Tenant is required',
39
+ },
40
+ ]);
41
+ resolvedTenant = inputTenant;
42
+ }
43
+
44
+ let resolvedUsername = options.username || username;
45
+ let resolvedPassword = options.password || password;
46
+
47
+ if (!resolvedUsername) {
48
+ const { username: inputUsername } = await inquirer.prompt([
49
+ {
50
+ type: 'input',
51
+ name: 'username',
52
+ message: 'Enter username:',
53
+ validate: (input: string) => input.length > 0 || 'Username is required',
54
+ },
55
+ ]);
56
+ resolvedUsername = inputUsername;
57
+ }
58
+
59
+ if (!resolvedPassword) {
60
+ const { password: inputPassword } = await inquirer.prompt([
61
+ {
62
+ type: 'password',
63
+ name: 'password',
64
+ message: 'Enter password:',
65
+ validate: (input: string) => input.length > 0 || 'Password is required',
66
+ },
67
+ ]);
68
+ resolvedPassword = inputPassword;
69
+ }
70
+
71
+ try {
72
+ const response = await apiClient.login('liberica', environment, resolvedTenant, resolvedUsername, resolvedPassword);
73
+
74
+ if (response.success && response.data) {
75
+ try {
76
+ await tokenManager.setToken(
77
+ {
78
+ service: 'liberica',
79
+ environment,
80
+ tenant: resolvedTenant,
81
+ },
82
+ response.data.token
83
+ );
84
+ } catch (keytarError) {
85
+ console.error(chalk.red(`\n✗ Failed to save token to keychain: ${keytarError instanceof Error ? keytarError.message : 'Unknown error'}`));
86
+ console.error(chalk.gray(' This may occur in headless environments without D-Bus/X11.'));
87
+ console.error(chalk.gray(' The login was successful but the token could not be persisted.\n'));
88
+ process.exit(1);
89
+ }
90
+
91
+ await configManager.setTenant(resolvedTenant);
92
+
93
+ console.log(chalk.green('\n✓ Login successful'));
94
+ if (response.data.expiresAt) {
95
+ console.log(chalk.gray(` Token expires: ${response.data.expiresAt}`));
96
+ }
97
+ console.log();
98
+ } else {
99
+ console.error(chalk.red(`\n✗ Login failed: ${response.error?.message || 'Unknown error'}`));
100
+ if (response.error?.code) {
101
+ console.error(chalk.gray(` Error code: ${response.error.code}`));
102
+ }
103
+ console.error();
104
+ process.exit(1);
105
+ }
106
+ } catch (error) {
107
+ console.error(chalk.red(`\n✗ Login error: ${error instanceof Error ? error.message : 'Unknown error'}\n`));
108
+ process.exit(1);
109
+ }
110
+ });
111
+
112
+ authCommand
113
+ .command('status')
114
+ .description('Check Liberica authentication status')
115
+ .action(async () => {
116
+ const environments: Environment[] = ['dev', 'test', 'prod-cn', 'prod-jp'];
117
+ const context = configManager.getCurrentContext();
118
+
119
+ console.log(chalk.bold('\n🔐 Liberica Authentication Status\n'));
120
+
121
+ for (const env of environments) {
122
+ const token = await tokenManager.getToken({ service: 'liberica', environment: env, tenant: context.tenant });
123
+ const statusIcon = token ? chalk.green('✓') : chalk.red('✗');
124
+ const statusText = token ? 'Logged in' : 'Not logged in';
125
+ const tenantText = context.tenant ? ` (${context.tenant})` : '';
126
+ console.log(` ${statusIcon} ${chalk.gray('liberica')}${tenantText} (${env}): ${statusText}`);
127
+ }
128
+ console.log();
129
+ });
130
+
131
+ authCommand
132
+ .command('logout')
133
+ .description('Logout from Liberica and clear token')
134
+ .option('--env <environment>', 'Environment (dev|test|prod-cn|prod-jp)')
135
+ .option('--all', 'Clear all Liberica tokens')
136
+ .action(async (options) => {
137
+ const context = configManager.getCurrentContext();
138
+
139
+ if (options.all) {
140
+ const allTokens = await tokenManager.findAllTokens();
141
+ for (const cred of allTokens) {
142
+ const parts = cred.account.split(':');
143
+ if (parts[0] === 'liberica') {
144
+ await tokenManager.deleteToken({
145
+ service: 'liberica',
146
+ environment: parts[1] as Environment,
147
+ tenant: parts[2],
148
+ });
149
+ }
150
+ }
151
+ console.log(chalk.green('\n✓ All Liberica tokens cleared\n'));
152
+ } else {
153
+ const environment = (options.env as Environment) || context.environment;
154
+
155
+ const deleted = await tokenManager.deleteToken({
156
+ service: 'liberica',
157
+ environment,
158
+ tenant: context.tenant,
159
+ });
160
+
161
+ if (deleted) {
162
+ console.log(chalk.green(`\n✓ Token cleared for Liberica (${environment})\n`));
163
+ } else {
164
+ console.log(chalk.gray(`\nNo token found for Liberica (${environment})\n`));
165
+ }
166
+ }
167
+ });
168
+
169
+ return authCommand;
170
+ }
@@ -0,0 +1,43 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { configManager } from '../../../core/config/manager.js';
4
+ import { tokenManager } from '../../../core/auth/token-manager.js';
5
+
6
+ export function createLibericaContextCommand(): Command {
7
+ const contextCommand = new Command('context');
8
+ contextCommand.description('Manage Liberica context (tenant, environment)');
9
+
10
+ contextCommand.action(async () => {
11
+ await contextCommand.help();
12
+ });
13
+
14
+ contextCommand
15
+ .command('show')
16
+ .description('Show current Liberica context')
17
+ .action(async () => {
18
+ const context = configManager.getCurrentContext();
19
+ const token = await tokenManager.getToken({
20
+ service: 'liberica',
21
+ environment: context.environment,
22
+ tenant: context.tenant,
23
+ });
24
+
25
+ console.log(chalk.bold('\n📋 Liberica Context\n'));
26
+ console.log(` ${chalk.gray('Environment:')} ${chalk.green(context.environment)}`);
27
+ console.log(` ${chalk.gray('Tenant:')} ${chalk.green(context.tenant)}`);
28
+ console.log(
29
+ ` ${chalk.gray('Auth Status:')} ${token ? chalk.green('✓ Logged in') : chalk.red('✗ Not logged in')}`
30
+ );
31
+ console.log();
32
+ });
33
+
34
+ contextCommand
35
+ .command('use-enterprise <tenant>')
36
+ .description('Switch Liberica tenant (enterprise)')
37
+ .action(async (tenant: string) => {
38
+ await configManager.setTenant(tenant);
39
+ console.log(chalk.green(`\n✓ Tenant set to ${tenant}\n`));
40
+ });
41
+
42
+ return contextCommand;
43
+ }
@@ -0,0 +1,275 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import inquirer from 'inquirer';
4
+ import { apiClient } from '../../../core/api/client.js';
5
+ import { configManager } from '../../../core/config/manager.js';
6
+ import { CreateEmployeeRequest } from '../../../types/employee.js';
7
+
8
+ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
9
+ const PHONE_REGEX = /^[\d\s\-()+]+$/;
10
+ const DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/;
11
+ const SEX_REGEX = /^[MF]$/;
12
+
13
+ function validateFields(data: Partial<CreateEmployeeRequest>): string | null {
14
+ if (data.employeeEmail && !EMAIL_REGEX.test(data.employeeEmail)) {
15
+ return 'Invalid email format. Please provide a valid email address.';
16
+ }
17
+ if (data.employeePhone && !PHONE_REGEX.test(data.employeePhone)) {
18
+ return 'Invalid phone format. Phone must contain only digits, spaces, hyphens, or parentheses.';
19
+ }
20
+ if (data.employeeJoinedDate && !DATE_REGEX.test(data.employeeJoinedDate)) {
21
+ return 'Invalid date format. Date must be in YYYY-MM-DD format.';
22
+ }
23
+ if (data.employeeSex && !SEX_REGEX.test(data.employeeSex)) {
24
+ return 'Invalid sex value. Must be M or F.';
25
+ }
26
+ return null;
27
+ }
28
+
29
+ function formatTable(data: CreateEmployeeRequest): string {
30
+ const lines: string[] = [];
31
+ lines.push(` ${chalk.gray('Name:')} ${data.employeeName}`);
32
+ lines.push(` ${chalk.gray('Employee No:')} ${data.employeeNo}`);
33
+ if (data.employeeCode) lines.push(` ${chalk.gray('Code:')} ${data.employeeCode}`);
34
+ if (data.employeePhone) lines.push(` ${chalk.gray('Phone:')} ${data.employeePhone}`);
35
+ if (data.employeeEmail) lines.push(` ${chalk.gray('Email:')} ${data.employeeEmail}`);
36
+ if (data.employeeSex) lines.push(` ${chalk.gray('Sex:')} ${data.employeeSex}`);
37
+ if (data.organizationId) lines.push(` ${chalk.gray('Org ID:')} ${data.organizationId}`);
38
+ if (data.positionId) lines.push(` ${chalk.gray('Position ID:')} ${data.positionId}`);
39
+ if (data.employeeJoinedDate) lines.push(` ${chalk.gray('Joined Date:')} ${data.employeeJoinedDate}`);
40
+ return lines.join('\n');
41
+ }
42
+
43
+ export function createEmployeeCreateCommand(): Command {
44
+ const cmd = new Command('create');
45
+ cmd.description('Create a new employee');
46
+
47
+ cmd
48
+ .option('-n, --name <name>', 'Employee name (required)')
49
+ .option('-c, --code <code>', 'Employee code (optional, auto-generated if empty)')
50
+ .option('-p, --phone <phone>', 'Phone number')
51
+ .option('-e, --email <email>', 'Email address')
52
+ .option('-s, --sex <sex>', 'Sex (M or F)')
53
+ .option('--org <name>', 'Organization name')
54
+ .option('--position <name>', 'Position name')
55
+ .option('-d, --joined-date <date>', 'Joined date (YYYY-MM-DD)')
56
+ .option('--no <no>', 'Employee number (required)')
57
+ .option('--dry-run', 'Preview without making API call')
58
+ .option('--json', 'Output as JSON');
59
+
60
+ cmd.action(async () => {
61
+ const context = configManager.getCurrentContext();
62
+ const opts = cmd.opts();
63
+
64
+ let name = opts.name as string | undefined;
65
+ let no = (opts.no as string | undefined) || (opts['no'] as string | undefined);
66
+ const code = opts.code as string | undefined;
67
+ const phone = opts.phone as string | undefined;
68
+ const email = opts.email as string | undefined;
69
+ const sex = opts.sex as string | undefined;
70
+ const orgName = opts.org as string | undefined;
71
+ const positionName = opts.position as string | undefined;
72
+ const joinedDate = opts.joinedDate as string | undefined;
73
+ const dryRun = opts.dryRun === true;
74
+ const jsonOutput = opts.json === true;
75
+
76
+ if (!name) {
77
+ const { inputName } = await inquirer.prompt([
78
+ {
79
+ type: 'input',
80
+ name: 'inputName',
81
+ message: 'Enter employee name:',
82
+ validate: (input: string) => input.trim().length > 0 || 'Employee name is required',
83
+ },
84
+ ]);
85
+ name = inputName;
86
+ }
87
+
88
+ if (!no) {
89
+ const { inputNo } = await inquirer.prompt([
90
+ {
91
+ type: 'input',
92
+ name: 'inputNo',
93
+ message: 'Enter employee number:',
94
+ validate: (input: string) => input.trim().length > 0 || 'Employee number is required',
95
+ },
96
+ ]);
97
+ no = inputNo;
98
+ }
99
+
100
+ let resolvedCode = code;
101
+ if (!resolvedCode) {
102
+ const codeResponse = await apiClient.getCodeByType(context.environment, context.tenant, 'TenantEmployeeCode');
103
+ if (codeResponse.success && codeResponse.data) {
104
+ resolvedCode = codeResponse.data;
105
+ }
106
+ }
107
+
108
+ let resolvedOrgId: string | undefined;
109
+ if (orgName) {
110
+ const orgResponse = await apiClient.getOrgListName(context.environment, context.tenant);
111
+ if (!orgResponse.success || !orgResponse.data) {
112
+ const errMsg = orgResponse.error?.message || 'Failed to fetch organization list';
113
+ if (jsonOutput) {
114
+ console.log(JSON.stringify({ success: false, error: { code: orgResponse.error?.code || 'FETCH_ORG_ERROR', message: errMsg } }));
115
+ } else {
116
+ console.error(chalk.red(`\n✗ ${errMsg}\n`));
117
+ }
118
+ process.exit(1);
119
+ }
120
+ const found = orgResponse.data.find((o) => o.name === orgName);
121
+ if (!found) {
122
+ const errMsg = `Organization '${orgName}' not found`;
123
+ if (jsonOutput) {
124
+ console.log(JSON.stringify({ success: false, error: { code: 'ORG_NOT_FOUND', message: errMsg } }));
125
+ } else {
126
+ console.error(chalk.red(`\n✗ ${errMsg}\n`));
127
+ }
128
+ process.exit(1);
129
+ }
130
+ resolvedOrgId = found.id;
131
+ }
132
+
133
+ let resolvedPositionId: string | undefined;
134
+ if (positionName) {
135
+ const posResponse = await apiClient.listPositions(context.environment, context.tenant);
136
+ if (!posResponse.success || !posResponse.data) {
137
+ const errMsg = posResponse.error?.message || 'Failed to fetch position list';
138
+ if (jsonOutput) {
139
+ console.log(JSON.stringify({ success: false, error: { code: posResponse.error?.code || 'FETCH_POSITION_ERROR', message: errMsg } }));
140
+ } else {
141
+ console.error(chalk.red(`\n✗ ${errMsg}\n`));
142
+ }
143
+ process.exit(1);
144
+ }
145
+ const found = posResponse.data.find((p) => p.name === positionName);
146
+ if (!found) {
147
+ const errMsg = `Position '${positionName}' not found`;
148
+ if (jsonOutput) {
149
+ console.log(JSON.stringify({ success: false, error: { code: 'POSITION_NOT_FOUND', message: errMsg } }));
150
+ } else {
151
+ console.error(chalk.red(`\n✗ ${errMsg}\n`));
152
+ }
153
+ process.exit(1);
154
+ }
155
+ resolvedPositionId = found.id;
156
+ }
157
+
158
+ const data: CreateEmployeeRequest = {
159
+ employeeName: name!,
160
+ employeeNo: no!,
161
+ employeeCode: resolvedCode,
162
+ employeePhone: phone,
163
+ employeeEmail: email,
164
+ employeeSex: sex as 'M' | 'F' | undefined,
165
+ organizationId: resolvedOrgId,
166
+ positionId: resolvedPositionId,
167
+ employeeJoinedDate: joinedDate,
168
+ };
169
+
170
+ const validationError = validateFields(data);
171
+ if (validationError) {
172
+ if (jsonOutput) {
173
+ console.log(JSON.stringify({ success: false, error: { code: 'VALIDATION_ERROR', message: validationError } }));
174
+ } else {
175
+ console.error(chalk.red(`\n✗ Validation error: ${validationError}\n`));
176
+ }
177
+ process.exit(1);
178
+ }
179
+
180
+ if (dryRun) {
181
+ if (jsonOutput) {
182
+ console.log(JSON.stringify({ success: true, dryRun: true, data }));
183
+ } else {
184
+ console.log(chalk.bold('\n🔍 Dry-Run Mode: No changes will be made\n'));
185
+ console.log(' Employee to be created:');
186
+ console.log(formatTable(data));
187
+ console.log();
188
+ }
189
+ return;
190
+ }
191
+
192
+ const requestData: CreateEmployeeRequest = {
193
+ employeeName: data.employeeName,
194
+ employeeNo: data.employeeNo,
195
+ employeeCode: data.employeeCode,
196
+ employeePhone: data.employeePhone,
197
+ employeeEmail: data.employeeEmail,
198
+ employeeSex: data.employeeSex,
199
+ organizationId: data.organizationId,
200
+ positionId: data.positionId,
201
+ employeeJoinedDate: data.employeeJoinedDate,
202
+ };
203
+
204
+ if (!jsonOutput) {
205
+ console.log(chalk.bold('\n📝 Creating Employee\n'));
206
+ }
207
+
208
+ const response = await apiClient.createEmployee(context.environment, context.tenant, requestData);
209
+
210
+ if (!response.success) {
211
+ const errorMsg = response.error?.message || 'Failed to create employee';
212
+ const errorCode = response.error?.code || 'CREATE_EMPLOYEE_ERROR';
213
+ if (jsonOutput) {
214
+ console.log(JSON.stringify({ success: false, error: { code: errorCode, message: errorMsg } }));
215
+ } else {
216
+ console.error(chalk.red(`\n✗ Failed to create employee: ${errorMsg}`));
217
+ if (response.error?.code) {
218
+ console.error(chalk.gray(` Error code: ${response.error.code}`));
219
+ }
220
+ console.error();
221
+ }
222
+ process.exit(1);
223
+ }
224
+
225
+ if (response.success) {
226
+ const emp = response.data as {
227
+ employeeId?: number;
228
+ employeeCode?: string;
229
+ employeeName?: string;
230
+ employeeNo?: string;
231
+ employeePhone?: string;
232
+ employeeEmail?: string;
233
+ employeeSex?: string;
234
+ organizationId?: number;
235
+ orgName?: string;
236
+ positionId?: string;
237
+ positionName?: string;
238
+ statusFlag?: number;
239
+ createTime?: string;
240
+ } | undefined;
241
+
242
+ if (jsonOutput) {
243
+ console.log(JSON.stringify({ success: true, data: emp }));
244
+ } else {
245
+ console.log(chalk.green('✓ Employee created successfully\n'));
246
+ if (emp?.employeeId) console.log(` ${chalk.gray('Employee ID:')} ${chalk.green(String(emp.employeeId))}`);
247
+ if (emp?.employeeName) console.log(` ${chalk.gray('Name:')} ${emp.employeeName}`);
248
+ if (emp?.employeeNo) console.log(` ${chalk.gray('Employee No:')} ${emp.employeeNo}`);
249
+ if (emp?.employeeCode) console.log(` ${chalk.gray('Code:')} ${emp.employeeCode}`);
250
+ if (emp?.orgName) console.log(` ${chalk.gray('Organization:')} ${emp.orgName}`);
251
+ if (emp?.positionName) console.log(` ${chalk.gray('Position:')} ${emp.positionName}`);
252
+ if (emp?.statusFlag !== undefined) {
253
+ const statusText = emp.statusFlag === 1 ? 'Enabled' : 'Disabled';
254
+ console.log(` ${chalk.gray('Status:')} ${chalk.green(statusText)}`);
255
+ }
256
+ console.log();
257
+ }
258
+ } else {
259
+ const errorMsg = response.error?.message || 'Failed to create employee';
260
+ const errorCode = response.error?.code || 'CREATE_EMPLOYEE_ERROR';
261
+ if (jsonOutput) {
262
+ console.log(JSON.stringify({ success: false, error: { code: errorCode, message: errorMsg } }));
263
+ } else {
264
+ console.error(chalk.red(`\n✗ Failed to create employee: ${errorMsg}`));
265
+ if (response.error?.code) {
266
+ console.error(chalk.gray(` Error code: ${response.error.code}`));
267
+ }
268
+ console.error();
269
+ }
270
+ process.exit(1);
271
+ }
272
+ });
273
+
274
+ return cmd;
275
+ }
@@ -0,0 +1,122 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import inquirer from 'inquirer';
4
+ import { configManager } from '../../../core/config/manager.js';
5
+ import { apiClient } from '../../../core/api/client.js';
6
+ import { Environment } from '../../../types/index.js';
7
+ import { Employee } from '../../../types/employee.js';
8
+
9
+ export function createEmployeeDeleteCommand(): Command {
10
+ const deleteCommand = new Command('delete');
11
+ deleteCommand
12
+ .description('Delete an employee')
13
+ .argument('<id>', 'Employee ID')
14
+ .option('--force', 'Skip confirmation prompt')
15
+ .option('--dry-run', 'Preview the operation without executing')
16
+ .option('--json', 'Output as JSON')
17
+ .action(async (id: string, options: Record<string, unknown>) => {
18
+ const context = configManager.getCurrentContext();
19
+ const environment = context.environment as Environment;
20
+ const tenant = context.tenant;
21
+ const jsonOutput = options.json === true;
22
+
23
+ const employeeId = id;
24
+
25
+ if (!jsonOutput) {
26
+ console.log(chalk.bold(`\n🗑️ Delete Employee (${environment})\n`));
27
+ console.log(chalk.gray(` Employee ID: ${employeeId}`));
28
+ }
29
+
30
+ if (options.dryRun) {
31
+ if (jsonOutput) {
32
+ console.log(JSON.stringify({ success: true, dryRun: true, employeeId }));
33
+ } else {
34
+ console.log(chalk.cyan('\n🔍 Dry-Run Mode: No actual API call will be made\n'));
35
+ console.log(chalk.gray(' This operation will delete the employee.'));
36
+ console.log();
37
+ }
38
+ return;
39
+ }
40
+
41
+ let employee: Employee | undefined;
42
+ try {
43
+ const employeeResponse = await apiClient.getEmployee(environment, tenant, employeeId);
44
+ if (!employeeResponse.success || !employeeResponse.data) {
45
+ const errMsg = `Employee not found: ${employeeId}`;
46
+ if (jsonOutput) {
47
+ console.log(JSON.stringify({ success: false, error: { code: 'NOT_FOUND', message: errMsg } }));
48
+ } else {
49
+ console.error(chalk.red(`\n✗ ${errMsg}\n`));
50
+ }
51
+ process.exit(1);
52
+ }
53
+ employee = employeeResponse.data as Employee;
54
+ } catch (error) {
55
+ const errMsg = error instanceof Error ? error.message : 'Unknown error';
56
+ if (jsonOutput) {
57
+ console.log(JSON.stringify({ success: false, error: { code: 'FETCH_ERROR', message: `Failed to fetch employee: ${errMsg}` } }));
58
+ } else {
59
+ console.error(chalk.red(`\n✗ Failed to fetch employee: ${errMsg}\n`));
60
+ }
61
+ process.exit(1);
62
+ }
63
+
64
+ if (!options.force) {
65
+ if (!jsonOutput) {
66
+ console.log(chalk.gray(` Name: ${employee.employeeName}`));
67
+ console.log(chalk.gray(` Code: ${employee.employeeNo}`));
68
+ }
69
+
70
+ const { confirm } = await inquirer.prompt([
71
+ {
72
+ type: 'confirm',
73
+ name: 'confirm',
74
+ message: `Delete employee "${employee.employeeName}"? This action cannot be undone.`,
75
+ default: false,
76
+ },
77
+ ]);
78
+
79
+ if (!confirm) {
80
+ if (!jsonOutput) {
81
+ console.log(chalk.gray('\n Operation cancelled.\n'));
82
+ }
83
+ process.exit(0);
84
+ }
85
+ }
86
+
87
+ try {
88
+ const response = await apiClient.deleteEmployee(environment, tenant, employeeId);
89
+
90
+ if (response.success) {
91
+ if (jsonOutput) {
92
+ console.log(JSON.stringify({ success: true, employeeId, name: employee.employeeName }));
93
+ } else {
94
+ console.log(chalk.green(`\n✓ Employee "${employee.employeeName}" (ID: ${employeeId}) deleted\n`));
95
+ }
96
+ } else {
97
+ const errorMsg = response.error?.message || 'Unknown error';
98
+ const errorCode = response.error?.code || 'DELETE_ERROR';
99
+ if (jsonOutput) {
100
+ console.log(JSON.stringify({ success: false, error: { code: errorCode, message: errorMsg } }));
101
+ } else {
102
+ console.error(chalk.red(`\n✗ Failed to delete employee: ${errorMsg}`));
103
+ if (response.error?.code) {
104
+ console.error(chalk.gray(` Error code: ${response.error.code}`));
105
+ }
106
+ console.error();
107
+ }
108
+ process.exit(1);
109
+ }
110
+ } catch (error) {
111
+ const errMsg = error instanceof Error ? error.message : 'Unknown error';
112
+ if (jsonOutput) {
113
+ console.log(JSON.stringify({ success: false, error: { code: 'ERROR', message: errMsg } }));
114
+ } else {
115
+ console.error(chalk.red(`\n✗ Error: ${errMsg}\n`));
116
+ }
117
+ process.exit(1);
118
+ }
119
+ });
120
+
121
+ return deleteCommand;
122
+ }