@subvalue/cli 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/PRE_RELEASE.md +7 -7
  2. package/README.md +7 -5
  3. package/dist/PRICING.md +15 -16
  4. package/dist/packages/cli/src/index.js +109 -20
  5. package/dist/packages/cli/src/protocol.js +13 -0
  6. package/dist/packages/cli/src/runtime.js +44 -14
  7. package/dist/packages/cli/src/server.js +306 -62
  8. package/dist/packages/core/src/calendar.js +41 -18
  9. package/dist/packages/core/src/coverage.js +12 -0
  10. package/dist/packages/core/src/demo.js +61 -9
  11. package/dist/packages/core/src/metadata.js +52 -9
  12. package/dist/packages/core/src/pricing/current.js +346 -297
  13. package/dist/packages/core/src/pricing/index.js +373 -73
  14. package/dist/packages/core/src/providers/claude/index.js +93 -21
  15. package/dist/packages/core/src/providers/codex/index.js +213 -46
  16. package/dist/packages/core/src/scanner.js +226 -46
  17. package/dist/packages/core/src/security.js +127 -34
  18. package/dist/packages/core/src/storage.js +215 -47
  19. package/dist/packages/core/src/subscriptions.js +146 -21
  20. package/dist/packages/core/src/summary.js +410 -59
  21. package/dist/packages/core/src/types.js +73 -18
  22. package/dist/packages/core/src/usage-json.js +166 -47
  23. package/dist/packages/receipt/src/index.js +392 -82
  24. package/dist/packages/ui/src/brand.js +43 -27
  25. package/dist/public/app.css +2303 -61
  26. package/dist/public/apps/web/src/landing.js +209 -8
  27. package/dist/public/apps/web/src/main.js +1143 -98
  28. package/dist/public/index.html +24 -1
  29. package/dist/public/landing.css +3286 -80
  30. package/dist/public/landing.html +21 -1
  31. package/dist/public/packages/core/src/calendar.js +41 -18
  32. package/dist/public/packages/core/src/coverage.js +12 -0
  33. package/dist/public/packages/core/src/subscriptions.js +146 -21
  34. package/dist/public/packages/receipt/src/index.js +392 -82
  35. package/dist/public/packages/ui/src/brand.js +43 -27
  36. package/dist/public/privacy.css +197 -9
  37. package/package.json +1 -1
@@ -1 +1,21 @@
1
- <!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"><meta name="theme-color" content="#080b0d"><meta name="description" content="See what your Codex and Claude Code usage would have cost via API. Local, read-only, no account."><title>SubValue — Know what your AI subscription is worth</title><link rel="icon" href="./icon.svg"><link rel="preload" href="/fonts/inter-latin.woff2" as="font" type="font/woff2" crossorigin><link rel="preload" href="/fonts/plex-mono.woff2" as="font" type="font/woff2" crossorigin><link rel="stylesheet" href="./landing.css"></head><body><div id="landing"></div><script type="module" src="./landing.js"></script></body></html>
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
6
+ <meta name="theme-color" content="#080b0d" />
7
+ <meta
8
+ name="description"
9
+ content="See what your Codex and Claude Code usage would have cost via API. Local, read-only, no account."
10
+ />
11
+ <title>SubValue — Know what your AI subscription is worth</title>
12
+ <link rel="icon" href="./icon.svg" />
13
+ <link rel="preload" href="/fonts/inter-latin.woff2" as="font" type="font/woff2" crossorigin />
14
+ <link rel="preload" href="/fonts/plex-mono.woff2" as="font" type="font/woff2" crossorigin />
15
+ <link rel="stylesheet" href="./landing.css" />
16
+ </head>
17
+ <body>
18
+ <div id="landing"></div>
19
+ <script type="module" src="./landing.js"></script>
20
+ </body>
21
+ </html>
@@ -1,24 +1,47 @@
1
1
  /** Count whole local calendar months; rolling presets are never subscription comparisons. */
