@kikkimo/claude-launcher 2.5.0 → 3.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.
@@ -5,6 +5,7 @@
5
5
 
6
6
  const readline = require('readline');
7
7
  const stdinManager = require('../utils/stdin-manager');
8
+ const screen = require('../ui/screen');
8
9
 
9
10
  /**
10
11
  * Get password input with proper masking (no plaintext display)
@@ -18,7 +19,7 @@ function getPasswordInput(prompt) {
18
19
  let cleanedUp = false;
19
20
 
20
21
  // Display prompt - this is necessary for password input
21
- process.stdout.write(prompt);
22
+ screen.write(prompt);
22
23
 
23
24
  if (!process.stdin.isTTY) {
24
25
  // Non-TTY environment fallback - use scope's createReadline
@@ -52,11 +53,14 @@ function getPasswordInput(prompt) {
52
53
  return;
53
54
  }
54
55
 
56
+ screen.showCursor();
57
+
55
58
  const cleanup = () => {
56
59
  if (cleanedUp) return;
57
60
  cleanedUp = true;
58
61
 
59
62
  try {
63
+ screen.hideCursor();
60
64
  scope.removeAllListeners('data');
61
65
  scope.release();
62
66
  } catch (error) {
@@ -85,7 +89,7 @@ function getPasswordInput(prompt) {
85
89
  case '\r': // Enter (CR)
86
90
  case '\n': // Line Feed (LF)
87
91
  case '\r\n': // CRLF
88
- process.stdout.write('\n');
92
+ screen.write('\n');
89
93
  cleanup();
90
94
  resolve(password);
91
95
  return;
@@ -95,7 +99,7 @@ function getPasswordInput(prompt) {
95
99
  if (password.length > 0) {
96
100
  password = password.slice(0, -1);
97
101
  // Clear the last asterisk
98
- process.stdout.write('\b \b');
102
+ screen.write('\b \b');
99
103
  }
100
104
  return;
101
105
 
@@ -114,7 +118,7 @@ function getPasswordInput(prompt) {
114
118
  // Filter out control characters (only accept ASCII printable)
115
119
  if (charCode >= 32 && charCode < 127) {
116
120
  password += char;
117
- process.stdout.write('*');
121
+ screen.write('*');
118
122
  }
119
123
  }
120
124
  return;
@@ -6,7 +6,9 @@
6
6
  const { getPasswordInput } = require('./password-input');
7
7
  const colors = require('../ui/colors');
8
8
  const { validatePasswordStrength, getPasswordRequirements, generatePasswordExample } = require('./password-strength');
9
+ const { waitForKey } = require('../ui/prompts');
9
10
  const i18n = require('../i18n');
11
+ const screen = require('../ui/screen');
10
12
 
11
13
  /**
12
14
  * Force cleanup stdin state to prevent navigation issues
@@ -43,36 +45,70 @@ function getTranslatedStrength(strength) {
43
45
  }
44
46
 
45
47
  /**
46
- * Verify user password for export/import operations
47
- * @param {Object} apiManager - The API manager instance
48
- * @param {string} operation - The operation being performed (for error messages)
49
- * @returns {Promise<boolean>} - True if password is verified, false otherwise
48
+ * Unified password guard for protected operations
49
+ * Mode A (delete/edit): clears screen, shows header, prompts password
50
+ * Mode B (export/import): no clear, no header, just prompts password
51
+ * @param {Object} apiManager - ApiManager instance
52
+ * @param {string} operation - 'delete' | 'edit' | 'export' | 'import'
53
+ * @returns {Promise<boolean>} true if authorized, false if denied/cancelled
50
54
  */
