korext 0.9.9 → 0.9.11

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 (2) hide show
  1. package/bin/korext.js +112 -17
  2. package/package.json +1 -1
package/bin/korext.js CHANGED
@@ -367,7 +367,7 @@ program
367
367
  program
368
368
  .command('login [token]')
369
369
  .description('Authenticate with the Korext platform using a personal access token')
370
- .action((tokenArg) => {
370
+ .action(async (tokenArg) => {
371
371
  let token = tokenArg;
372
372
  if (!token) {
373
373
  console.log(`\nTo authenticate, sign in at ${chalk.cyan('https://app.korext.com')} and use your session token.`);
@@ -377,10 +377,31 @@ program
377
377
  console.log(`\nThen run: ${chalk.green('korext login <your-token>')}\n`);
378
378
  process.exit(1);
379
379
  }
380
- const config = getConfig();
381
- config.token = token;
382
- saveConfig(config);
383
- console.log(chalk.green('✔ Successfully logged in and saved token.'));
380
+ // Validate token with the server before saving
381
+ const spinner = ora('Validating token...').start();
382
+ try {
383
+ const res = await fetch(`${API_URL}/api/ide/status`, {
384
+ headers: { Authorization: `Bearer ${token}` },
385
+ signal: AbortSignal.timeout(8000),
386
+ });
387
+ if (res.ok) {
388
+ const config = getConfig();
389
+ config.token = token;
390
+ saveConfig(config);
391
+ const data = await res.json().catch(() => ({}));
392
+ spinner.succeed(chalk.green(`Authenticated as ${data.workspace || 'developer'}. Token saved.`));
393
+ } else {
394
+ spinner.fail(chalk.red('Invalid or expired token. Not saved.'));
395
+ console.log(chalk.dim(`\nSign in at ${chalk.cyan('https://app.korext.com')} to get a valid token.`));
396
+ process.exit(1);
397
+ }
398
+ } catch (e) {
399
+ // Network error: save token optimistically but warn
400
+ const config = getConfig();
401
+ config.token = token;
402
+ saveConfig(config);
403
+ spinner.warn(chalk.yellow('Could not reach server to validate token. Token saved (will be verified on next command).'));
404
+ }
384
405
  });
385
406
 
386
407
  program
@@ -609,18 +630,65 @@ program
609
630
  });
610
631
 
611
632
  console.log();
612
- const industryInput = await ask(chalk.bold(' Select industries (comma-separated numbers or IDs): '));
633
+ const industryInput = await ask(chalk.bold(' Select industries or packs (numbers, IDs, or names): '));
613
634
 
