claude-git-hooks 2.7.1 → 2.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/claude-hooks CHANGED
@@ -8,7 +8,7 @@ import readline from 'readline';
8
8
  import https from 'https';
9
9
  import { fileURLToPath } from 'url';
10
10
  import { dirname } from 'path';
11
- import { executeClaude, extractJSON } from '../lib/utils/claude-client.js';
11
+ import { executeClaude, executeClaudeWithRetry, extractJSON, analyzeCode } from '../lib/utils/claude-client.js';
12
12
  import { loadPrompt } from '../lib/utils/prompt-builder.js';
13
13
  import { listPresets } from '../lib/utils/preset-loader.js';
14
14
  import { getConfig } from '../lib/config.js';
@@ -16,6 +16,7 @@ import { getOrPromptTaskId, formatWithTaskId } from '../lib/utils/task-id.js';
16
16
  import { createPullRequest, getReviewersForFiles, parseGitHubRepo, setupGitHubMcp, getGitHubMcpStatus } from '../lib/utils/github-client.js';
17
17
  import { showPRPreview, promptConfirmation, promptMenu, showSuccess, showError, showInfo, showWarning, showSpinner, promptEditField } from '../lib/utils/interactive-ui.js';
18
18
  import { setupGitHubMCP } from '../lib/utils/mcp-setup.js';
19
+ import { displayStatistics as showTelemetryStats, clearTelemetry as clearTelemetryData } from '../lib/utils/telemetry.js';
19
20
  import logger from '../lib/utils/logger.js';
20
21
 
21
22
  // Why: ES6 modules don't have __dirname, need to recreate it
@@ -398,28 +399,64 @@ async function install(args) {
398
399
  }
399
400
  });
400
401
 
401
- // Copy ALL template files (.md and .json) to .claude directory
402
+ // Create .claude/prompts directory for markdown templates
403
+ const promptsDir = path.join(claudeDir, 'prompts');
404
+ if (!fs.existsSync(promptsDir)) {
405
+ fs.mkdirSync(promptsDir, { recursive: true });
406
+ success('.claude/prompts directory created');
407
+ }
408
+
409
+ // Copy template files (.md and .json) to appropriate locations
402
410
  const templateFiles = fs.readdirSync(templatesPath)
403
411
  .filter(file => {
404
412
  const filePath = path.join(templatesPath, file);
405
- return fs.statSync(filePath).isFile() && (file.endsWith('.md') || file.endsWith('.json'));
413
+ // Exclude example.json files and only include .md and .json files
414
+ return fs.statSync(filePath).isFile() &&
415
+ (file.endsWith('.md') || file.endsWith('.json')) &&
416
+ !file.includes('example.json');
406
417
  });
407
418
 
408
419
  templateFiles.forEach(file => {
409
420
  const sourcePath = path.join(templatesPath, file);
410
- const destPath = path.join(claudeDir, file);
421
+ let destPath;
422
+ let destLocation;
423
+
424
+ // .md files go to .claude/prompts/, .json files go to .claude/
425
+ if (file.endsWith('.md')) {
426
+ destPath = path.join(promptsDir, file);
427
+ destLocation = '.claude/prompts/';
428
+ } else {
429
+ destPath = path.join(claudeDir, file);
430
+ destLocation = '.claude/';
431
+ }
411
432
 
412
433
  // In force mode or if it doesn't exist, copy the file
413
434
  if (isForce || !fs.existsSync(destPath)) {
414
435
  if (fs.existsSync(sourcePath)) {
415
436
  fs.copyFileSync(sourcePath, destPath);
416
- success(`${file} installed in .claude/`);
437
+ success(`${file} installed in ${destLocation}`);
417
438
  }
418
439
  } else {
419
440
  info(`${file} already exists (skipped)`);
420
441
  }
421
442
  });
422
443
 
444
+ // Clean up old .md files from .claude/ root (v2.8.0 migration)
445
+ // .md files should now be in .claude/prompts/, not .claude/
446
+ const oldMdFiles = fs.readdirSync(claudeDir)
447
+ .filter(file => {
448
+ const filePath = path.join(claudeDir, file);
449
+ return fs.statSync(filePath).isFile() && file.endsWith('.md');
450
+ });
451
+
452
+ if (oldMdFiles.length > 0) {
453
+ oldMdFiles.forEach(file => {
454
+ const oldPath = path.join(claudeDir, file);
455
+ fs.unlinkSync(oldPath);
456
+ info(`Removed old template from .claude/: ${file} (now in prompts/)`);
457
+ });
458
+ }
459
+
423
460
  // Copy presets directory structure
