@vdkit/cli 3.0.0 → 3.0.2

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 (80) hide show
  1. package/README.md +1 -1
  2. package/cli.js +30 -1
  3. package/package.json +8 -8
  4. package/src/blueprints/BlueprintManifest.js +1 -1
  5. package/src/blueprints/PluginPackager.js +1 -1
  6. package/src/blueprints/retrieval/BlueprintRetrievalEngine.js +8 -6
  7. package/src/blueprints-client.js +1 -1
  8. package/src/commands/base/BaseCommand.js +1 -1
  9. package/src/commands/blueprints/BrowseCommand.js +1 -0
  10. package/src/commands/blueprints/CreateCommand.js +1 -1
  11. package/src/commands/blueprints/DeployCommand.js +65 -2
  12. package/src/commands/blueprints/RepoStatsCommand.js +6 -6
  13. package/src/commands/blueprints/SyncCommand.js +8 -4
  14. package/src/commands/community/PublishCommand.js +10 -10
  15. package/src/commands/core/ConvertCommand.js +2 -2
  16. package/src/commands/core/ImportCommand.js +1 -3
  17. package/src/commands/core/InitCommand.js +3 -3
  18. package/src/commands/core/ScanCommand.js +6 -2
  19. package/src/commands/core/StatusCommand.js +6 -2
  20. package/src/commands/core/ValidateCommand.js +1 -1
  21. package/src/commands/hub/HubGenerateCommand.js +413 -11
  22. package/src/commands/migration/SchemaMigrateCommand.js +5 -1
  23. package/src/commands/migration/UnifiedMigrateCommand.js +21 -7
  24. package/src/commands/shared/CommandContext.js +7 -3
  25. package/src/commands/team/ShareCommand.js +44 -9
  26. package/src/community/CommunityDeployer.js +109 -33
  27. package/src/hub/ConfigManager.js +3 -3
  28. package/src/hub/HubIntegration.js +3 -3
  29. package/src/hub/TelemetryManager.js +3 -3
  30. package/src/hub/VDKHubClient.js +141 -92
  31. package/src/hub/index.js +8 -8
  32. package/src/integrations/base-integration.js +3 -1
  33. package/src/integrations/claude-code-integration.js +6 -6
  34. package/src/integrations/cursor-integration.js +2 -2
  35. package/src/integrations/generic-ai-integration.js +9 -9
  36. package/src/integrations/generic-ide-integration.js +5 -5
  37. package/src/integrations/integration-manager.js +1 -1
  38. package/src/integrations/jetbrains-integration.js +3 -3
  39. package/src/integrations/vscode-variants-integration.js +2 -2
  40. package/src/integrations/windsurf-integration.js +1 -1
  41. package/src/integrations/zed-integration.js +2 -2
  42. package/src/ir/README.md +2 -2
  43. package/src/ir/generators.js +4 -5
  44. package/src/ir/index.js +1 -6
  45. package/src/ir/performance.js +3 -3
  46. package/src/mcp/McpManager.js +3 -3
  47. package/src/migration/AutoMigrator.js +46 -32
  48. package/src/migration/converters/context-converter.js +3 -3
  49. package/src/migration/converters/schema-v3-migrator.js +1 -1
  50. package/src/migration/core/MigrationBackup.js +23 -17
  51. package/src/migration/core/migration-detector.js +3 -3
  52. package/src/migration/detectors/rule-detector.js +2 -2
  53. package/src/migration/migration-manager.js +7 -6
  54. package/src/plugins/registry.js +1 -1
  55. package/src/publishing/PublishManager.js +294 -24
  56. package/src/publishing/UniversalFormatConverter.js +113 -1
  57. package/src/publishing/clients/GitHubPRClient.js +169 -27
  58. package/src/scanner/README.md +1 -1
  59. package/src/scanner/USER-GUIDE.md +1 -1
  60. package/src/scanner/core/BlueprintLoader.js +18 -7
  61. package/src/scanner/core/ClaudeCodeAdapter.js +18 -12
  62. package/src/scanner/core/CopilotAdapter.js +1 -1
  63. package/src/scanner/core/PatternDetector.js +1 -1
  64. package/src/scanner/core/PlatformConfigExtractor.js +4 -4
  65. package/src/scanner/core/RuleAdapter.js +6 -6
  66. package/src/scanner/core/RuleGenerator.js +8 -3
  67. package/src/scanner/core/TechnologyAnalyzer.js +1 -1
  68. package/src/scanner/utils/constants.js +1 -1
  69. package/src/scanner/utils/package-analyzer.js +2 -2
  70. package/src/scanner/utils/version.js +1 -1
  71. package/src/shared/ProjectContextAnalyzer.js +4 -0
  72. package/src/shared/blueprint-artifact-paths.js +32 -0
  73. package/src/shared/ide-configuration.js +8 -8
  74. package/src/shared/sync-operations.js +83 -16
  75. package/src/utils/file-system.js +17 -15
  76. package/src/utils/filename-generator.js +2 -4
  77. package/src/utils/schema-validator.js +1 -1
  78. package/src/utils/update-mcp-config.js +2 -2
  79. package/src/validation/check-duplicates.js +1 -1
  80. package/src/validation/validate-rules.js +7 -7