51
- async function verifyExportPassword(apiManager, operation = 'operation') {
55
+ async function passwordGuard(apiManager, operation) {
56
+ const hasPassword = apiManager.hasExportPassword();
57
+
58
+ // Mode A: delete/edit — no password means allow freely
59
+ if ((operation === 'delete' || operation === 'edit') && !hasPassword) {
60
+ return true;
61
+ }
62
+
63
+ // Mode B: export/import — no password means block (defense-in-depth)
64
+ if ((operation === 'export' || operation === 'import') && !hasPassword) {
65
+ return false;
66
+ }
67
+
68
+ // Mode A: clear screen and show header
69
+ if (operation === 'delete' || operation === 'edit') {
70
+ const headerKey = `password.guard.${operation}.header`;
71
+ screen.render([
72
+ '',
73
+ colors.bright + colors.orange + i18n.tSync(headerKey) + colors.reset,
74
+ '',
75
+ ]);
76
+ }
77
+
52
78
  try {
53
79
  const password = await getPasswordInput(i18n.tSync('messages.prompts.enter_password'));
54
80
 
81
+ // Empty password check
55
82
  if (!password) {
56
83
  forceStdinCleanup();
57
- console.log(colors.red + i18n.tSync('errors.password.empty') + colors.reset);
84
+ screen.write(colors.red + i18n.tSync('errors.password.empty') + colors.reset + '\n');
85
+ await waitForKey(i18n.tSync('ui.general.press_any_key_continue'));
58
86
  return false;
59
87
  }
60
88
 
89
+ // Verify password
61
90
  if (!apiManager.verifyExportPassword(password)) {
62
91
  forceStdinCleanup();
63
- console.log(colors.red + '❌ ' + i18n.tSync('errors.password.verification_failed') + colors.reset);
64
- console.log(colors.gray + i18n.tSync('errors.general.operation_failed', operation) + colors.reset);
92
+ screen.write(colors.red + '❌ ' + i18n.tSync('errors.password.verification_failed') + colors.reset + '\n');
93
+ await waitForKey(i18n.tSync('ui.general.press_any_key_continue'));
65
94
  return false;
66
95
  }
67
96
 
68
97
  return true;
69
98
  } catch (error) {
70
99
  forceStdinCleanup();
71
- if (error.message.includes('cancelled')) {
72
- console.log(colors.yellow + '\n' + i18n.tSync('errors.general.cancelled_by_user').replace('{0}', operation) + colors.reset);
73
- } else {
74
- console.log(colors.red + `❌ Password verification error: ${error.message}` + colors.reset);
100
+ if (error.message === 'Password input cancelled') {
101
+ // Esc silent return
102
+ return false;
103
+ }
104
+ if (error.message.includes('Ctrl+C')) {
105
+ // Delegate to stdinManager double-tap exit
106
+ const stdinManager = require('../utils/stdin-manager');
107
+ stdinManager.handleCtrlC();
108
+ return false;
75
109
  }
110
+ // Unexpected error — treat as failure
111
+ screen.write(colors.red + `❌ ${error.message}` + colors.reset + '\n');
76
112
  return false;
77
113
  }
78
114
  }
@@ -88,13 +124,13 @@ async function verifyCurrentPassword(apiManager) {
88
124
 
89
125
  if (!currentPassword) {
90
126
  forceStdinCleanup();
91
- console.log(colors.red + i18n.tSync('errors.password.empty') + colors.reset);
127
+ screen.write(colors.red + i18n.tSync('errors.password.empty') + colors.reset + '\n');
92
128
  return false;
93
129
  }
94
130
 
95
131
  if (!apiManager.verifyExportPassword(currentPassword)) {
96
132
  forceStdinCleanup();
97
- console.log(colors.red + '❌ ' + i18n.tSync('errors.password.current_incorrect') + colors.reset);
133
+ screen.write(colors.red + '❌ ' + i18n.tSync('errors.password.current_incorrect') + colors.reset + '\n');
98
134
  return false;
99
135
  }
100
136
 
@@ -102,9 +138,9 @@ async function verifyCurrentPassword(apiManager) {
102
138
  } catch (error) {
103
139
  forceStdinCleanup();
104
140
  if (error.message.includes('cancelled')) {
105
- console.log(colors.yellow + '\n' + i18n.tSync('errors.password.verification_cancelled') + colors.reset);
141
+ screen.write(colors.yellow + '\n' + i18n.tSync('errors.password.verification_cancelled') + colors.reset + '\n');
106
142
  } else {
107
- console.log(colors.red + `❌ Password verification error: ${error.message}` + colors.reset);
143
+ screen.write(colors.red + `❌ Password verification error: ${error.message}` + colors.reset + '\n');
108
144
  }
109
145
  return false;
110
146
  }
@@ -118,24 +154,23 @@ async function verifyCurrentPassword(apiManager) {
118
154
  */
119
155
  async function setupNewPassword(apiManager, isFirstTime = false) {
120
156
  try {
121
- console.log('');
122
- console.log(colors.cyan + (isFirstTime ? i18n.tSync('password.setup.title') : i18n.tSync('password.setup.change_title')) + colors.reset);
123
- console.log('');
157
+ const titleLine = colors.cyan + (isFirstTime ? i18n.tSync('password.setup.title') : i18n.tSync('password.setup.change_title')) + colors.reset;
158
+ const requirements = getPasswordRequirements();
124
159
 
160
+ const headerLines = ['', titleLine, ''];
125
161
  if (!isFirstTime) {
126
- console.log(colors.yellow + '⚠️ ' + i18n.tSync('password.setup.warning') + colors.reset);
127
- console.log('');
162
+ headerLines.push(colors.yellow + '⚠️ ' + i18n.tSync('password.setup.warning') + colors.reset);
163
+ headerLines.push('');
128
164
  }
129
-
130
- // Display password requirements
131
- console.log(colors.cyan + i18n.tSync('ui.general.password_requirements_title') + colors.reset);
132
- const requirements = getPasswordRequirements();
165
+ headerLines.push(colors.cyan + i18n.tSync('ui.general.password_requirements_title') + colors.reset);
133
166
  requirements.forEach(req => {
134
- console.log(colors.gray + ' ' + req + colors.reset);
167
+ headerLines.push(colors.gray + ' ' + req + colors.reset);
135
168
  });
136
- console.log('');
137
- console.log(colors.gray + i18n.tSync('ui.general.example_strong_password', generatePasswordExample()) + colors.reset);
138
- console.log('');
169
+ headerLines.push('');
170
+ headerLines.push(colors.gray + i18n.tSync('ui.general.example_strong_password', generatePasswordExample()) + colors.reset);
171
+ headerLines.push('');
172
+
173
+ screen.render(headerLines);
139
174
 
140
175
  let attempts = 0;
141
176
  const maxAttempts = 3;
@@ -149,9 +184,9 @@ async function setupNewPassword(apiManager, isFirstTime = false) {
149
184
 
150
185
  if (!newPassword) {
151
186
  forceStdinCleanup();
152
- console.log(colors.red + i18n.tSync('errors.password.empty') + colors.reset);
187
+ screen.write(colors.red + i18n.tSync('errors.password.empty') + colors.reset + '\n');
153
188
  if (attempts < maxAttempts) {
154
- console.log('');
189
+ screen.write('\n');
155
190
  continue;
156
191
  } else {
157
192
  return false;
@@ -162,27 +197,27 @@ async function setupNewPassword(apiManager, isFirstTime = false) {
162
197
  const validation = validatePasswordStrength(newPassword);
163
198
 
164
199
  if (!validation.valid) {
165
- console.log(colors.red + '❌ ' + i18n.tSync('errors.password.requirements_not_met') + colors.reset);
200
+ screen.write(colors.red + '❌ ' + i18n.tSync('errors.password.requirements_not_met') + colors.reset + '\n');
166
201
  validation.errors.forEach(error => {
167
- console.log(colors.red + ' • ' + error + colors.reset);
202
+ screen.write(colors.red + ' • ' + error + colors.reset + '\n');
168
203
  });
169
204
 
170
205
  if (validation.suggestions.length > 0) {
171
- console.log(colors.yellow + '💡 ' + i18n.tSync('ui.general.suggestions') + colors.reset);
206
+ screen.write(colors.yellow + '💡 ' + i18n.tSync('ui.general.suggestions') + colors.reset + '\n');
172
207
  validation.suggestions.forEach(suggestion => {
173
- console.log(colors.yellow + ' • ' + suggestion + colors.reset);
208
+ screen.write(colors.yellow + ' • ' + suggestion + colors.reset + '\n');
174
209
  });
175
210
  }
176
211
  // If password is invalid, force display strength as "Weak" regardless of technical score
177
212
  const strengthKey = validation.valid ? validation.strength : 'Weak';
178
213
  const translatedStrength = getTranslatedStrength(strengthKey);
179
- console.log(colors.gray + i18n.tSync('ui.general.current_password_strength', translatedStrength) + colors.reset);
214
+ screen.write(colors.gray + i18n.tSync('ui.general.current_password_strength', translatedStrength) + colors.reset + '\n');
180
215
 
181
216
  if (attempts < maxAttempts) {
182
- console.log('');
217
+ screen.write('\n');
183
218
  continue;
184
219
  } else {
185
- console.log(colors.red + i18n.tSync('ui.general.max_attempts_password_failed') + colors.reset);
220
+ screen.write(colors.red + i18n.tSync('ui.general.max_attempts_password_failed') + colors.reset + '\n');
186
221
  return false;
187
222
  }
188
223
  }
@@ -192,9 +227,9 @@ async function setupNewPassword(apiManager, isFirstTime = false) {
192
227
 
193
228
  if (newPassword !== confirmPassword) {
194
229
  forceStdinCleanup();
195
- console.log(colors.red + i18n.tSync('ui.general.passwords_mismatch') + colors.reset);
230
+ screen.write(colors.red + i18n.tSync('ui.general.passwords_mismatch') + colors.reset + '\n');
196
231
  if (attempts < maxAttempts) {
197
- console.log('');
232
+ screen.write('\n');
198
233
  continue;
199
234
  } else {
200
235
  return false;
@@ -203,8 +238,8 @@ async function setupNewPassword(apiManager, isFirstTime = false) {
203
238
 
204
239
  // Success - set the password
205
240
  apiManager.setExportPassword(newPassword);
206
- console.log('');
207
- console.log(colors.green + '✓ ' + i18n.tSync('password.setup.password_success', getTranslatedStrength(validation.strength)) + colors.reset);
241
+ screen.write('\n');
242
+ screen.write(colors.green + '✓ ' + i18n.tSync('password.setup.password_success', getTranslatedStrength(validation.strength)) + colors.reset + '\n');
208
243
  return true;
209
244
  }
210
245
 
@@ -213,9 +248,9 @@ async function setupNewPassword(apiManager, isFirstTime = false) {
213
248
  } catch (error) {
214
249
  forceStdinCleanup();
215
250
  if (error.message.includes('cancelled')) {
216
- console.log(colors.yellow + '\n' + i18n.tSync('errors.password.setup_cancelled') + colors.reset);
251
+ screen.write(colors.yellow + '\n' + i18n.tSync('errors.password.setup_cancelled') + colors.reset + '\n');
217
252
  } else {
218
- console.log(colors.red + `❌ Failed to set password: ${error.message}` + colors.reset);
253
+ screen.write(colors.red + `❌ Failed to set password: ${error.message}` + colors.reset + '\n');
219
254
  }
220
255
  return false;
221
256
  }
@@ -234,21 +269,21 @@ async function changePassword(apiManager) {
234
269
  return false;
235
270
  }
236
271
 
237
- console.log(colors.green + i18n.tSync('errors.password.current_password_verified') + colors.reset);
238
- console.log('');
272
+ screen.write(colors.green + i18n.tSync('errors.password.current_password_verified') + colors.reset + '\n');
273
+ screen.write('\n');
239
274
 
240
275
  // Set new password
241
276
  return await setupNewPassword(apiManager, false);
242
277
 
243
278
  } catch (error) {
244
279
  forceStdinCleanup();
245
- console.log(colors.red + `❌ Password change failed: ${error.message}` + colors.reset);
280
+ screen.write(colors.red + `❌ Password change failed: ${error.message}` + colors.reset + '\n');
246
281
  return false;
247
282
  }
248
283
  }
249
284
 
250
285
  module.exports = {
251
- verifyExportPassword,
286
+ passwordGuard,
252
287
  verifyCurrentPassword,
253
288
  setupNewPassword,
254
289
  changePassword
package/lib/i18n/index.js CHANGED
@@ -5,6 +5,7 @@
5
5
 
6
6
  const LanguageManager = require('./language-manager');
7
7
  const MessageFormatter = require('./formatter');
8
+ const screen = require('../ui/screen');
8
9
 
9
10
  class I18n {
10
11
  constructor() {
@@ -34,14 +35,14 @@ class I18n {
34
35
  for (const k of keys) {
35
36
  value = value?.[k];
36
37
  if (value === undefined) {
37
- console.warn(`Translation key not found: ${key}`);
38
+ screen.debug(`Translation key not found: ${key}`);
38
39
  return key; // Return key as fallback
39
40
  }
40
41
  }
41
42
 
42
43
  // If value is not a string, return the key as fallback
43
44
  if (typeof value !== 'string') {
44
- console.warn(`Translation value is not a string for key: ${key}`);
45
+ screen.debug(`Translation value is not a string for key: ${key}`);
45
46
  return key;
46
47
  }
47
48
 
@@ -56,7 +57,7 @@ class I18n {
56
57
 
57
58
  return result;
58
59
  } catch (error) {
59
- console.warn(`Translation error for key ${key}:`, error.message);
60
+ screen.debug(`Translation error for key ${key}: ` + error.message);
60
61
  return key; // Return key as fallback
61
62
  }
62
63
  }
@@ -6,6 +6,7 @@
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
8
  const os = require('os');
9
+ const screen = require('../ui/screen');
9
10
 
10
11
  class LanguageManager {
11
12
  constructor() {
@@ -83,7 +84,7 @@ class LanguageManager {
83
84
  this.languageCache.set(this.currentLanguage, languagePack);
84
85
  return languagePack;
85
86
  } catch (error) {
86
- console.warn(`Failed to load language pack for ${this.currentLanguage}, falling back to English. Error: ${error.message}`);
87
+ screen.debug(`Failed to load language pack for ${this.currentLanguage}, falling back to English. Error: ${error.message}`);
87
88
 
88
89
  // Fallback to English
89
90
  if (this.currentLanguage !== 'en') {
@@ -118,7 +119,7 @@ class LanguageManager {
118
119
 
119
120
  fs.writeFileSync(this.configFile, JSON.stringify(config, null, 2));
120
121
  } catch (error) {
121
- console.warn('Failed to save language preference:', error.message);
122
+ screen.debug('Failed to save language preference: ' + error.message);
122
123
  }
123
124
  }
124
125
 
@@ -134,7 +135,7 @@ class LanguageManager {
134
135
  }
135
136
  }
136
137
  } catch (error) {
137
- console.warn('Failed to load language preference, using default (English)');
138
+ screen.debug('Failed to load language preference, using default (English)');
138
139
  }
139
140
  }
140
141