@girardmedia/bootspring 1.2.0 → 2.0.3
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/README.md +107 -14
- package/bin/bootspring.js +166 -27
- package/cli/agent.js +189 -17
- package/cli/analyze.js +499 -0
- package/cli/audit.js +557 -0
- package/cli/auth.js +495 -38
- package/cli/billing.js +302 -0
- package/cli/build.js +695 -0
- package/cli/business.js +109 -26
- package/cli/checkpoint-utils.js +168 -0
- package/cli/checkpoint.js +639 -0
- package/cli/cloud-sync.js +447 -0
- package/cli/content.js +198 -0
- package/cli/context.js +1 -1
- package/cli/deploy.js +543 -0
- package/cli/fundraise.js +112 -50
- package/cli/github-cmd.js +435 -0
- package/cli/health.js +477 -0
- package/cli/init.js +84 -13
- package/cli/legal.js +107 -95
- package/cli/log.js +2 -2
- package/cli/loop.js +976 -73
- package/cli/manager.js +711 -0
- package/cli/metrics.js +480 -0
- package/cli/monitor.js +812 -0
- package/cli/onboard.js +521 -0
- package/cli/orchestrator.js +12 -24
- package/cli/prd.js +594 -0
- package/cli/preseed-start.js +1483 -0
- package/cli/preseed.js +2302 -0
- package/cli/project.js +436 -0
- package/cli/quality.js +233 -0
- package/cli/security.js +913 -0
- package/cli/seed.js +1441 -5
- package/cli/skill.js +273 -211
- package/cli/suggest.js +989 -0
- package/cli/switch.js +453 -0
- package/cli/visualize.js +527 -0
- package/cli/watch.js +769 -0
- package/cli/workspace.js +607 -0
- package/core/analyze-workflow.js +1134 -0
- package/core/api-client.js +535 -22
- package/core/audit-workflow.js +1350 -0
- package/core/build-orchestrator.js +480 -0
- package/core/build-state.js +577 -0
- package/core/checkpoint-engine.js +408 -0
- package/core/config.js +1109 -26
- package/core/context-loader.js +21 -1
- package/core/deploy-workflow.js +836 -0
- package/core/entitlements.js +93 -22
- package/core/github-sync.js +610 -0
- package/core/index.js +8 -1
- package/core/ingest.js +1111 -0
- package/core/metrics-engine.js +768 -0
- package/core/onboard-workflow.js +1007 -0
- package/core/preseed-workflow.js +934 -0
- package/core/preseed.js +1617 -0
- package/core/project-context.js +325 -0
- package/core/project-state.js +694 -0
- package/core/r2-sync.js +583 -0
- package/core/scaffold.js +525 -7
- package/core/session.js +258 -0
- package/core/task-extractor.js +758 -0
- package/core/telemetry.js +28 -6
- package/core/tier-enforcement.js +737 -0
- package/core/utils.js +38 -14
- package/generators/questionnaire.js +15 -12
- package/generators/sections/ai.js +7 -7
- package/generators/sections/content.js +300 -0
- package/generators/sections/index.js +3 -0
- package/generators/sections/plugins.js +7 -6
- package/generators/templates/build-planning.template.js +596 -0
- package/generators/templates/content.template.js +819 -0
- package/generators/templates/index.js +2 -1
- package/hooks/git-autopilot.js +1250 -0
- package/hooks/index.js +9 -0
- package/intelligence/agent-collab.js +2057 -0
- package/intelligence/auto-suggest.js +634 -0
- package/intelligence/content-gen.js +1589 -0
- package/intelligence/cross-project.js +1647 -0
- package/intelligence/index.js +184 -0
- package/intelligence/learning/insights.json +517 -7
- package/intelligence/learning/pattern-learner.js +1008 -14
- package/intelligence/memory/decision-tracker.js +1431 -31
- package/intelligence/memory/decisions.jsonl +0 -0
- package/intelligence/orchestrator.js +2896 -1
- package/intelligence/prd.js +92 -1
- package/intelligence/recommendation-weights.json +14 -2
- package/intelligence/recommendations.js +463 -9
- package/intelligence/workflow-composer.js +1451 -0
- package/marketplace/index.d.ts +324 -0
- package/marketplace/index.js +1921 -0
- package/mcp/contracts/mcp-contract.v1.json +342 -4
- package/mcp/registry.js +680 -3
- package/mcp/response-formatter.js +23 -0
- package/mcp/tools/assist-tool.js +78 -4
- package/mcp/tools/autopilot-tool.js +408 -0
- package/mcp/tools/content-tool.js +571 -0
- package/mcp/tools/dashboard-tool.js +251 -5
- package/mcp/tools/mvp-tool.js +344 -0
- package/mcp/tools/plugin-tool.js +23 -1
- package/mcp/tools/prd-tool.js +579 -0
- package/mcp/tools/seed-tool.js +447 -0
- package/mcp/tools/skill-tool.js +43 -14
- package/mcp/tools/suggest-tool.js +147 -0
- package/package.json +15 -6
- package/agents/README.md +0 -93
- package/agents/ai-integration-expert/context.md +0 -386
- package/agents/api-expert/context.md +0 -416
- package/agents/architecture-expert/context.md +0 -454
- package/agents/auth-expert/context.md +0 -399
- package/agents/backend-expert/context.md +0 -483
- package/agents/business-strategy-expert/context.md +0 -180
- package/agents/code-review-expert/context.md +0 -365
- package/agents/competitive-analysis-expert/context.md +0 -239
- package/agents/data-modeling-expert/context.md +0 -352
- package/agents/database-expert/context.md +0 -250
- package/agents/devops-expert/context.md +0 -446
- package/agents/email-expert/context.md +0 -379
- package/agents/financial-expert/context.md +0 -213
- package/agents/frontend-expert/context.md +0 -364
- package/agents/fundraising-expert/context.md +0 -257
- package/agents/growth-expert/context.md +0 -249
- package/agents/index.js +0 -140
- package/agents/investor-relations-expert/context.md +0 -266
- package/agents/legal-expert/context.md +0 -284
- package/agents/marketing-expert/context.md +0 -236
- package/agents/monitoring-expert/context.md +0 -362
- package/agents/operations-expert/context.md +0 -279
- package/agents/partnerships-expert/context.md +0 -286
- package/agents/payment-expert/context.md +0 -340
- package/agents/performance-expert/context.md +0 -377
- package/agents/private-equity-expert/context.md +0 -246
- package/agents/railway-expert/context.md +0 -284
- package/agents/research-expert/context.md +0 -245
- package/agents/sales-expert/context.md +0 -241
- package/agents/security-expert/context.md +0 -343
- package/agents/testing-expert/context.md +0 -414
- package/agents/ui-ux-expert/context.md +0 -448
- package/agents/vercel-expert/context.md +0 -426
- package/skills/index.js +0 -787
- package/skills/patterns/README.md +0 -163
- package/skills/patterns/ai/agents.md +0 -281
- package/skills/patterns/ai/claude.md +0 -138
- package/skills/patterns/ai/embeddings.md +0 -150
- package/skills/patterns/ai/rag.md +0 -266
- package/skills/patterns/ai/streaming.md +0 -170
- package/skills/patterns/ai/structured-output.md +0 -162
- package/skills/patterns/ai/tools.md +0 -154
- package/skills/patterns/analytics/tracking.md +0 -220
- package/skills/patterns/api/errors.md +0 -296
- package/skills/patterns/api/graphql.md +0 -440
- package/skills/patterns/api/middleware.md +0 -279
- package/skills/patterns/api/openapi.md +0 -285
- package/skills/patterns/api/rate-limiting.md +0 -231
- package/skills/patterns/api/route-handler.md +0 -217
- package/skills/patterns/api/server-action.md +0 -249
- package/skills/patterns/api/versioning.md +0 -443
- package/skills/patterns/api/webhooks.md +0 -247
- package/skills/patterns/auth/clerk.md +0 -132
- package/skills/patterns/auth/mfa.md +0 -313
- package/skills/patterns/auth/nextauth.md +0 -140
- package/skills/patterns/auth/oauth.md +0 -237
- package/skills/patterns/auth/rbac.md +0 -152
- package/skills/patterns/auth/session-management.md +0 -367
- package/skills/patterns/auth/session.md +0 -120
- package/skills/patterns/database/audit.md +0 -177
- package/skills/patterns/database/migrations.md +0 -177
- package/skills/patterns/database/pagination.md +0 -230
- package/skills/patterns/database/pooling.md +0 -357
- package/skills/patterns/database/prisma.md +0 -180
- package/skills/patterns/database/relations.md +0 -187
- package/skills/patterns/database/seeding.md +0 -246
- package/skills/patterns/database/soft-delete.md +0 -153
- package/skills/patterns/database/transactions.md +0 -162
- package/skills/patterns/deployment/ci-cd.md +0 -231
- package/skills/patterns/deployment/docker.md +0 -188
- package/skills/patterns/deployment/monitoring.md +0 -387
- package/skills/patterns/deployment/vercel.md +0 -160
- package/skills/patterns/email/resend.md +0 -143
- package/skills/patterns/email/templates.md +0 -245
- package/skills/patterns/email/transactional.md +0 -503
- package/skills/patterns/email/verification.md +0 -176
- package/skills/patterns/files/download.md +0 -243
- package/skills/patterns/files/upload.md +0 -239
- package/skills/patterns/i18n/nextintl.md +0 -188
- package/skills/patterns/logging/structured.md +0 -292
- package/skills/patterns/notifications/email-queue.md +0 -248
- package/skills/patterns/notifications/push.md +0 -279
- package/skills/patterns/payments/checkout.md +0 -303
- package/skills/patterns/payments/invoices.md +0 -287
- package/skills/patterns/payments/portal.md +0 -245
- package/skills/patterns/payments/stripe.md +0 -272
- package/skills/patterns/payments/subscriptions.md +0 -300
- package/skills/patterns/payments/usage.md +0 -279
- package/skills/patterns/performance/caching.md +0 -276
- package/skills/patterns/performance/code-splitting.md +0 -233
- package/skills/patterns/performance/edge.md +0 -254
- package/skills/patterns/performance/isr.md +0 -266
- package/skills/patterns/performance/lazy-loading.md +0 -281
- package/skills/patterns/realtime/sse.md +0 -327
- package/skills/patterns/realtime/websockets.md +0 -336
- package/skills/patterns/search/filtering.md +0 -329
- package/skills/patterns/search/fulltext.md +0 -260
- package/skills/patterns/security/audit-logging.md +0 -444
- package/skills/patterns/security/csrf.md +0 -234
- package/skills/patterns/security/headers.md +0 -252
- package/skills/patterns/security/sanitization.md +0 -258
- package/skills/patterns/security/secrets.md +0 -261
- package/skills/patterns/security/validation.md +0 -268
- package/skills/patterns/security/xss.md +0 -229
- package/skills/patterns/seo/metadata.md +0 -252
- package/skills/patterns/state/context.md +0 -349
- package/skills/patterns/state/react-query.md +0 -313
- package/skills/patterns/state/url-state.md +0 -482
- package/skills/patterns/state/zustand.md +0 -262
- package/skills/patterns/testing/api.md +0 -259
- package/skills/patterns/testing/component.md +0 -233
- package/skills/patterns/testing/coverage.md +0 -207
- package/skills/patterns/testing/fixtures.md +0 -225
- package/skills/patterns/testing/integration.md +0 -436
- package/skills/patterns/testing/mocking.md +0 -177
- package/skills/patterns/testing/playwright.md +0 -162
- package/skills/patterns/testing/snapshot.md +0 -175
- package/skills/patterns/testing/vitest.md +0 -307
- package/skills/patterns/ui/accordions.md +0 -395
- package/skills/patterns/ui/cards.md +0 -299
- package/skills/patterns/ui/dropdowns.md +0 -476
- package/skills/patterns/ui/empty-states.md +0 -320
- package/skills/patterns/ui/forms.md +0 -405
- package/skills/patterns/ui/inputs.md +0 -319
- package/skills/patterns/ui/layouts.md +0 -282
- package/skills/patterns/ui/loading.md +0 -291
- package/skills/patterns/ui/modals.md +0 -338
- package/skills/patterns/ui/navigation.md +0 -374
- package/skills/patterns/ui/tables.md +0 -407
- package/skills/patterns/ui/toasts.md +0 -300
- package/skills/patterns/ui/tooltips.md +0 -396
- package/skills/patterns/utils/dates.md +0 -435
- package/skills/patterns/utils/errors.md +0 -451
- package/skills/patterns/utils/formatting.md +0 -345
- package/skills/patterns/utils/validation.md +0 -434
- package/templates/bootspring.config.js +0 -83
- package/templates/business/business-model-canvas.md +0 -246
- package/templates/business/business-plan.md +0 -266
- package/templates/business/competitive-analysis.md +0 -312
- package/templates/fundraising/data-room-checklist.md +0 -300
- package/templates/fundraising/investor-research.md +0 -243
- package/templates/fundraising/pitch-deck-outline.md +0 -253
- package/templates/legal/gdpr-checklist.md +0 -339
- package/templates/legal/privacy-policy.md +0 -285
- package/templates/legal/terms-of-service.md +0 -222
- package/templates/mcp.json +0 -9
|
@@ -0,0 +1,737 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootspring Tier Enforcement
|
|
3
|
+
*
|
|
4
|
+
* Centralized tier/entitlement enforcement for the CLI.
|
|
5
|
+
* Fetches and caches entitlements from the API, enforces limits.
|
|
6
|
+
*
|
|
7
|
+
* @package bootspring
|
|
8
|
+
* @module core/tier-enforcement
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
const auth = require('./auth');
|
|
15
|
+
|
|
16
|
+
const BOOTSPRING_DIR = path.join(os.homedir(), '.bootspring');
|
|
17
|
+
const ENTITLEMENTS_CACHE_FILE = path.join(BOOTSPRING_DIR, 'entitlements.json');
|
|
18
|
+
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
19
|
+
|
|
20
|
+
// Tier hierarchy for comparison
|
|
21
|
+
const TIER_HIERARCHY = {
|
|
22
|
+
free: 0,
|
|
23
|
+
founder: 1, // Founder = lifetime pro
|
|
24
|
+
pro: 1,
|
|
25
|
+
team: 2,
|
|
26
|
+
enterprise: 3,
|
|
27
|
+
custom: 3,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Default limits per tier (fallback if API unavailable)
|
|
31
|
+
const DEFAULT_LIMITS = {
|
|
32
|
+
free: {
|
|
33
|
+
projects: 1,
|
|
34
|
+
apiCallsPerMonth: 500,
|
|
35
|
+
devices: 1,
|
|
36
|
+
teamSeats: 1,
|
|
37
|
+
storage: '100MB',
|
|
38
|
+
},
|
|
39
|
+
founder: {
|
|
40
|
+
projects: 5,
|
|
41
|
+
apiCallsPerMonth: 10000,
|
|
42
|
+
devices: 3,
|
|
43
|
+
teamSeats: 1,
|
|
44
|
+
storage: '5GB',
|
|
45
|
+
},
|
|
46
|
+
pro: {
|
|
47
|
+
projects: 5,
|
|
48
|
+
apiCallsPerMonth: 10000,
|
|
49
|
+
devices: 3,
|
|
50
|
+
teamSeats: 1,
|
|
51
|
+
storage: '5GB',
|
|
52
|
+
},
|
|
53
|
+
team: {
|
|
54
|
+
projects: 20,
|
|
55
|
+
apiCallsPerMonth: 50000,
|
|
56
|
+
devices: 10,
|
|
57
|
+
teamSeats: 5,
|
|
58
|
+
storage: '50GB',
|
|
59
|
+
},
|
|
60
|
+
enterprise: {
|
|
61
|
+
projects: 100,
|
|
62
|
+
apiCallsPerMonth: 500000,
|
|
63
|
+
devices: 50,
|
|
64
|
+
teamSeats: 20,
|
|
65
|
+
storage: '500GB',
|
|
66
|
+
},
|
|
67
|
+
custom: {
|
|
68
|
+
projects: 999,
|
|
69
|
+
apiCallsPerMonth: 999999,
|
|
70
|
+
devices: 999,
|
|
71
|
+
teamSeats: 999,
|
|
72
|
+
storage: '1TB',
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Feature gates by tier
|
|
77
|
+
const FEATURE_GATES = {
|
|
78
|
+
free: [
|
|
79
|
+
'agents.technical',
|
|
80
|
+
'skills.basic',
|
|
81
|
+
'workflows.basic',
|
|
82
|
+
'telemetry',
|
|
83
|
+
// Free preseed: setup, init, generate (local), sync, status, update, export
|
|
84
|
+
'preseed.setup',
|
|
85
|
+
'preseed.init',
|
|
86
|
+
'preseed.generate',
|
|
87
|
+
'preseed.sync',
|
|
88
|
+
'preseed.status',
|
|
89
|
+
'preseed.update',
|
|
90
|
+
'preseed.export',
|
|
91
|
+
// Free seed: setup, init, status, export
|
|
92
|
+
'seed.setup',
|
|
93
|
+
'seed.init',
|
|
94
|
+
'seed.status',
|
|
95
|
+
'seed.export',
|
|
96
|
+
],
|
|
97
|
+
founder: [
|
|
98
|
+
'agents.technical',
|
|
99
|
+
'agents.business',
|
|
100
|
+
'skills.basic',
|
|
101
|
+
'skills.advanced',
|
|
102
|
+
'workflows.basic',
|
|
103
|
+
'workflows.advanced',
|
|
104
|
+
'telemetry',
|
|
105
|
+
'priority_support',
|
|
106
|
+
// Full preseed/seed access
|
|
107
|
+
'preseed.*',
|
|
108
|
+
'seed.*',
|
|
109
|
+
],
|
|
110
|
+
pro: [
|
|
111
|
+
'agents.technical',
|
|
112
|
+
'agents.business',
|
|
113
|
+
'skills.basic',
|
|
114
|
+
'skills.advanced',
|
|
115
|
+
'workflows.basic',
|
|
116
|
+
'workflows.advanced',
|
|
117
|
+
'telemetry',
|
|
118
|
+
'priority_support',
|
|
119
|
+
// Full preseed/seed access
|
|
120
|
+
'preseed.*',
|
|
121
|
+
'seed.*',
|
|
122
|
+
],
|
|
123
|
+
team: [
|
|
124
|
+
'agents.technical',
|
|
125
|
+
'agents.business',
|
|
126
|
+
'agents.enterprise',
|
|
127
|
+
'skills.basic',
|
|
128
|
+
'skills.advanced',
|
|
129
|
+
'skills.enterprise',
|
|
130
|
+
'workflows.basic',
|
|
131
|
+
'workflows.advanced',
|
|
132
|
+
'workflows.enterprise',
|
|
133
|
+
'telemetry',
|
|
134
|
+
'priority_support',
|
|
135
|
+
'team_features',
|
|
136
|
+
'presence',
|
|
137
|
+
// Full preseed/seed access
|
|
138
|
+
'preseed.*',
|
|
139
|
+
'seed.*',
|
|
140
|
+
],
|
|
141
|
+
enterprise: [
|
|
142
|
+
'agents.*',
|
|
143
|
+
'skills.*',
|
|
144
|
+
'workflows.*',
|
|
145
|
+
'preseed.*',
|
|
146
|
+
'seed.*',
|
|
147
|
+
'telemetry',
|
|
148
|
+
'priority_support',
|
|
149
|
+
'team_features',
|
|
150
|
+
'presence',
|
|
151
|
+
'audit_logs',
|
|
152
|
+
'sso',
|
|
153
|
+
'custom_policies',
|
|
154
|
+
],
|
|
155
|
+
custom: [
|
|
156
|
+
'agents.*',
|
|
157
|
+
'skills.*',
|
|
158
|
+
'workflows.*',
|
|
159
|
+
'preseed.*',
|
|
160
|
+
'seed.*',
|
|
161
|
+
'telemetry',
|
|
162
|
+
'priority_support',
|
|
163
|
+
'team_features',
|
|
164
|
+
'presence',
|
|
165
|
+
'audit_logs',
|
|
166
|
+
'sso',
|
|
167
|
+
'custom_policies',
|
|
168
|
+
],
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// Preseed commands that require paid tier
|
|
172
|
+
const PRESEED_PAID_COMMANDS = [
|
|
173
|
+
'pull', // Cloud sync download
|
|
174
|
+
'push', // Cloud sync upload
|
|
175
|
+
'workflow', // Approval workflows
|
|
176
|
+
'merge', // Document merging
|
|
177
|
+
'start', // Smart entry (includes merge)
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
// Seed commands that require paid tier
|
|
181
|
+
const SEED_PAID_COMMANDS = [
|
|
182
|
+
'scaffold', // Project generation
|
|
183
|
+
'synthesize', // AI synthesis from preseed
|
|
184
|
+
'generate', // Advanced generation
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
// Agent tier requirements
|
|
188
|
+
const AGENT_TIERS = {
|
|
189
|
+
// Pro tier agents
|
|
190
|
+
'business-strategy-expert': 'pro',
|
|
191
|
+
'competitive-analysis-expert': 'pro',
|
|
192
|
+
'financial-expert': 'pro',
|
|
193
|
+
'growth-expert': 'pro',
|
|
194
|
+
'legal-expert': 'pro',
|
|
195
|
+
'operations-expert': 'pro',
|
|
196
|
+
'sales-expert': 'pro',
|
|
197
|
+
// Team tier agents
|
|
198
|
+
'fundraising-expert': 'team',
|
|
199
|
+
'investor-relations-expert': 'team',
|
|
200
|
+
'partnerships-expert': 'team',
|
|
201
|
+
'private-equity-expert': 'team',
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// Premium skill patterns (by category or specific pattern)
|
|
205
|
+
const PREMIUM_SKILL_CATEGORIES = [
|
|
206
|
+
'ai',
|
|
207
|
+
'payments',
|
|
208
|
+
'security',
|
|
209
|
+
'performance',
|
|
210
|
+
'deployment',
|
|
211
|
+
'email',
|
|
212
|
+
'notifications',
|
|
213
|
+
'realtime',
|
|
214
|
+
'search',
|
|
215
|
+
'seo',
|
|
216
|
+
'files',
|
|
217
|
+
'analytics',
|
|
218
|
+
];
|
|
219
|
+
|
|
220
|
+
const PREMIUM_SKILL_PATTERNS = [
|
|
221
|
+
'auth/mfa',
|
|
222
|
+
'auth/rbac',
|
|
223
|
+
'database/multi-tenant',
|
|
224
|
+
'database/full-text-search',
|
|
225
|
+
'testing/e2e',
|
|
226
|
+
'testing/coverage',
|
|
227
|
+
'state/react-query',
|
|
228
|
+
'ui/command-palette',
|
|
229
|
+
'ui/data-tables',
|
|
230
|
+
];
|
|
231
|
+
|
|
232
|
+
// Free overrides (free even in premium category)
|
|
233
|
+
const FREE_SKILL_OVERRIDES = [
|
|
234
|
+
'security/validation',
|
|
235
|
+
'deployment/docker',
|
|
236
|
+
];
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Get cached entitlements
|
|
240
|
+
* @returns {object|null} Cached entitlements or null
|
|
241
|
+
*/
|
|
242
|
+
function getCachedEntitlements() {
|
|
243
|
+
try {
|
|
244
|
+
if (fs.existsSync(ENTITLEMENTS_CACHE_FILE)) {
|
|
245
|
+
const data = JSON.parse(fs.readFileSync(ENTITLEMENTS_CACHE_FILE, 'utf-8'));
|
|
246
|
+
|
|
247
|
+
// Check if cache is still valid
|
|
248
|
+
if (data.cachedAt && Date.now() - new Date(data.cachedAt).getTime() < CACHE_TTL_MS) {
|
|
249
|
+
return data;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
} catch {
|
|
253
|
+
// Ignore cache read errors
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Save entitlements to cache
|
|
260
|
+
* @param {object} entitlements - Entitlements data from API
|
|
261
|
+
*/
|
|
262
|
+
function cacheEntitlements(entitlements) {
|
|
263
|
+
try {
|
|
264
|
+
if (!fs.existsSync(BOOTSPRING_DIR)) {
|
|
265
|
+
fs.mkdirSync(BOOTSPRING_DIR, { recursive: true, mode: 0o700 });
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const data = {
|
|
269
|
+
...entitlements,
|
|
270
|
+
cachedAt: new Date().toISOString(),
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
fs.writeFileSync(ENTITLEMENTS_CACHE_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
274
|
+
} catch {
|
|
275
|
+
// Ignore cache write errors
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Clear entitlements cache
|
|
281
|
+
*/
|
|
282
|
+
function clearCache() {
|
|
283
|
+
try {
|
|
284
|
+
if (fs.existsSync(ENTITLEMENTS_CACHE_FILE)) {
|
|
285
|
+
fs.unlinkSync(ENTITLEMENTS_CACHE_FILE);
|
|
286
|
+
}
|
|
287
|
+
} catch {
|
|
288
|
+
// Ignore
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Fetch entitlements from API
|
|
294
|
+
* @returns {Promise<object|null>} Entitlements or null if not authenticated
|
|
295
|
+
*/
|
|
296
|
+
async function fetchEntitlements() {
|
|
297
|
+
if (!auth.isAuthenticated()) {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
// Lazy require to avoid circular dependency
|
|
303
|
+
const api = require('./api-client');
|
|
304
|
+
const entitlements = await api.resolveEntitlements();
|
|
305
|
+
|
|
306
|
+
// Cache the result
|
|
307
|
+
cacheEntitlements(entitlements);
|
|
308
|
+
|
|
309
|
+
// Update stored user tier if different
|
|
310
|
+
const user = auth.getUser();
|
|
311
|
+
if (user && entitlements.tier !== user.tier) {
|
|
312
|
+
const creds = auth.getCredentials();
|
|
313
|
+
if (creds) {
|
|
314
|
+
auth.saveCredentials({
|
|
315
|
+
...creds,
|
|
316
|
+
user: { ...creds.user, tier: entitlements.tier }
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return entitlements;
|
|
322
|
+
} catch (_error) {
|
|
323
|
+
// Return cached data if available, otherwise return default
|
|
324
|
+
const cached = getCachedEntitlements();
|
|
325
|
+
if (cached) return cached;
|
|
326
|
+
|
|
327
|
+
// Return minimal default entitlements
|
|
328
|
+
const tier = auth.getTier() || 'free';
|
|
329
|
+
return {
|
|
330
|
+
tier,
|
|
331
|
+
limits: DEFAULT_LIMITS[tier] || DEFAULT_LIMITS.free,
|
|
332
|
+
features: FEATURE_GATES[tier] || FEATURE_GATES.free,
|
|
333
|
+
agents: { denied: [] },
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Get current entitlements (cached or fetched)
|
|
340
|
+
* @param {object} options - Options
|
|
341
|
+
* @param {boolean} options.forceRefresh - Force refresh from API
|
|
342
|
+
* @returns {Promise<object>} Entitlements
|
|
343
|
+
*/
|
|
344
|
+
async function getEntitlements(options = {}) {
|
|
345
|
+
// Check cache first unless force refresh
|
|
346
|
+
if (!options.forceRefresh) {
|
|
347
|
+
const cached = getCachedEntitlements();
|
|
348
|
+
if (cached) return cached;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Fetch from API
|
|
352
|
+
const fetched = await fetchEntitlements();
|
|
353
|
+
if (fetched) return fetched;
|
|
354
|
+
|
|
355
|
+
// Fallback to defaults based on stored tier
|
|
356
|
+
const tier = auth.getTier() || 'free';
|
|
357
|
+
return {
|
|
358
|
+
tier,
|
|
359
|
+
limits: DEFAULT_LIMITS[tier] || DEFAULT_LIMITS.free,
|
|
360
|
+
features: FEATURE_GATES[tier] || FEATURE_GATES.free,
|
|
361
|
+
agents: { denied: [] },
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Get current tier
|
|
367
|
+
* @returns {string} User tier
|
|
368
|
+
*/
|
|
369
|
+
function getTier() {
|
|
370
|
+
// Check cache first
|
|
371
|
+
const cached = getCachedEntitlements();
|
|
372
|
+
if (cached?.tier) return cached.tier;
|
|
373
|
+
|
|
374
|
+
// Fall back to stored tier
|
|
375
|
+
return auth.getTier() || 'free';
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Get tier level for comparison
|
|
380
|
+
* @param {string} tier - Tier name
|
|
381
|
+
* @returns {number} Tier level
|
|
382
|
+
*/
|
|
383
|
+
function getTierLevel(tier) {
|
|
384
|
+
return TIER_HIERARCHY[tier?.toLowerCase()] ?? 0;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Check if user tier meets required tier
|
|
389
|
+
* @param {string} requiredTier - Required tier
|
|
390
|
+
* @param {string} userTier - User's tier (optional, uses current)
|
|
391
|
+
* @returns {boolean} Whether user meets tier requirement
|
|
392
|
+
*/
|
|
393
|
+
function meetsTierRequirement(requiredTier, userTier = null) {
|
|
394
|
+
const user = userTier || getTier();
|
|
395
|
+
return getTierLevel(user) >= getTierLevel(requiredTier);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Check if user can access an agent
|
|
400
|
+
* @param {string} agentId - Agent ID
|
|
401
|
+
* @returns {{allowed: boolean, requiredTier?: string, userTier: string}}
|
|
402
|
+
*/
|
|
403
|
+
function checkAgentAccess(agentId) {
|
|
404
|
+
const requiredTier = AGENT_TIERS[agentId];
|
|
405
|
+
const userTier = getTier();
|
|
406
|
+
|
|
407
|
+
if (!requiredTier) {
|
|
408
|
+
return { allowed: true, userTier };
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
allowed: meetsTierRequirement(requiredTier, userTier),
|
|
413
|
+
requiredTier,
|
|
414
|
+
userTier,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Check if a skill/pattern is premium
|
|
420
|
+
* @param {string} skillId - Skill ID (e.g., "auth/oauth" or "external/some-skill")
|
|
421
|
+
* @returns {boolean} Whether skill is premium
|
|
422
|
+
*/
|
|
423
|
+
function isSkillPremium(skillId) {
|
|
424
|
+
// External skills are always premium
|
|
425
|
+
if (skillId.startsWith('external/')) {
|
|
426
|
+
return true;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Check free overrides first
|
|
430
|
+
if (FREE_SKILL_OVERRIDES.includes(skillId)) {
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Check specific premium patterns
|
|
435
|
+
if (PREMIUM_SKILL_PATTERNS.includes(skillId)) {
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Check premium categories
|
|
440
|
+
const category = skillId.split('/')[0];
|
|
441
|
+
if (PREMIUM_SKILL_CATEGORIES.includes(category)) {
|
|
442
|
+
return true;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Check if user can access a skill
|
|
450
|
+
* @param {string} skillId - Skill ID
|
|
451
|
+
* @returns {{allowed: boolean, reason?: string, userTier: string}}
|
|
452
|
+
*/
|
|
453
|
+
function checkSkillAccess(skillId) {
|
|
454
|
+
const userTier = getTier();
|
|
455
|
+
const isPremium = isSkillPremium(skillId);
|
|
456
|
+
|
|
457
|
+
if (!isPremium) {
|
|
458
|
+
return { allowed: true, userTier };
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Premium skills require pro or higher
|
|
462
|
+
if (meetsTierRequirement('pro', userTier)) {
|
|
463
|
+
return { allowed: true, userTier };
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return {
|
|
467
|
+
allowed: false,
|
|
468
|
+
reason: `This skill requires a Pro subscription. Current tier: ${userTier}`,
|
|
469
|
+
userTier,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Check if user has a specific feature
|
|
475
|
+
* @param {string} feature - Feature name (e.g., "team_features", "sso")
|
|
476
|
+
* @returns {boolean} Whether user has feature
|
|
477
|
+
*/
|
|
478
|
+
function hasFeature(feature) {
|
|
479
|
+
const tier = getTier();
|
|
480
|
+
const features = FEATURE_GATES[tier] || FEATURE_GATES.free;
|
|
481
|
+
|
|
482
|
+
// Check for wildcard match
|
|
483
|
+
const featureParts = feature.split('.');
|
|
484
|
+
for (const f of features) {
|
|
485
|
+
if (f === feature) return true;
|
|
486
|
+
if (f.endsWith('.*')) {
|
|
487
|
+
const prefix = f.slice(0, -2);
|
|
488
|
+
if (feature.startsWith(prefix)) return true;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Get limits for current tier
|
|
497
|
+
* @returns {Promise<object>} Limits object
|
|
498
|
+
*/
|
|
499
|
+
async function getLimits() {
|
|
500
|
+
const entitlements = await getEntitlements();
|
|
501
|
+
return entitlements.limits || DEFAULT_LIMITS[entitlements.tier] || DEFAULT_LIMITS.free;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Check if user is within a specific limit
|
|
506
|
+
* @param {string} limitType - Limit type (projects, apiCallsPerMonth, etc.)
|
|
507
|
+
* @param {number} currentUsage - Current usage count
|
|
508
|
+
* @returns {Promise<{allowed: boolean, limit: number, usage: number, remaining: number}>}
|
|
509
|
+
*/
|
|
510
|
+
async function checkLimit(limitType, currentUsage) {
|
|
511
|
+
const limits = await getLimits();
|
|
512
|
+
const limit = limits[limitType] || 0;
|
|
513
|
+
|
|
514
|
+
return {
|
|
515
|
+
allowed: currentUsage < limit,
|
|
516
|
+
limit,
|
|
517
|
+
usage: currentUsage,
|
|
518
|
+
remaining: Math.max(0, limit - currentUsage),
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Generate upgrade prompt for a blocked feature
|
|
524
|
+
* @param {string} feature - Feature or capability that's blocked
|
|
525
|
+
* @param {string} requiredTier - Tier required for feature
|
|
526
|
+
* @returns {string} Formatted upgrade prompt
|
|
527
|
+
*/
|
|
528
|
+
function getUpgradePrompt(feature, requiredTier = 'pro') {
|
|
529
|
+
const userTier = getTier();
|
|
530
|
+
|
|
531
|
+
const CYAN = '\x1b[36m';
|
|
532
|
+
const YELLOW = '\x1b[33m';
|
|
533
|
+
const DIM = '\x1b[2m';
|
|
534
|
+
const BOLD = '\x1b[1m';
|
|
535
|
+
const RESET = '\x1b[0m';
|
|
536
|
+
|
|
537
|
+
return `
|
|
538
|
+
${YELLOW}${BOLD}Upgrade Required${RESET}
|
|
539
|
+
|
|
540
|
+
${DIM}This feature requires ${requiredTier} tier or higher.${RESET}
|
|
541
|
+
${DIM}Your current tier: ${userTier}${RESET}
|
|
542
|
+
|
|
543
|
+
${BOLD}Upgrade options:${RESET}
|
|
544
|
+
${CYAN}bootspring billing upgrade${RESET}
|
|
545
|
+
|
|
546
|
+
${DIM}Or visit: https://bootspring.com/pricing${RESET}
|
|
547
|
+
`;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Format tier badge for display
|
|
552
|
+
* @param {string} tier - Tier name
|
|
553
|
+
* @returns {string} Formatted badge
|
|
554
|
+
*/
|
|
555
|
+
function formatTierBadge(tier) {
|
|
556
|
+
const YELLOW = '\x1b[33m';
|
|
557
|
+
const GREEN = '\x1b[32m';
|
|
558
|
+
const CYAN = '\x1b[36m';
|
|
559
|
+
const RESET = '\x1b[0m';
|
|
560
|
+
|
|
561
|
+
switch (tier?.toLowerCase()) {
|
|
562
|
+
case 'pro':
|
|
563
|
+
case 'founder':
|
|
564
|
+
return `${YELLOW}[PRO]${RESET}`;
|
|
565
|
+
case 'team':
|
|
566
|
+
return `${CYAN}[TEAM]${RESET}`;
|
|
567
|
+
case 'enterprise':
|
|
568
|
+
case 'custom':
|
|
569
|
+
return `${CYAN}[ENT]${RESET}`;
|
|
570
|
+
default:
|
|
571
|
+
return `${GREEN}[FREE]${RESET}`;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Require a specific tier - throws if not met
|
|
577
|
+
* @param {string} requiredTier - Required tier
|
|
578
|
+
* @param {string} feature - Feature name for error message
|
|
579
|
+
* @throws {Error} If tier requirement not met
|
|
580
|
+
*/
|
|
581
|
+
function requireTier(requiredTier, feature = 'this feature') {
|
|
582
|
+
if (!meetsTierRequirement(requiredTier)) {
|
|
583
|
+
const error = new Error(`${feature} requires ${requiredTier} tier or higher`);
|
|
584
|
+
error.code = 'TIER_REQUIRED';
|
|
585
|
+
error.requiredTier = requiredTier;
|
|
586
|
+
error.userTier = getTier();
|
|
587
|
+
error.upgradePrompt = getUpgradePrompt(feature, requiredTier);
|
|
588
|
+
throw error;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Require a specific feature - throws if not available
|
|
594
|
+
* @param {string} feature - Feature name
|
|
595
|
+
* @throws {Error} If feature not available
|
|
596
|
+
*/
|
|
597
|
+
function requireFeature(feature) {
|
|
598
|
+
if (!hasFeature(feature)) {
|
|
599
|
+
const error = new Error(`${feature} is not available on your current plan`);
|
|
600
|
+
error.code = 'FEATURE_REQUIRED';
|
|
601
|
+
error.feature = feature;
|
|
602
|
+
error.userTier = getTier();
|
|
603
|
+
throw error;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Check if a preseed command requires paid tier
|
|
609
|
+
* @param {string} command - Preseed subcommand
|
|
610
|
+
* @returns {{allowed: boolean, requiredTier: string, userTier: string, feature: string}}
|
|
611
|
+
*/
|
|
612
|
+
function checkPreseedAccess(command) {
|
|
613
|
+
const userTier = getTier();
|
|
614
|
+
const isPaidCommand = PRESEED_PAID_COMMANDS.includes(command);
|
|
615
|
+
|
|
616
|
+
if (!isPaidCommand) {
|
|
617
|
+
return { allowed: true, requiredTier: 'free', userTier, feature: `preseed ${command}` };
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Check if user has preseed.* or specific preseed.command
|
|
621
|
+
const hasAccess = hasFeature(`preseed.${command}`) || hasFeature('preseed.*');
|
|
622
|
+
|
|
623
|
+
return {
|
|
624
|
+
allowed: hasAccess,
|
|
625
|
+
requiredTier: 'pro',
|
|
626
|
+
userTier,
|
|
627
|
+
feature: `preseed ${command}`,
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Check if a seed command requires paid tier
|
|
633
|
+
* @param {string} command - Seed subcommand
|
|
634
|
+
* @returns {{allowed: boolean, requiredTier: string, userTier: string, feature: string}}
|
|
635
|
+
*/
|
|
636
|
+
function checkSeedAccess(command) {
|
|
637
|
+
const userTier = getTier();
|
|
638
|
+
const isPaidCommand = SEED_PAID_COMMANDS.includes(command);
|
|
639
|
+
|
|
640
|
+
if (!isPaidCommand) {
|
|
641
|
+
return { allowed: true, requiredTier: 'free', userTier, feature: `seed ${command}` };
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Check if user has seed.* or specific seed.command
|
|
645
|
+
const hasAccess = hasFeature(`seed.${command}`) || hasFeature('seed.*');
|
|
646
|
+
|
|
647
|
+
return {
|
|
648
|
+
allowed: hasAccess,
|
|
649
|
+
requiredTier: 'pro',
|
|
650
|
+
userTier,
|
|
651
|
+
feature: `seed ${command}`,
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Require preseed command access - throws if not allowed
|
|
657
|
+
* @param {string} command - Preseed subcommand
|
|
658
|
+
* @throws {Error} If access not allowed
|
|
659
|
+
*/
|
|
660
|
+
function requirePreseedAccess(command) {
|
|
661
|
+
const access = checkPreseedAccess(command);
|
|
662
|
+
if (!access.allowed) {
|
|
663
|
+
const error = new Error(`preseed ${command} requires ${access.requiredTier} tier or higher`);
|
|
664
|
+
error.code = 'TIER_REQUIRED';
|
|
665
|
+
error.requiredTier = access.requiredTier;
|
|
666
|
+
error.userTier = access.userTier;
|
|
667
|
+
error.upgradePrompt = getUpgradePrompt(`preseed ${command}`, access.requiredTier);
|
|
668
|
+
throw error;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Require seed command access - throws if not allowed
|
|
674
|
+
* @param {string} command - Seed subcommand
|
|
675
|
+
* @throws {Error} If access not allowed
|
|
676
|
+
*/
|
|
677
|
+
function requireSeedAccess(command) {
|
|
678
|
+
const access = checkSeedAccess(command);
|
|
679
|
+
if (!access.allowed) {
|
|
680
|
+
const error = new Error(`seed ${command} requires ${access.requiredTier} tier or higher`);
|
|
681
|
+
error.code = 'TIER_REQUIRED';
|
|
682
|
+
error.requiredTier = access.requiredTier;
|
|
683
|
+
error.userTier = access.userTier;
|
|
684
|
+
error.upgradePrompt = getUpgradePrompt(`seed ${command}`, access.requiredTier);
|
|
685
|
+
throw error;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
module.exports = {
|
|
690
|
+
// Cache management
|
|
691
|
+
getCachedEntitlements,
|
|
692
|
+
cacheEntitlements,
|
|
693
|
+
clearCache,
|
|
694
|
+
|
|
695
|
+
// Entitlements
|
|
696
|
+
fetchEntitlements,
|
|
697
|
+
getEntitlements,
|
|
698
|
+
|
|
699
|
+
// Tier checks
|
|
700
|
+
getTier,
|
|
701
|
+
getTierLevel,
|
|
702
|
+
meetsTierRequirement,
|
|
703
|
+
requireTier,
|
|
704
|
+
|
|
705
|
+
// Agent access
|
|
706
|
+
checkAgentAccess,
|
|
707
|
+
AGENT_TIERS,
|
|
708
|
+
|
|
709
|
+
// Skill access
|
|
710
|
+
isSkillPremium,
|
|
711
|
+
checkSkillAccess,
|
|
712
|
+
PREMIUM_SKILL_CATEGORIES,
|
|
713
|
+
PREMIUM_SKILL_PATTERNS,
|
|
714
|
+
|
|
715
|
+
// Feature access
|
|
716
|
+
hasFeature,
|
|
717
|
+
requireFeature,
|
|
718
|
+
FEATURE_GATES,
|
|
719
|
+
|
|
720
|
+
// Preseed/Seed access
|
|
721
|
+
checkPreseedAccess,
|
|
722
|
+
checkSeedAccess,
|
|
723
|
+
requirePreseedAccess,
|
|
724
|
+
requireSeedAccess,
|
|
725
|
+
PRESEED_PAID_COMMANDS,
|
|
726
|
+
SEED_PAID_COMMANDS,
|
|
727
|
+
|
|
728
|
+
// Limits
|
|
729
|
+
getLimits,
|
|
730
|
+
checkLimit,
|
|
731
|
+
DEFAULT_LIMITS,
|
|
732
|
+
|
|
733
|
+
// Display helpers
|
|
734
|
+
getUpgradePrompt,
|
|
735
|
+
formatTierBadge,
|
|
736
|
+
TIER_HIERARCHY,
|
|
737
|
+
};
|