@aiready/cli 0.9.45 ā 0.9.47
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/.turbo/turbo-build.log +9 -9
- package/.turbo/turbo-test.log +4 -4
- package/README.md +24 -7
- package/dist/chunk-6FOVC2OE.mjs +392 -0
- package/dist/chunk-MEXEG3IJ.mjs +389 -0
- package/dist/cli.js +337 -268
- package/dist/cli.mjs +158 -237
- package/dist/index.js +149 -2
- package/dist/index.mjs +5 -3
- package/package.json +12 -12
- package/src/.aiready/aiready-report-20260301-141543.json +8261 -0
- package/src/.aiready/aiready-report-20260301-141556.json +8261 -0
- package/src/.aiready/aiready-report-20260301-141611.json +8261 -0
- package/src/.aiready/aiready-report-20260304-125348.json +8324 -0
- package/src/.aiready/aiready-report-20260304-234659.json +8288 -0
- package/src/commands/scan.ts +138 -279
- package/src/commands/upload.ts +51 -13
- package/src/index.ts +190 -3
package/src/commands/scan.ts
CHANGED
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
getRepoMetadata,
|
|
25
25
|
type ToolScoringOutput,
|
|
26
26
|
} from '@aiready/core';
|
|
27
|
-
import { analyzeUnified } from '../index';
|
|
27
|
+
import { analyzeUnified, scoreUnified, type ScoringResult } from '../index';
|
|
28
28
|
import {
|
|
29
29
|
getReportTimestamp,
|
|
30
30
|
warnIfGraphCapExceeded,
|
|
@@ -86,11 +86,11 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
86
86
|
|
|
87
87
|
let profileTools = options.tools
|
|
88
88
|
? options.tools.split(',').map((t: string) => {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
const tool = t.trim();
|
|
90
|
+
if (tool === 'hallucination' || tool === 'hallucination-risk')
|
|
91
|
+
return 'aiSignalClarity';
|
|
92
|
+
return tool;
|
|
93
|
+
})
|
|
94
94
|
: undefined;
|
|
95
95
|
if (options.profile) {
|
|
96
96
|
switch (options.profile.toLowerCase()) {
|
|
@@ -488,303 +488,162 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
488
488
|
void elapsedTime;
|
|
489
489
|
|
|
490
490
|
// Calculate score if requested: assemble per-tool scoring outputs
|
|
491
|
-
let scoringResult:
|
|
491
|
+
let scoringResult: ScoringResult | undefined;
|
|
492
492
|
if (options.score || finalOptions.scoring?.showBreakdown) {
|
|
493
|
-
|
|
493
|
+
scoringResult = await scoreUnified(results, finalOptions);
|
|
494
494
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
const { calculatePatternScore } =
|
|
498
|
-
await import('@aiready/pattern-detect');
|
|
499
|
-
try {
|
|
500
|
-
const patternScore = calculatePatternScore(
|
|
501
|
-
results.duplicates,
|
|
502
|
-
results.patterns?.length || 0
|
|
503
|
-
);
|
|
504
|
-
|
|
505
|
-
// Calculate token budget for patterns (waste = duplication)
|
|
506
|
-
const wastedTokens = results.duplicates.reduce((sum: number, d: any) => sum + (d.tokenCost || 0), 0);
|
|
507
|
-
patternScore.tokenBudget = calculateTokenBudget({
|
|
508
|
-
totalContextTokens: wastedTokens * 2, // Estimated context
|
|
509
|
-
wastedTokens: {
|
|
510
|
-
duplication: wastedTokens,
|
|
511
|
-
fragmentation: 0,
|
|
512
|
-
chattiness: 0
|
|
513
|
-
}
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
toolScores.set('pattern-detect', patternScore);
|
|
517
|
-
} catch (err) {
|
|
518
|
-
void err;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// Context score
|
|
523
|
-
if (results.context) {
|
|
524
|
-
const { generateSummary: genContextSummary, calculateContextScore } =
|
|
525
|
-
await import('@aiready/context-analyzer');
|
|
526
|
-
try {
|
|
527
|
-
const ctxSummary = genContextSummary(results.context);
|
|
528
|
-
const contextScore = calculateContextScore(ctxSummary);
|
|
529
|
-
|
|
530
|
-
// Calculate token budget for context (waste = fragmentation + depth overhead)
|
|
531
|
-
contextScore.tokenBudget = calculateTokenBudget({
|
|
532
|
-
totalContextTokens: ctxSummary.totalTokens,
|
|
533
|
-
wastedTokens: {
|
|
534
|
-
duplication: 0,
|
|
535
|
-
fragmentation: ctxSummary.totalPotentialSavings || 0,
|
|
536
|
-
chattiness: 0
|
|
537
|
-
}
|
|
538
|
-
});
|
|
495
|
+
console.log(chalk.bold('\nš AI Readiness Overall Score'));
|
|
496
|
+
console.log(` ${formatScore(scoringResult)}`);
|
|
539
497
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
void err;
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// Consistency score
|
|
547
|
-
if (results.consistency) {
|
|
548
|
-
const { calculateConsistencyScore } =
|
|
549
|
-
await import('@aiready/consistency');
|
|
550
|
-
try {
|
|
551
|
-
const issues =
|
|
552
|
-
results.consistency.results?.flatMap((r: any) => r.issues) || [];
|
|
553
|
-
const totalFiles = results.consistency.summary?.filesAnalyzed || 0;
|
|
554
|
-
const consistencyScore = calculateConsistencyScore(
|
|
555
|
-
issues,
|
|
556
|
-
totalFiles
|
|
557
|
-
);
|
|
558
|
-
toolScores.set('consistency', consistencyScore);
|
|
559
|
-
} catch (err) {
|
|
560
|
-
void err;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
498
|
+
// Parse CLI weight overrides (if any)
|
|
499
|
+
// Note: weights are already handled inside scoreUnified via finalOptions and calculateOverallScore
|
|
563
500
|
|
|
564
|
-
//
|
|
565
|
-
if (
|
|
566
|
-
const { calculateAiSignalClarityScore } =
|
|
567
|
-
await import('@aiready/ai-signal-clarity');
|
|
501
|
+
// Check if we need to compare to a previous report
|
|
502
|
+
if (options.compareTo) {
|
|
568
503
|
try {
|
|
569
|
-
const
|
|
570
|
-
|
|
504
|
+
const prevReportStr = readFileSync(
|
|
505
|
+
resolvePath(process.cwd(), options.compareTo),
|
|
506
|
+
'utf8'
|
|
571
507
|
);
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
}
|
|
576
|
-
}
|
|
508
|
+
const prevReport = JSON.parse(prevReportStr);
|
|
509
|
+
const prevScore =
|
|
510
|
+
prevReport.scoring?.score || prevReport.scoring?.overallScore;
|
|
577
511
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
toolScores.set('testability', tbScore);
|
|
597
|
-
} catch (err) {
|
|
598
|
-
void err;
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
// Documentation Drift score
|
|
603
|
-
if (results.docDrift) {
|
|
604
|
-
toolScores.set('doc-drift', {
|
|
605
|
-
toolName: 'doc-drift',
|
|
606
|
-
score: results.docDrift.summary.score,
|
|
607
|
-
rawMetrics: results.docDrift.rawData,
|
|
608
|
-
factors: [],
|
|
609
|
-
recommendations: (results.docDrift.recommendations || []).map(
|
|
610
|
-
(action: string) => ({
|
|
611
|
-
action,
|
|
612
|
-
estimatedImpact: 5,
|
|
613
|
-
priority: 'medium',
|
|
614
|
-
})
|
|
615
|
-
),
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
// Dependency Health score
|
|
620
|
-
if (results.deps) {
|
|
621
|
-
toolScores.set('dependency-health', {
|
|
622
|
-
toolName: 'dependency-health',
|
|
623
|
-
score: results.deps.summary.score,
|
|
624
|
-
rawMetrics: results.deps.rawData,
|
|
625
|
-
factors: [],
|
|
626
|
-
recommendations: (results.deps.recommendations || []).map(
|
|
627
|
-
(action: string) => ({
|
|
628
|
-
action,
|
|
629
|
-
estimatedImpact: 5,
|
|
630
|
-
priority: 'medium',
|
|
631
|
-
})
|
|
632
|
-
),
|
|
633
|
-
});
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
// Change Amplification score
|
|
637
|
-
if (results.changeAmplification) {
|
|
638
|
-
toolScores.set('change-amplification', {
|
|
639
|
-
toolName: 'change-amplification',
|
|
640
|
-
score: results.changeAmplification.summary.score,
|
|
641
|
-
rawMetrics: results.changeAmplification.rawData,
|
|
642
|
-
factors: [],
|
|
643
|
-
recommendations: (
|
|
644
|
-
results.changeAmplification.recommendations || []
|
|
645
|
-
).map((action: string) => ({
|
|
646
|
-
action,
|
|
647
|
-
estimatedImpact: 5,
|
|
648
|
-
priority: 'medium',
|
|
649
|
-
})),
|
|
650
|
-
});
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
// Parse CLI weight overrides (if any)
|
|
654
|
-
const cliWeights = parseWeightString((options as any).weights);
|
|
655
|
-
|
|
656
|
-
// Only calculate overall score if we have at least one tool score
|
|
657
|
-
if (toolScores.size > 0) {
|
|
658
|
-
scoringResult = calculateOverallScore(
|
|
659
|
-
toolScores,
|
|
660
|
-
finalOptions,
|
|
661
|
-
cliWeights.size ? cliWeights : undefined
|
|
662
|
-
);
|
|
663
|
-
|
|
664
|
-
console.log(chalk.bold('\nš AI Readiness Overall Score'));
|
|
665
|
-
console.log(` ${formatScore(scoringResult)}`);
|
|
666
|
-
|
|
667
|
-
// Check if we need to compare to a previous report
|
|
668
|
-
if (options.compareTo) {
|
|
669
|
-
try {
|
|
670
|
-
const prevReportStr = readFileSync(
|
|
671
|
-
resolvePath(process.cwd(), options.compareTo),
|
|
672
|
-
'utf8'
|
|
673
|
-
);
|
|
674
|
-
const prevReport = JSON.parse(prevReportStr);
|
|
675
|
-
const prevScore =
|
|
676
|
-
prevReport.scoring?.score || prevReport.scoring?.overallScore;
|
|
677
|
-
|
|
678
|
-
if (typeof prevScore === 'number') {
|
|
679
|
-
const diff = scoringResult.overall - prevScore;
|
|
680
|
-
const diffStr = diff > 0 ? `+${diff}` : String(diff);
|
|
681
|
-
console.log();
|
|
682
|
-
if (diff > 0) {
|
|
683
|
-
console.log(
|
|
684
|
-
chalk.green(
|
|
685
|
-
` š Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} ā ${scoringResult.overall})`
|
|
686
|
-
)
|
|
687
|
-
);
|
|
688
|
-
} else if (diff < 0) {
|
|
689
|
-
console.log(
|
|
690
|
-
chalk.red(
|
|
691
|
-
` š Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} ā ${scoringResult.overall})`
|
|
692
|
-
)
|
|
693
|
-
);
|
|
694
|
-
// Trend gating: if we regressed and CI is on or threshold is present, we could lower the threshold effectively,
|
|
695
|
-
// but for now, we just highlight the regression.
|
|
696
|
-
} else {
|
|
697
|
-
console.log(
|
|
698
|
-
chalk.blue(
|
|
699
|
-
` ā Trend: No change compared to ${options.compareTo} (${prevScore} ā ${scoringResult.overall})`
|
|
700
|
-
)
|
|
701
|
-
);
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
// Add trend info to scoringResult for programmatic use
|
|
705
|
-
(scoringResult as any).trend = {
|
|
706
|
-
previousScore: prevScore,
|
|
707
|
-
difference: diff,
|
|
708
|
-
};
|
|
512
|
+
if (typeof prevScore === 'number') {
|
|
513
|
+
const diff = scoringResult.overall - prevScore;
|
|
514
|
+
const diffStr = diff > 0 ? `+${diff}` : String(diff);
|
|
515
|
+
console.log();
|
|
516
|
+
if (diff > 0) {
|
|
517
|
+
console.log(
|
|
518
|
+
chalk.green(
|
|
519
|
+
` š Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} ā ${scoringResult.overall})`
|
|
520
|
+
)
|
|
521
|
+
);
|
|
522
|
+
} else if (diff < 0) {
|
|
523
|
+
console.log(
|
|
524
|
+
chalk.red(
|
|
525
|
+
` š Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} ā ${scoringResult.overall})`
|
|
526
|
+
)
|
|
527
|
+
);
|
|
528
|
+
// Trend gating: if we regressed and CI is on or threshold is present, we could lower the threshold effectively,
|
|
529
|
+
// but for now, we just highlight the regression.
|
|
709
530
|
} else {
|
|
710
531
|
console.log(
|
|
711
|
-
chalk.
|
|
712
|
-
|
|
532
|
+
chalk.blue(
|
|
533
|
+
` ā Trend: No change compared to ${options.compareTo} (${prevScore} ā ${scoringResult.overall})`
|
|
713
534
|
)
|
|
714
535
|
);
|
|
715
536
|
}
|
|
716
|
-
|
|
717
|
-
|
|
537
|
+
|
|
538
|
+
// Add trend info to scoringResult for programmatic use
|
|
539
|
+
(scoringResult as any).trend = {
|
|
540
|
+
previousScore: prevScore,
|
|
541
|
+
difference: diff,
|
|
542
|
+
};
|
|
543
|
+
} else {
|
|
718
544
|
console.log(
|
|
719
545
|
chalk.yellow(
|
|
720
|
-
`\n ā ļø
|
|
546
|
+
`\n ā ļø Previous report at ${options.compareTo} does not contain an overall score.`
|
|
721
547
|
)
|
|
722
548
|
);
|
|
723
549
|
}
|
|
550
|
+
} catch (e) {
|
|
551
|
+
void e;
|
|
552
|
+
console.log(
|
|
553
|
+
chalk.yellow(
|
|
554
|
+
`\n ā ļø Could not read or parse previous report at ${options.compareTo}.`
|
|
555
|
+
)
|
|
556
|
+
);
|
|
724
557
|
}
|
|
558
|
+
}
|
|
725
559
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
560
|
+
// Unified Token Budget Analysis
|
|
561
|
+
const totalWastedDuplication = (scoringResult.breakdown || []).reduce(
|
|
562
|
+
(sum, s) =>
|
|
563
|
+
sum + (s.tokenBudget?.wastedTokens.bySource.duplication || 0),
|
|
564
|
+
0
|
|
565
|
+
);
|
|
566
|
+
const totalWastedFragmentation = (scoringResult.breakdown || []).reduce(
|
|
567
|
+
(sum, s) =>
|
|
568
|
+
sum + (s.tokenBudget?.wastedTokens.bySource.fragmentation || 0),
|
|
569
|
+
0
|
|
570
|
+
);
|
|
571
|
+
const totalContext = Math.max(
|
|
572
|
+
...(scoringResult.breakdown || []).map(
|
|
573
|
+
(s) => s.tokenBudget?.totalContextTokens || 0
|
|
574
|
+
)
|
|
575
|
+
);
|
|
742
576
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
577
|
+
if (totalContext > 0) {
|
|
578
|
+
const unifiedBudget = calculateTokenBudget({
|
|
579
|
+
totalContextTokens: totalContext,
|
|
580
|
+
wastedTokens: {
|
|
581
|
+
duplication: totalWastedDuplication,
|
|
582
|
+
fragmentation: totalWastedFragmentation,
|
|
583
|
+
chattiness: 0,
|
|
584
|
+
},
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
const targetModel = options.model || 'claude-4.6';
|
|
588
|
+
const modelPreset = getModelPreset(targetModel);
|
|
589
|
+
const costEstimate = estimateCostFromBudget(unifiedBudget, modelPreset);
|
|
590
|
+
|
|
591
|
+
const barWidth = 20;
|
|
592
|
+
const filled = Math.round(unifiedBudget.efficiencyRatio * barWidth);
|
|
593
|
+
const bar =
|
|
594
|
+
chalk.green('ā'.repeat(filled)) +
|
|
595
|
+
chalk.dim('ā'.repeat(barWidth - filled));
|
|
596
|
+
|
|
597
|
+
console.log(chalk.bold('\nš AI Token Budget Analysis (v0.13)'));
|
|
598
|
+
console.log(
|
|
599
|
+
` Efficiency: [${bar}] ${(unifiedBudget.efficiencyRatio * 100).toFixed(0)}%`
|
|
600
|
+
);
|
|
601
|
+
console.log(
|
|
602
|
+
` Total Context: ${chalk.bold(unifiedBudget.totalContextTokens.toLocaleString())} tokens`
|
|
603
|
+
);
|
|
604
|
+
console.log(
|
|
605
|
+
` Wasted Tokens: ${chalk.red(unifiedBudget.wastedTokens.total.toLocaleString())} (${((unifiedBudget.wastedTokens.total / unifiedBudget.totalContextTokens) * 100).toFixed(1)}%)`
|
|
606
|
+
);
|
|
607
|
+
console.log(` Waste Breakdown:`);
|
|
608
|
+
console.log(
|
|
609
|
+
` ⢠Duplication: ${unifiedBudget.wastedTokens.bySource.duplication.toLocaleString()} tokens`
|
|
610
|
+
);
|
|
611
|
+
console.log(
|
|
612
|
+
` ⢠Fragmentation: ${unifiedBudget.wastedTokens.bySource.fragmentation.toLocaleString()} tokens`
|
|
613
|
+
);
|
|
614
|
+
console.log(
|
|
615
|
+
` Potential Savings: ${chalk.green(unifiedBudget.potentialRetrievableTokens.toLocaleString())} tokens retrievable`
|
|
616
|
+
);
|
|
617
|
+
console.log(
|
|
618
|
+
`\n Est. Monthly Cost (${modelPreset.name}): ${chalk.bold('$' + costEstimate.total)} [range: $${costEstimate.range[0]}-$${costEstimate.range[1]}]`
|
|
619
|
+
);
|
|
768
620
|
|
|
769
|
-
//
|
|
770
|
-
|
|
771
|
-
|
|
621
|
+
// Attach unified budget to report for JSON persistence
|
|
622
|
+
(scoringResult as any).tokenBudget = unifiedBudget;
|
|
623
|
+
(scoringResult as any).costEstimate = {
|
|
624
|
+
model: modelPreset.name,
|
|
625
|
+
...costEstimate,
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Show concise breakdown; detailed breakdown only if config requests it
|
|
630
|
+
if (scoringResult.breakdown && scoringResult.breakdown.length > 0) {
|
|
631
|
+
console.log(chalk.bold('\nTool breakdown:'));
|
|
632
|
+
scoringResult.breakdown.forEach((tool) => {
|
|
633
|
+
const rating = getRating(tool.score);
|
|
634
|
+
const rd = getRatingDisplay(rating);
|
|
635
|
+
console.log(
|
|
636
|
+
` - ${tool.toolName}: ${tool.score}/100 (${rating}) ${rd.emoji}`
|
|
637
|
+
);
|
|
638
|
+
});
|
|
639
|
+
console.log();
|
|
640
|
+
|
|
641
|
+
if (finalOptions.scoring?.showBreakdown) {
|
|
642
|
+
console.log(chalk.bold('Detailed tool breakdown:'));
|
|
772
643
|
scoringResult.breakdown.forEach((tool) => {
|
|
773
|
-
|
|
774
|
-
const rd = getRatingDisplay(rating);
|
|
775
|
-
console.log(
|
|
776
|
-
` - ${tool.toolName}: ${tool.score}/100 (${rating}) ${rd.emoji}`
|
|
777
|
-
);
|
|
644
|
+
console.log(formatToolScore(tool));
|
|
778
645
|
});
|
|
779
646
|
console.log();
|
|
780
|
-
|
|
781
|
-
if (finalOptions.scoring?.showBreakdown) {
|
|
782
|
-
console.log(chalk.bold('Detailed tool breakdown:'));
|
|
783
|
-
scoringResult.breakdown.forEach((tool) => {
|
|
784
|
-
console.log(formatToolScore(tool));
|
|
785
|
-
});
|
|
786
|
-
console.log();
|
|
787
|
-
}
|
|
788
647
|
}
|
|
789
648
|
}
|
|
790
649
|
}
|
package/src/commands/upload.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
-
import
|
|
2
|
+
import { resolve as resolvePath } from 'path';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
|
-
import {
|
|
5
|
-
handleCLIError,
|
|
6
|
-
} from '@aiready/core';
|
|
4
|
+
import { handleCLIError } from '@aiready/core';
|
|
7
5
|
|
|
8
6
|
interface UploadOptions {
|
|
9
7
|
apiKey?: string;
|
|
@@ -14,13 +12,22 @@ interface UploadOptions {
|
|
|
14
12
|
export async function uploadAction(file: string, options: UploadOptions) {
|
|
15
13
|
const startTime = Date.now();
|
|
16
14
|
const filePath = resolvePath(process.cwd(), file);
|
|
17
|
-
const serverUrl =
|
|
15
|
+
const serverUrl =
|
|
16
|
+
options.server ||
|
|
17
|
+
process.env.AIREADY_SERVER ||
|
|
18
|
+
'https://dev.platform.getaiready.dev';
|
|
18
19
|
const apiKey = options.apiKey || process.env.AIREADY_API_KEY;
|
|
19
20
|
|
|
20
21
|
if (!apiKey) {
|
|
21
22
|
console.error(chalk.red('ā API Key is required for upload.'));
|
|
22
|
-
console.log(
|
|
23
|
-
|
|
23
|
+
console.log(
|
|
24
|
+
chalk.dim(
|
|
25
|
+
' Set AIREADY_API_KEY environment variable or use --api-key flag.'
|
|
26
|
+
)
|
|
27
|
+
);
|
|
28
|
+
console.log(
|
|
29
|
+
chalk.dim(' Get an API key from https://getaiready.dev/dashboard')
|
|
30
|
+
);
|
|
24
31
|
process.exit(1);
|
|
25
32
|
}
|
|
26
33
|
|
|
@@ -33,7 +40,10 @@ export async function uploadAction(file: string, options: UploadOptions) {
|
|
|
33
40
|
console.log(chalk.blue(`š Uploading report to ${serverUrl}...`));
|
|
34
41
|
|
|
35
42
|
// Read the report file
|
|
36
|
-
|
|
43
|
+
console.log(chalk.dim(` Reading report from ${filePath}...`));
|
|
44
|
+
const reportContent = fs.readFileSync(filePath, 'utf-8');
|
|
45
|
+
const reportData = JSON.parse(reportContent);
|
|
46
|
+
console.log(chalk.dim(` Successfully parsed report JSON.`));
|
|
37
47
|
|
|
38
48
|
// Prepare upload payload
|
|
39
49
|
// Note: repoId is optional if the metadata contains it, but for now we'll require it or infer from metadata
|
|
@@ -43,7 +53,7 @@ export async function uploadAction(file: string, options: UploadOptions) {
|
|
|
43
53
|
method: 'POST',
|
|
44
54
|
headers: {
|
|
45
55
|
'Content-Type': 'application/json',
|
|
46
|
-
|
|
56
|
+
Authorization: `Bearer ${apiKey}`,
|
|
47
57
|
},
|
|
48
58
|
body: JSON.stringify({
|
|
49
59
|
data: reportData,
|
|
@@ -51,12 +61,41 @@ export async function uploadAction(file: string, options: UploadOptions) {
|
|
|
51
61
|
}),
|
|
52
62
|
});
|
|
53
63
|
|
|
54
|
-
const
|
|
64
|
+
const contentType = res.headers.get('content-type');
|
|
65
|
+
let result: any = {};
|
|
66
|
+
|
|
67
|
+
if (contentType?.includes('application/json')) {
|
|
68
|
+
result = await res.json();
|
|
69
|
+
} else {
|
|
70
|
+
const text = await res.text();
|
|
71
|
+
result = { error: text || res.statusText };
|
|
72
|
+
}
|
|
55
73
|
|
|
56
74
|
if (!res.ok) {
|
|
57
|
-
console.error(
|
|
75
|
+
console.error(
|
|
76
|
+
chalk.red(`ā Upload failed: ${result.error || res.statusText}`)
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// Special case for redirects or HTML error pages
|
|
80
|
+
if (contentType?.includes('text/html')) {
|
|
81
|
+
console.log(
|
|
82
|
+
chalk.yellow(
|
|
83
|
+
' Note: Received an HTML response. This often indicates a redirect (e.g., to a login page) or a server error.'
|
|
84
|
+
)
|
|
85
|
+
);
|
|
86
|
+
if (result.error?.includes('Redirecting')) {
|
|
87
|
+
console.log(
|
|
88
|
+
chalk.dim(
|
|
89
|
+
' Detected redirect. Check if the API endpoint requires authentication or has changed.'
|
|
90
|
+
)
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
58
95
|
if (res.status === 401) {
|
|
59
|
-
console.log(
|
|
96
|
+
console.log(
|
|
97
|
+
chalk.dim(' Hint: Your API key may be invalid or expired.')
|
|
98
|
+
);
|
|
60
99
|
}
|
|
61
100
|
process.exit(1);
|
|
62
101
|
}
|
|
@@ -69,7 +108,6 @@ export async function uploadAction(file: string, options: UploadOptions) {
|
|
|
69
108
|
console.log(chalk.dim(` Analysis ID: ${result.analysis.id}`));
|
|
70
109
|
console.log(chalk.dim(` Score: ${result.analysis.aiScore}/100`));
|
|
71
110
|
}
|
|
72
|
-
|
|
73
111
|
} catch (error) {
|
|
74
112
|
handleCLIError(error, 'Upload');
|
|
75
113
|
}
|