@vibecheckai/cli 3.2.2 → 3.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/.generated +25 -25
- package/bin/dev/run-v2-torture.js +30 -30
- package/bin/runners/ENHANCEMENT_GUIDE.md +121 -121
- package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -295
- package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +474 -0
- package/bin/runners/lib/agent-firewall/claims/extractor.js +117 -28
- package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +23 -14
- package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +72 -1
- package/bin/runners/lib/agent-firewall/interceptor/base.js +2 -2
- package/bin/runners/lib/agent-firewall/policy/default-policy.json +6 -0
- package/bin/runners/lib/agent-firewall/policy/engine.js +34 -3
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +29 -4
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +12 -0
- package/bin/runners/lib/agent-firewall/truthpack/loader.js +21 -0
- package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +118 -0
- package/bin/runners/lib/analyzers.js +606 -325
- package/bin/runners/lib/auth-truth.js +193 -193
- package/bin/runners/lib/backup.js +62 -62
- package/bin/runners/lib/billing.js +107 -107
- package/bin/runners/lib/claims.js +118 -118
- package/bin/runners/lib/cli-ui.js +540 -540
- package/bin/runners/lib/contracts/auth-contract.js +202 -202
- package/bin/runners/lib/contracts/env-contract.js +181 -181
- package/bin/runners/lib/contracts/external-contract.js +206 -206
- package/bin/runners/lib/contracts/guard.js +168 -168
- package/bin/runners/lib/contracts/index.js +89 -89
- package/bin/runners/lib/contracts/plan-validator.js +311 -311
- package/bin/runners/lib/contracts/route-contract.js +199 -199
- package/bin/runners/lib/contracts.js +804 -804
- package/bin/runners/lib/detect.js +89 -89
- package/bin/runners/lib/doctor/autofix.js +254 -254
- package/bin/runners/lib/doctor/index.js +37 -37
- package/bin/runners/lib/doctor/modules/dependencies.js +325 -325
- package/bin/runners/lib/doctor/modules/index.js +46 -46
- package/bin/runners/lib/doctor/modules/network.js +250 -250
- package/bin/runners/lib/doctor/modules/project.js +312 -312
- package/bin/runners/lib/doctor/modules/runtime.js +224 -224
- package/bin/runners/lib/doctor/modules/security.js +348 -348
- package/bin/runners/lib/doctor/modules/system.js +213 -213
- package/bin/runners/lib/doctor/modules/vibecheck.js +394 -394
- package/bin/runners/lib/doctor/reporter.js +262 -262
- package/bin/runners/lib/doctor/service.js +262 -262
- package/bin/runners/lib/doctor/types.js +113 -113
- package/bin/runners/lib/doctor/ui.js +263 -263
- package/bin/runners/lib/doctor-v2.js +608 -608
- package/bin/runners/lib/drift.js +425 -425
- package/bin/runners/lib/enforcement.js +72 -72
- package/bin/runners/lib/engines/accessibility-engine.js +190 -0
- package/bin/runners/lib/engines/api-consistency-engine.js +162 -0
- package/bin/runners/lib/engines/ast-cache.js +99 -0
- package/bin/runners/lib/engines/code-quality-engine.js +255 -0
- package/bin/runners/lib/engines/console-logs-engine.js +115 -0
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +268 -0
- package/bin/runners/lib/engines/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/deprecated-api-engine.js +226 -0
- package/bin/runners/lib/engines/empty-catch-engine.js +150 -0
- package/bin/runners/lib/engines/file-filter.js +131 -0
- package/bin/runners/lib/engines/hardcoded-secrets-engine.js +251 -0
- package/bin/runners/lib/engines/mock-data-engine.js +272 -0
- package/bin/runners/lib/engines/parallel-processor.js +71 -0
- package/bin/runners/lib/engines/performance-issues-engine.js +265 -0
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +243 -0
- package/bin/runners/lib/engines/todo-fixme-engine.js +115 -0
- package/bin/runners/lib/engines/type-aware-engine.js +152 -0
- package/bin/runners/lib/engines/unsafe-regex-engine.js +225 -0
- package/bin/runners/lib/engines/vibecheck-engines/README.md +53 -0
- package/bin/runners/lib/engines/vibecheck-engines/index.js +15 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +139 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
- package/bin/runners/lib/engines/vibecheck-engines/package.json +13 -0
- package/bin/runners/lib/enterprise-detect.js +603 -603
- package/bin/runners/lib/enterprise-init.js +942 -942
- package/bin/runners/lib/env-resolver.js +417 -417
- package/bin/runners/lib/env-template.js +66 -66
- package/bin/runners/lib/env.js +189 -189
- package/bin/runners/lib/extractors/client-calls.js +990 -990
- package/bin/runners/lib/extractors/fastify-route-dump.js +573 -573
- package/bin/runners/lib/extractors/fastify-routes.js +426 -426
- package/bin/runners/lib/extractors/index.js +363 -363
- package/bin/runners/lib/extractors/next-routes.js +524 -524
- package/bin/runners/lib/extractors/proof-graph.js +431 -431
- package/bin/runners/lib/extractors/route-matcher.js +451 -451
- package/bin/runners/lib/extractors/truthpack-v2.js +377 -377
- package/bin/runners/lib/extractors/ui-bindings.js +547 -547
- package/bin/runners/lib/findings-schema.js +281 -281
- package/bin/runners/lib/firewall-prompt.js +50 -50
- package/bin/runners/lib/global-flags.js +213 -213
- package/bin/runners/lib/graph/graph-builder.js +265 -265
- package/bin/runners/lib/graph/html-renderer.js +413 -413
- package/bin/runners/lib/graph/index.js +32 -32
- package/bin/runners/lib/graph/runtime-collector.js +215 -215
- package/bin/runners/lib/graph/static-extractor.js +518 -518
- package/bin/runners/lib/html-report.js +650 -650
- package/bin/runners/lib/interactive-menu.js +1496 -1496
- package/bin/runners/lib/llm.js +75 -75
- package/bin/runners/lib/meter.js +61 -61
- package/bin/runners/lib/missions/evidence.js +126 -126
- package/bin/runners/lib/patch.js +40 -40
- package/bin/runners/lib/permissions/auth-model.js +213 -213
- package/bin/runners/lib/permissions/idor-prover.js +205 -205
- package/bin/runners/lib/permissions/index.js +45 -45
- package/bin/runners/lib/permissions/matrix-builder.js +198 -198
- package/bin/runners/lib/pkgjson.js +28 -28
- package/bin/runners/lib/policy.js +295 -295
- package/bin/runners/lib/preflight.js +142 -142
- package/bin/runners/lib/reality/correlation-detectors.js +359 -359
- package/bin/runners/lib/reality/index.js +318 -318
- package/bin/runners/lib/reality/request-hashing.js +416 -416
- package/bin/runners/lib/reality/request-mapper.js +453 -453
- package/bin/runners/lib/reality/safety-rails.js +463 -463
- package/bin/runners/lib/reality/semantic-snapshot.js +408 -408
- package/bin/runners/lib/reality/toast-detector.js +393 -393
- package/bin/runners/lib/reality-findings.js +84 -84
- package/bin/runners/lib/receipts.js +179 -179
- package/bin/runners/lib/redact.js +29 -29
- package/bin/runners/lib/replay/capsule-manager.js +154 -154
- package/bin/runners/lib/replay/index.js +263 -263
- package/bin/runners/lib/replay/player.js +348 -348
- package/bin/runners/lib/replay/recorder.js +331 -331
- package/bin/runners/lib/report-output.js +187 -187
- package/bin/runners/lib/report.js +135 -135
- package/bin/runners/lib/route-detection.js +1140 -1140
- package/bin/runners/lib/sandbox/index.js +59 -59
- package/bin/runners/lib/sandbox/proof-chain.js +399 -399
- package/bin/runners/lib/sandbox/sandbox-runner.js +205 -205
- package/bin/runners/lib/sandbox/worktree.js +174 -174
- package/bin/runners/lib/scan-output.js +525 -190
- package/bin/runners/lib/schema-validator.js +350 -350
- package/bin/runners/lib/schemas/contracts.schema.json +160 -160
- package/bin/runners/lib/schemas/finding.schema.json +100 -100
- package/bin/runners/lib/schemas/mission-pack.schema.json +206 -206
- package/bin/runners/lib/schemas/proof-graph.schema.json +176 -176
- package/bin/runners/lib/schemas/reality-report.schema.json +162 -162
- package/bin/runners/lib/schemas/share-pack.schema.json +180 -180
- package/bin/runners/lib/schemas/ship-report.schema.json +117 -117
- package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -303
- package/bin/runners/lib/schemas/validator.js +438 -438
- package/bin/runners/lib/score-history.js +282 -282
- package/bin/runners/lib/share-pack.js +239 -239
- package/bin/runners/lib/snippets.js +67 -67
- package/bin/runners/lib/status-output.js +253 -253
- package/bin/runners/lib/terminal-ui.js +351 -271
- package/bin/runners/lib/upsell.js +510 -510
- package/bin/runners/lib/usage.js +153 -153
- package/bin/runners/lib/validate-patch.js +156 -156
- package/bin/runners/lib/verdict-engine.js +628 -628
- package/bin/runners/reality/engine.js +917 -917
- package/bin/runners/reality/flows.js +122 -122
- package/bin/runners/reality/report.js +378 -378
- package/bin/runners/reality/session.js +193 -193
- package/bin/runners/runGuard.js +168 -168
- package/bin/runners/runProof.zip +0 -0
- package/bin/runners/runProve.js +8 -0
- package/bin/runners/runReality.js +14 -0
- package/bin/runners/runScan.js +17 -1
- package/bin/runners/runTruth.js +15 -3
- package/mcp-server/tier-auth.js +4 -4
- package/mcp-server/tools/index.js +72 -72
- package/package.json +1 -1
|
@@ -1,510 +1,510 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Upsell Copy Module - Central copy generator for tier upgrades
|
|
3
|
-
*
|
|
4
|
-
* This is the SINGLE SOURCE OF TRUTH for all upsell/upgrade messaging.
|
|
5
|
-
* All upgrade nudges flow through these functions.
|
|
6
|
-
*
|
|
7
|
-
* Rules:
|
|
8
|
-
* - Blunt, confident, minimal tone
|
|
9
|
-
* - No cringe language ("please upgrade")
|
|
10
|
-
* - Every upsell includes a free path if one exists
|
|
11
|
-
* - No web links besides https://vibecheckai.dev/pricing
|
|
12
|
-
*
|
|
13
|
-
* @module bin/runners/lib/upsell
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
"use strict";
|
|
17
|
-
|
|
18
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
19
|
-
// ANSI STYLING
|
|
20
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
21
|
-
const SUPPORTS_COLOR = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
22
|
-
|
|
23
|
-
const c = SUPPORTS_COLOR ? {
|
|
24
|
-
reset: "\x1b[0m",
|
|
25
|
-
bold: "\x1b[1m",
|
|
26
|
-
dim: "\x1b[2m",
|
|
27
|
-
red: "\x1b[31m",
|
|
28
|
-
green: "\x1b[32m",
|
|
29
|
-
yellow: "\x1b[33m",
|
|
30
|
-
cyan: "\x1b[36m",
|
|
31
|
-
magenta: "\x1b[35m",
|
|
32
|
-
white: "\x1b[37m",
|
|
33
|
-
gray: "\x1b[90m",
|
|
34
|
-
} : {
|
|
35
|
-
reset: "", bold: "", dim: "", red: "", green: "", yellow: "",
|
|
36
|
-
cyan: "", magenta: "", white: "", gray: "",
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const sym = {
|
|
40
|
-
lock: "🔒",
|
|
41
|
-
arrow: "→",
|
|
42
|
-
warning: "⚠",
|
|
43
|
-
check: "✓",
|
|
44
|
-
cross: "✗",
|
|
45
|
-
star: "★",
|
|
46
|
-
rocket: "🚀",
|
|
47
|
-
badge: "🏆",
|
|
48
|
-
lightning: "⚡",
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
52
|
-
// TIER CONFIGURATION
|
|
53
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
54
|
-
const TIER_LABELS = {
|
|
55
|
-
free: "FREE",
|
|
56
|
-
starter: "STARTER",
|
|
57
|
-
pro: "PRO",
|
|
58
|
-
complete: "COMPLETE",
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const TIER_COLORS = {
|
|
62
|
-
free: c.green,
|
|
63
|
-
starter: c.cyan,
|
|
64
|
-
pro: c.magenta,
|
|
65
|
-
complete: c.yellow,
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const PRICING_URL = "https://vibecheckai.dev
|
|
69
|
-
|
|
70
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
71
|
-
// DENIAL COPY - Command-specific reasons and alternatives
|
|
72
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
73
|
-
const DENIAL_COPY = {
|
|
74
|
-
ship: {
|
|
75
|
-
feature: "release gate",
|
|
76
|
-
why: "Full ship analysis with runtime verification",
|
|
77
|
-
freeAlt: "vibecheck scan",
|
|
78
|
-
freeAltDesc: "static analysis",
|
|
79
|
-
},
|
|
80
|
-
prove: {
|
|
81
|
-
feature: "proof loop",
|
|
82
|
-
why: "Complete ctx → reality → ship → fix cycle",
|
|
83
|
-
freeAlt: "vibecheck ship",
|
|
84
|
-
freeAltDesc: "static verdict",
|
|
85
|
-
},
|
|
86
|
-
permissions: {
|
|
87
|
-
feature: "auth boundary / IDOR detection",
|
|
88
|
-
why: "Deep authorization matrix and IDOR analysis",
|
|
89
|
-
freeAlt: "vibecheck reality --verify-auth",
|
|
90
|
-
freeAltDesc: "basic auth boundary check",
|
|
91
|
-
},
|
|
92
|
-
"fix.apply_patches": {
|
|
93
|
-
feature: "patch generator / PR-ready diff",
|
|
94
|
-
why: "Apply LLM-generated patches automatically",
|
|
95
|
-
freeAlt: "vibecheck fix",
|
|
96
|
-
freeAltDesc: "plan-only mode",
|
|
97
|
-
},
|
|
98
|
-
fix: {
|
|
99
|
-
feature: "auto-fix with patches",
|
|
100
|
-
why: "Generate and apply fixes to your codebase",
|
|
101
|
-
freeAlt: "vibecheck fix --plan-only",
|
|
102
|
-
freeAltDesc: "view fix plan without applying",
|
|
103
|
-
},
|
|
104
|
-
badge: {
|
|
105
|
-
feature: "status artifact",
|
|
106
|
-
why: "Verified ship badge for README",
|
|
107
|
-
freeAlt: "vibecheck report",
|
|
108
|
-
freeAltDesc: "HTML/MD report",
|
|
109
|
-
},
|
|
110
|
-
gate: {
|
|
111
|
-
feature: "CI/CD gate",
|
|
112
|
-
why: "Block deploys on verification failures",
|
|
113
|
-
freeAlt: "vibecheck ship --ci",
|
|
114
|
-
freeAltDesc: "exit codes for scripts",
|
|
115
|
-
},
|
|
116
|
-
pr: {
|
|
117
|
-
feature: "PR comment generator",
|
|
118
|
-
why: "Auto-generated PR comments with findings",
|
|
119
|
-
freeAlt: "vibecheck report --format md",
|
|
120
|
-
freeAltDesc: "markdown report",
|
|
121
|
-
},
|
|
122
|
-
launch: {
|
|
123
|
-
feature: "pre-launch checklist",
|
|
124
|
-
why: "Guided launch readiness wizard",
|
|
125
|
-
freeAlt: "vibecheck ship",
|
|
126
|
-
freeAltDesc: "verdict check",
|
|
127
|
-
},
|
|
128
|
-
mcp: {
|
|
129
|
-
feature: "MCP server for AI IDEs",
|
|
130
|
-
why: "Real-time AI IDE integration",
|
|
131
|
-
freeAlt: "vibecheck ctx",
|
|
132
|
-
freeAltDesc: "generate truthpack manually",
|
|
133
|
-
},
|
|
134
|
-
share: {
|
|
135
|
-
feature: "share pack generator",
|
|
136
|
-
why: "Shareable proof bundle for PRs/docs",
|
|
137
|
-
freeAlt: "vibecheck report",
|
|
138
|
-
freeAltDesc: "local report",
|
|
139
|
-
},
|
|
140
|
-
"ai-test": {
|
|
141
|
-
feature: "autonomous test generation",
|
|
142
|
-
why: "AI-generated test coverage",
|
|
143
|
-
freeAlt: "vibecheck scan",
|
|
144
|
-
freeAltDesc: "static analysis",
|
|
145
|
-
},
|
|
146
|
-
replay: {
|
|
147
|
-
feature: "session replay",
|
|
148
|
-
why: "Record and replay user sessions",
|
|
149
|
-
freeAlt: "vibecheck reality",
|
|
150
|
-
freeAltDesc: "one-time runtime proof",
|
|
151
|
-
},
|
|
152
|
-
graph: {
|
|
153
|
-
feature: "reality proof graph",
|
|
154
|
-
why: "Visual dependency and proof graph",
|
|
155
|
-
freeAlt: "vibecheck ctx",
|
|
156
|
-
freeAltDesc: "truthpack",
|
|
157
|
-
},
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
161
|
-
// CAPS COPY - Downgrade mode descriptions
|
|
162
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
163
|
-
const CAPS_COPY = {
|
|
164
|
-
"reality.preview": {
|
|
165
|
-
short: "5 pages, no auth boundary",
|
|
166
|
-
full: "Preview mode: 5 pages max, 20 clicks, no auth boundary verification",
|
|
167
|
-
upgradeBenefit: "Unlimited pages + full auth boundary testing",
|
|
168
|
-
},
|
|
169
|
-
"fix.plan_only": {
|
|
170
|
-
short: "plan-only, no apply",
|
|
171
|
-
full: "Plan mode: generates fix missions but cannot apply patches",
|
|
172
|
-
upgradeBenefit: "Apply patches automatically with --apply",
|
|
173
|
-
},
|
|
174
|
-
"report.html_md": {
|
|
175
|
-
short: "HTML/MD only",
|
|
176
|
-
full: "Basic formats: HTML and Markdown reports only",
|
|
177
|
-
upgradeBenefit: "SARIF, CSV, and compliance pack exports",
|
|
178
|
-
},
|
|
179
|
-
"ship.static": {
|
|
180
|
-
short: "static-only",
|
|
181
|
-
full: "Static analysis only, no runtime verification",
|
|
182
|
-
upgradeBenefit: "Full runtime + static verification",
|
|
183
|
-
},
|
|
184
|
-
"mcp.help_only": {
|
|
185
|
-
short: "help and print-config only",
|
|
186
|
-
full: "MCP server limited to help and config commands",
|
|
187
|
-
upgradeBenefit: "Full MCP server with all tools",
|
|
188
|
-
},
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
192
|
-
// formatDenied() - Hard denial message
|
|
193
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
194
|
-
/**
|
|
195
|
-
* Format a denial message for a command that requires a higher tier.
|
|
196
|
-
*
|
|
197
|
-
* @param {string} cmd - The command that was denied
|
|
198
|
-
* @param {object} opts - Options
|
|
199
|
-
* @param {string} opts.currentTier - User's current tier
|
|
200
|
-
* @param {string} opts.requiredTier - Required tier for the command
|
|
201
|
-
* @param {string} [opts.reason] - Additional context
|
|
202
|
-
* @param {string} [opts.suggestedNext] - Suggested next command
|
|
203
|
-
* @param {string} [opts.freeAlternative] - Free alternative command
|
|
204
|
-
* @returns {string} Formatted denial message
|
|
205
|
-
*/
|
|
206
|
-
function formatDenied(cmd, opts = {}) {
|
|
207
|
-
const { currentTier = "free", requiredTier = "pro" } = opts;
|
|
208
|
-
|
|
209
|
-
const copy = DENIAL_COPY[cmd] || {
|
|
210
|
-
feature: cmd,
|
|
211
|
-
why: `${cmd} command`,
|
|
212
|
-
freeAlt: null,
|
|
213
|
-
freeAltDesc: null,
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
const reqColor = TIER_COLORS[requiredTier] || c.yellow;
|
|
217
|
-
const reqLabel = TIER_LABELS[requiredTier] || requiredTier.toUpperCase();
|
|
218
|
-
const curLabel = TIER_LABELS[currentTier] || currentTier.toUpperCase();
|
|
219
|
-
|
|
220
|
-
let msg = `
|
|
221
|
-
${c.red}${c.bold}${sym.lock} LOCKED${c.reset}
|
|
222
|
-
|
|
223
|
-
${c.bold}What:${c.reset} ${c.yellow}${cmd}${c.reset} ${c.dim}(${copy.feature})${c.reset}
|
|
224
|
-
${c.bold}Why:${c.reset} ${copy.why}
|
|
225
|
-
${c.bold}Requires:${c.reset} ${reqColor}${reqLabel}${c.reset} plan
|
|
226
|
-
${c.bold}You have:${c.reset} ${c.dim}${curLabel}${c.reset}
|
|
227
|
-
`;
|
|
228
|
-
|
|
229
|
-
// Free alternative
|
|
230
|
-
if (copy.freeAlt) {
|
|
231
|
-
msg += `
|
|
232
|
-
${c.green}${sym.arrow} Free path:${c.reset} ${c.cyan}${copy.freeAlt}${c.reset} ${c.dim}(${copy.freeAltDesc})${c.reset}`;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// Upgrade CTA
|
|
236
|
-
msg += `
|
|
237
|
-
${c.bold}${sym.arrow} Upgrade:${c.reset} ${PRICING_URL}
|
|
238
|
-
`;
|
|
239
|
-
|
|
240
|
-
return msg;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
244
|
-
// formatDowngrade() - Caps/downgrade notice
|
|
245
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
246
|
-
/**
|
|
247
|
-
* Format a downgrade notice for a command running in capped mode.
|
|
248
|
-
*
|
|
249
|
-
* @param {string} cmd - The command being run
|
|
250
|
-
* @param {object} opts - Options
|
|
251
|
-
* @param {string} opts.currentTier - User's current tier
|
|
252
|
-
* @param {string} opts.effectiveMode - The downgraded mode being used
|
|
253
|
-
* @param {object} [opts.caps] - Specific caps applied
|
|
254
|
-
* @returns {string} Formatted downgrade notice
|
|
255
|
-
*/
|
|
256
|
-
function formatDowngrade(cmd, opts = {}) {
|
|
257
|
-
const { currentTier = "free", effectiveMode, caps } = opts;
|
|
258
|
-
|
|
259
|
-
const copy = CAPS_COPY[effectiveMode] || {
|
|
260
|
-
short: effectiveMode || "limited mode",
|
|
261
|
-
full: `Running in ${effectiveMode || "limited"} mode`,
|
|
262
|
-
upgradeBenefit: "Full access",
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
const curLabel = TIER_LABELS[currentTier] || currentTier.toUpperCase();
|
|
266
|
-
|
|
267
|
-
// Single line notice for start of run
|
|
268
|
-
let shortNotice = `${c.yellow}${sym.warning}${c.reset} Running in ${c.yellow}${curLabel}${c.reset} mode: ${c.dim}${copy.short}${c.reset}`;
|
|
269
|
-
|
|
270
|
-
return shortNotice;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Format a cap-hit notice when user reaches a limit during execution.
|
|
275
|
-
*
|
|
276
|
-
* @param {string} cmd - The command being run
|
|
277
|
-
* @param {object} opts - Options
|
|
278
|
-
* @param {string} opts.limitType - Type of limit hit (e.g., "pages", "clicks")
|
|
279
|
-
* @param {number} opts.limitValue - The limit value
|
|
280
|
-
* @param {string} opts.upgradeTier - Tier to upgrade to
|
|
281
|
-
* @returns {string} Formatted cap-hit message
|
|
282
|
-
*/
|
|
283
|
-
function formatCapHit(cmd, opts = {}) {
|
|
284
|
-
const { limitType = "limit", limitValue, upgradeTier = "pro" } = opts;
|
|
285
|
-
|
|
286
|
-
const tierColor = TIER_COLORS[upgradeTier] || c.magenta;
|
|
287
|
-
const tierLabel = TIER_LABELS[upgradeTier] || upgradeTier.toUpperCase();
|
|
288
|
-
|
|
289
|
-
return `${c.yellow}${sym.warning}${c.reset} Hit FREE ${limitType} cap (${limitValue}). Upgrade to ${tierColor}${tierLabel}${c.reset} ${sym.arrow} ${PRICING_URL}`;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
293
|
-
// formatEarnedUpsell() - End-of-run upsell
|
|
294
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
295
|
-
/**
|
|
296
|
-
* Format an earned upsell shown at end of run.
|
|
297
|
-
*
|
|
298
|
-
* @param {object} opts - Options
|
|
299
|
-
* @param {string} opts.cmd - The command that was run
|
|
300
|
-
* @param {string} opts.verdict - The verdict (SHIP/WARN/BLOCK)
|
|
301
|
-
* @param {Array} [opts.topIssues] - Top 3 issues to show
|
|
302
|
-
* @param {string} [opts.withheldArtifact] - Artifact that was withheld (e.g., "badge")
|
|
303
|
-
* @param {string} [opts.upgradeTier] - Tier to suggest upgrading to
|
|
304
|
-
* @param {string} [opts.why] - Why this upsell is relevant
|
|
305
|
-
* @returns {string} Formatted earned upsell message
|
|
306
|
-
*/
|
|
307
|
-
function formatEarnedUpsell(opts = {}) {
|
|
308
|
-
const {
|
|
309
|
-
cmd,
|
|
310
|
-
verdict,
|
|
311
|
-
topIssues = [],
|
|
312
|
-
withheldArtifact,
|
|
313
|
-
upgradeTier = "pro",
|
|
314
|
-
why,
|
|
315
|
-
currentTier = "free",
|
|
316
|
-
suggestedCmd,
|
|
317
|
-
} = opts;
|
|
318
|
-
|
|
319
|
-
const tierColor = TIER_COLORS[upgradeTier] || c.magenta;
|
|
320
|
-
const tierLabel = TIER_LABELS[upgradeTier] || upgradeTier.toUpperCase();
|
|
321
|
-
|
|
322
|
-
let msg = "";
|
|
323
|
-
|
|
324
|
-
// Badge withheld case
|
|
325
|
-
if (withheldArtifact === "badge") {
|
|
326
|
-
msg += `
|
|
327
|
-
${c.dim}─────────────────────────────────────────────────────────────${c.reset}
|
|
328
|
-
${c.yellow}${sym.badge} Badge withheld${c.reset} ${c.dim}(verdict: ${verdict})${c.reset}
|
|
329
|
-
`;
|
|
330
|
-
|
|
331
|
-
if (topIssues.length > 0) {
|
|
332
|
-
msg += `\n${c.bold}Top blockers:${c.reset}\n`;
|
|
333
|
-
topIssues.slice(0, 3).forEach((issue, i) => {
|
|
334
|
-
const sev = issue.severity === "BLOCK" ? c.red : c.yellow;
|
|
335
|
-
msg += ` ${c.dim}${i + 1}.${c.reset} ${sev}${issue.severity}${c.reset} ${issue.message || issue.id || issue.type}\n`;
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
if (suggestedCmd) {
|
|
340
|
-
msg += `\n${c.green}${sym.arrow} Fix:${c.reset} ${c.cyan}${suggestedCmd}${c.reset}`;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
msg += `\n${c.dim}Badge unlocks when verdict = SHIP${c.reset}\n`;
|
|
344
|
-
return msg;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Fix plan-only case
|
|
348
|
-
if (cmd === "fix" && withheldArtifact === "apply") {
|
|
349
|
-
msg += `
|
|
350
|
-
${c.dim}─────────────────────────────────────────────────────────────${c.reset}
|
|
351
|
-
${c.yellow}${sym.lightning} Fix plan generated${c.reset}
|
|
352
|
-
|
|
353
|
-
Patches ready but ${c.yellow}--apply${c.reset} requires ${tierColor}${tierLabel}${c.reset} plan.
|
|
354
|
-
|
|
355
|
-
${c.green}${sym.arrow} Review:${c.reset} .vibecheck/missions/
|
|
356
|
-
${c.bold}${sym.arrow} Apply:${c.reset} Upgrade ${sym.arrow} ${PRICING_URL}
|
|
357
|
-
`;
|
|
358
|
-
return msg;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// Reality cap hit case
|
|
362
|
-
if (cmd === "reality" && why === "cap_hit") {
|
|
363
|
-
msg += `
|
|
364
|
-
${c.dim}─────────────────────────────────────────────────────────────${c.reset}
|
|
365
|
-
${c.yellow}${sym.warning} Preview complete${c.reset}
|
|
366
|
-
|
|
367
|
-
Crawled ${topIssues.length || 5} pages (FREE limit).
|
|
368
|
-
${tierColor}${tierLabel}${c.reset} unlocks unlimited pages + auth boundary testing.
|
|
369
|
-
|
|
370
|
-
${c.bold}${sym.arrow} Upgrade:${c.reset} ${PRICING_URL}
|
|
371
|
-
`;
|
|
372
|
-
return msg;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Generic earned upsell
|
|
376
|
-
if (why) {
|
|
377
|
-
msg += `
|
|
378
|
-
${c.dim}─────────────────────────────────────────────────────────────${c.reset}
|
|
379
|
-
${c.cyan}${sym.star} ${why}${c.reset}
|
|
380
|
-
|
|
381
|
-
${tierColor}${tierLabel}${c.reset} unlocks this feature.
|
|
382
|
-
${c.bold}${sym.arrow} Upgrade:${c.reset} ${PRICING_URL}
|
|
383
|
-
`;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
return msg;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
390
|
-
// formatBadgeWithheld() - Specific badge denial
|
|
391
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
392
|
-
/**
|
|
393
|
-
* Format badge withheld message with top issues.
|
|
394
|
-
*
|
|
395
|
-
* @param {string} verdict - Current verdict
|
|
396
|
-
* @param {Array} findings - All findings
|
|
397
|
-
* @param {string} currentTier - User's tier
|
|
398
|
-
* @returns {string} Formatted message
|
|
399
|
-
*/
|
|
400
|
-
function formatBadgeWithheld(verdict, findings = [], currentTier = "free") {
|
|
401
|
-
const blockers = findings.filter(f => f.severity === "BLOCK").slice(0, 3);
|
|
402
|
-
const topIssues = blockers.length > 0 ? blockers : findings.slice(0, 3);
|
|
403
|
-
|
|
404
|
-
let msg = `
|
|
405
|
-
${c.yellow}${sym.badge} Badge withheld${c.reset}
|
|
406
|
-
|
|
407
|
-
${c.bold}Verdict:${c.reset} ${verdict === "BLOCK" ? c.red : c.yellow}${verdict}${c.reset}
|
|
408
|
-
${c.dim}Badge requires SHIP verdict${c.reset}
|
|
409
|
-
`;
|
|
410
|
-
|
|
411
|
-
if (topIssues.length > 0) {
|
|
412
|
-
msg += `\n ${c.bold}Fix these first:${c.reset}\n`;
|
|
413
|
-
topIssues.forEach((issue, i) => {
|
|
414
|
-
const sev = issue.severity === "BLOCK" ? c.red : c.yellow;
|
|
415
|
-
const label = issue.id || issue.type || issue.ruleId || "issue";
|
|
416
|
-
const desc = issue.message || issue.description || "";
|
|
417
|
-
msg += ` ${c.dim}${i + 1}.${c.reset} ${sev}[${issue.severity}]${c.reset} ${label}${desc ? `: ${c.dim}${desc.slice(0, 50)}${c.reset}` : ""}\n`;
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// Suggest fix command if available
|
|
422
|
-
const canFix = currentTier !== "free";
|
|
423
|
-
if (canFix) {
|
|
424
|
-
msg += `\n ${c.green}${sym.arrow} Run:${c.reset} ${c.cyan}vibecheck fix${c.reset}\n`;
|
|
425
|
-
} else {
|
|
426
|
-
msg += `\n ${c.green}${sym.arrow} Run:${c.reset} ${c.cyan}vibecheck fix --plan-only${c.reset} ${c.dim}(view fix plan)${c.reset}\n`;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
return msg;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
433
|
-
// formatNextSteps() - Smart suggestions
|
|
434
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
435
|
-
/**
|
|
436
|
-
* Format next step suggestions based on current command and result.
|
|
437
|
-
*
|
|
438
|
-
* @param {string} cmd - Command just run
|
|
439
|
-
* @param {string} verdict - Result/verdict
|
|
440
|
-
* @param {string} currentTier - User's tier
|
|
441
|
-
* @returns {string} Next step suggestion
|
|
442
|
-
*/
|
|
443
|
-
function formatNextSteps(cmd, verdict, currentTier = "free") {
|
|
444
|
-
const steps = [];
|
|
445
|
-
|
|
446
|
-
switch (cmd) {
|
|
447
|
-
case "scan":
|
|
448
|
-
if (currentTier === "free") {
|
|
449
|
-
steps.push({ cmd: "vibecheck ship", desc: "get verdict" });
|
|
450
|
-
} else {
|
|
451
|
-
steps.push({ cmd: "vibecheck ship", desc: "get verdict" });
|
|
452
|
-
steps.push({ cmd: "vibecheck prove --url <url>", desc: "full proof loop" });
|
|
453
|
-
}
|
|
454
|
-
break;
|
|
455
|
-
|
|
456
|
-
case "ship":
|
|
457
|
-
if (verdict === "SHIP") {
|
|
458
|
-
if (currentTier !== "free") {
|
|
459
|
-
steps.push({ cmd: "vibecheck badge", desc: "generate badge" });
|
|
460
|
-
}
|
|
461
|
-
steps.push({ cmd: "vibecheck report", desc: "export report" });
|
|
462
|
-
} else {
|
|
463
|
-
if (currentTier === "free") {
|
|
464
|
-
steps.push({ cmd: "vibecheck fix --plan-only", desc: "view fix plan" });
|
|
465
|
-
} else {
|
|
466
|
-
steps.push({ cmd: "vibecheck fix", desc: "auto-fix issues" });
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
break;
|
|
470
|
-
|
|
471
|
-
case "fix":
|
|
472
|
-
steps.push({ cmd: "vibecheck ship", desc: "verify fixes" });
|
|
473
|
-
break;
|
|
474
|
-
|
|
475
|
-
case "reality":
|
|
476
|
-
steps.push({ cmd: "vibecheck ship", desc: "get verdict with runtime proof" });
|
|
477
|
-
break;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
if (steps.length === 0) return "";
|
|
481
|
-
|
|
482
|
-
let msg = `\n${c.dim}Next:${c.reset} `;
|
|
483
|
-
msg += steps.map(s => `${c.cyan}${s.cmd}${c.reset} ${c.dim}(${s.desc})${c.reset}`).join(" or ");
|
|
484
|
-
|
|
485
|
-
return msg;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
489
|
-
// EXPORTS
|
|
490
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
491
|
-
module.exports = {
|
|
492
|
-
// Core formatters
|
|
493
|
-
formatDenied,
|
|
494
|
-
formatDowngrade,
|
|
495
|
-
formatCapHit,
|
|
496
|
-
formatEarnedUpsell,
|
|
497
|
-
formatBadgeWithheld,
|
|
498
|
-
formatNextSteps,
|
|
499
|
-
|
|
500
|
-
// Copy data (for testing/docs)
|
|
501
|
-
DENIAL_COPY,
|
|
502
|
-
CAPS_COPY,
|
|
503
|
-
TIER_LABELS,
|
|
504
|
-
TIER_COLORS,
|
|
505
|
-
PRICING_URL,
|
|
506
|
-
|
|
507
|
-
// Styling (for consistent use)
|
|
508
|
-
c,
|
|
509
|
-
sym,
|
|
510
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Upsell Copy Module - Central copy generator for tier upgrades
|
|
3
|
+
*
|
|
4
|
+
* This is the SINGLE SOURCE OF TRUTH for all upsell/upgrade messaging.
|
|
5
|
+
* All upgrade nudges flow through these functions.
|
|
6
|
+
*
|
|
7
|
+
* Rules:
|
|
8
|
+
* - Blunt, confident, minimal tone
|
|
9
|
+
* - No cringe language ("please upgrade")
|
|
10
|
+
* - Every upsell includes a free path if one exists
|
|
11
|
+
* - No web links besides https://vibecheckai.dev/pricing
|
|
12
|
+
*
|
|
13
|
+
* @module bin/runners/lib/upsell
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
"use strict";
|
|
17
|
+
|
|
18
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
19
|
+
// ANSI STYLING
|
|
20
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
21
|
+
const SUPPORTS_COLOR = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
22
|
+
|
|
23
|
+
const c = SUPPORTS_COLOR ? {
|
|
24
|
+
reset: "\x1b[0m",
|
|
25
|
+
bold: "\x1b[1m",
|
|
26
|
+
dim: "\x1b[2m",
|
|
27
|
+
red: "\x1b[31m",
|
|
28
|
+
green: "\x1b[32m",
|
|
29
|
+
yellow: "\x1b[33m",
|
|
30
|
+
cyan: "\x1b[36m",
|
|
31
|
+
magenta: "\x1b[35m",
|
|
32
|
+
white: "\x1b[37m",
|
|
33
|
+
gray: "\x1b[90m",
|
|
34
|
+
} : {
|
|
35
|
+
reset: "", bold: "", dim: "", red: "", green: "", yellow: "",
|
|
36
|
+
cyan: "", magenta: "", white: "", gray: "",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const sym = {
|
|
40
|
+
lock: "🔒",
|
|
41
|
+
arrow: "→",
|
|
42
|
+
warning: "⚠",
|
|
43
|
+
check: "✓",
|
|
44
|
+
cross: "✗",
|
|
45
|
+
star: "★",
|
|
46
|
+
rocket: "🚀",
|
|
47
|
+
badge: "🏆",
|
|
48
|
+
lightning: "⚡",
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
52
|
+
// TIER CONFIGURATION
|
|
53
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
54
|
+
const TIER_LABELS = {
|
|
55
|
+
free: "FREE",
|
|
56
|
+
starter: "STARTER",
|
|
57
|
+
pro: "PRO",
|
|
58
|
+
complete: "COMPLETE",
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const TIER_COLORS = {
|
|
62
|
+
free: c.green,
|
|
63
|
+
starter: c.cyan,
|
|
64
|
+
pro: c.magenta,
|
|
65
|
+
complete: c.yellow,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const PRICING_URL = "https://vibecheckai.dev";
|
|
69
|
+
|
|
70
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
71
|
+
// DENIAL COPY - Command-specific reasons and alternatives
|
|
72
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
73
|
+
const DENIAL_COPY = {
|
|
74
|
+
ship: {
|
|
75
|
+
feature: "release gate",
|
|
76
|
+
why: "Full ship analysis with runtime verification",
|
|
77
|
+
freeAlt: "vibecheck scan",
|
|
78
|
+
freeAltDesc: "static analysis",
|
|
79
|
+
},
|
|
80
|
+
prove: {
|
|
81
|
+
feature: "proof loop",
|
|
82
|
+
why: "Complete ctx → reality → ship → fix cycle",
|
|
83
|
+
freeAlt: "vibecheck ship",
|
|
84
|
+
freeAltDesc: "static verdict",
|
|
85
|
+
},
|
|
86
|
+
permissions: {
|
|
87
|
+
feature: "auth boundary / IDOR detection",
|
|
88
|
+
why: "Deep authorization matrix and IDOR analysis",
|
|
89
|
+
freeAlt: "vibecheck reality --verify-auth",
|
|
90
|
+
freeAltDesc: "basic auth boundary check",
|
|
91
|
+
},
|
|
92
|
+
"fix.apply_patches": {
|
|
93
|
+
feature: "patch generator / PR-ready diff",
|
|
94
|
+
why: "Apply LLM-generated patches automatically",
|
|
95
|
+
freeAlt: "vibecheck fix",
|
|
96
|
+
freeAltDesc: "plan-only mode",
|
|
97
|
+
},
|
|
98
|
+
fix: {
|
|
99
|
+
feature: "auto-fix with patches",
|
|
100
|
+
why: "Generate and apply fixes to your codebase",
|
|
101
|
+
freeAlt: "vibecheck fix --plan-only",
|
|
102
|
+
freeAltDesc: "view fix plan without applying",
|
|
103
|
+
},
|
|
104
|
+
badge: {
|
|
105
|
+
feature: "status artifact",
|
|
106
|
+
why: "Verified ship badge for README",
|
|
107
|
+
freeAlt: "vibecheck report",
|
|
108
|
+
freeAltDesc: "HTML/MD report",
|
|
109
|
+
},
|
|
110
|
+
gate: {
|
|
111
|
+
feature: "CI/CD gate",
|
|
112
|
+
why: "Block deploys on verification failures",
|
|
113
|
+
freeAlt: "vibecheck ship --ci",
|
|
114
|
+
freeAltDesc: "exit codes for scripts",
|
|
115
|
+
},
|
|
116
|
+
pr: {
|
|
117
|
+
feature: "PR comment generator",
|
|
118
|
+
why: "Auto-generated PR comments with findings",
|
|
119
|
+
freeAlt: "vibecheck report --format md",
|
|
120
|
+
freeAltDesc: "markdown report",
|
|
121
|
+
},
|
|
122
|
+
launch: {
|
|
123
|
+
feature: "pre-launch checklist",
|
|
124
|
+
why: "Guided launch readiness wizard",
|
|
125
|
+
freeAlt: "vibecheck ship",
|
|
126
|
+
freeAltDesc: "verdict check",
|
|
127
|
+
},
|
|
128
|
+
mcp: {
|
|
129
|
+
feature: "MCP server for AI IDEs",
|
|
130
|
+
why: "Real-time AI IDE integration",
|
|
131
|
+
freeAlt: "vibecheck ctx",
|
|
132
|
+
freeAltDesc: "generate truthpack manually",
|
|
133
|
+
},
|
|
134
|
+
share: {
|
|
135
|
+
feature: "share pack generator",
|
|
136
|
+
why: "Shareable proof bundle for PRs/docs",
|
|
137
|
+
freeAlt: "vibecheck report",
|
|
138
|
+
freeAltDesc: "local report",
|
|
139
|
+
},
|
|
140
|
+
"ai-test": {
|
|
141
|
+
feature: "autonomous test generation",
|
|
142
|
+
why: "AI-generated test coverage",
|
|
143
|
+
freeAlt: "vibecheck scan",
|
|
144
|
+
freeAltDesc: "static analysis",
|
|
145
|
+
},
|
|
146
|
+
replay: {
|
|
147
|
+
feature: "session replay",
|
|
148
|
+
why: "Record and replay user sessions",
|
|
149
|
+
freeAlt: "vibecheck reality",
|
|
150
|
+
freeAltDesc: "one-time runtime proof",
|
|
151
|
+
},
|
|
152
|
+
graph: {
|
|
153
|
+
feature: "reality proof graph",
|
|
154
|
+
why: "Visual dependency and proof graph",
|
|
155
|
+
freeAlt: "vibecheck ctx",
|
|
156
|
+
freeAltDesc: "truthpack",
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
161
|
+
// CAPS COPY - Downgrade mode descriptions
|
|
162
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
163
|
+
const CAPS_COPY = {
|
|
164
|
+
"reality.preview": {
|
|
165
|
+
short: "5 pages, no auth boundary",
|
|
166
|
+
full: "Preview mode: 5 pages max, 20 clicks, no auth boundary verification",
|
|
167
|
+
upgradeBenefit: "Unlimited pages + full auth boundary testing",
|
|
168
|
+
},
|
|
169
|
+
"fix.plan_only": {
|
|
170
|
+
short: "plan-only, no apply",
|
|
171
|
+
full: "Plan mode: generates fix missions but cannot apply patches",
|
|
172
|
+
upgradeBenefit: "Apply patches automatically with --apply",
|
|
173
|
+
},
|
|
174
|
+
"report.html_md": {
|
|
175
|
+
short: "HTML/MD only",
|
|
176
|
+
full: "Basic formats: HTML and Markdown reports only",
|
|
177
|
+
upgradeBenefit: "SARIF, CSV, and compliance pack exports",
|
|
178
|
+
},
|
|
179
|
+
"ship.static": {
|
|
180
|
+
short: "static-only",
|
|
181
|
+
full: "Static analysis only, no runtime verification",
|
|
182
|
+
upgradeBenefit: "Full runtime + static verification",
|
|
183
|
+
},
|
|
184
|
+
"mcp.help_only": {
|
|
185
|
+
short: "help and print-config only",
|
|
186
|
+
full: "MCP server limited to help and config commands",
|
|
187
|
+
upgradeBenefit: "Full MCP server with all tools",
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
192
|
+
// formatDenied() - Hard denial message
|
|
193
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
194
|
+
/**
|
|
195
|
+
* Format a denial message for a command that requires a higher tier.
|
|
196
|
+
*
|
|
197
|
+
* @param {string} cmd - The command that was denied
|
|
198
|
+
* @param {object} opts - Options
|
|
199
|
+
* @param {string} opts.currentTier - User's current tier
|
|
200
|
+
* @param {string} opts.requiredTier - Required tier for the command
|
|
201
|
+
* @param {string} [opts.reason] - Additional context
|
|
202
|
+
* @param {string} [opts.suggestedNext] - Suggested next command
|
|
203
|
+
* @param {string} [opts.freeAlternative] - Free alternative command
|
|
204
|
+
* @returns {string} Formatted denial message
|
|
205
|
+
*/
|
|
206
|
+
function formatDenied(cmd, opts = {}) {
|
|
207
|
+
const { currentTier = "free", requiredTier = "pro" } = opts;
|
|
208
|
+
|
|
209
|
+
const copy = DENIAL_COPY[cmd] || {
|
|
210
|
+
feature: cmd,
|
|
211
|
+
why: `${cmd} command`,
|
|
212
|
+
freeAlt: null,
|
|
213
|
+
freeAltDesc: null,
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const reqColor = TIER_COLORS[requiredTier] || c.yellow;
|
|
217
|
+
const reqLabel = TIER_LABELS[requiredTier] || requiredTier.toUpperCase();
|
|
218
|
+
const curLabel = TIER_LABELS[currentTier] || currentTier.toUpperCase();
|
|
219
|
+
|
|
220
|
+
let msg = `
|
|
221
|
+
${c.red}${c.bold}${sym.lock} LOCKED${c.reset}
|
|
222
|
+
|
|
223
|
+
${c.bold}What:${c.reset} ${c.yellow}${cmd}${c.reset} ${c.dim}(${copy.feature})${c.reset}
|
|
224
|
+
${c.bold}Why:${c.reset} ${copy.why}
|
|
225
|
+
${c.bold}Requires:${c.reset} ${reqColor}${reqLabel}${c.reset} plan
|
|
226
|
+
${c.bold}You have:${c.reset} ${c.dim}${curLabel}${c.reset}
|
|
227
|
+
`;
|
|
228
|
+
|
|
229
|
+
// Free alternative
|
|
230
|
+
if (copy.freeAlt) {
|
|
231
|
+
msg += `
|
|
232
|
+
${c.green}${sym.arrow} Free path:${c.reset} ${c.cyan}${copy.freeAlt}${c.reset} ${c.dim}(${copy.freeAltDesc})${c.reset}`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Upgrade CTA
|
|
236
|
+
msg += `
|
|
237
|
+
${c.bold}${sym.arrow} Upgrade:${c.reset} ${PRICING_URL}
|
|
238
|
+
`;
|
|
239
|
+
|
|
240
|
+
return msg;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
244
|
+
// formatDowngrade() - Caps/downgrade notice
|
|
245
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
246
|
+
/**
|
|
247
|
+
* Format a downgrade notice for a command running in capped mode.
|
|
248
|
+
*
|
|
249
|
+
* @param {string} cmd - The command being run
|
|
250
|
+
* @param {object} opts - Options
|
|
251
|
+
* @param {string} opts.currentTier - User's current tier
|
|
252
|
+
* @param {string} opts.effectiveMode - The downgraded mode being used
|
|
253
|
+
* @param {object} [opts.caps] - Specific caps applied
|
|
254
|
+
* @returns {string} Formatted downgrade notice
|
|
255
|
+
*/
|
|
256
|
+
function formatDowngrade(cmd, opts = {}) {
|
|
257
|
+
const { currentTier = "free", effectiveMode, caps } = opts;
|
|
258
|
+
|
|
259
|
+
const copy = CAPS_COPY[effectiveMode] || {
|
|
260
|
+
short: effectiveMode || "limited mode",
|
|
261
|
+
full: `Running in ${effectiveMode || "limited"} mode`,
|
|
262
|
+
upgradeBenefit: "Full access",
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const curLabel = TIER_LABELS[currentTier] || currentTier.toUpperCase();
|
|
266
|
+
|
|
267
|
+
// Single line notice for start of run
|
|
268
|
+
let shortNotice = `${c.yellow}${sym.warning}${c.reset} Running in ${c.yellow}${curLabel}${c.reset} mode: ${c.dim}${copy.short}${c.reset}`;
|
|
269
|
+
|
|
270
|
+
return shortNotice;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Format a cap-hit notice when user reaches a limit during execution.
|
|
275
|
+
*
|
|
276
|
+
* @param {string} cmd - The command being run
|
|
277
|
+
* @param {object} opts - Options
|
|
278
|
+
* @param {string} opts.limitType - Type of limit hit (e.g., "pages", "clicks")
|
|
279
|
+
* @param {number} opts.limitValue - The limit value
|
|
280
|
+
* @param {string} opts.upgradeTier - Tier to upgrade to
|
|
281
|
+
* @returns {string} Formatted cap-hit message
|
|
282
|
+
*/
|
|
283
|
+
function formatCapHit(cmd, opts = {}) {
|
|
284
|
+
const { limitType = "limit", limitValue, upgradeTier = "pro" } = opts;
|
|
285
|
+
|
|
286
|
+
const tierColor = TIER_COLORS[upgradeTier] || c.magenta;
|
|
287
|
+
const tierLabel = TIER_LABELS[upgradeTier] || upgradeTier.toUpperCase();
|
|
288
|
+
|
|
289
|
+
return `${c.yellow}${sym.warning}${c.reset} Hit FREE ${limitType} cap (${limitValue}). Upgrade to ${tierColor}${tierLabel}${c.reset} ${sym.arrow} ${PRICING_URL}`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
293
|
+
// formatEarnedUpsell() - End-of-run upsell
|
|
294
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
295
|
+
/**
|
|
296
|
+
* Format an earned upsell shown at end of run.
|
|
297
|
+
*
|
|
298
|
+
* @param {object} opts - Options
|
|
299
|
+
* @param {string} opts.cmd - The command that was run
|
|
300
|
+
* @param {string} opts.verdict - The verdict (SHIP/WARN/BLOCK)
|
|
301
|
+
* @param {Array} [opts.topIssues] - Top 3 issues to show
|
|
302
|
+
* @param {string} [opts.withheldArtifact] - Artifact that was withheld (e.g., "badge")
|
|
303
|
+
* @param {string} [opts.upgradeTier] - Tier to suggest upgrading to
|
|
304
|
+
* @param {string} [opts.why] - Why this upsell is relevant
|
|
305
|
+
* @returns {string} Formatted earned upsell message
|
|
306
|
+
*/
|
|
307
|
+
function formatEarnedUpsell(opts = {}) {
|
|
308
|
+
const {
|
|
309
|
+
cmd,
|
|
310
|
+
verdict,
|
|
311
|
+
topIssues = [],
|
|
312
|
+
withheldArtifact,
|
|
313
|
+
upgradeTier = "pro",
|
|
314
|
+
why,
|
|
315
|
+
currentTier = "free",
|
|
316
|
+
suggestedCmd,
|
|
317
|
+
} = opts;
|
|
318
|
+
|
|
319
|
+
const tierColor = TIER_COLORS[upgradeTier] || c.magenta;
|
|
320
|
+
const tierLabel = TIER_LABELS[upgradeTier] || upgradeTier.toUpperCase();
|
|
321
|
+
|
|
322
|
+
let msg = "";
|
|
323
|
+
|
|
324
|
+
// Badge withheld case
|
|
325
|
+
if (withheldArtifact === "badge") {
|
|
326
|
+
msg += `
|
|
327
|
+
${c.dim}─────────────────────────────────────────────────────────────${c.reset}
|
|
328
|
+
${c.yellow}${sym.badge} Badge withheld${c.reset} ${c.dim}(verdict: ${verdict})${c.reset}
|
|
329
|
+
`;
|
|
330
|
+
|
|
331
|
+
if (topIssues.length > 0) {
|
|
332
|
+
msg += `\n${c.bold}Top blockers:${c.reset}\n`;
|
|
333
|
+
topIssues.slice(0, 3).forEach((issue, i) => {
|
|
334
|
+
const sev = issue.severity === "BLOCK" ? c.red : c.yellow;
|
|
335
|
+
msg += ` ${c.dim}${i + 1}.${c.reset} ${sev}${issue.severity}${c.reset} ${issue.message || issue.id || issue.type}\n`;
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (suggestedCmd) {
|
|
340
|
+
msg += `\n${c.green}${sym.arrow} Fix:${c.reset} ${c.cyan}${suggestedCmd}${c.reset}`;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
msg += `\n${c.dim}Badge unlocks when verdict = SHIP${c.reset}\n`;
|
|
344
|
+
return msg;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Fix plan-only case
|
|
348
|
+
if (cmd === "fix" && withheldArtifact === "apply") {
|
|
349
|
+
msg += `
|
|
350
|
+
${c.dim}─────────────────────────────────────────────────────────────${c.reset}
|
|
351
|
+
${c.yellow}${sym.lightning} Fix plan generated${c.reset}
|
|
352
|
+
|
|
353
|
+
Patches ready but ${c.yellow}--apply${c.reset} requires ${tierColor}${tierLabel}${c.reset} plan.
|
|
354
|
+
|
|
355
|
+
${c.green}${sym.arrow} Review:${c.reset} .vibecheck/missions/
|
|
356
|
+
${c.bold}${sym.arrow} Apply:${c.reset} Upgrade ${sym.arrow} ${PRICING_URL}
|
|
357
|
+
`;
|
|
358
|
+
return msg;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Reality cap hit case
|
|
362
|
+
if (cmd === "reality" && why === "cap_hit") {
|
|
363
|
+
msg += `
|
|
364
|
+
${c.dim}─────────────────────────────────────────────────────────────${c.reset}
|
|
365
|
+
${c.yellow}${sym.warning} Preview complete${c.reset}
|
|
366
|
+
|
|
367
|
+
Crawled ${topIssues.length || 5} pages (FREE limit).
|
|
368
|
+
${tierColor}${tierLabel}${c.reset} unlocks unlimited pages + auth boundary testing.
|
|
369
|
+
|
|
370
|
+
${c.bold}${sym.arrow} Upgrade:${c.reset} ${PRICING_URL}
|
|
371
|
+
`;
|
|
372
|
+
return msg;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Generic earned upsell
|
|
376
|
+
if (why) {
|
|
377
|
+
msg += `
|
|
378
|
+
${c.dim}─────────────────────────────────────────────────────────────${c.reset}
|
|
379
|
+
${c.cyan}${sym.star} ${why}${c.reset}
|
|
380
|
+
|
|
381
|
+
${tierColor}${tierLabel}${c.reset} unlocks this feature.
|
|
382
|
+
${c.bold}${sym.arrow} Upgrade:${c.reset} ${PRICING_URL}
|
|
383
|
+
`;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return msg;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
390
|
+
// formatBadgeWithheld() - Specific badge denial
|
|
391
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
392
|
+
/**
|
|
393
|
+
* Format badge withheld message with top issues.
|
|
394
|
+
*
|
|
395
|
+
* @param {string} verdict - Current verdict
|
|
396
|
+
* @param {Array} findings - All findings
|
|
397
|
+
* @param {string} currentTier - User's tier
|
|
398
|
+
* @returns {string} Formatted message
|
|
399
|
+
*/
|
|
400
|
+
function formatBadgeWithheld(verdict, findings = [], currentTier = "free") {
|
|
401
|
+
const blockers = findings.filter(f => f.severity === "BLOCK").slice(0, 3);
|
|
402
|
+
const topIssues = blockers.length > 0 ? blockers : findings.slice(0, 3);
|
|
403
|
+
|
|
404
|
+
let msg = `
|
|
405
|
+
${c.yellow}${sym.badge} Badge withheld${c.reset}
|
|
406
|
+
|
|
407
|
+
${c.bold}Verdict:${c.reset} ${verdict === "BLOCK" ? c.red : c.yellow}${verdict}${c.reset}
|
|
408
|
+
${c.dim}Badge requires SHIP verdict${c.reset}
|
|
409
|
+
`;
|
|
410
|
+
|
|
411
|
+
if (topIssues.length > 0) {
|
|
412
|
+
msg += `\n ${c.bold}Fix these first:${c.reset}\n`;
|
|
413
|
+
topIssues.forEach((issue, i) => {
|
|
414
|
+
const sev = issue.severity === "BLOCK" ? c.red : c.yellow;
|
|
415
|
+
const label = issue.id || issue.type || issue.ruleId || "issue";
|
|
416
|
+
const desc = issue.message || issue.description || "";
|
|
417
|
+
msg += ` ${c.dim}${i + 1}.${c.reset} ${sev}[${issue.severity}]${c.reset} ${label}${desc ? `: ${c.dim}${desc.slice(0, 50)}${c.reset}` : ""}\n`;
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Suggest fix command if available
|
|
422
|
+
const canFix = currentTier !== "free";
|
|
423
|
+
if (canFix) {
|
|
424
|
+
msg += `\n ${c.green}${sym.arrow} Run:${c.reset} ${c.cyan}vibecheck fix${c.reset}\n`;
|
|
425
|
+
} else {
|
|
426
|
+
msg += `\n ${c.green}${sym.arrow} Run:${c.reset} ${c.cyan}vibecheck fix --plan-only${c.reset} ${c.dim}(view fix plan)${c.reset}\n`;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return msg;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
433
|
+
// formatNextSteps() - Smart suggestions
|
|
434
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
435
|
+
/**
|
|
436
|
+
* Format next step suggestions based on current command and result.
|
|
437
|
+
*
|
|
438
|
+
* @param {string} cmd - Command just run
|
|
439
|
+
* @param {string} verdict - Result/verdict
|
|
440
|
+
* @param {string} currentTier - User's tier
|
|
441
|
+
* @returns {string} Next step suggestion
|
|
442
|
+
*/
|
|
443
|
+
function formatNextSteps(cmd, verdict, currentTier = "free") {
|
|
444
|
+
const steps = [];
|
|
445
|
+
|
|
446
|
+
switch (cmd) {
|
|
447
|
+
case "scan":
|
|
448
|
+
if (currentTier === "free") {
|
|
449
|
+
steps.push({ cmd: "vibecheck ship", desc: "get verdict" });
|
|
450
|
+
} else {
|
|
451
|
+
steps.push({ cmd: "vibecheck ship", desc: "get verdict" });
|
|
452
|
+
steps.push({ cmd: "vibecheck prove --url <url>", desc: "full proof loop" });
|
|
453
|
+
}
|
|
454
|
+
break;
|
|
455
|
+
|
|
456
|
+
case "ship":
|
|
457
|
+
if (verdict === "SHIP") {
|
|
458
|
+
if (currentTier !== "free") {
|
|
459
|
+
steps.push({ cmd: "vibecheck badge", desc: "generate badge" });
|
|
460
|
+
}
|
|
461
|
+
steps.push({ cmd: "vibecheck report", desc: "export report" });
|
|
462
|
+
} else {
|
|
463
|
+
if (currentTier === "free") {
|
|
464
|
+
steps.push({ cmd: "vibecheck fix --plan-only", desc: "view fix plan" });
|
|
465
|
+
} else {
|
|
466
|
+
steps.push({ cmd: "vibecheck fix", desc: "auto-fix issues" });
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
break;
|
|
470
|
+
|
|
471
|
+
case "fix":
|
|
472
|
+
steps.push({ cmd: "vibecheck ship", desc: "verify fixes" });
|
|
473
|
+
break;
|
|
474
|
+
|
|
475
|
+
case "reality":
|
|
476
|
+
steps.push({ cmd: "vibecheck ship", desc: "get verdict with runtime proof" });
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (steps.length === 0) return "";
|
|
481
|
+
|
|
482
|
+
let msg = `\n${c.dim}Next:${c.reset} `;
|
|
483
|
+
msg += steps.map(s => `${c.cyan}${s.cmd}${c.reset} ${c.dim}(${s.desc})${c.reset}`).join(" or ");
|
|
484
|
+
|
|
485
|
+
return msg;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
489
|
+
// EXPORTS
|
|
490
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
491
|
+
module.exports = {
|
|
492
|
+
// Core formatters
|
|
493
|
+
formatDenied,
|
|
494
|
+
formatDowngrade,
|
|
495
|
+
formatCapHit,
|
|
496
|
+
formatEarnedUpsell,
|
|
497
|
+
formatBadgeWithheld,
|
|
498
|
+
formatNextSteps,
|
|
499
|
+
|
|
500
|
+
// Copy data (for testing/docs)
|
|
501
|
+
DENIAL_COPY,
|
|
502
|
+
CAPS_COPY,
|
|
503
|
+
TIER_LABELS,
|
|
504
|
+
TIER_COLORS,
|
|
505
|
+
PRICING_URL,
|
|
506
|
+
|
|
507
|
+
// Styling (for consistent use)
|
|
508
|
+
c,
|
|
509
|
+
sym,
|
|
510
|
+
};
|