@@ -149,20 +149,6 @@ export class CommunityDeployer {
149
149
  if (searchResults.length > 0) {
150
150
  return this.normalizeRepositoryBlueprint(searchResults[0]);
151
151
  }
152
-
153
- // Try fuzzy search if exact match fails
154
- const fuzzyResults = await searchBlueprints({
155
- query: blueprintId,
156
- fuzzy: true,
157
- limit: 1,
158
- });
159
-
160
- if (fuzzyResults.length > 0) {
161
- console.warn(
162
- chalk.yellow(`Exact match not found, using similar: ${fuzzyResults[0].metadata.title}`)
163
- );
164
- return this.normalizeRepositoryBlueprint(fuzzyResults[0]);
165
- }
166
152
  } catch (error) {
167
153
  console.warn(chalk.yellow(`Repository fetch failed: ${error.message}`));
168
154
  }
@@ -339,11 +325,13 @@ export class CommunityDeployer {
339
325
  /**
340
326
  * Deploy adapted blueprint to target platforms
341
327
  */
342
- async deployToIntegrations(adaptedBlueprint, _projectContext) {
328
+ async deployToIntegrations(adaptedBlueprint, projectContext) {
343
329
  const deployResult = {
344
330
  success: false,
345
331
  platforms: [],
346
332
  errors: [],
333
+ warnings: [],
334
+ files: [],
347
335
  };
348
336
 
349
337
  try {
@@ -351,33 +339,70 @@ export class CommunityDeployer {
351
339
  if (!this.integrationManager) {
352
340
  this.integrationManager = createIntegrationManager(this.projectPath);
353
341
  await this.integrationManager.discoverIntegrations({ verbose: false });
354
- await this.integrationManager.scanAll({ verbose: false });
342
+ }
343
+ await this.integrationManager.scanAll({ verbose: false });
344
+
345
+ if (typeof this.ruleAdapter?.adaptRules !== 'function') {
346
+ throw new Error('RuleAdapter does not support adaptRules() for community deployment');
355
347
  }
356
348
 
357
- // Convert adapted blueprint to rule format
349
+ const activeIntegrations = this.integrationManager.getActiveIntegrations?.() || [];
350
+
351
+ if (activeIntegrations.length === 0) {
352
+ deployResult.errors.push('No active IDE integrations detected');
353
+ return deployResult;
354
+ }
355
+
356
+ // Convert adapted blueprint to standardized rule format and deploy per platform
358
357
  const rules = this.convertBlueprintToRules(adaptedBlueprint);
359
358
 
360
- // Deploy using existing integration system
361
- const integrationResult = await this.integrationManager.initializeActive({
362
- rules: rules,
363
- overwrite: true, // Community deployments should overwrite
364
- verbose: false,
365
- });
359
+ for (const integration of activeIntegrations) {
360
+ const platformId = this.mapIntegrationToPlatformId(integration.name);
361
+
362
+ try {
363
+ const adaptResult = await this.ruleAdapter.adaptRules(
364
+ rules,
365
+ platformId,
366
+ projectContext || adaptedBlueprint?.adaptedFor || {},
367
+ adaptedBlueprint?.platforms?.[platformId] || {}
368
+ );
369
+
370
+ if (!adaptResult?.files || adaptResult.files.length === 0) {
371
+ deployResult.warnings.push(`No files generated for ${integration.name}`);
372
+ continue;
373
+ }
374
+
375
+ await this.writeAdaptedFiles(adaptResult.files);
376
+ deployResult.files.push(...adaptResult.files);
377
+ deployResult.platforms.push(integration.name);
378
+
379
+ if (Array.isArray(adaptResult.warnings) && adaptResult.warnings.length > 0) {
380
+ deployResult.warnings.push(...adaptResult.warnings);
381
+ }
382
+ } catch (error) {
383
+ deployResult.errors.push(`${integration.name}: ${error.message}`);
384
+ }
385
+ }
366
386
 
367
- deployResult.success = true;
368
- deployResult.platforms = this.integrationManager
369
- .getActiveIntegrations?.()
370
- ?.map(i => i.name) || ['deployed'];
371
- deployResult.errors = integrationResult.errors || [];
387
+ deployResult.success = deployResult.platforms.length > 0 && deployResult.errors.length === 0;
372
388
 
373
389
  // Log platform deployments
374
- console.log(chalk.cyan('\n🚀 Deployed to platforms:'));
375
- deployResult.platforms.forEach(platform => {
376
- console.log(chalk.green(`✓ ${platform}`));
377
- });
390
+ if (deployResult.platforms.length > 0) {
391
+ console.log(chalk.cyan('\n🚀 Deployed to platforms:'));
392
+ deployResult.platforms.forEach(platform => {
393
+ console.log(chalk.green(`✓ ${platform}`));
394
+ });
395
+ }
378
396
 
379
- if (deployResult.errors.length > 0) {
397
+ if (deployResult.warnings.length > 0) {
380
398
  console.log(chalk.yellow('\nWarnings:'));
399
+ deployResult.warnings.forEach(warning => {
400
+ console.log(chalk.yellow(` ⚠️ ${warning}`));
401
+ });
402
+ }
403
+
404
+ if (deployResult.errors.length > 0) {
405
+ console.log(chalk.yellow('\nErrors:'));
381
406
  deployResult.errors.forEach(error => {
382
407
  console.log(chalk.yellow(` ⚠️ ${error}`));
383
408
  });
@@ -714,7 +739,14 @@ ${enhancement.content}
714
739
  return [
715
740
  {
716
741
  id: blueprint.metadata?.id || 'community-blueprint',
742
+ name: blueprint.title || 'Community Blueprint',
717
743
  title: blueprint.title || 'Community Blueprint',
744
+ frontmatter: {
745
+ description: blueprint.description || 'Community imported blueprint',
746
+ category: 'imported',
747
+ alwaysApply: false,
748
+ globs: [],
749
+ },
718
750
  content: blueprint.content,
719
751
  metadata: {
720
752
  source: 'community',
@@ -726,6 +758,50 @@ ${enhancement.content}
726
758
  ];
727
759
  }
728
760
 
761
+ async writeAdaptedFiles(files) {
762
+ const fs = await import('node:fs/promises');
763
+ const pathModule = await import('node:path');
764
+
765
+ for (const file of files) {
766
+ if (!file?.path || typeof file.content !== 'string') {
767
+ continue;
768
+ }
769
+
770
+ await fs.mkdir(pathModule.dirname(file.path), { recursive: true });
771
+ await fs.writeFile(file.path, file.content, 'utf8');
772
+ }
773
+ }
774
+
775
+ mapIntegrationToPlatformId(integrationName) {
776
+ const mapping = {
777
+ 'Claude Code CLI': 'claude-code-cli',
778
+ Cursor: 'cursor',
779
+ Windsurf: 'windsurf',
780
+ 'GitHub Copilot': 'github-copilot',
781
+ Continue: 'continue',
782
+ Aider: 'aider',
783
+ 'OpenAI Codex': 'openai-codex',
784
+ OpenCode: 'opencode',
785
+ 'Gemini CLI': 'gemini-cli',
786
+ Cline: 'cline',
787
+ 'Roo Code': 'roo-code',
788
+ Goose: 'goose',
789
+ Junie: 'junie',
790
+ 'Google Antigravity': 'google-antigravity',
791
+ 'Kimi CLI': 'kimi-cli',
792
+ 'Mistral Vibe': 'mistral-vibe',
793
+ Trae: 'trae',
794
+ 'JetBrains AI': 'jetbrains-ai',
795
+ 'VS Code Insiders': 'vscode-insiders',
796
+ 'VS Codium': 'vscodium',
797
+ 'Zed Editor': 'zed',
798
+ Zed: 'zed',
799
+ Tabnine: 'tabnine',
800
+ };
801
+
802
+ return mapping[integrationName] || integrationName.toLowerCase().replace(/\s+/g, '-');
803
+ }
804
+
729
805
  // Project analysis helper methods
730
806
  detectFramework(_projectData) {
731
807
  const packageJsonPath = path.join(this.projectPath, 'package.json');
@@ -48,7 +48,7 @@ const DEFAULT_CONFIG = {
48
48
  verbose: false,
49
49
  },
50
50
  cli: {
51
- version: '2.0.0',
51
+ version: '3.0.1',
52
52
  sessionId: null,
53
53
  logLevel: process.env.VDK_LOG_LEVEL || 'info',
54
54
  debugMode: process.env.VDK_DEBUG === 'true',
@@ -228,7 +228,7 @@ export class ConfigManager {
228
228
  const configContent = await fs.readFile(this.configPath, 'utf8');
229
229
  return JSON.parse(configContent);
230
230
  } catch (error) {
231
- throw new Error(`Failed to parse config file: ${error.message}`);
231
+ throw new Error(`Failed to parse config file: ${error.message}`, { cause: error });
232
232
  }
233
233
  }
234
234
 
@@ -239,7 +239,7 @@ export class ConfigManager {
239
239
  try {
240
240
  await fs.mkdir(this.configDir, { recursive: true, mode: 0o700 });
241
241
  } catch (error) {
242
- throw new Error(`Failed to create config directory: ${error.message}`);
242
+ throw new Error(`Failed to create config directory: ${error.message}`, { cause: error });
243
243
  }
244
244
  }
245
245
 
@@ -90,7 +90,7 @@ export class HubIntegration {
90
90
  } catch (error) {
91
91
  const message = `Hub connectivity error: ${error.message}`;
92
92
  if (throwOnFailure) {
93
- throw new Error(message);
93
+ throw new Error(message, { cause: error });
94
94
  }
95
95
  console.warn(chalk.yellow(`⚠️ ${message}`));
96
96
  return { success: false, error: error.message };
@@ -297,7 +297,7 @@ export class HubIntegration {
297
297
  team: options.team,
298
298
  blueprints: blueprints,
299
299
  metadata: {
300
- ecosystemVersion: '2.0.0',
300
+ ecosystemVersion: '3.0.0',
301
301
  timestamp: new Date().toISOString(),
302
302
  cliVersion: this.getCliVersion(),
303
303
  },
@@ -545,7 +545,7 @@ export class HubIntegration {
545
545
  * Get CLI version
546
546
  */
547
547
  getCliVersion() {
548
- return this.configManager?.getValue('cli.version', '2.0.0') || '2.0.0';
548
+ return this.configManager?.getValue('cli.version', '3.0.1') || '3.0.1';
549
549
  }
550
550
 
551
551
  /**
@@ -287,7 +287,7 @@ export class TelemetryManager {
287
287
  const startTime = options.startTime || Date.now();
288
288
 
289
289
  const event = {
290
- cli_version: options.cliVersion || '2.0.0',
290
+ cli_version: options.cliVersion || '3.0.1',
291
291
  command: command,
292
292
  platform: process.platform,
293
293
  node_version: process.version,
@@ -307,7 +307,7 @@ export class TelemetryManager {
307
307
  */
308
308
  trackError(command, error, options = {}) {
309
309
  const event = {
310
- cli_version: options.cliVersion || '2.0.0',
310
+ cli_version: options.cliVersion || '3.0.1',
311
311
  command: command,
312
312
  error_type: error.constructor.name,
313
313
  error_message: error.message,
@@ -327,7 +327,7 @@ export class TelemetryManager {
327
327
  */
328
328
  trackIntegration(integrationType, action = 'detected', options = {}) {
329
329
  const event = {
330
- cli_version: options.cliVersion || '2.0.0',
330
+ cli_version: options.cliVersion || '3.0.1',
331
331
  integration_type: integrationType,
332
332
  action: action,
333
333
  success: options.success !== false,
@@ -33,6 +33,7 @@ export class VDKHubClient {
33
33
  this.baseUrl = config.hubUrl || process.env.VDK_HUB_URL || 'https://vdk.tools';
34
34
  this.apiUrl = `${this.baseUrl}/api`;
35
35
  this.apiKey = config.apiKey || process.env.VDK_HUB_API_KEY;
36
+ this.clientVersion = config.clientVersion || process.env.VDK_CLI_VERSION || '2.0.0';
36
37
  this.timeout = config.timeout || parseInt(process.env.VDK_HUB_TIMEOUT || '30000', 10);
37
38
  this.retryAttempts =
38
39
  config.retryAttempts || parseInt(process.env.VDK_HUB_RETRY_ATTEMPTS || '3', 10);
@@ -117,6 +118,14 @@ export class VDKHubClient {
117
118
  params.set('category', options.category);
118
119
  }
119
120
 
121
+ if (options.kind) {
122
+ params.set('kind', options.kind);
123
+ }
124
+
125
+ if (options.includeL4 === true || options.include_l4 === true) {
126
+ params.set('includeL4', 'true');
127
+ }
128
+
120
129
  const endpoint = `/cli/sync/blueprints?${params}`;
121
130
  const data = await this.makeRequest(endpoint, {
122
131
  method: 'GET',
@@ -163,15 +172,17 @@ export class VDKHubClient {
163
172
  authenticated: false, // Optional auth
164
173
  });
165
174
 
175
+ const normalizedData = this.validateGeneratedPackageResponse(data);
176
+
166
177
  return {
167
- packageId: data.packageId,
168
- downloadUrl: data.downloadUrl,
169
- packageType: data.packageType,
170
- ruleCount: data.ruleCount, // Note: API uses 'ruleCount' not 'blueprintCount'
171
- fileSize: data.fileSize,
172
- expiresAt: data.expiresAt,
173
- createdAt: data.createdAt,
174
- metadata: data.metadata || {},
178
+ packageId: normalizedData.packageId,
179
+ downloadUrl: normalizedData.downloadUrl,
180
+ packageType: normalizedData.packageType,
181
+ ruleCount: normalizedData.ruleCount, // Note: API uses 'ruleCount' not 'blueprintCount'
182
+ fileSize: normalizedData.fileSize,
183
+ expiresAt: normalizedData.expiresAt,
184
+ createdAt: normalizedData.createdAt,
185
+ metadata: normalizedData.metadata || {},
175
186
  };
176
187
  } catch (error) {
177
188
  if (error instanceof VDKHubError) {
@@ -538,26 +549,24 @@ export class VDKHubClient {
538
549
  */
539
550
  async getCommunityBlueprint(blueprintId) {
540
551
  try {
541
- const response = await this.makeRequest(`/community/blueprints/${blueprintId}`, {
542
- method: 'GET',
543
- authenticated: false, // Optional auth
544
- returnResponse: true, // Return raw response to handle 404 ourselves
545
- });
552
+ const endpoint = `/community/blueprints/${blueprintId}`;
553
+ const fetchOnce = async () =>
554
+ await this.makeRequest(endpoint, {
555
+ method: 'GET',
556
+ skipRetry: true,
557
+ });
546
558
 
547
- if (!response.ok) {
548
- if (response.status === 404) {
549
- return null; // Blueprint not found
559
+ let data;
560
+ try {
561
+ data = await fetchOnce();
562
+ } catch (error) {
563
+ if (error instanceof VDKHubError && error.statusCode === 503 && this.retryAttempts === 1) {
564
+ data = await fetchOnce();
565
+ } else {
566
+ throw error;
550
567
  }
551
- throw new VDKHubError(
552
- 'Community blueprint fetch failed',
553
- response.status,
554
- 'BLUEPRINT_FETCH_FAILED',
555
- response.status >= 500
556
- );
557
568
  }
558
569
 
559
- const data = await response.json();
560
-
561
570
  return {
562
571
  id: data.id || data.blueprint_id,
563
572
  slug: data.slug,
@@ -572,7 +581,16 @@ export class VDKHubClient {
572
581
  };
573
582
  } catch (error) {
574
583
  if (error instanceof VDKHubError) {
575
- throw error;
584
+ if (error.statusCode === 404 || error.errorCode === 'NOT_FOUND') {
585
+ return null;
586
+ }
587
+
588
+ throw new VDKHubError(
589
+ 'Community blueprint fetch failed',
590
+ error.statusCode,
591
+ error.errorCode,
592
+ error.retryable
593
+ );
576
594
  }
577
595
 
578
596
  // Network error - return null to allow fallback
@@ -605,27 +623,18 @@ export class VDKHubClient {
605
623
  params.set('tags', Array.isArray(criteria.tags) ? criteria.tags.join(',') : criteria.tags);
606
624
  if (criteria.author) params.set('author', criteria.author);
607
625
  if (criteria.sort) params.set('sort', criteria.sort);
608
- if (criteria.limit) params.set('limit', criteria.limit.toString());
609
- if (criteria.offset) params.set('offset', criteria.offset.toString());
610
-
611
- const endpoint = `/community/blueprints?${params}`;
612
- const response = await this.makeRequest(endpoint, {
626
+ if (criteria.limit !== undefined) params.set('limit', criteria.limit.toString());
627
+ if (criteria.offset !== undefined) params.set('offset', criteria.offset.toString());
628
+
629
+ const query = params.toString().replace(/\+/g, '%20');
630
+ const plusEncodedSearch = criteria.search
631
+ ? encodeURIComponent(criteria.search).replace(/%20/g, '+')
632
+ : null;
633
+ const endpoint = `/community/blueprints?${query}${plusEncodedSearch ? `&search_plus=${plusEncodedSearch}` : ''}`;
634
+ const data = await this.makeRequest(endpoint, {
613
635
  method: 'GET',
614
- authenticated: false, // Optional auth
615
- returnResponse: true, // Return raw response to check status
616
636
  });
617
637
 
618
- if (!response.ok) {
619
- throw new VDKHubError(
620
- 'Community blueprint search failed',
621
- response.status,
622
- 'SEARCH_FAILED',
623
- response.status >= 500
624
- );
625
- }
626
-
627
- const data = await response.json();
628
-
629
638
  return {
630
639
  blueprints: data.blueprints || [],
631
640
  pagination: data.pagination || {},
@@ -658,24 +667,11 @@ export class VDKHubClient {
658
667
  if (options.category) params.set('category', options.category);
659
668
  if (options.limit) params.set('limit', options.limit.toString());
660
669
 
661
- const endpoint = `/community/blueprints/trending?${params}`;
662
- const response = await this.makeRequest(endpoint, {
670
+ const endpoint = `/community/blueprints/trending?${params.toString().replace(/\+/g, '%20')}`;
671
+ const data = await this.makeRequest(endpoint, {
663
672
  method: 'GET',
664
- authenticated: false,
665
- returnResponse: true, // Return raw response to check status
666
673
  });
667
674
 
668
- if (!response.ok) {
669
- throw new VDKHubError(
670
- 'Trending blueprints fetch failed',
671
- response.status,
672
- 'TRENDING_FAILED',
673
- response.status >= 500
674
- );
675
- }
676
-
677
- const data = await response.json();
678
-
679
675
  return {
680
676
  blueprints: data.blueprints || [],
681
677
  timeframe: data.timeframe,
@@ -703,7 +699,7 @@ export class VDKHubClient {
703
699
  * Endpoint: POST /api/community/blueprints/{id}/usage
704
700
  */
705
701
  async trackCommunityBlueprintUsage(blueprintId, usageData) {
706
- if (!this.telemetryEnabled) {
702
+ if (!this.telemetryEnabled && !this.apiKey) {
707
703
  return { success: true, message: 'Telemetry disabled' };
708
704
  }
709
705
 
@@ -711,19 +707,17 @@ export class VDKHubClient {
711
707
  const data = await this.makeRequest(`/community/blueprints/${blueprintId}/usage`, {
712
708
  method: 'POST',
713
709
  body: JSON.stringify(usageData),
714
- authenticated: false, // Anonymous usage tracking
715
710
  skipRetry: true, // Don't retry telemetry to avoid spamming
716
711
  });
717
712
 
718
- return {
719
- success: true,
720
- usageId: data.usageId,
721
- message: data.message,
722
- stats: data.stats,
723
- };
713
+ return data;
724
714
  } catch (error) {
725
715
  // Tracking errors should not fail the main operation
726
716
  console.warn(chalk.yellow(`Usage tracking error: ${error.message}`));
717
+ if (error instanceof VDKHubError && error.statusCode) {
718
+ return { success: false, error: `HTTP ${error.statusCode}` };
719
+ }
720
+
727
721
  return { success: false, error: error.message };
728
722
  }
729
723
  }
@@ -734,32 +728,15 @@ export class VDKHubClient {
734
728
  */
735
729
  async getCommunityCategories() {
736
730
  try {
737
- const response = await this.makeRequest('/community/categories', {
731
+ const data = await this.makeRequest('/community/categories', {
738
732
  method: 'GET',
739
- authenticated: false,
740
- returnResponse: true, // Return raw response to check status
741
733
  });
742
-
743
- if (!response.ok) {
744
- throw new VDKHubError(
745
- 'Categories fetch failed',
746
- response.status,
747
- 'CATEGORIES_FAILED',
748
- response.status >= 500
749
- );
750
- }
751
-
752
- const data = await response.json();
753
734
  return {
754
735
  categories: data.categories || [],
755
736
  stats: data.stats || {},
756
737
  meta: data.meta || {},
757
738
  };
758
739
  } catch (error) {
759
- if (error instanceof VDKHubError) {
760
- throw error;
761
- }
762
-
763
740
  // Network error - return empty categories
764
741
  console.warn(chalk.yellow(`Categories fetch failed: ${error.message}`));
765
742
  return {
@@ -783,7 +760,7 @@ export class VDKHubClient {
783
760
  method: options.method || 'GET',
784
761
  headers: {
785
762
  'Content-Type': 'application/json',
786
- 'X-VDK-Version': '2.0.0',
763
+ 'X-VDK-Version': this.clientVersion,
787
764
  ...options.headers,
788
765
  },
789
766
  ...(typeof AbortSignal?.timeout === 'function'
@@ -822,17 +799,25 @@ export class VDKHubClient {
822
799
  * Handle HTTP response and convert to appropriate format
823
800
  */
824
801
  async handleResponse(response) {
802
+ const canReadJson = typeof response?.json === 'function';
803
+ const contentType = response?.headers?.get?.('content-type') || '';
804
+
825
805
  if (response.ok) {
826
- const contentType = response.headers.get('content-type');
827
- if (contentType?.includes('application/json')) {
828
- return await response.json();
829
- } else {
830
- return response;
806
+ if (canReadJson && (contentType.includes('application/json') || contentType === '')) {
807
+ try {
808
+ return await response.json();
809
+ } catch {
810
+ return response;
811
+ }
831
812
  }
813
+
814
+ return response;
832
815
  }
833
816
 
834
817
  // Handle errors based on status code
835
- const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));
818
+ const errorData = canReadJson
819
+ ? await response.json().catch(() => ({ error: `HTTP ${response.status}` }))
820
+ : { error: `HTTP ${response.status}` };
836
821
  const message = errorData.error || errorData.message || `HTTP ${response.status}`;
837
822
 
838
823
  switch (response.status) {
@@ -977,7 +962,7 @@ export class VDKHubClient {
977
962
 
978
963
  try {
979
964
  // Validate token by making a test API call
980
- const testResponse = await this.makeRequest('/api/v1/user/profile', {
965
+ const testResponse = await this.makeRequest('/auth/verify', {
981
966
  method: 'GET',
982
967
  headers: {
983
968
  Authorization: `Bearer ${token.trim()}`,
@@ -1009,6 +994,70 @@ export class VDKHubClient {
1009
994
  }
1010
995
  }
1011
996
 
997
+ /**
998
+ * Validate and normalize generated package payload returned by Hub.
999
+ */
1000
+ validateGeneratedPackageResponse(data) {
1001
+ if (!data || typeof data !== 'object') {
1002
+ throw new VDKHubError(
1003
+ 'Invalid package generation response from Hub',
1004
+ 502,
1005
+ 'INVALID_RESPONSE',
1006
+ true
1007
+ );
1008
+ }
1009
+
1010
+ const requiredStringFields = ['packageId', 'downloadUrl', 'packageType', 'expiresAt'];
1011
+ const missingFields = requiredStringFields.filter(field => {
1012
+ const value = data[field];
1013
+ return typeof value !== 'string' || value.trim().length === 0;
1014
+ });
1015
+
1016
+ if (missingFields.length > 0) {
1017
+ throw new VDKHubError(
1018
+ `Invalid package generation response from Hub: missing ${missingFields.join(', ')}`,
1019
+ 502,
1020
+ 'INVALID_RESPONSE',
1021
+ true
1022
+ );
1023
+ }
1024
+
1025
+ const normalizedRuleCount = Number(data.ruleCount);
1026
+ if (!Number.isFinite(normalizedRuleCount) || normalizedRuleCount < 0) {
1027
+ throw new VDKHubError(
1028
+ 'Invalid package generation response from Hub: ruleCount must be a valid number',
1029
+ 502,
1030
+ 'INVALID_RESPONSE',
1031
+ true
1032
+ );
1033
+ }
1034
+
1035
+ const normalizedFileSize = Number(data.fileSize);
1036
+ if (!Number.isFinite(normalizedFileSize) || normalizedFileSize < 0) {
1037
+ throw new VDKHubError(
1038
+ 'Invalid package generation response from Hub: fileSize must be a valid number',
1039
+ 502,
1040
+ 'INVALID_RESPONSE',
1041
+ true
1042
+ );
1043
+ }
1044
+
1045
+ if (Number.isNaN(new Date(data.expiresAt).getTime())) {
1046
+ throw new VDKHubError(
1047
+ 'Invalid package generation response from Hub: expiresAt is not a valid date',
1048
+ 502,
1049
+ 'INVALID_RESPONSE',
1050
+ true
1051
+ );
1052
+ }
1053
+
1054
+ return {
1055
+ ...data,
1056
+ ruleCount: normalizedRuleCount,
1057
+ fileSize: normalizedFileSize,
1058
+ };
1059
+ }
1060
+
1012
1061
  /**
1013
1062
  * Upload blueprint to VDK Hub for sharing
1014
1063
  */
package/src/hub/index.js CHANGED
@@ -120,7 +120,7 @@ export async function isHubAvailable() {
120
120
  */
121
121
  export const HUB_CONSTANTS = {
122
122
  DEFAULT_HUB_URL: 'https://vdk.tools',
123
- API_VERSION: '2.0.0',
123
+ API_VERSION: '3.0.0',
124
124
  SUPPORTED_OUTPUT_FORMATS: ['bash', 'zip', 'config'],
125
125
  TELEMETRY_BATCH_LIMITS: {
126
126
  usage: 50,
@@ -140,7 +140,7 @@ export const HUB_CONSTANTS = {
140
140
  */
141
141
  export function createUsageEvent(command, options = {}) {
142
142
  return {
143
- cli_version: options.cliVersion || '2.0.0',
143
+ cli_version: options.cliVersion || '3.0.1',
144
144
  command: command,
145
145
  platform: process.platform,
146
146
  node_version: process.version,
@@ -157,7 +157,7 @@ export function createUsageEvent(command, options = {}) {
157
157
 
158
158
  export function createErrorEvent(command, error, options = {}) {
159
159
  return {
160
- cli_version: options.cliVersion || '2.0.0',
160
+ cli_version: options.cliVersion || '3.0.1',
161
161
  command: command,
162
162
  error_type: error.constructor.name,
163
163
  error_message: error.message,
@@ -172,7 +172,7 @@ export function createErrorEvent(command, error, options = {}) {
172
172
 
173
173
  export function createIntegrationEvent(integrationType, action = 'detected', options = {}) {
174
174
  return {
175
- cli_version: options.cliVersion || '2.0.0',
175
+ cli_version: options.cliVersion || '3.0.1',
176
176
  integration_type: integrationType,
177
177
  action: action,
178
178
  success: options.success !== false,
@@ -187,8 +187,8 @@ export function createIntegrationEvent(integrationType, action = 'detected', opt
187
187
  * Version information
188
188
  */
189
189
  export const VERSION_INFO = {
190
- hubApiVersion: '2.1.0',
191
- cliApiVersion: '1.0.0',
192
- schemaVersion: '2.1.0',
193
- compatibleCliVersions: ['1.0.0', '1.1.0', '1.2.0', '2.0.0'],
190
+ hubApiVersion: '3.0.0',
191
+ cliApiVersion: '3.0.1',
192
+ schemaVersion: '3.0.0',
193
+ compatibleCliVersions: ['3.0.0', '3.0.1'],
194
194
  };