@fmdzc/cli-ai 3.0.3 → 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.
@@ -1,332 +1,332 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * CLI AI Uninstall Script
5
- * Safely removes all cli-ai components from the system
6
- *
7
- * Usage: node scripts/uninstall.js [--force]
8
- *
9
- * Components removed:
10
- * - API key from system keyring
11
- * - Config directory (~/.cli_ai_assistant)
12
- * - Global npm/pnpm link (optional)
13
- */
14
-
15
- import { execSync, spawnSync } from 'node:child_process';
16
- import { existsSync, rmSync, readFileSync, writeFileSync, copyFileSync } from 'node:fs';
17
- import { homedir, platform } from 'node:os';
18
- import { join } from 'node:path';
19
- import { createInterface } from 'node:readline';
20
-
21
- const CONFIG_DIR_NAME = '.cli_ai_assistant';
22
- const KEYRING_SERVICE = 'cli-ai';
23
- const KEYRING_ACCOUNT = 'anthropic';
24
-
25
- const isWindows = platform() === 'win32';
26
- const isMac = platform() === 'darwin';
27
- const isLinux = platform() === 'linux';
28
-
29
- const forceMode = process.argv.includes('--force') || process.argv.includes('-f');
30
-
31
- /**
32
- * Print colored output
33
- */
34
- function log(message, type = 'info') {
35
- const colors = {
36
- info: '\x1b[36m', // cyan
37
- success: '\x1b[32m', // green
38
- warn: '\x1b[33m', // yellow
39
- error: '\x1b[31m', // red
40
- reset: '\x1b[0m',
41
- };
42
-
43
- const prefix = {
44
- info: 'ℹ',
45
- success: '✓',
46
- warn: '⚠',
47
- error: '✗',
48
- };
49
-
50
- console.log(`${colors[type]}${prefix[type]} ${message}${colors.reset}`);
51
- }
52
-
53
- /**
54
- * Prompt user for confirmation
55
- */
56
- async function confirm(question) {
57
- if (forceMode) return true;
58
-
59
- const rl = createInterface({
60
- input: process.stdin,
61
- output: process.stdout,
62
- });
63
-
64
- return new Promise((resolve) => {
65
- rl.question(`${question} (y/N): `, (answer) => {
66
- rl.close();
67
- resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
68
- });
69
- });
70
- }
71
-
72
- /**
73
- * Remove API key from system keyring
74
- */
75
- async function removeKeyringEntry() {
76
- log('Removing API key from system keyring...');
77
-
78
- try {
79
- // Try using @napi-rs/keyring first (if available)
80
- try {
81
- const { Entry } = await import('@napi-rs/keyring');
82
- const entry = new Entry(KEYRING_SERVICE, KEYRING_ACCOUNT);
83
- entry.deleteCredential();
84
- log('API key removed from keyring (via @napi-rs/keyring)', 'success');
85
- return true;
86
- } catch {
87
- // Fall through to platform-specific methods
88
- }
89
-
90
- // Platform-specific fallbacks
91
- if (isWindows) {
92
- // Windows Credential Manager
93
- const result = spawnSync('cmdkey', ['/delete:cli-ai'], {
94
- encoding: 'utf-8',
95
- shell: true,
96
- });
97
- if (result.status === 0) {
98
- log('API key removed from Windows Credential Manager', 'success');
99
- return true;
100
- }
101
- } else if (isMac) {
102
- // macOS Keychain
103
- const result = spawnSync('security', [
104
- 'delete-generic-password',
105
- '-s', KEYRING_SERVICE,
106
- '-a', KEYRING_ACCOUNT,
107
- ], { encoding: 'utf-8' });
108
- if (result.status === 0) {
109
- log('API key removed from macOS Keychain', 'success');
110
- return true;
111
- }
112
- } else if (isLinux) {
113
- // Linux secret-tool (GNOME Keyring)
114
- const result = spawnSync('secret-tool', [
115
- 'clear',
116
- 'service', KEYRING_SERVICE,
117
- 'account', KEYRING_ACCOUNT,
118
- ], { encoding: 'utf-8' });
119
- if (result.status === 0) {
120
- log('API key removed from Linux keyring', 'success');
121
- return true;
122
- }
123
- }
124
-
125
- log('No API key found in keyring (may not have been stored there)', 'warn');
126
- return true;
127
- } catch (error) {
128
- log(`Could not remove keyring entry: ${error.message}`, 'warn');
129
- return false;
130
- }
131
- }
132
-
133
- /**
134
- * Remove config directory
135
- */
136
- function removeConfigDirectory() {
137
- const configDir = join(homedir(), CONFIG_DIR_NAME);
138
-
139
- if (!existsSync(configDir)) {
140
- log(`Config directory not found: ${configDir}`, 'warn');
141
- return true;
142
- }
143
-
144
- log(`Removing config directory: ${configDir}`);
145
-
146
- try {
147
- rmSync(configDir, { recursive: true, force: true });
148
- log('Config directory removed', 'success');
149
- return true;
150
- } catch (error) {
151
- log(`Failed to remove config directory: ${error.message}`, 'error');
152
- return false;
153
- }
154
- }
155
-
156
- /**
157
- * Remove global package link
158
- */
159
- function removeGlobalLink() {
160
- log('Checking for global package installation...');
161
-
162
- // Try pnpm first
163
- try {
164
- const pnpmResult = spawnSync('pnpm', ['list', '-g', '--depth=0'], {
165
- encoding: 'utf-8',
166
- shell: true,
167
- });
168
-
169
- if (pnpmResult.stdout && pnpmResult.stdout.includes('cli-ai')) {
170
- log('Found pnpm global link, removing...');
171
- const unlinkResult = spawnSync('pnpm', ['unlink', '--global', '@fmdzc/cli-ai'], {
172
- encoding: 'utf-8',
173
- shell: true,
174
- });
175
- if (unlinkResult.status === 0) {
176
- log('Removed pnpm global link', 'success');
177
- return true;
178
- }
179
- }
180
- } catch {
181
- // pnpm not available
182
- }
183
-
184
- // Try npm
185
- try {
186
- const npmResult = spawnSync('npm', ['list', '-g', '--depth=0'], {
187
- encoding: 'utf-8',
188
- shell: true,
189
- });
190
-
191
- if (npmResult.stdout && npmResult.stdout.includes('cli-ai')) {
192
- log('Found npm global link, removing...');
193
- const unlinkResult = spawnSync('npm', ['unlink', '-g', '@fmdzc/cli-ai'], {
194
- encoding: 'utf-8',
195
- shell: true,
196
- });
197
- if (unlinkResult.status === 0) {
198
- log('Removed npm global link', 'success');
199
- return true;
200
- }
201
- }
202
- } catch {
203
- // npm not available
204
- }
205
-
206
- log('No global package link found', 'info');
207
- return true;
208
- }
209
-
210
- /**
211
- * Remove shell alias from config files
212
- */
213
- function removeShellAliases() {
214
- log('Checking for shell aliases...');
215
-
216
- const shellConfigs = [];
217
-
218
- if (isWindows) {
219
- // PowerShell profile locations
220
- const psProfile = join(homedir(), 'Documents', 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1');
221
- const pwshProfile = join(homedir(), 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1');
222
- if (existsSync(psProfile)) shellConfigs.push(psProfile);
223
- if (existsSync(pwshProfile)) shellConfigs.push(pwshProfile);
224
- } else {
225
- // Unix shell configs
226
- const bashrc = join(homedir(), '.bashrc');
227
- const zshrc = join(homedir(), '.zshrc');
228
- const fishConfig = join(homedir(), '.config', 'fish', 'config.fish');
229
-
230
- if (existsSync(bashrc)) shellConfigs.push(bashrc);
231
- if (existsSync(zshrc)) shellConfigs.push(zshrc);
232
- if (existsSync(fishConfig)) shellConfigs.push(fishConfig);
233
- }
234
-
235
- let aliasesRemoved = 0;
236
-
237
- for (const configPath of shellConfigs) {
238
- try {
239
- const content = readFileSync(configPath, 'utf-8');
240
-
241
- // Look for cli-ai related aliases
242
- const aliasPatterns = [
243
- /^.*alias\s+s\s*=.*cli[_-]?ai.*$/gm,
244
- /^.*Set-Alias.*cli[_-]?ai.*$/gm,
245
- /^.*function\s+s\s*\(\).*cli[_-]?ai.*$/gm,
246
- /^# CLI AI.*$/gm,
247
- ];
248
-
249
- let newContent = content;
250
- let hasChanges = false;
251
-
252
- for (const pattern of aliasPatterns) {
253
- if (pattern.test(newContent)) {
254
- hasChanges = true;
255
- newContent = newContent.replace(pattern, '');
256
- }
257
- }
258
-
259
- if (hasChanges) {
260
- // Create backup
261
- const backupPath = `${configPath}.cli-ai-backup`;
262
- copyFileSync(configPath, backupPath);
263
- log(`Created backup: ${backupPath}`, 'info');
264
-
265
- // Clean up empty lines
266
- newContent = newContent.replace(/\n{3,}/g, '\n\n').trim() + '\n';
267
- writeFileSync(configPath, newContent);
268
- log(`Removed alias from: ${configPath}`, 'success');
269
- aliasesRemoved++;
270
- }
271
- } catch (error) {
272
- log(`Could not process ${configPath}: ${error.message}`, 'warn');
273
- }
274
- }
275
-
276
- if (aliasesRemoved === 0) {
277
- log('No shell aliases found', 'info');
278
- }
279
-
280
- return true;
281
- }
282
-
283
- /**
284
- * Main uninstall flow
285
- */
286
- async function main() {
287
- console.log('\n\x1b[1m\x1b[35m🗑️ CLI AI Uninstaller\x1b[0m\n');
288
-
289
- if (!forceMode) {
290
- console.log('This will remove:');
291
- console.log(' • API key from system keyring');
292
- console.log(` • Config directory (~/${CONFIG_DIR_NAME})`);
293
- console.log(' • Global package link (if exists)');
294
- console.log(' • Shell aliases (if configured)\n');
295
-
296
- const proceed = await confirm('Do you want to proceed?');
297
- if (!proceed) {
298
- log('Uninstall cancelled', 'info');
299
- process.exit(0);
300
- }
301
- console.log();
302
- }
303
-
304
- const results = {
305
- keyring: await removeKeyringEntry(),
306
- config: removeConfigDirectory(),
307
- globalLink: removeGlobalLink(),
308
- aliases: removeShellAliases(),
309
- };
310
-
311
- console.log('\n\x1b[1m--- Summary ---\x1b[0m\n');
312
-
313
- const allSuccess = Object.values(results).every(Boolean);
314
-
315
- if (allSuccess) {
316
- log('CLI AI has been completely removed from your system!', 'success');
317
- console.log('\nNote: If you installed via npm/pnpm globally, you may also want to run:');
318
- console.log(' npm uninstall -g @fmdzc/cli-ai');
319
- console.log(' # or');
320
- console.log(' pnpm uninstall -g @fmdzc/cli-ai\n');
321
- } else {
322
- log('Uninstall completed with some warnings (see above)', 'warn');
323
- }
324
-
325
- // Remind about shell restart
326
- console.log('\x1b[33mRestart your terminal for changes to take effect.\x1b[0m\n');
327
- }
328
-
329
- main().catch((error) => {
330
- log(`Uninstall failed: ${error.message}`, 'error');
331
- process.exit(1);
332
- });
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * CLI AI Uninstall Script
5
+ * Safely removes all cli-ai components from the system
6
+ *
7
+ * Usage: node scripts/uninstall.js [--force]
8
+ *
9
+ * Components removed:
10
+ * - API key from system keyring
11
+ * - Config directory (~/.cli_ai_assistant)
12
+ * - Global npm/pnpm link (optional)
13
+ */
14
+
15
+ import { execSync, spawnSync } from 'node:child_process';
16
+ import { existsSync, rmSync, readFileSync, writeFileSync, copyFileSync } from 'node:fs';
17
+ import { homedir, platform } from 'node:os';
18
+ import { join } from 'node:path';
19
+ import { createInterface } from 'node:readline';
20
+
21
+ const CONFIG_DIR_NAME = '.cli_ai_assistant';
22
+ const KEYRING_SERVICE = 'cli-ai';
23
+ const KEYRING_ACCOUNT = 'anthropic';
24
+
25
+ const isWindows = platform() === 'win32';
26
+ const isMac = platform() === 'darwin';
27
+ const isLinux = platform() === 'linux';
28
+
29
+ const forceMode = process.argv.includes('--force') || process.argv.includes('-f');
30
+
31
+ /**
32
+ * Print colored output
33
+ */
34
+ function log(message, type = 'info') {
35
+ const colors = {
36
+ info: '\x1b[36m', // cyan
37
+ success: '\x1b[32m', // green
38
+ warn: '\x1b[33m', // yellow
39
+ error: '\x1b[31m', // red
40
+ reset: '\x1b[0m',
41
+ };
42
+
43
+ const prefix = {
44
+ info: 'ℹ',
45
+ success: '✓',
46
+ warn: '⚠',
47
+ error: '✗',
48
+ };
49
+
50
+ console.log(`${colors[type]}${prefix[type]} ${message}${colors.reset}`);
51
+ }
52
+
53
+ /**
54
+ * Prompt user for confirmation
55
+ */
56
+ async function confirm(question) {
57
+ if (forceMode) return true;
58
+
59
+ const rl = createInterface({
60
+ input: process.stdin,
61
+ output: process.stdout,
62
+ });
63
+
64
+ return new Promise((resolve) => {
65
+ rl.question(`${question} (y/N): `, (answer) => {
66
+ rl.close();
67
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
68
+ });
69
+ });
70
+ }
71
+
72
+ /**
73
+ * Remove API key from system keyring
74
+ */
75
+ async function removeKeyringEntry() {
76
+ log('Removing API key from system keyring...');
77
+
78
+ try {
79
+ // Try using @napi-rs/keyring first (if available)
80
+ try {
81
+ const { Entry } = await import('@napi-rs/keyring');
82
+ const entry = new Entry(KEYRING_SERVICE, KEYRING_ACCOUNT);
83
+ entry.deleteCredential();
84
+ log('API key removed from keyring (via @napi-rs/keyring)', 'success');
85
+ return true;
86
+ } catch {
87
+ // Fall through to platform-specific methods
88
+ }
89
+
90
+ // Platform-specific fallbacks
91
+ if (isWindows) {
92
+ // Windows Credential Manager
93
+ const result = spawnSync('cmdkey', ['/delete:cli-ai'], {
94
+ encoding: 'utf-8',
95
+ shell: true,
96
+ });
97
+ if (result.status === 0) {
98
+ log('API key removed from Windows Credential Manager', 'success');
99
+ return true;
100
+ }
101
+ } else if (isMac) {
102
+ // macOS Keychain
103
+ const result = spawnSync('security', [
104
+ 'delete-generic-password',
105
+ '-s', KEYRING_SERVICE,
106
+ '-a', KEYRING_ACCOUNT,
107
+ ], { encoding: 'utf-8' });
108
+ if (result.status === 0) {
109
+ log('API key removed from macOS Keychain', 'success');
110
+ return true;
111
+ }
112
+ } else if (isLinux) {
113
+ // Linux secret-tool (GNOME Keyring)
114
+ const result = spawnSync('secret-tool', [
115
+ 'clear',
116
+ 'service', KEYRING_SERVICE,
117
+ 'account', KEYRING_ACCOUNT,
118
+ ], { encoding: 'utf-8' });
119
+ if (result.status === 0) {
120
+ log('API key removed from Linux keyring', 'success');
121
+ return true;
122
+ }
123
+ }
124
+
125
+ log('No API key found in keyring (may not have been stored there)', 'warn');
126
+ return true;
127
+ } catch (error) {
128
+ log(`Could not remove keyring entry: ${error.message}`, 'warn');
129
+ return false;
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Remove config directory
135
+ */
136
+ function removeConfigDirectory() {
137
+ const configDir = join(homedir(), CONFIG_DIR_NAME);
138
+
139
+ if (!existsSync(configDir)) {
140
+ log(`Config directory not found: ${configDir}`, 'warn');
141
+ return true;
142
+ }
143
+
144
+ log(`Removing config directory: ${configDir}`);
145
+
146
+ try {
147
+ rmSync(configDir, { recursive: true, force: true });
148
+ log('Config directory removed', 'success');
149
+ return true;
150
+ } catch (error) {
151
+ log(`Failed to remove config directory: ${error.message}`, 'error');
152
+ return false;
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Remove global package link
158
+ */
159
+ function removeGlobalLink() {
160
+ log('Checking for global package installation...');
161
+
162
+ // Try pnpm first
163
+ try {
164
+ const pnpmResult = spawnSync('pnpm', ['list', '-g', '--depth=0'], {
165
+ encoding: 'utf-8',
166
+ shell: true,
167
+ });
168
+
169
+ if (pnpmResult.stdout && pnpmResult.stdout.includes('cli-ai')) {
170
+ log('Found pnpm global link, removing...');
171
+ const unlinkResult = spawnSync('pnpm', ['unlink', '--global', '@fmdzc/cli-ai'], {
172
+ encoding: 'utf-8',
173
+ shell: true,
174
+ });
175
+ if (unlinkResult.status === 0) {
176
+ log('Removed pnpm global link', 'success');
177
+ return true;
178
+ }
179
+ }
180
+ } catch {
181
+ // pnpm not available
182
+ }
183
+
184
+ // Try npm
185
+ try {
186
+ const npmResult = spawnSync('npm', ['list', '-g', '--depth=0'], {
187
+ encoding: 'utf-8',
188
+ shell: true,
189
+ });
190
+
191
+ if (npmResult.stdout && npmResult.stdout.includes('cli-ai')) {
192
+ log('Found npm global link, removing...');
193
+ const unlinkResult = spawnSync('npm', ['unlink', '-g', '@fmdzc/cli-ai'], {
194
+ encoding: 'utf-8',
195
+ shell: true,
196
+ });
197
+ if (unlinkResult.status === 0) {
198
+ log('Removed npm global link', 'success');
199
+ return true;
200
+ }
201
+ }
202
+ } catch {
203
+ // npm not available
204
+ }
205
+
206
+ log('No global package link found', 'info');
207
+ return true;
208
+ }
209
+
210
+ /**
211
+ * Remove shell alias from config files
212
+ */
213
+ function removeShellAliases() {
214
+ log('Checking for shell aliases...');
215
+
216
+ const shellConfigs = [];
217
+
218
+ if (isWindows) {
219
+ // PowerShell profile locations
220
+ const psProfile = join(homedir(), 'Documents', 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1');
221
+ const pwshProfile = join(homedir(), 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1');
222
+ if (existsSync(psProfile)) shellConfigs.push(psProfile);
223
+ if (existsSync(pwshProfile)) shellConfigs.push(pwshProfile);
224
+ } else {
225
+ // Unix shell configs
226
+ const bashrc = join(homedir(), '.bashrc');
227
+ const zshrc = join(homedir(), '.zshrc');
228
+ const fishConfig = join(homedir(), '.config', 'fish', 'config.fish');
229
+
230
+ if (existsSync(bashrc)) shellConfigs.push(bashrc);
231
+ if (existsSync(zshrc)) shellConfigs.push(zshrc);
232
+ if (existsSync(fishConfig)) shellConfigs.push(fishConfig);
233
+ }
234
+
235
+ let aliasesRemoved = 0;
236
+
237
+ for (const configPath of shellConfigs) {
238
+ try {
239
+ const content = readFileSync(configPath, 'utf-8');
240
+
241
+ // Look for cli-ai related aliases
242
+ const aliasPatterns = [
243
+ /^.*alias\s+s\s*=.*cli[_-]?ai.*$/gm,
244
+ /^.*Set-Alias.*cli[_-]?ai.*$/gm,
245
+ /^.*function\s+s\s*\(\).*cli[_-]?ai.*$/gm,
246
+ /^# CLI AI.*$/gm,
247
+ ];
248
+
249
+ let newContent = content;
250
+ let hasChanges = false;
251
+
252
+ for (const pattern of aliasPatterns) {
253
+ if (pattern.test(newContent)) {
254
+ hasChanges = true;
255
+ newContent = newContent.replace(pattern, '');
256
+ }
257
+ }
258
+
259
+ if (hasChanges) {
260
+ // Create backup
261
+ const backupPath = `${configPath}.cli-ai-backup`;
262
+ copyFileSync(configPath, backupPath);
263
+ log(`Created backup: ${backupPath}`, 'info');
264
+
265
+ // Clean up empty lines
266
+ newContent = newContent.replace(/\n{3,}/g, '\n\n').trim() + '\n';
267
+ writeFileSync(configPath, newContent);
268
+ log(`Removed alias from: ${configPath}`, 'success');
269
+ aliasesRemoved++;
270
+ }
271
+ } catch (error) {
272
+ log(`Could not process ${configPath}: ${error.message}`, 'warn');
273
+ }
274
+ }
275
+
276
+ if (aliasesRemoved === 0) {
277
+ log('No shell aliases found', 'info');
278
+ }
279
+
280
+ return true;
281
+ }
282
+
283
+ /**
284
+ * Main uninstall flow
285
+ */
286
+ async function main() {
287
+ console.log('\n\x1b[1m\x1b[35m🗑️ CLI AI Uninstaller\x1b[0m\n');
288
+
289
+ if (!forceMode) {
290
+ console.log('This will remove:');
291
+ console.log(' • API key from system keyring');
292
+ console.log(` • Config directory (~/${CONFIG_DIR_NAME})`);
293
+ console.log(' • Global package link (if exists)');
294
+ console.log(' • Shell aliases (if configured)\n');
295
+
296
+ const proceed = await confirm('Do you want to proceed?');
297
+ if (!proceed) {
298
+ log('Uninstall cancelled', 'info');
299
+ process.exit(0);
300
+ }
301
+ console.log();
302
+ }
303
+
304
+ const results = {
305
+ keyring: await removeKeyringEntry(),
306
+ config: removeConfigDirectory(),
307
+ globalLink: removeGlobalLink(),
308
+ aliases: removeShellAliases(),
309
+ };
310
+
311
+ console.log('\n\x1b[1m--- Summary ---\x1b[0m\n');
312
+
313
+ const allSuccess = Object.values(results).every(Boolean);
314
+
315
+ if (allSuccess) {
316
+ log('CLI AI has been completely removed from your system!', 'success');
317
+ console.log('\nNote: If you installed via npm/pnpm globally, you may also want to run:');
318
+ console.log(' npm uninstall -g @fmdzc/cli-ai');
319
+ console.log(' # or');
320
+ console.log(' pnpm uninstall -g @fmdzc/cli-ai\n');
321
+ } else {
322
+ log('Uninstall completed with some warnings (see above)', 'warn');
323
+ }
324
+
325
+ // Remind about shell restart
326
+ console.log('\x1b[33mRestart your terminal for changes to take effect.\x1b[0m\n');
327
+ }
328
+
329
+ main().catch((error) => {
330
+ log(`Uninstall failed: ${error.message}`, 'error');
331
+ process.exit(1);
332
+ });