2
- export function fullCalendarMonths(range ) {
3
- if(range.preset==='7d'||range.preset==='30d')return null;
2
+ export function fullCalendarMonths(range
3
+
4
+
5
+
6
+
7
+ ) {
8
+ if (range.preset === '7d' || range.preset === '30d') return null;
4
9
  // Browser timezone overrides must not change the server's calendar selection.
5
- if(range.calendarMonths!==undefined)return Number.isInteger(range.calendarMonths)&&range.calendarMonths >0&&range.calendarMonths <=1200?range.calendarMonths:null;
6
- const start=new Date(range.from),end=new Date(range.until);
7
- const boundary=(date )=>Number.isFinite(+date)&&date.getDate()===1&&date.getHours()===0&&date.getMinutes()===0&&date.getSeconds()===0&&date.getMilliseconds()===0;
8
- if(!boundary(start)||!boundary(end))return null;
9
- const months=(end.getFullYear()-start.getFullYear())*12+end.getMonth()-start.getMonth();
10
- return months>0&&months<=1200?months:null;
10
+ if (range.calendarMonths !== undefined)
11
+ return Number.isInteger(range.calendarMonths) &&
12
+ range.calendarMonths > 0 &&
13
+ range.calendarMonths <= 1200
14
+ ? range.calendarMonths
15
+ : null;
16
+ const start = new Date(range.from),
17
+ end = new Date(range.until);
18
+ const boundary = (date ) =>
19
+ Number.isFinite(+date) &&
20
+ date.getDate() === 1 &&
21
+ date.getHours() === 0 &&
22
+ date.getMinutes() === 0 &&
23
+ date.getSeconds() === 0 &&
24
+ date.getMilliseconds() === 0;
25
+ if (!boundary(start) || !boundary(end)) return null;
26
+ const months = (end.getFullYear() - start.getFullYear()) * 12 + end.getMonth() - start.getMonth();
27
+ return months > 0 && months <= 1200 ? months : null;
11
28
  }
12
29
  /** Civil-date movement uses UTC arithmetic so DST never drops or repeats a day. */
13
- export function shiftCalendarDays(day ,days ) {
14
- if(!/^\d{4}-\d{2}-\d{2}$/.test(day)||!Number.isInteger(days)||Math.abs(days)>36600)return null;
15
- const date=new Date(day+'T12:00:00.000Z');
16
- if(!Number.isFinite(+date)||date.toISOString().slice(0,10)!==day)return null;
17
- date.setUTCDate(date.getUTCDate()+days);
18
- const result=date.toISOString().slice(0,10);return /^\d{4}-\d{2}-\d{2}$/.test(result)?result:null;
30
+ export function shiftCalendarDays(day , days ) {
31
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(day) || !Number.isInteger(days) || Math.abs(days) > 36600)
32
+ return null;
33
+ const date = new Date(day + 'T12:00:00.000Z');
34
+ if (!Number.isFinite(+date) || date.toISOString().slice(0, 10) !== day) return null;
35
+ date.setUTCDate(date.getUTCDate() + days);
36
+ const result = date.toISOString().slice(0, 10);
37
+ return /^\d{4}-\d{2}-\d{2}$/.test(result) ? result : null;
19
38
  }
20
- export function shiftCalendarMonth(month ,months ) {
21
- if(!/^(?:19|20|21)\d{2}-(?:0[1-9]|1[0-2])$/.test(month)||!Number.isInteger(months))return null;
22
- const ordinal=Number(month.slice(0,4))*12+Number(month.slice(5))-1+months,year=Math.floor(ordinal/12);
23
- return year>=1900&&year<=2199?`${year}-${String(ordinal%12+1).padStart(2,'0')}`:null;
39
+ export function shiftCalendarMonth(month , months ) {
40
+ if (!/^(?:19|20|21)\d{2}-(?:0[1-9]|1[0-2])$/.test(month) || !Number.isInteger(months))
41
+ return null;
42
+ const ordinal = Number(month.slice(0, 4)) * 12 + Number(month.slice(5)) - 1 + months,
43
+ year = Math.floor(ordinal / 12);
44
+ return year >= 1900 && year <= 2199
45
+ ? `${year}-${String((ordinal % 12) + 1).padStart(2, '0')}`
46
+ : null;
24
47
  }
@@ -0,0 +1,12 @@
1
+ /** Reserve 100% for fully priced usage; missing coverage never becomes zero. */
2
+ export function formatPricingCoverage(coverage ) {
3
+ if (coverage === null || !Number.isFinite(coverage) || coverage < 0 || coverage > 1) {
4
+ return 'Unknown';
5
+ }
6
+ if (coverage === 1) return '100%';
7
+ if (coverage === 0) return '0%';
8
+ const percent = Math.round(coverage * 100);
9
+ if (percent === 100) return '<100%';
10
+ if (percent === 0) return '<1%';
11
+ return `${percent}%`;
12
+ }
@@ -1,22 +1,147 @@
1
-
2
-
3
- export const subscriptionVerified='2026-10-05';
4
- const openai='https://learn.chatgpt.com/docs/pricing',claude='https://claude.com/pricing';
5
- export const subscriptionPlans =[
6
- {id:'chatgpt-free',provider:'codex',name:'ChatGPT Free',monthly:0,interval:'monthly',source:openai},
7
- {id:'chatgpt-go',provider:'codex',name:'ChatGPT Go',monthly:8,interval:'monthly',source:openai},
8
- {id:'chatgpt-plus',provider:'codex',name:'ChatGPT Plus',monthly:20,interval:'monthly',source:openai},
9
- ...[100,200,500].map(monthly=>({id:`chatgpt-pro-${monthly}`,provider:'codex' ,name:'ChatGPT Pro',monthly,interval:'monthly' ,source:openai})),
10
- {id:'chatgpt-business-monthly',provider:'codex',name:'ChatGPT Business',monthly:25,interval:'monthly',perSeat:true,source:openai},
11
- {id:'chatgpt-business-annual',provider:'codex',name:'ChatGPT Business',monthly:20,annual:240,interval:'annual',perSeat:true,source:openai},
12
- {id:'claude-pro-monthly',provider:'claude',name:'Claude Pro',monthly:20,interval:'monthly',source:claude},
13
- {id:'claude-pro-annual',provider:'claude',name:'Claude Pro',monthly:200/12,annual:200,interval:'annual',source:claude},
14
- {id:'claude-max-5x',provider:'claude',name:'Claude Max 5x',monthly:100,interval:'monthly',source:'https://support.claude.com/en/articles/11049741-what-is-the-max-plan'},
15
- {id:'claude-max-20x',provider:'claude',name:'Claude Max 20x',monthly:200,interval:'monthly',source:'https://support.claude.com/en/articles/11049741-what-is-the-max-plan'},
16
- {id:'claude-team-standard-monthly',provider:'claude',name:'Claude Team · Standard seat',monthly:25,interval:'monthly',perSeat:true,source:claude},
17
- {id:'claude-team-standard-annual',provider:'claude',name:'Claude Team · Standard seat',monthly:20,annual:240,interval:'annual',perSeat:true,source:claude},
18
- {id:'claude-team-premium-monthly',provider:'claude',name:'Claude Team · Premium seat',monthly:125,interval:'monthly',perSeat:true,source:claude},
19
- {id:'claude-team-premium-annual',provider:'claude',name:'Claude Team · Premium seat',monthly:100,annual:1200,interval:'annual',perSeat:true,source:claude}
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+ export const subscriptionVerified = '2026-10-05';
13
+ const openai = 'https://learn.chatgpt.com/docs/pricing',
14
+ claude = 'https://claude.com/pricing';
15
+ export const subscriptionPlans = [
16
+ {
17
+ id: 'chatgpt-free',
18
+ provider: 'codex',
19
+ name: 'ChatGPT Free',
20
+ monthly: 0,
21
+ interval: 'monthly',
22
+ source: openai,
23
+ },
24
+ {
25
+ id: 'chatgpt-go',
26
+ provider: 'codex',
27
+ name: 'ChatGPT Go',
28
+ monthly: 8,
29
+ interval: 'monthly',
30
+ source: openai,
31
+ },
32
+ {
33
+ id: 'chatgpt-plus',
34
+ provider: 'codex',
35
+ name: 'ChatGPT Plus',
36
+ monthly: 20,
37
+ interval: 'monthly',
38
+ source: openai,
39
+ },
40
+ ...[100, 200, 500].map((monthly) => ({
41
+ id: `chatgpt-pro-${monthly}`,
42
+ provider: 'codex' ,
43
+ name: 'ChatGPT Pro',
44
+ monthly,
45
+ interval: 'monthly' ,
46
+ source: openai,
47
+ })),
48
+ {
49
+ id: 'chatgpt-business-monthly',
50
+ provider: 'codex',
51
+ name: 'ChatGPT Business',
52
+ monthly: 25,
53
+ interval: 'monthly',
54
+ perSeat: true,
55
+ source: openai,
56
+ },
57
+ {
58
+ id: 'chatgpt-business-annual',
59
+ provider: 'codex',
60
+ name: 'ChatGPT Business',
61
+ monthly: 20,
62
+ annual: 240,
63
+ interval: 'annual',
64
+ perSeat: true,
65
+ source: openai,
66
+ },
67
+ {
68
+ id: 'claude-pro-monthly',
69
+ provider: 'claude',
70
+ name: 'Claude Pro',
71
+ monthly: 20,
72
+ interval: 'monthly',
73
+ source: claude,
74
+ },
75
+ {
76
+ id: 'claude-pro-annual',
77
+ provider: 'claude',
78
+ name: 'Claude Pro',
79
+ monthly: 200 / 12,
80
+ annual: 200,
81
+ interval: 'annual',
82
+ source: claude,
83
+ },
84
+ {
85
+ id: 'claude-max-5x',
86
+ provider: 'claude',
87
+ name: 'Claude Max 5x',
88
+ monthly: 100,
89
+ interval: 'monthly',
90
+ source: 'https://support.claude.com/en/articles/11049741-what-is-the-max-plan',
91
+ },
92
+ {
93
+ id: 'claude-max-20x',
94
+ provider: 'claude',
95
+ name: 'Claude Max 20x',
96
+ monthly: 200,
97
+ interval: 'monthly',
98
+ source: 'https://support.claude.com/en/articles/11049741-what-is-the-max-plan',
99
+ },
100
+ {
101
+ id: 'claude-team-standard-monthly',
102
+ provider: 'claude',
103
+ name: 'Claude Team · Standard seat',
104
+ monthly: 25,
105
+ interval: 'monthly',
106
+ perSeat: true,
107
+ source: claude,
108
+ },
109
+ {
110
+ id: 'claude-team-standard-annual',
111
+ provider: 'claude',
112
+ name: 'Claude Team · Standard seat',
113
+ monthly: 20,
114
+ annual: 240,
115
+ interval: 'annual',
116
+ perSeat: true,
117
+ source: claude,
118
+ },
119
+ {
120
+ id: 'claude-team-premium-monthly',
121
+ provider: 'claude',
122
+ name: 'Claude Team · Premium seat',
123
+ monthly: 125,
124
+ interval: 'monthly',
125
+ perSeat: true,
126
+ source: claude,
127
+ },
128
+ {
129
+ id: 'claude-team-premium-annual',
130
+ provider: 'claude',
131
+ name: 'Claude Team · Premium seat',
132
+ monthly: 100,
133
+ annual: 1200,
134
+ interval: 'annual',
135
+ perSeat: true,
136
+ source: claude,
137
+ },
20
138
  ];
21
- export function subscriptionPlan(provider ,id ) {return subscriptionPlans.find(p=>p.provider===provider&&p.id===id);}
22
- export function subscriptionPlanLabel(p ) {return `${p.name} · $${p.interval==='annual'?p.annual+'/year':p.monthly+'/month'}${p.perSeat?' / seat':''}`;}
139
+ export function subscriptionPlan(
140
+ provider ,
141
+ id ,
142
+ ) {
143
+ return subscriptionPlans.find((p) => p.provider === provider && p.id === id);
144
+ }
145
+ export function subscriptionPlanLabel(p ) {
146
+ return `${p.name} · $${p.interval === 'annual' ? p.annual + '/year' : p.monthly + '/month'}${p.perSeat ? ' / seat' : ''}`;
147
+ }