424
461
  const presetsSourcePath = path.join(templatesPath, 'presets');
425
462
  const presetsDestPath = path.join(claudeDir, 'presets');
@@ -460,15 +497,74 @@ async function install(args) {
460
497
  success(`${presetDirs.length} presets installed in .claude/presets/`);
461
498
  }
462
499
 
463
- // Special handling for config.json: rename config.example.json config.json
464
- const configExamplePath = path.join(claudeDir, 'config.example.json');
500
+ // Special handling for config.json (v2.8.0+): backup old, create new simplified
465
501
  const configPath = path.join(claudeDir, 'config.json');
502
+ const configOldDir = path.join(claudeDir, 'config_old');
503
+ const configExampleDir = path.join(claudeDir, 'config_example');
504
+
505
+ // Create config_old directory if needed
506
+ if (!fs.existsSync(configOldDir)) {
507
+ fs.mkdirSync(configOldDir, { recursive: true });
508
+ }
466
509
 
467
- if (fs.existsSync(configExamplePath) && !fs.existsSync(configPath)) {
468
- fs.copyFileSync(configExamplePath, configPath);
469
- success('config.json created from example (customize as needed)');
470
- } else if (!fs.existsSync(configPath)) {
471
- warning('config.json not found - using defaults');
510
+ // Create config_example directory
511
+ if (!fs.existsSync(configExampleDir)) {
512
+ fs.mkdirSync(configExampleDir, { recursive: true });
513
+ }
514
+
515
+ // Copy example configs to config_example/ directly from templates/
516
+ const exampleConfigs = ['config.example.json', 'config.advanced.example.json'];
517
+ exampleConfigs.forEach(exampleFile => {
518
+ const sourcePath = path.join(templatesPath, exampleFile);
519
+ const destPath = path.join(configExampleDir, exampleFile);
520
+ if (fs.existsSync(sourcePath)) {
521
+ fs.copyFileSync(sourcePath, destPath);
522
+ }
523
+ });
524
+ success('Example configs installed in .claude/config_example/');
525
+
526
+ // Backup existing config if it exists (legacy format migration)
527
+ let needsMigration = false;
528
+ if (fs.existsSync(configPath)) {
529
+ const backupPath = path.join(configOldDir, `config.json.${Date.now()}`);
530
+ fs.copyFileSync(configPath, backupPath);
531
+ info(`Existing config backed up: ${backupPath}`);
532
+
533
+ // Read old config to check if it's legacy format
534
+ const oldConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
535
+ if (!oldConfig.version || oldConfig.version !== '2.8.0') {
536
+ warning('Legacy config detected - will be replaced with v2.8.0 format');
537
+ needsMigration = true;
538
+
539
+ // Delete old config to force new format
540
+ fs.unlinkSync(configPath);
541
+ } else {
542
+ info('Config already in v2.8.0 format - keeping existing file');
543
+ }
544
+ }
545
+
546
+ // Create new config.json from minimal example if it doesn't exist
547
+ if (!fs.existsSync(configPath)) {
548
+ // Read example and extract minimal config
549
+ const examplePath = path.join(configExampleDir, 'config.example.json');
550
+ const exampleContent = fs.readFileSync(examplePath, 'utf8');
551
+ const exampleJson = JSON.parse(exampleContent);
552
+
553
+ // Create minimal config: just version and preset
554
+ const minimalConfig = {
555
+ version: exampleJson.version,
556
+ preset: exampleJson.preset
557
+ };
558
+
559
+ fs.writeFileSync(configPath, JSON.stringify(minimalConfig, null, 4));
560
+ success('config.json created with minimal v2.8.0 format');
561
+ info('📝 Customize: .claude/config.json (see config_example/ for examples)');
562
+
563
+ // Auto-run migration if needed to preserve settings
564
+ if (needsMigration) {
565
+ info('🔄 Auto-migrating settings from backup...');
566
+ await autoMigrateConfig(configPath, path.join(configOldDir, fs.readdirSync(configOldDir).sort().pop()));
567
+ }
472
568
  }
473
569
 
474
570
  // Create settings.local.json for sensitive data (gitignored)
@@ -493,21 +589,31 @@ async function install(args) {
493
589
  console.log(' git commit -m "auto" # Generate message automatically');
494
590
  console.log(' git commit -m "message" # Analyze code before commit');
495
591
  console.log(' git commit --no-verify # Skip analysis completely');
496
- console.log('\n💡 Configuration:');
592
+ console.log('\n💡 Configuration (v2.8.0):');
497
593
  console.log(' 📁 All templates installed in .claude/');
498
- console.log(' 📝 Edit .claude/config.json to customize settings');
499
- console.log(' 🎯 Use presets: backend, frontend, fullstack, database, ai, default');
500
- console.log(' 🚀 Enable parallel analysis: set subagents.enabled = true');
501
- console.log(' 🐛 Enable debug mode: claude-hooks --debug true');
594
+ console.log(' 📝 Edit .claude/config.json (minimal by default)');
595
+ console.log(' 📂 Examples: .claude/config_example/');
596
+ console.log(' 📦 Backups: .claude/config_old/');
597
+ console.log(' 🎯 Presets: backend, frontend, fullstack, database, ai, default');
598
+ console.log(' 🚀 Parallel analysis enabled by default (hardcoded)');
599
+ console.log(' 🐛 Debug mode: claude-hooks --debug true');
502
600
  console.log('\n🔗 GitHub PR Creation (v2.5.0+):');
503
- console.log(' claude-hooks setup-github # Configure GitHub token for create-pr');
504
- console.log(' claude-hooks create-pr main # Create PR with auto-generated metadata');
505
- console.log('\n📖 Example config.json:');
601
+ console.log(' claude-hooks setup-github # Configure GitHub token');
602
+ console.log(' claude-hooks create-pr main # Create PR with auto-metadata');
603
+ console.log('\n📖 Minimal config.json (v2.8.0):');
604
+ console.log(' {');
605
+ console.log(' "version": "2.8.0",');
606
+ console.log(' "preset": "backend"');
607
+ console.log(' }');
608
+ console.log('\n📖 With GitHub customization:');
506
609
  console.log(' {');
610
+ console.log(' "version": "2.8.0",');
507
611
  console.log(' "preset": "backend",');
508
- console.log(' "subagents": { "enabled": true, "model": "haiku", "batchSize": 3 },');
509
- console.log(' "github": { "pr": { "reviewers": ["your-username"] } }');
612
+ console.log(' "overrides": {');
613
+ console.log(' "github": { "pr": { "reviewers": ["your-username"] } }');
614
+ console.log(' }');
510
615
  console.log(' }');
616
+ console.log('\n🔧 Advanced: see .claude/config_example/config.advanced.example.json');
511
617
  console.log('\nFor more options: claude-hooks --help');
512
618
  }
513
619
 
@@ -967,9 +1073,22 @@ async function analyzeDiff(args) {
967
1073
  info('Sending to Claude for analysis...');
968
1074
  const startTime = Date.now();
969
1075
 
1076
+ // Prepare telemetry context
1077
+ const filesChanged = diffFiles.split('\n').length;
1078
+ const telemetryContext = {
1079
+ fileCount: filesChanged,
1080
+ batchSize: filesChanged,
1081
+ totalBatches: 1,
1082
+ model: subagentModel || 'sonnet',
1083
+ hook: 'analyze-diff'
1084
+ };
1085
+
970
1086
  try {
971
- // Use cross-platform executeClaude from claude-client.js
972
- const response = await executeClaude(prompt, { timeout: 180000 }); // 3 minutes for diff analysis
1087
+ // Use cross-platform executeClaudeWithRetry from claude-client.js with telemetry
1088
+ const response = await executeClaudeWithRetry(prompt, {
1089
+ timeout: 180000, // 3 minutes for diff analysis
1090
+ telemetryContext
1091
+ });
973
1092
 
974
1093
  // Extract JSON from response using claude-client utility
975
1094
  const result = extractJSON(response);
@@ -1231,7 +1350,20 @@ async function createPr(args) {
1231
1350
 
1232
1351
  showInfo('Generating PR metadata with Claude...');
1233
1352
  logger.debug('create-pr', 'Calling Claude with prompt', { promptLength: prompt.length });
1234
- const response = await executeClaude(prompt, { timeout: 180000 });
1353
+
1354
+ // Prepare telemetry context for create-pr
1355
+ const telemetryContext = {
1356
+ fileCount: filesArray.length,
1357
+ batchSize: filesArray.length,
1358
+ totalBatches: 1,
1359
+ model: 'sonnet', // create-pr always uses main model
1360
+ hook: 'create-pr'
1361
+ };
1362
+
1363
+ const response = await executeClaudeWithRetry(prompt, {
1364
+ timeout: 180000,
1365
+ telemetryContext
1366
+ });
1235
1367
  logger.debug('create-pr', 'Claude response received', { responseLength: response.length });
1236
1368
 
1237
1369
  const analysisResult = extractJSON(response);
@@ -1598,6 +1730,7 @@ Commands:
1598
1730
  presets List all available presets
1599
1731
  --set-preset <name> Set the active preset
1600
1732
  preset current Show the current active preset
1733
+ telemetry [action] Telemetry management (show or clear)
1601
1734
  --debug <value> Set debug mode (true, false, or status)
1602
1735
  --version, -v Show the current version
1603
1736
  help Show this help
@@ -1620,6 +1753,8 @@ Examples:
1620
1753
  claude-hooks presets # List available presets
1621
1754
  claude-hooks --set-preset backend # Set backend preset
1622
1755
  claude-hooks preset current # Show current preset
1756
+ claude-hooks telemetry show # Show telemetry statistics
1757
+ claude-hooks telemetry clear # Clear telemetry data
1623
1758
  claude-hooks --debug true # Enable debug mode
1624
1759
  claude-hooks --debug status # Check debug status
1625
1760
 
@@ -1853,6 +1988,179 @@ async function currentPreset() {
1853
1988
  }
1854
1989
  }
1855
1990
 
1991
+ // ============================================================================
1992
+ // DEPRECATED CODE SECTION - Will be removed in v3.0.0
1993
+ // ============================================================================
1994
+ // This section contains migration code for legacy configs (pre-v2.8.0)
1995
+ // Auto-executed during install when legacy config detected
1996
+ // Manual command: claude-hooks migrate-config
1997
+ // ============================================================================
1998
+
1999
+ /**
2000
+ * Extracts allowed settings from legacy config format
2001
+ * Shared by both autoMigrateConfig and migrateConfig
2002
+ *
2003
+ * @param {Object} rawConfig - Legacy format config
2004
+ * @returns {Object} Allowed overrides only
2005
+ */
2006
+ function extractLegacySettings(rawConfig) {
2007
+ const allowedOverrides = {};
2008
+
2009
+ // GitHub PR config (fully allowed)
2010
+ if (rawConfig.github?.pr) {
2011
+ allowedOverrides.github = { pr: {} };
2012
+ if (rawConfig.github.pr.defaultBase !== undefined) {
2013
+ allowedOverrides.github.pr.defaultBase = rawConfig.github.pr.defaultBase;
2014
+ }
2015
+ if (rawConfig.github.pr.reviewers !== undefined) {
2016
+ allowedOverrides.github.pr.reviewers = rawConfig.github.pr.reviewers;
2017
+ }
2018
+ if (rawConfig.github.pr.labelRules !== undefined) {
2019
+ allowedOverrides.github.pr.labelRules = rawConfig.github.pr.labelRules;
2020
+ }
2021
+ }
2022
+
2023
+ // Subagent batchSize (allowed)
2024
+ if (rawConfig.subagents?.batchSize !== undefined) {
2025
+ allowedOverrides.subagents = { batchSize: rawConfig.subagents.batchSize };
2026
+ }
2027
+
2028
+ // Advanced params (preserved with warning in manual migration)
2029
+ if (rawConfig.analysis?.ignoreExtensions !== undefined) {
2030
+ if (!allowedOverrides.analysis) allowedOverrides.analysis = {};
2031
+ allowedOverrides.analysis.ignoreExtensions = rawConfig.analysis.ignoreExtensions;
2032
+ }
2033
+
2034
+ if (rawConfig.commitMessage?.taskIdPattern !== undefined) {
2035
+ if (!allowedOverrides.commitMessage) allowedOverrides.commitMessage = {};
2036
+ allowedOverrides.commitMessage.taskIdPattern = rawConfig.commitMessage.taskIdPattern;
2037
+ }
2038
+
2039
+ if (rawConfig.subagents?.model !== undefined) {
2040
+ if (!allowedOverrides.subagents) allowedOverrides.subagents = {};
2041
+ allowedOverrides.subagents.model = rawConfig.subagents.model;
2042
+ }
2043
+
2044
+ return allowedOverrides;
2045
+ }
2046
+
2047
+ /**
2048
+ * Auto-migrates legacy config during installation
2049
+ * Called automatically by installer when legacy format detected
2050
+ *
2051
+ * @param {string} newConfigPath - Path to new config.json
2052
+ * @param {string} backupConfigPath - Path to backup of old config
2053
+ */
2054
+ async function autoMigrateConfig(newConfigPath, backupConfigPath) {
2055
+ try {
2056
+ const rawConfig = JSON.parse(fs.readFileSync(backupConfigPath, 'utf8'));
2057
+ const allowedOverrides = extractLegacySettings(rawConfig);
2058
+
2059
+ // Read the newly created minimal config
2060
+ const newConfig = JSON.parse(fs.readFileSync(newConfigPath, 'utf8'));
2061
+
2062
+ // Add overrides if any
2063
+ if (Object.keys(allowedOverrides).length > 0) {
2064
+ newConfig.overrides = allowedOverrides;
2065
+ fs.writeFileSync(newConfigPath, JSON.stringify(newConfig, null, 4));
2066
+ success('✅ Settings migrated from legacy config');
2067
+ info(`📋 Preserved ${Object.keys(allowedOverrides).length} custom settings`);
2068
+ }
2069
+ } catch (err) {
2070
+ warning(`⚠️ Could not auto-migrate settings: ${err.message}`);
2071
+ info('💡 Run "claude-hooks migrate-config" manually if needed');
2072
+ }
2073
+ }
2074
+
2075
+ /**
2076
+ * Migrates legacy config.json to v2.8.0 format (Manual command)
2077
+ * Why: Simplifies configuration, reduces redundancy
2078
+ *
2079
+ * DEPRECATED: Will be removed in v3.0.0 (most users will have migrated by then)
2080
+ */
2081
+ async function migrateConfig() {
2082
+ const claudeDir = '.claude';
2083
+ const configPath = path.join(claudeDir, 'config.json');
2084
+
2085
+ if (!fs.existsSync(configPath)) {
2086
+ info('ℹ️ No config file found. Nothing to migrate.');
2087
+ console.log('\n💡 To create a new config:');
2088
+ console.log(' 1. Run: claude-hooks install --force');
2089
+ console.log(' 2. Or copy from: .claude/config_example/config.example.json');
2090
+ return;
2091
+ }
2092
+
2093
+ try {
2094
+ const rawConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
2095
+
2096
+ // Check if already in v2.8.0 format
2097
+ if (rawConfig.version === '2.8.0') {
2098
+ success('✅ Config is already in v2.8.0 format.');
2099
+ return;
2100
+ }
2101
+
2102
+ info('📦 Starting config migration to v2.8.0...');
2103
+
2104
+ // Create backup in config_old/
2105
+ const configOldDir = path.join(claudeDir, 'config_old');
2106
+ if (!fs.existsSync(configOldDir)) {
2107
+ fs.mkdirSync(configOldDir, { recursive: true });
2108
+ }
2109
+ const backupPath = path.join(configOldDir, `config.json.${Date.now()}`);
2110
+ fs.copyFileSync(configPath, backupPath);
2111
+ success(`Backup created: ${backupPath}`);
2112
+
2113
+ // Extract only allowed parameters
2114
+ const allowedOverrides = extractLegacySettings(rawConfig);
2115
+
2116
+ // Check for advanced params
2117
+ const hasAdvancedParams = allowedOverrides.analysis?.ignoreExtensions ||
2118
+ allowedOverrides.commitMessage?.taskIdPattern ||
2119
+ allowedOverrides.subagents?.model;
2120
+
2121
+ // Build new config
2122
+ const newConfig = {
2123
+ version: '2.8.0',
2124
+ preset: rawConfig.preset || 'default'
2125
+ };
2126
+
2127
+ // Only add overrides if there are any
2128
+ if (Object.keys(allowedOverrides).length > 0) {
2129
+ newConfig.overrides = allowedOverrides;
2130
+ }
2131
+
2132
+ // Show diff
2133
+ console.log('\n📝 Migration preview:');
2134
+ console.log(` Old format: ${Object.keys(rawConfig).length} top-level keys`);
2135
+ console.log(` New format: ${Object.keys(newConfig).length} top-level keys`);
2136
+ if (Object.keys(allowedOverrides).length > 0) {
2137
+ console.log(` Preserved: ${Object.keys(allowedOverrides).length} override sections`);
2138
+ }
2139
+
2140
+ // Write new config
2141
+ fs.writeFileSync(configPath, JSON.stringify(newConfig, null, 4));
2142
+ success('✅ Config migrated to v2.8.0 successfully!');
2143
+
2144
+ if (hasAdvancedParams) {
2145
+ warning('⚠️ Advanced parameters detected and preserved');
2146
+ info('📖 See .claude/config.advanced.example.json for documentation');
2147
+ }
2148
+
2149
+ console.log(`\n✨ New config:`);
2150
+ console.log(JSON.stringify(newConfig, null, 2));
2151
+ console.log(`\n💾 Old config backed up to: ${backupPath}`);
2152
+ console.log('\n💡 Many parameters are now hardcoded with sensible defaults');
2153
+ console.log(' See CHANGELOG.md for full list of changes');
2154
+
2155
+ } catch (error) {
2156
+ error(`Failed to migrate config: ${error.message}`);
2157
+ console.log('\n💡 Manual migration:');
2158
+ console.log(' 1. Backup your current config');
2159
+ console.log(' 2. See .claude/config.example.json for new format');
2160
+ console.log(' 3. Copy minimal example and customize');
2161
+ }
2162
+ }
2163
+
1856
2164
  /**
1857
2165
  * Sets debug mode
1858
2166
  * Why: Enables detailed logging for troubleshooting
@@ -1893,6 +2201,41 @@ async function setDebug(value) {
1893
2201
  }
1894
2202
 
1895
2203
  // Main
2204
+ /**
2205
+ * Show telemetry statistics
2206
+ * Why: Help users understand JSON parsing patterns and batch performance
2207
+ */
2208
+ async function showTelemetry() {
2209
+ await showTelemetryStats();
2210
+ }
2211
+
2212
+ /**
2213
+ * Clear telemetry data
2214
+ * Why: Allow users to reset telemetry
2215
+ */
2216
+ async function clearTelemetry() {
2217
+ const config = await getConfig();
2218
+
2219
+ if (!config.system?.telemetry && config.system?.telemetry !== undefined) {
2220
+ console.log('\n⚠️ Telemetry is currently disabled.\n');
2221
+ console.log('To re-enable (default), remove or set to true in .claude/config.json:');
2222
+ console.log('{');
2223
+ console.log(' "system": {');
2224
+ console.log(' "telemetry": true');
2225
+ console.log(' }');
2226
+ console.log('}\n');
2227
+ return;
2228
+ }
2229
+
2230
+ const confirmed = await promptConfirmation('Are you sure you want to clear all telemetry data?');
2231
+ if (confirmed) {
2232
+ await clearTelemetryData();
2233
+ success('Telemetry data cleared successfully');
2234
+ } else {
2235
+ info('Telemetry data was not cleared');
2236
+ }
2237
+ }
2238
+
1896
2239
  async function main() {
1897
2240
  const args = process.argv.slice(2);
1898
2241
  const command = args[0];
@@ -1942,6 +2285,19 @@ async function main() {
1942
2285
  error(`Unknown preset subcommand: ${args[1]}`);
1943
2286
  }
1944
2287
  break;
2288
+ case 'migrate-config':
2289
+ await migrateConfig();
2290
+ break;
2291
+ case 'telemetry':
2292
+ // Handle subcommands: telemetry show, telemetry clear
2293
+ if (args[1] === 'show' || args[1] === undefined) {
2294
+ await showTelemetry();
2295
+ } else if (args[1] === 'clear') {
2296
+ await clearTelemetry();
2297
+ } else {
2298
+ error(`Unknown telemetry subcommand: ${args[1]}`);
2299
+ }
2300
+ break;
1945
2301
  case '--debug':
1946
2302
  await setDebug(args[1]);
1947
2303
  break;