614
- // Parse user input (numbers or IDs)
615
- const selectedIndustries = industryInput.split(',').map(s => s.trim()).filter(Boolean).map(s => {
616
- const num = parseInt(s, 10);
635
+ // Parse user input: accept numbers, industry IDs, industry names (fuzzy), or pack IDs
636
+ const rawTokens = industryInput.split(',').map(s => s.trim()).filter(Boolean);
637
+ const selectedIndustries = [];
638
+ const directPacks = [];
639
+
640
+ for (const token of rawTokens) {
641
+ const lower = token.toLowerCase();
642
+
643
+ // Strategy 1: Number (e.g., "8" for Finance)
644
+ const num = parseInt(token, 10);
617
645
  if (!isNaN(num) && num >= 1 && num <= sortedIndustries.length) {
618
- return sortedIndustries[num - 1][0];
646
+ selectedIndustries.push(sortedIndustries[num - 1][0]);
647
+ continue;
648
+ }
649
+
650
+ // Strategy 2: Industry ID exact match (e.g., "finance")
651
+ if (taxonomy.industries[lower]) {
652
+ selectedIndustries.push(lower);
653
+ continue;
654
+ }
655
+
656
+ // Strategy 3: Industry label fuzzy match (e.g., "Finance", "health", "aero")
657
+ const labelMatch = sortedIndustries.find(([id, group]) =>
658
+ group.label.toLowerCase().startsWith(lower)
659
+ );
660
+ if (labelMatch) {
661
+ selectedIndustries.push(labelMatch[0]);
662
+ console.log(chalk.dim(` "${token}" matched industry: ${labelMatch[1].label}`));
663
+ continue;
664
+ }
665
+
666
+ // Strategy 4: Direct pack ID (e.g., "web", "pci-dss-v1", "security")
667
+ const allPackIds = Object.keys(defs.packs);
668
+ if (allPackIds.includes(lower)) {
669
+ directPacks.push(lower);
670
+ console.log(chalk.dim(` "${token}" is a pack ID, adding directly.`));
671
+ continue;
619
672
  }
620
- return s.toLowerCase();
621
- }).filter(id => taxonomy.industries[id]);
622
673
 
623
- if (selectedIndustries.length === 0) {
674
+ // Nothing matched
675
+ console.log(chalk.yellow(` Unknown input: "${token}" (not an industry, pack ID, or name)`));
676
+ }
677
+
678
+ // If user typed only pack IDs (e.g., "web" or "web, security"), skip region prompt
679
+ if (selectedIndustries.length === 0 && directPacks.length > 0) {
680
+ const config = {
681
+ targetPacks: directPacks,
682
+ exclude: ['node_modules', 'dist', 'build', '.next']
683
+ };
684
+ fs.writeFileSync(outputPath, JSON.stringify(config, null, 2));
685
+ console.log(chalk.green(`\n Created ${outputPath} with packs: ${directPacks.join(', ')}\n`));
686
+ console.log(chalk.dim(` Run: ${chalk.green('korext enforce .')} to enforce these packs.\n`));
687
+ rl.close();
688
+ process.exit(0);
689
+ }
690
+
691
+ if (selectedIndustries.length === 0 && directPacks.length === 0) {
624
692
  console.log(chalk.yellow('\n No valid industries selected. Using default: web\n'));
625
693
  const config = {
626
694
  targetPacks: ['web'],
@@ -655,22 +723,25 @@ program
655
723
  }
656
724
  }
657
725
 
658
- // Resolve packs
726
+ // Resolve packs from industries
659
727
  const resolvedPacks = resolvePacksForIndustryRegion(
660
728
  selectedIndustries.join(','),
661
729
  selectedRegion,
662
730
  taxonomy
663
731
  );
664
732
 
665
- console.log(chalk.green(`\n Resolved ${resolvedPacks.length} packs for ${selectedIndustries.map(id => INDUSTRY_LABELS[id]).join(', ')}${selectedRegion ? ` (${REGION_LABELS[selectedRegion] || selectedRegion})` : ''}:`));
666
- for (const pid of resolvedPacks) {
733
+ // Merge any direct pack IDs the user typed alongside industries
734
+ const allPacks = [...new Set([...resolvedPacks, ...directPacks])];
735
+
736
+ console.log(chalk.green(`\n Resolved ${allPacks.length} packs for ${selectedIndustries.map(id => INDUSTRY_LABELS[id]).join(', ')}${selectedRegion ? ` (${REGION_LABELS[selectedRegion] || selectedRegion})` : ''}${directPacks.length > 0 ? ` + ${directPacks.join(', ')}` : ''}:`));
737
+ for (const pid of allPacks) {
667
738
  console.log(` ${chalk.cyan(pid)}`);
668
739
  }
669
740
 
670
741
  const config = {
671
742
  industry: selectedIndustries.join(','),
672
743
  ...(selectedRegion && { region: selectedRegion }),
673
- targetPacks: resolvedPacks,
744
+ targetPacks: allPacks,
674
745
  exclude: ['node_modules', 'dist', 'build', '.next']
675
746
  };
676
747
 
@@ -823,6 +894,22 @@ program
823
894
  // Priority 1: User explicitly passed --pack
824
895
  packIds = options.pack.split(',').map(s => s.trim()).filter(Boolean);
825
896
  packSource = 'flag';
897
+
898
+ // Validate pack IDs against cached definitions
899
+ const valDefs = getRuleDefinitionsCache();
900
+ if (valDefs && valDefs.packs) {
901
+ const unknownPacks = packIds.filter(pid => !valDefs.packs[pid]);
902
+ if (unknownPacks.length > 0) {
903
+ if (isText) {
904
+ console.error(chalk.red(`\n✖ Unknown pack ID${unknownPacks.length > 1 ? 's' : ''}: ${unknownPacks.join(', ')}`));
905
+ console.error(chalk.dim(` Run ${chalk.green('korext packs list')} to see available packs.`));
906
+ console.error(chalk.dim(` Run ${chalk.green('korext rules sync')} to update cached definitions.\n`));
907
+ } else {
908
+ console.error(JSON.stringify({ error: `Unknown pack ID(s): ${unknownPacks.join(', ')}` }));
909
+ }
910
+ process.exit(1);
911
+ }
912
+ }
826
913
  } else if (options.industry) {
827
914
  // Priority 2: --industry (and optional --region) flags
828
915
  const taxonomy = tryBuildTaxonomy();
@@ -958,6 +1045,14 @@ program
958
1045
  console.log(chalk.dim(`Run ${chalk.green('korext login')} to authenticate for unlimited CI/CD analytics.\n`));
959
1046
  }
960
1047
 
1048
+ // BUG-002 fix: Warn when --sign is used without valid auth
1049
+ if (options.sign && !token) {
1050
+ if (isText) {
1051
+ console.log(chalk.red('⚠ --sign requires authentication. Proof bundles will not be signed.'));
1052
+ console.log(chalk.dim(` Run ${chalk.green('korext login <token>')} first, then retry with --sign.\n`));
1053
+ }
1054
+ }
1055
+
961
1056
  const report = {
962
1057
  version,
963
1058
  packId: Array.isArray(pack) ? pack[0] : pack,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "korext",
3
- "version": "0.9.9",
3
+ "version": "0.9.11",
4
4
  "mcpName": "io.github.Korext/governance",
5
5
  "description": "Korext Command Line Interface",
6
6
  "type": "module",