@peakinfer/cli 1.0.133
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/.claude/settings.local.json +8 -0
- package/.env.example +6 -0
- package/.github/workflows/peakinfer.yml +64 -0
- package/CHANGELOG.md +31 -0
- package/LICENSE +190 -0
- package/README.md +335 -0
- package/data/inferencemax.json +274 -0
- package/dist/agent-analyzer.d.ts +45 -0
- package/dist/agent-analyzer.d.ts.map +1 -0
- package/dist/agent-analyzer.js +374 -0
- package/dist/agent-analyzer.js.map +1 -0
- package/dist/agent.d.ts +76 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +965 -0
- package/dist/agent.js.map +1 -0
- package/dist/agents/correlation-analyzer.d.ts +34 -0
- package/dist/agents/correlation-analyzer.d.ts.map +1 -0
- package/dist/agents/correlation-analyzer.js +261 -0
- package/dist/agents/correlation-analyzer.js.map +1 -0
- package/dist/agents/index.d.ts +91 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +111 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/runtime-analyzer.d.ts +38 -0
- package/dist/agents/runtime-analyzer.d.ts.map +1 -0
- package/dist/agents/runtime-analyzer.js +244 -0
- package/dist/agents/runtime-analyzer.js.map +1 -0
- package/dist/analysis-types.d.ts +500 -0
- package/dist/analysis-types.d.ts.map +1 -0
- package/dist/analysis-types.js +11 -0
- package/dist/analysis-types.js.map +1 -0
- package/dist/analytics.d.ts +25 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.js +94 -0
- package/dist/analytics.js.map +1 -0
- package/dist/analyzer.d.ts +48 -0
- package/dist/analyzer.d.ts.map +1 -0
- package/dist/analyzer.js +547 -0
- package/dist/analyzer.js.map +1 -0
- package/dist/artifacts.d.ts +44 -0
- package/dist/artifacts.d.ts.map +1 -0
- package/dist/artifacts.js +165 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/benchmarks/index.d.ts +88 -0
- package/dist/benchmarks/index.d.ts.map +1 -0
- package/dist/benchmarks/index.js +205 -0
- package/dist/benchmarks/index.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +427 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/ci.d.ts +19 -0
- package/dist/commands/ci.d.ts.map +1 -0
- package/dist/commands/ci.js +253 -0
- package/dist/commands/ci.js.map +1 -0
- package/dist/commands/config.d.ts +16 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +249 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/demo.d.ts +15 -0
- package/dist/commands/demo.d.ts.map +1 -0
- package/dist/commands/demo.js +106 -0
- package/dist/commands/demo.js.map +1 -0
- package/dist/commands/export.d.ts +14 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +209 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/history.d.ts +15 -0
- package/dist/commands/history.d.ts.map +1 -0
- package/dist/commands/history.js +389 -0
- package/dist/commands/history.js.map +1 -0
- package/dist/commands/template.d.ts +14 -0
- package/dist/commands/template.d.ts.map +1 -0
- package/dist/commands/template.js +341 -0
- package/dist/commands/template.js.map +1 -0
- package/dist/commands/validate-map.d.ts +12 -0
- package/dist/commands/validate-map.d.ts.map +1 -0
- package/dist/commands/validate-map.js +274 -0
- package/dist/commands/validate-map.js.map +1 -0
- package/dist/commands/whatif.d.ts +17 -0
- package/dist/commands/whatif.d.ts.map +1 -0
- package/dist/commands/whatif.js +206 -0
- package/dist/commands/whatif.js.map +1 -0
- package/dist/comparison.d.ts +38 -0
- package/dist/comparison.d.ts.map +1 -0
- package/dist/comparison.js +223 -0
- package/dist/comparison.js.map +1 -0
- package/dist/config.d.ts +42 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +158 -0
- package/dist/config.js.map +1 -0
- package/dist/connectors/helicone.d.ts +9 -0
- package/dist/connectors/helicone.d.ts.map +1 -0
- package/dist/connectors/helicone.js +106 -0
- package/dist/connectors/helicone.js.map +1 -0
- package/dist/connectors/index.d.ts +37 -0
- package/dist/connectors/index.d.ts.map +1 -0
- package/dist/connectors/index.js +65 -0
- package/dist/connectors/index.js.map +1 -0
- package/dist/connectors/langsmith.d.ts +9 -0
- package/dist/connectors/langsmith.d.ts.map +1 -0
- package/dist/connectors/langsmith.js +122 -0
- package/dist/connectors/langsmith.js.map +1 -0
- package/dist/connectors/types.d.ts +83 -0
- package/dist/connectors/types.d.ts.map +1 -0
- package/dist/connectors/types.js +98 -0
- package/dist/connectors/types.js.map +1 -0
- package/dist/cost-estimator.d.ts +46 -0
- package/dist/cost-estimator.d.ts.map +1 -0
- package/dist/cost-estimator.js +104 -0
- package/dist/cost-estimator.js.map +1 -0
- package/dist/costs.d.ts +57 -0
- package/dist/costs.d.ts.map +1 -0
- package/dist/costs.js +251 -0
- package/dist/costs.js.map +1 -0
- package/dist/counterfactuals.d.ts +29 -0
- package/dist/counterfactuals.d.ts.map +1 -0
- package/dist/counterfactuals.js +448 -0
- package/dist/counterfactuals.js.map +1 -0
- package/dist/enhancement-prompts.d.ts +41 -0
- package/dist/enhancement-prompts.d.ts.map +1 -0
- package/dist/enhancement-prompts.js +88 -0
- package/dist/enhancement-prompts.js.map +1 -0
- package/dist/envelopes.d.ts +20 -0
- package/dist/envelopes.d.ts.map +1 -0
- package/dist/envelopes.js +790 -0
- package/dist/envelopes.js.map +1 -0
- package/dist/format-normalizer.d.ts +71 -0
- package/dist/format-normalizer.d.ts.map +1 -0
- package/dist/format-normalizer.js +1331 -0
- package/dist/format-normalizer.js.map +1 -0
- package/dist/history.d.ts +79 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +313 -0
- package/dist/history.js.map +1 -0
- package/dist/html.d.ts +11 -0
- package/dist/html.d.ts.map +1 -0
- package/dist/html.js +463 -0
- package/dist/html.js.map +1 -0
- package/dist/impact.d.ts +42 -0
- package/dist/impact.d.ts.map +1 -0
- package/dist/impact.js +443 -0
- package/dist/impact.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/insights.d.ts +5 -0
- package/dist/insights.d.ts.map +1 -0
- package/dist/insights.js +271 -0
- package/dist/insights.js.map +1 -0
- package/dist/joiner.d.ts +9 -0
- package/dist/joiner.d.ts.map +1 -0
- package/dist/joiner.js +247 -0
- package/dist/joiner.js.map +1 -0
- package/dist/orchestrator.d.ts +34 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +827 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/pdf.d.ts +26 -0
- package/dist/pdf.d.ts.map +1 -0
- package/dist/pdf.js +84 -0
- package/dist/pdf.js.map +1 -0
- package/dist/prediction.d.ts +33 -0
- package/dist/prediction.d.ts.map +1 -0
- package/dist/prediction.js +316 -0
- package/dist/prediction.js.map +1 -0
- package/dist/prompts/loader.d.ts +38 -0
- package/dist/prompts/loader.d.ts.map +1 -0
- package/dist/prompts/loader.js +60 -0
- package/dist/prompts/loader.js.map +1 -0
- package/dist/renderer.d.ts +64 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +923 -0
- package/dist/renderer.js.map +1 -0
- package/dist/runid.d.ts +57 -0
- package/dist/runid.d.ts.map +1 -0
- package/dist/runid.js +199 -0
- package/dist/runid.js.map +1 -0
- package/dist/runtime.d.ts +29 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +366 -0
- package/dist/runtime.js.map +1 -0
- package/dist/scanner.d.ts +11 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +426 -0
- package/dist/scanner.js.map +1 -0
- package/dist/templates.d.ts +120 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +429 -0
- package/dist/templates.js.map +1 -0
- package/dist/tools/index.d.ts +153 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +177 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types.d.ts +3647 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +703 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +7 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +23 -0
- package/dist/version.js.map +1 -0
- package/docs/demo-guide.md +423 -0
- package/docs/events-format.md +295 -0
- package/docs/inferencemap-spec.md +344 -0
- package/docs/migration-v2.md +293 -0
- package/fixtures/demo/precomputed.json +142 -0
- package/fixtures/demo-project/README.md +52 -0
- package/fixtures/demo-project/ai-service.ts +65 -0
- package/fixtures/demo-project/sample-events.jsonl +15 -0
- package/fixtures/demo-project/src/ai-service.ts +128 -0
- package/fixtures/demo-project/src/llm-client.ts +155 -0
- package/package.json +65 -0
- package/prompts/agent-analyzer.yaml +47 -0
- package/prompts/ci-gate.yaml +98 -0
- package/prompts/correlation-analyzer.yaml +178 -0
- package/prompts/format-normalizer.yaml +46 -0
- package/prompts/peak-performance.yaml +180 -0
- package/prompts/pr-comment.yaml +111 -0
- package/prompts/runtime-analyzer.yaml +189 -0
- package/prompts/unified-analyzer.yaml +241 -0
- package/schemas/inference-map.v0.1.json +215 -0
- package/scripts/benchmark.ts +394 -0
- package/scripts/demo-v1.5.sh +158 -0
- package/scripts/sync-from-site.sh +197 -0
- package/scripts/validate-sync.sh +178 -0
- package/src/agent-analyzer.ts +481 -0
- package/src/agent.ts +1232 -0
- package/src/agents/correlation-analyzer.ts +353 -0
- package/src/agents/index.ts +235 -0
- package/src/agents/runtime-analyzer.ts +343 -0
- package/src/analysis-types.ts +558 -0
- package/src/analytics.ts +100 -0
- package/src/analyzer.ts +692 -0
- package/src/artifacts.ts +218 -0
- package/src/benchmarks/index.ts +309 -0
- package/src/cli.ts +503 -0
- package/src/commands/ci.ts +336 -0
- package/src/commands/config.ts +288 -0
- package/src/commands/demo.ts +175 -0
- package/src/commands/export.ts +297 -0
- package/src/commands/history.ts +425 -0
- package/src/commands/template.ts +385 -0
- package/src/commands/validate-map.ts +324 -0
- package/src/commands/whatif.ts +272 -0
- package/src/comparison.ts +283 -0
- package/src/config.ts +188 -0
- package/src/connectors/helicone.ts +164 -0
- package/src/connectors/index.ts +93 -0
- package/src/connectors/langsmith.ts +179 -0
- package/src/connectors/types.ts +180 -0
- package/src/cost-estimator.ts +146 -0
- package/src/costs.ts +347 -0
- package/src/counterfactuals.ts +516 -0
- package/src/enhancement-prompts.ts +118 -0
- package/src/envelopes.ts +814 -0
- package/src/format-normalizer.ts +1486 -0
- package/src/history.ts +400 -0
- package/src/html.ts +512 -0
- package/src/impact.ts +522 -0
- package/src/index.ts +83 -0
- package/src/insights.ts +341 -0
- package/src/joiner.ts +289 -0
- package/src/orchestrator.ts +1015 -0
- package/src/pdf.ts +110 -0
- package/src/prediction.ts +392 -0
- package/src/prompts/loader.ts +88 -0
- package/src/renderer.ts +1045 -0
- package/src/runid.ts +261 -0
- package/src/runtime.ts +450 -0
- package/src/scanner.ts +508 -0
- package/src/templates.ts +561 -0
- package/src/tools/index.ts +214 -0
- package/src/types.ts +873 -0
- package/src/version.ts +24 -0
- package/templates/context-accumulation.yaml +23 -0
- package/templates/cost-concentration.yaml +20 -0
- package/templates/dead-code.yaml +20 -0
- package/templates/latency-explainer.yaml +23 -0
- package/templates/optimizations/ab-testing-framework.yaml +74 -0
- package/templates/optimizations/api-gateway-optimization.yaml +81 -0
- package/templates/optimizations/api-model-routing-strategy.yaml +126 -0
- package/templates/optimizations/auto-scaling-optimization.yaml +85 -0
- package/templates/optimizations/batch-utilization-diagnostic.yaml +142 -0
- package/templates/optimizations/comprehensive-apm.yaml +76 -0
- package/templates/optimizations/context-window-optimization.yaml +91 -0
- package/templates/optimizations/cost-sensitive-batch-processing.yaml +77 -0
- package/templates/optimizations/distributed-training-optimization.yaml +77 -0
- package/templates/optimizations/document-analysis-edge.yaml +77 -0
- package/templates/optimizations/document-pipeline-optimization.yaml +78 -0
- package/templates/optimizations/domain-specific-distillation.yaml +78 -0
- package/templates/optimizations/error-handling-optimization.yaml +76 -0
- package/templates/optimizations/gptq-4bit-quantization.yaml +96 -0
- package/templates/optimizations/long-context-memory-management.yaml +78 -0
- package/templates/optimizations/max-tokens-optimization.yaml +76 -0
- package/templates/optimizations/memory-bandwidth-optimization.yaml +73 -0
- package/templates/optimizations/multi-framework-resilience.yaml +75 -0
- package/templates/optimizations/multi-tenant-optimization.yaml +75 -0
- package/templates/optimizations/prompt-caching-optimization.yaml +143 -0
- package/templates/optimizations/pytorch-to-onnx-migration.yaml +109 -0
- package/templates/optimizations/quality-monitoring.yaml +74 -0
- package/templates/optimizations/realtime-budget-controls.yaml +74 -0
- package/templates/optimizations/realtime-latency-optimization.yaml +74 -0
- package/templates/optimizations/sglang-concurrency-optimization.yaml +78 -0
- package/templates/optimizations/smart-model-routing.yaml +96 -0
- package/templates/optimizations/streaming-batch-selection.yaml +167 -0
- package/templates/optimizations/system-prompt-optimization.yaml +75 -0
- package/templates/optimizations/tensorrt-llm-performance.yaml +77 -0
- package/templates/optimizations/vllm-high-throughput-optimization.yaml +93 -0
- package/templates/optimizations/vllm-migration-memory-bound.yaml +78 -0
- package/templates/overpowered-extraction.yaml +32 -0
- package/templates/overpowered-model.yaml +31 -0
- package/templates/prompt-bloat.yaml +24 -0
- package/templates/retry-explosion.yaml +28 -0
- package/templates/schema/insight.schema.json +113 -0
- package/templates/schema/optimization.schema.json +180 -0
- package/templates/streaming-drift.yaml +30 -0
- package/templates/throughput-gap.yaml +21 -0
- package/templates/token-underutilization.yaml +28 -0
- package/templates/untested-fallback.yaml +21 -0
- package/tests/accuracy/drift-detection.test.ts +184 -0
- package/tests/accuracy/false-positives.test.ts +166 -0
- package/tests/accuracy/templates.test.ts +205 -0
- package/tests/action/commands.test.ts +125 -0
- package/tests/action/comments.test.ts +347 -0
- package/tests/cli.test.ts +203 -0
- package/tests/comparison.test.ts +309 -0
- package/tests/correlation-analyzer.test.ts +534 -0
- package/tests/counterfactuals.test.ts +347 -0
- package/tests/fixtures/events/missing-id.jsonl +1 -0
- package/tests/fixtures/events/missing-input.jsonl +1 -0
- package/tests/fixtures/events/missing-latency.jsonl +1 -0
- package/tests/fixtures/events/missing-model.jsonl +1 -0
- package/tests/fixtures/events/missing-output.jsonl +1 -0
- package/tests/fixtures/events/missing-provider.jsonl +1 -0
- package/tests/fixtures/events/missing-ts.jsonl +1 -0
- package/tests/fixtures/events/valid.csv +3 -0
- package/tests/fixtures/events/valid.json +1 -0
- package/tests/fixtures/events/valid.jsonl +2 -0
- package/tests/fixtures/events/with-callsite.jsonl +1 -0
- package/tests/fixtures/events/with-intent.jsonl +1 -0
- package/tests/fixtures/events/wrong-type.jsonl +1 -0
- package/tests/fixtures/repos/empty/.gitkeep +0 -0
- package/tests/fixtures/repos/hybrid-router/router.py +35 -0
- package/tests/fixtures/repos/saas-anthropic/agent.ts +27 -0
- package/tests/fixtures/repos/saas-openai/assistant.js +33 -0
- package/tests/fixtures/repos/saas-openai/client.py +26 -0
- package/tests/fixtures/repos/self-hosted-vllm/inference.py +22 -0
- package/tests/github-action.test.ts +292 -0
- package/tests/insights.test.ts +878 -0
- package/tests/joiner.test.ts +168 -0
- package/tests/performance/action-latency.test.ts +132 -0
- package/tests/performance/benchmark.test.ts +189 -0
- package/tests/performance/cli-latency.test.ts +102 -0
- package/tests/pr-comment.test.ts +313 -0
- package/tests/prediction.test.ts +296 -0
- package/tests/runtime-analyzer.test.ts +375 -0
- package/tests/runtime.test.ts +205 -0
- package/tests/scanner.test.ts +122 -0
- package/tests/template-conformance.test.ts +526 -0
- package/tests/unit/cost-calculator.test.ts +303 -0
- package/tests/unit/credits.test.ts +180 -0
- package/tests/unit/inference-map.test.ts +276 -0
- package/tests/unit/schema.test.ts +300 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +14 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CI Command (v1.6)
|
|
3
|
+
*
|
|
4
|
+
* CLI command for CI/CD integration:
|
|
5
|
+
* - Runs analysis with baseline comparison
|
|
6
|
+
* - Returns exit codes for CI gates
|
|
7
|
+
* - Outputs machine-readable JSON
|
|
8
|
+
*
|
|
9
|
+
* Exit codes:
|
|
10
|
+
* - 0: Pass (no regressions)
|
|
11
|
+
* - 1: Warning (minor regressions)
|
|
12
|
+
* - 2: Fail (major regressions)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { Command } from 'commander';
|
|
16
|
+
import { existsSync, readFileSync } from 'fs';
|
|
17
|
+
import { Agent } from '../agent.js';
|
|
18
|
+
|
|
19
|
+
// =============================================================================
|
|
20
|
+
// TYPES
|
|
21
|
+
// =============================================================================
|
|
22
|
+
|
|
23
|
+
interface CIOptions {
|
|
24
|
+
baseline?: string;
|
|
25
|
+
targetP95?: number;
|
|
26
|
+
failOnRegression?: boolean;
|
|
27
|
+
output?: string;
|
|
28
|
+
verbose?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface CIResult {
|
|
32
|
+
status: 'pass' | 'warning' | 'fail';
|
|
33
|
+
exitCode: number;
|
|
34
|
+
summary: {
|
|
35
|
+
inferencePoints: number;
|
|
36
|
+
estimatedMonthlyCost?: number;
|
|
37
|
+
p95Latency?: number;
|
|
38
|
+
driftCount?: number;
|
|
39
|
+
insightCount?: number;
|
|
40
|
+
};
|
|
41
|
+
baseline?: {
|
|
42
|
+
inferencePoints: number;
|
|
43
|
+
estimatedMonthlyCost?: number;
|
|
44
|
+
p95Latency?: number;
|
|
45
|
+
};
|
|
46
|
+
delta?: {
|
|
47
|
+
inferencePointsDelta: number;
|
|
48
|
+
costDeltaPercent?: number;
|
|
49
|
+
latencyDeltaPercent?: number;
|
|
50
|
+
};
|
|
51
|
+
regressions: string[];
|
|
52
|
+
improvements: string[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface BaselineData {
|
|
56
|
+
inferencePoints: number;
|
|
57
|
+
estimatedMonthlyCost?: number;
|
|
58
|
+
p95Latency?: number;
|
|
59
|
+
version?: string;
|
|
60
|
+
timestamp?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// =============================================================================
|
|
64
|
+
// HELPERS
|
|
65
|
+
// =============================================================================
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Load baseline from file
|
|
69
|
+
*/
|
|
70
|
+
function loadBaseline(path: string): BaselineData | null {
|
|
71
|
+
if (!existsSync(path)) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const content = readFileSync(path, 'utf-8');
|
|
77
|
+
const data = JSON.parse(content);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
inferencePoints: data.summary?.totalCallsites || data.inferencePoints || 0,
|
|
81
|
+
estimatedMonthlyCost: data.estimatedMonthlyCost,
|
|
82
|
+
p95Latency: data.p95Latency || data.global?.p95,
|
|
83
|
+
version: data.version,
|
|
84
|
+
timestamp: data.generatedAt || data.timestamp,
|
|
85
|
+
};
|
|
86
|
+
} catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Determine CI status based on analysis results and baseline
|
|
93
|
+
*/
|
|
94
|
+
function determineCIStatus(
|
|
95
|
+
result: CIResult['summary'],
|
|
96
|
+
baseline: BaselineData | null,
|
|
97
|
+
options: CIOptions
|
|
98
|
+
): CIResult {
|
|
99
|
+
const regressions: string[] = [];
|
|
100
|
+
const improvements: string[] = [];
|
|
101
|
+
let status: 'pass' | 'warning' | 'fail' = 'pass';
|
|
102
|
+
|
|
103
|
+
const ciResult: CIResult = {
|
|
104
|
+
status: 'pass',
|
|
105
|
+
exitCode: 0,
|
|
106
|
+
summary: result,
|
|
107
|
+
regressions,
|
|
108
|
+
improvements,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Check target p95 if specified
|
|
112
|
+
if (options.targetP95 && result.p95Latency) {
|
|
113
|
+
if (result.p95Latency > options.targetP95) {
|
|
114
|
+
regressions.push(`p95 latency ${result.p95Latency}ms exceeds target ${options.targetP95}ms`);
|
|
115
|
+
status = 'fail';
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Compare with baseline if available
|
|
120
|
+
if (baseline) {
|
|
121
|
+
ciResult.baseline = {
|
|
122
|
+
inferencePoints: baseline.inferencePoints,
|
|
123
|
+
estimatedMonthlyCost: baseline.estimatedMonthlyCost,
|
|
124
|
+
p95Latency: baseline.p95Latency,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const inferencePointsDelta = result.inferencePoints - baseline.inferencePoints;
|
|
128
|
+
|
|
129
|
+
ciResult.delta = {
|
|
130
|
+
inferencePointsDelta,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Check inference point changes
|
|
134
|
+
if (inferencePointsDelta > 0) {
|
|
135
|
+
improvements.push(`${inferencePointsDelta} new inference point${inferencePointsDelta !== 1 ? 's' : ''} detected`);
|
|
136
|
+
} else if (inferencePointsDelta < 0) {
|
|
137
|
+
improvements.push(`${Math.abs(inferencePointsDelta)} inference point${Math.abs(inferencePointsDelta) !== 1 ? 's' : ''} removed`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Check cost regression
|
|
141
|
+
if (baseline.estimatedMonthlyCost && result.estimatedMonthlyCost) {
|
|
142
|
+
const costDelta = result.estimatedMonthlyCost - baseline.estimatedMonthlyCost;
|
|
143
|
+
const costDeltaPercent = (costDelta / baseline.estimatedMonthlyCost) * 100;
|
|
144
|
+
ciResult.delta.costDeltaPercent = costDeltaPercent;
|
|
145
|
+
|
|
146
|
+
if (costDeltaPercent > 100) {
|
|
147
|
+
regressions.push(`Cost increased by ${costDeltaPercent.toFixed(0)}% (>${100}% threshold)`);
|
|
148
|
+
status = 'fail';
|
|
149
|
+
} else if (costDeltaPercent > 50) {
|
|
150
|
+
regressions.push(`Cost increased by ${costDeltaPercent.toFixed(0)}% (warning threshold)`);
|
|
151
|
+
if (status === 'pass') status = 'warning';
|
|
152
|
+
} else if (costDeltaPercent < -10) {
|
|
153
|
+
improvements.push(`Cost decreased by ${Math.abs(costDeltaPercent).toFixed(0)}%`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Check latency regression
|
|
158
|
+
if (baseline.p95Latency && result.p95Latency) {
|
|
159
|
+
const latencyDelta = result.p95Latency - baseline.p95Latency;
|
|
160
|
+
const latencyDeltaPercent = (latencyDelta / baseline.p95Latency) * 100;
|
|
161
|
+
ciResult.delta.latencyDeltaPercent = latencyDeltaPercent;
|
|
162
|
+
|
|
163
|
+
if (latencyDeltaPercent > 50) {
|
|
164
|
+
regressions.push(`p95 latency increased by ${latencyDeltaPercent.toFixed(0)}% (>${50}% threshold)`);
|
|
165
|
+
status = 'fail';
|
|
166
|
+
} else if (latencyDeltaPercent > 25) {
|
|
167
|
+
regressions.push(`p95 latency increased by ${latencyDeltaPercent.toFixed(0)}% (warning threshold)`);
|
|
168
|
+
if (status === 'pass') status = 'warning';
|
|
169
|
+
} else if (latencyDeltaPercent < -10) {
|
|
170
|
+
improvements.push(`p95 latency improved by ${Math.abs(latencyDeltaPercent).toFixed(0)}%`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Set final status and exit code
|
|
176
|
+
ciResult.status = status;
|
|
177
|
+
ciResult.exitCode = status === 'fail' ? 2 : status === 'warning' ? 1 : 0;
|
|
178
|
+
|
|
179
|
+
// Override exit code if --fail-on-regression is set
|
|
180
|
+
if (options.failOnRegression && regressions.length > 0) {
|
|
181
|
+
ciResult.exitCode = 2;
|
|
182
|
+
ciResult.status = 'fail';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return ciResult;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Format CI result for console output
|
|
190
|
+
*/
|
|
191
|
+
function formatCIResult(result: CIResult): string {
|
|
192
|
+
const lines: string[] = [];
|
|
193
|
+
const statusIndicator = result.status === 'pass' ? '[PASS]' : result.status === 'warning' ? '[WARN]' : '[FAIL]';
|
|
194
|
+
|
|
195
|
+
lines.push(`\n${statusIndicator} PeakInfer CI Check: ${result.status.toUpperCase()}`);
|
|
196
|
+
lines.push('═'.repeat(50));
|
|
197
|
+
|
|
198
|
+
lines.push('\nSummary:');
|
|
199
|
+
lines.push(` Inference Points: ${result.summary.inferencePoints}`);
|
|
200
|
+
if (result.summary.estimatedMonthlyCost) {
|
|
201
|
+
lines.push(` Est. Monthly Cost: $${result.summary.estimatedMonthlyCost.toLocaleString()}`);
|
|
202
|
+
}
|
|
203
|
+
if (result.summary.p95Latency) {
|
|
204
|
+
lines.push(` p95 Latency: ${result.summary.p95Latency}ms`);
|
|
205
|
+
}
|
|
206
|
+
if (result.summary.driftCount) {
|
|
207
|
+
lines.push(` Drift Signals: ${result.summary.driftCount}`);
|
|
208
|
+
}
|
|
209
|
+
if (result.summary.insightCount) {
|
|
210
|
+
lines.push(` Insights: ${result.summary.insightCount}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (result.delta) {
|
|
214
|
+
lines.push('\nChanges vs Baseline:');
|
|
215
|
+
lines.push(` Inference Points: ${result.delta.inferencePointsDelta >= 0 ? '+' : ''}${result.delta.inferencePointsDelta}`);
|
|
216
|
+
if (result.delta.costDeltaPercent !== undefined) {
|
|
217
|
+
lines.push(` Cost: ${result.delta.costDeltaPercent >= 0 ? '+' : ''}${result.delta.costDeltaPercent.toFixed(1)}%`);
|
|
218
|
+
}
|
|
219
|
+
if (result.delta.latencyDeltaPercent !== undefined) {
|
|
220
|
+
lines.push(` p95 Latency: ${result.delta.latencyDeltaPercent >= 0 ? '+' : ''}${result.delta.latencyDeltaPercent.toFixed(1)}%`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (result.regressions.length > 0) {
|
|
225
|
+
lines.push('\nRegressions:');
|
|
226
|
+
for (const r of result.regressions) {
|
|
227
|
+
lines.push(` - ${r}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (result.improvements.length > 0) {
|
|
232
|
+
lines.push('\nImprovements:');
|
|
233
|
+
for (const i of result.improvements) {
|
|
234
|
+
lines.push(` + ${i}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
lines.push(`\nExit code: ${result.exitCode}`);
|
|
239
|
+
lines.push('');
|
|
240
|
+
|
|
241
|
+
return lines.join('\n');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// =============================================================================
|
|
245
|
+
// COMMAND
|
|
246
|
+
// =============================================================================
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Register CI command
|
|
250
|
+
*/
|
|
251
|
+
export function registerCICommand(program: Command): void {
|
|
252
|
+
program
|
|
253
|
+
.command('ci')
|
|
254
|
+
.description('run analysis in CI mode with exit codes')
|
|
255
|
+
.argument('<path>', 'path to analyze')
|
|
256
|
+
.option('--baseline <file>', 'baseline file for comparison (inference-map.json)')
|
|
257
|
+
.option('--target-p95 <ms>', 'target p95 latency in milliseconds', parseInt)
|
|
258
|
+
.option('--fail-on-regression', 'exit with code 2 on any regression')
|
|
259
|
+
.option('--output <format>', 'output format: text (default) or json', 'text')
|
|
260
|
+
.option('--verbose', 'show detailed output')
|
|
261
|
+
.action(async (path: string, options: CIOptions) => {
|
|
262
|
+
try {
|
|
263
|
+
// Validate path
|
|
264
|
+
if (!existsSync(path)) {
|
|
265
|
+
console.error(`Error: Path not found: ${path}`);
|
|
266
|
+
process.exit(2);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Load baseline if provided
|
|
270
|
+
let baseline: BaselineData | null = null;
|
|
271
|
+
if (options.baseline) {
|
|
272
|
+
baseline = loadBaseline(options.baseline);
|
|
273
|
+
if (!baseline) {
|
|
274
|
+
console.error(`Warning: Could not load baseline from ${options.baseline}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Define analysis result type
|
|
279
|
+
interface AnalysisResult {
|
|
280
|
+
inferenceMap?: { callsites: unknown[]; summary: { totalCallsites: number } };
|
|
281
|
+
insights?: unknown[];
|
|
282
|
+
joined?: { drift?: unknown[] };
|
|
283
|
+
runtime?: { global?: { p95: number } };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Run analysis
|
|
287
|
+
const analysisResult = await new Promise<AnalysisResult | null>((resolve) => {
|
|
288
|
+
const agent = new Agent({
|
|
289
|
+
onComplete: (results: AnalysisResult) => {
|
|
290
|
+
resolve(results);
|
|
291
|
+
},
|
|
292
|
+
onError: (error: Error) => {
|
|
293
|
+
console.error(`Analysis error: ${error.message}`);
|
|
294
|
+
resolve(null);
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
agent.run({
|
|
299
|
+
path,
|
|
300
|
+
offline: false,
|
|
301
|
+
noCache: true,
|
|
302
|
+
verbose: options.verbose,
|
|
303
|
+
noHistory: true, // Don't save CI runs to history
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
if (!analysisResult) {
|
|
308
|
+
console.error('Analysis failed to produce results');
|
|
309
|
+
process.exit(2);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Build summary
|
|
313
|
+
const summary: CIResult['summary'] = {
|
|
314
|
+
inferencePoints: analysisResult.inferenceMap?.summary?.totalCallsites || 0,
|
|
315
|
+
driftCount: analysisResult.joined?.drift?.length,
|
|
316
|
+
insightCount: analysisResult.insights?.length,
|
|
317
|
+
p95Latency: analysisResult.runtime?.global?.p95,
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// Determine CI status
|
|
321
|
+
const ciResult = determineCIStatus(summary, baseline, options);
|
|
322
|
+
|
|
323
|
+
// Output result
|
|
324
|
+
if (options.output === 'json') {
|
|
325
|
+
console.log(JSON.stringify(ciResult, null, 2));
|
|
326
|
+
} else {
|
|
327
|
+
console.log(formatCIResult(ciResult));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
process.exit(ciResult.exitCode);
|
|
331
|
+
} catch (error) {
|
|
332
|
+
console.error('Error:', error instanceof Error ? error.message : 'CI check failed');
|
|
333
|
+
process.exit(2);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Commands (v1.6)
|
|
3
|
+
*
|
|
4
|
+
* CLI commands for managing PeakInfer configuration:
|
|
5
|
+
* - set: Set a configuration value
|
|
6
|
+
* - show: Display current configuration
|
|
7
|
+
*
|
|
8
|
+
* Configuration resolution chain:
|
|
9
|
+
* CLI flags → env vars → ~/.peakinfer/config.yaml → ./peakinfer.yaml → defaults
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Command } from 'commander';
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
14
|
+
import { join } from 'path';
|
|
15
|
+
import { homedir } from 'os';
|
|
16
|
+
import { parse as parseYAML, stringify as stringifyYAML } from 'yaml';
|
|
17
|
+
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// CONSTANTS
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
const GLOBAL_CONFIG_DIR = join(homedir(), '.peakinfer');
|
|
23
|
+
const GLOBAL_CONFIG_FILE = join(GLOBAL_CONFIG_DIR, 'config.yaml');
|
|
24
|
+
const LOCAL_CONFIG_FILE = 'peakinfer.yaml';
|
|
25
|
+
|
|
26
|
+
// Allowed configuration keys
|
|
27
|
+
const ALLOWED_KEYS = [
|
|
28
|
+
'api-key',
|
|
29
|
+
'model',
|
|
30
|
+
'mode',
|
|
31
|
+
'verbose',
|
|
32
|
+
'history-retention-days',
|
|
33
|
+
] as const;
|
|
34
|
+
|
|
35
|
+
type ConfigKey = typeof ALLOWED_KEYS[number];
|
|
36
|
+
|
|
37
|
+
// Internal key mapping (CLI name → config key)
|
|
38
|
+
const KEY_MAP: Record<string, string> = {
|
|
39
|
+
'api-key': 'apiKey',
|
|
40
|
+
'model': 'model',
|
|
41
|
+
'mode': 'analysisMode',
|
|
42
|
+
'verbose': 'verbose',
|
|
43
|
+
'history-retention-days': 'historyRetentionDays',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// =============================================================================
|
|
47
|
+
// TYPES
|
|
48
|
+
// =============================================================================
|
|
49
|
+
|
|
50
|
+
interface ConfigFile {
|
|
51
|
+
apiKey?: string;
|
|
52
|
+
model?: string;
|
|
53
|
+
analysisMode?: string;
|
|
54
|
+
verbose?: boolean;
|
|
55
|
+
historyRetentionDays?: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// =============================================================================
|
|
59
|
+
// HELPERS
|
|
60
|
+
// =============================================================================
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Load global config file
|
|
64
|
+
*/
|
|
65
|
+
function loadGlobalConfig(): ConfigFile {
|
|
66
|
+
if (!existsSync(GLOBAL_CONFIG_FILE)) {
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const content = readFileSync(GLOBAL_CONFIG_FILE, 'utf-8');
|
|
71
|
+
return parseYAML(content) || {};
|
|
72
|
+
} catch {
|
|
73
|
+
return {};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Load local config file
|
|
79
|
+
*/
|
|
80
|
+
function loadLocalConfig(): ConfigFile {
|
|
81
|
+
if (!existsSync(LOCAL_CONFIG_FILE)) {
|
|
82
|
+
return {};
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const content = readFileSync(LOCAL_CONFIG_FILE, 'utf-8');
|
|
86
|
+
return parseYAML(content) || {};
|
|
87
|
+
} catch {
|
|
88
|
+
return {};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Save global config file
|
|
94
|
+
*/
|
|
95
|
+
function saveGlobalConfig(config: ConfigFile): void {
|
|
96
|
+
mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true });
|
|
97
|
+
writeFileSync(GLOBAL_CONFIG_FILE, stringifyYAML(config));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Save local config file
|
|
102
|
+
*/
|
|
103
|
+
function saveLocalConfig(config: ConfigFile): void {
|
|
104
|
+
writeFileSync(LOCAL_CONFIG_FILE, stringifyYAML(config));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get merged configuration with resolution chain
|
|
109
|
+
*/
|
|
110
|
+
function getMergedConfig(): ConfigFile & { _sources: Record<string, string> } {
|
|
111
|
+
const defaults: ConfigFile = {
|
|
112
|
+
model: 'claude-sonnet-4-20250514',
|
|
113
|
+
analysisMode: 'agent',
|
|
114
|
+
verbose: false,
|
|
115
|
+
historyRetentionDays: 90,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const local = loadLocalConfig();
|
|
119
|
+
const global = loadGlobalConfig();
|
|
120
|
+
|
|
121
|
+
// Track where each value comes from
|
|
122
|
+
const sources: Record<string, string> = {};
|
|
123
|
+
|
|
124
|
+
const result: ConfigFile = {};
|
|
125
|
+
|
|
126
|
+
// Resolution: defaults → global → local → env
|
|
127
|
+
for (const key of Object.keys(defaults) as (keyof ConfigFile)[]) {
|
|
128
|
+
if (key in defaults) {
|
|
129
|
+
result[key] = defaults[key] as never;
|
|
130
|
+
sources[key] = 'default';
|
|
131
|
+
}
|
|
132
|
+
if (key in global && global[key] !== undefined) {
|
|
133
|
+
result[key] = global[key] as never;
|
|
134
|
+
sources[key] = 'global (~/.peakinfer/config.yaml)';
|
|
135
|
+
}
|
|
136
|
+
if (key in local && local[key] !== undefined) {
|
|
137
|
+
result[key] = local[key] as never;
|
|
138
|
+
sources[key] = 'local (./peakinfer.yaml)';
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Environment variable overrides
|
|
143
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
144
|
+
result.apiKey = process.env.ANTHROPIC_API_KEY;
|
|
145
|
+
sources['apiKey'] = 'env (ANTHROPIC_API_KEY)';
|
|
146
|
+
}
|
|
147
|
+
if (process.env.PEAKINFER_MODEL) {
|
|
148
|
+
result.model = process.env.PEAKINFER_MODEL;
|
|
149
|
+
sources['model'] = 'env (PEAKINFER_MODEL)';
|
|
150
|
+
}
|
|
151
|
+
if (process.env.PEAKINFER_MODE) {
|
|
152
|
+
result.analysisMode = process.env.PEAKINFER_MODE;
|
|
153
|
+
sources['analysisMode'] = 'env (PEAKINFER_MODE)';
|
|
154
|
+
}
|
|
155
|
+
if (process.env.PEAKINFER_VERBOSE === '1' || process.env.PEAKINFER_VERBOSE === 'true') {
|
|
156
|
+
result.verbose = true;
|
|
157
|
+
sources['verbose'] = 'env (PEAKINFER_VERBOSE)';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return { ...result, _sources: sources };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Mask sensitive values for display
|
|
165
|
+
*/
|
|
166
|
+
function maskValue(key: string, value: unknown): string {
|
|
167
|
+
if (key === 'apiKey' && typeof value === 'string') {
|
|
168
|
+
if (value.length > 8) {
|
|
169
|
+
return value.slice(0, 4) + '...' + value.slice(-4);
|
|
170
|
+
}
|
|
171
|
+
return '****';
|
|
172
|
+
}
|
|
173
|
+
return String(value);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Format config for display
|
|
178
|
+
*/
|
|
179
|
+
function displayConfig(config: ConfigFile & { _sources: Record<string, string> }): void {
|
|
180
|
+
console.log('\nPeakInfer Configuration');
|
|
181
|
+
console.log('═'.repeat(60));
|
|
182
|
+
|
|
183
|
+
const { _sources, ...values } = config;
|
|
184
|
+
|
|
185
|
+
const displayKeys: Array<{ key: keyof ConfigFile; label: string }> = [
|
|
186
|
+
{ key: 'apiKey', label: 'API Key' },
|
|
187
|
+
{ key: 'model', label: 'Model' },
|
|
188
|
+
{ key: 'analysisMode', label: 'Analysis Mode' },
|
|
189
|
+
{ key: 'verbose', label: 'Verbose' },
|
|
190
|
+
{ key: 'historyRetentionDays', label: 'History Retention' },
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
for (const { key, label } of displayKeys) {
|
|
194
|
+
const value = values[key];
|
|
195
|
+
const source = _sources[key];
|
|
196
|
+
|
|
197
|
+
if (value !== undefined) {
|
|
198
|
+
const displayValue = maskValue(key, value);
|
|
199
|
+
const suffix = key === 'historyRetentionDays' ? ' days' : '';
|
|
200
|
+
console.log(` ${label.padEnd(20)} ${displayValue}${suffix}`);
|
|
201
|
+
console.log(` ${''.padEnd(20)} └─ source: ${source}`);
|
|
202
|
+
} else {
|
|
203
|
+
console.log(` ${label.padEnd(20)} (not set)`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
console.log('');
|
|
208
|
+
console.log('Resolution chain: CLI → env → global → local → defaults');
|
|
209
|
+
console.log('');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// =============================================================================
|
|
213
|
+
// COMMANDS
|
|
214
|
+
// =============================================================================
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Register config commands
|
|
218
|
+
*/
|
|
219
|
+
export function registerConfigCommands(program: Command): void {
|
|
220
|
+
const configCmd = program
|
|
221
|
+
.command('config')
|
|
222
|
+
.description('manage configuration');
|
|
223
|
+
|
|
224
|
+
// Set config value
|
|
225
|
+
configCmd
|
|
226
|
+
.command('set')
|
|
227
|
+
.description('set a configuration value')
|
|
228
|
+
.argument('<key>', `configuration key (${ALLOWED_KEYS.join(', ')})`)
|
|
229
|
+
.argument('<value>', 'configuration value')
|
|
230
|
+
.option('--global', 'save to global config (~/.peakinfer/config.yaml)', true)
|
|
231
|
+
.option('--local', 'save to local config (./peakinfer.yaml)')
|
|
232
|
+
.action((key: string, value: string, options: { global?: boolean; local?: boolean }) => {
|
|
233
|
+
try {
|
|
234
|
+
// Validate key
|
|
235
|
+
if (!ALLOWED_KEYS.includes(key as ConfigKey)) {
|
|
236
|
+
console.error(`Invalid key: ${key}`);
|
|
237
|
+
console.log(`\nAllowed keys: ${ALLOWED_KEYS.join(', ')}`);
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const configKey = KEY_MAP[key] || key;
|
|
242
|
+
|
|
243
|
+
// Parse value based on key
|
|
244
|
+
let parsedValue: string | number | boolean = value;
|
|
245
|
+
if (key === 'verbose') {
|
|
246
|
+
parsedValue = value === 'true' || value === '1';
|
|
247
|
+
} else if (key === 'history-retention-days') {
|
|
248
|
+
parsedValue = parseInt(value, 10);
|
|
249
|
+
if (isNaN(parsedValue)) {
|
|
250
|
+
console.error('history-retention-days must be a number');
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Determine target file
|
|
256
|
+
const useLocal = options.local && !options.global;
|
|
257
|
+
|
|
258
|
+
if (useLocal) {
|
|
259
|
+
const config = loadLocalConfig();
|
|
260
|
+
(config as Record<string, unknown>)[configKey] = parsedValue;
|
|
261
|
+
saveLocalConfig(config);
|
|
262
|
+
console.log(`Set ${key} = ${maskValue(configKey, parsedValue)} in ./peakinfer.yaml`);
|
|
263
|
+
} else {
|
|
264
|
+
const config = loadGlobalConfig();
|
|
265
|
+
(config as Record<string, unknown>)[configKey] = parsedValue;
|
|
266
|
+
saveGlobalConfig(config);
|
|
267
|
+
console.log(`Set ${key} = ${maskValue(configKey, parsedValue)} in ~/.peakinfer/config.yaml`);
|
|
268
|
+
}
|
|
269
|
+
} catch (error) {
|
|
270
|
+
console.error('Error:', error instanceof Error ? error.message : 'Failed to set config');
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Show config
|
|
276
|
+
configCmd
|
|
277
|
+
.command('show')
|
|
278
|
+
.description('display current configuration')
|
|
279
|
+
.action(() => {
|
|
280
|
+
try {
|
|
281
|
+
const config = getMergedConfig();
|
|
282
|
+
displayConfig(config);
|
|
283
|
+
} catch (error) {
|
|
284
|
+
console.error('Error:', error instanceof Error ? error.message : 'Failed to load config');
|
|
285
|
+
process.exit(1);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|