ray-finance 0.2.2 → 0.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/README.md +38 -11
- package/dist/ai/agent.js +16 -3
- package/dist/ai/context.js +6 -2
- package/dist/ai/insights.js +26 -3
- package/dist/ai/redactor.js +11 -0
- package/dist/ai/system-prompt.js +2 -2
- package/dist/ai/tools.js +4 -0
- package/dist/cli/backup.js +18 -9
- package/dist/cli/chat.js +146 -40
- package/dist/cli/format.d.ts +2 -0
- package/dist/cli/format.js +25 -0
- package/dist/cli/index.js +12 -2
- package/dist/cli/setup.js +7 -1
- package/dist/daily-sync.js +19 -4
- package/dist/db/connection.js +9 -1
- package/dist/db/encryption.js +18 -7
- package/dist/db/schema.js +6 -1
- package/dist/public/link.html +47 -24
- package/dist/public/ray-logo-dark.png +0 -0
- package/dist/queries/index.js +8 -8
- package/dist/server.js +33 -1
- package/package.json +7 -2
- package/.claude/settings.local.json +0 -16
- package/.env.example +0 -13
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -19
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -9
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -5
- package/.github/workflows/ci.yml +0 -21
- package/CHANGELOG.md +0 -16
- package/CODE_OF_CONDUCT.md +0 -31
- package/CONTRIBUTING.md +0 -41
- package/Dockerfile +0 -8
- package/SECURITY.md +0 -36
- package/SPEC.md +0 -374
- package/docker-compose.yml +0 -9
- package/site/next-env.d.ts +0 -6
- package/site/next.config.ts +0 -7
- package/site/package-lock.json +0 -1661
- package/site/package.json +0 -24
- package/site/postcss.config.mjs +0 -7
- package/site/public/ray-og.jpg +0 -0
- package/site/public/robots.txt +0 -4
- package/site/public/sitemap.xml +0 -8
- package/site/src/app/copy-command.tsx +0 -31
- package/site/src/app/globals.css +0 -87
- package/site/src/app/layout.tsx +0 -64
- package/site/src/app/page.tsx +0 -841
- package/site/src/app/pii-scramble.tsx +0 -190
- package/site/src/app/reveal.tsx +0 -29
- package/site/tsconfig.json +0 -21
- package/src/ai/agent.ts +0 -106
- package/src/ai/audit.ts +0 -11
- package/src/ai/context.ts +0 -93
- package/src/ai/insights.ts +0 -474
- package/src/ai/memory.ts +0 -21
- package/src/ai/redactor.ts +0 -102
- package/src/ai/system-prompt.ts +0 -90
- package/src/ai/tools.ts +0 -716
- package/src/alerts/index.ts +0 -123
- package/src/cli/backup.ts +0 -113
- package/src/cli/chat.ts +0 -105
- package/src/cli/commands.ts +0 -240
- package/src/cli/format.ts +0 -149
- package/src/cli/index.ts +0 -193
- package/src/cli/scheduler.ts +0 -116
- package/src/cli/setup.ts +0 -189
- package/src/config.ts +0 -81
- package/src/daily-sync.ts +0 -155
- package/src/db/connection.ts +0 -38
- package/src/db/encryption.ts +0 -29
- package/src/db/helpers.ts +0 -47
- package/src/db/schema.ts +0 -196
- package/src/index.ts +0 -3
- package/src/plaid/client.ts +0 -25
- package/src/plaid/link.ts +0 -25
- package/src/plaid/sync.ts +0 -219
- package/src/public/link.html +0 -161
- package/src/queries/index.ts +0 -586
- package/src/scoring/index.ts +0 -468
- package/src/server.ts +0 -162
- package/tsconfig.json +0 -16
- /package/{site → dist}/public/favicon.png +0 -0
package/site/src/app/page.tsx
DELETED
|
@@ -1,841 +0,0 @@
|
|
|
1
|
-
import { CopyCommand } from "./copy-command";
|
|
2
|
-
import { PIIScramble } from "./pii-scramble";
|
|
3
|
-
import { Reveal } from "./reveal";
|
|
4
|
-
|
|
5
|
-
const jsonLd = {
|
|
6
|
-
"@context": "https://schema.org",
|
|
7
|
-
"@type": "SoftwareApplication",
|
|
8
|
-
name: "Ray Finance",
|
|
9
|
-
description:
|
|
10
|
-
"An open-source CLI that connects to your bank and gives you AI-powered financial advice — all running locally on your machine.",
|
|
11
|
-
applicationCategory: "FinanceApplication",
|
|
12
|
-
operatingSystem: "macOS, Linux, Windows",
|
|
13
|
-
url: "https://rayfinance.app",
|
|
14
|
-
offers: [
|
|
15
|
-
{
|
|
16
|
-
"@type": "Offer",
|
|
17
|
-
price: "0",
|
|
18
|
-
priceCurrency: "USD",
|
|
19
|
-
description: "Self-hosted with your own API keys",
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
"@type": "Offer",
|
|
23
|
-
price: "10",
|
|
24
|
-
priceCurrency: "USD",
|
|
25
|
-
description: "Ray API Key — managed setup",
|
|
26
|
-
},
|
|
27
|
-
],
|
|
28
|
-
license: "https://opensource.org/licenses/MIT",
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export default function Home() {
|
|
32
|
-
return (
|
|
33
|
-
<main id="main">
|
|
34
|
-
<a
|
|
35
|
-
href="#main"
|
|
36
|
-
className="sr-only focus:not-sr-only focus:fixed focus:top-4 focus:left-4 focus:z-[60] focus:rounded-lg focus:bg-stone-900 focus:px-4 focus:py-2 focus:text-sm focus:font-medium focus:text-white"
|
|
37
|
-
>
|
|
38
|
-
Skip to content
|
|
39
|
-
</a>
|
|
40
|
-
<script
|
|
41
|
-
type="application/ld+json"
|
|
42
|
-
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
|
43
|
-
/>
|
|
44
|
-
<Nav />
|
|
45
|
-
<Hero />
|
|
46
|
-
<Terminal />
|
|
47
|
-
<TrustBlock />
|
|
48
|
-
<Reveal><Story /></Reveal>
|
|
49
|
-
<HowItWorks />
|
|
50
|
-
<Reveal><Privacy /></Reveal>
|
|
51
|
-
<Reveal><Features /></Reveal>
|
|
52
|
-
<Reveal><Pricing /></Reveal>
|
|
53
|
-
<CTA />
|
|
54
|
-
<BuiltBy />
|
|
55
|
-
<Footer />
|
|
56
|
-
</main>
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/* ─── Nav ─── */
|
|
61
|
-
function Nav() {
|
|
62
|
-
return (
|
|
63
|
-
<nav aria-label="Main navigation" className="fixed top-0 left-0 right-0 z-50 border-b border-stone-200/60 bg-stone-50/80 backdrop-blur-xl">
|
|
64
|
-
<div className="mx-auto flex max-w-6xl items-center justify-between px-6 py-4">
|
|
65
|
-
<div className="flex items-center gap-2">
|
|
66
|
-
<span className="text-lg font-pixel tracking-tight">ray</span>
|
|
67
|
-
<span className="rounded-full bg-lime-400/10 px-2 py-0.5 font-mono text-xs text-lime-500">
|
|
68
|
-
v0.2
|
|
69
|
-
</span>
|
|
70
|
-
</div>
|
|
71
|
-
{/* Desktop nav */}
|
|
72
|
-
<div className="hidden items-center gap-8 sm:flex">
|
|
73
|
-
<a
|
|
74
|
-
href="#how-it-works"
|
|
75
|
-
className="rounded-lg px-3 py-2 text-sm text-stone-500 transition-colors hover:text-stone-900"
|
|
76
|
-
>
|
|
77
|
-
How it works
|
|
78
|
-
</a>
|
|
79
|
-
<a
|
|
80
|
-
href="#privacy"
|
|
81
|
-
className="rounded-lg px-3 py-2 text-sm text-stone-500 transition-colors hover:text-stone-900"
|
|
82
|
-
>
|
|
83
|
-
Privacy
|
|
84
|
-
</a>
|
|
85
|
-
<a
|
|
86
|
-
href="#pricing"
|
|
87
|
-
className="rounded-lg px-3 py-2 text-sm text-stone-500 transition-colors hover:text-stone-900"
|
|
88
|
-
>
|
|
89
|
-
Pricing
|
|
90
|
-
</a>
|
|
91
|
-
<a
|
|
92
|
-
href="https://github.com/cdinnison/ray-finance"
|
|
93
|
-
className="inline-flex items-center gap-2 rounded-full bg-stone-900 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-stone-800"
|
|
94
|
-
>
|
|
95
|
-
<GitHubIcon />
|
|
96
|
-
View on GitHub
|
|
97
|
-
</a>
|
|
98
|
-
</div>
|
|
99
|
-
{/* Mobile nav */}
|
|
100
|
-
<div className="flex items-center gap-3 sm:hidden">
|
|
101
|
-
<a href="#how-it-works" className="rounded-full px-2 py-2 text-xs text-stone-500">How</a>
|
|
102
|
-
<a href="#pricing" className="rounded-full px-2 py-2 text-xs text-stone-500">Pricing</a>
|
|
103
|
-
<a
|
|
104
|
-
href="https://github.com/cdinnison/ray-finance"
|
|
105
|
-
className="inline-flex items-center gap-2 rounded-full bg-stone-900 px-3 py-2 text-sm font-medium text-white transition-colors hover:bg-stone-800"
|
|
106
|
-
>
|
|
107
|
-
<GitHubIcon />
|
|
108
|
-
GitHub
|
|
109
|
-
</a>
|
|
110
|
-
</div>
|
|
111
|
-
</div>
|
|
112
|
-
</nav>
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/* ─── Hero ─── */
|
|
117
|
-
function Hero() {
|
|
118
|
-
return (
|
|
119
|
-
<section className="relative overflow-hidden pt-32 pb-12">
|
|
120
|
-
<div className="mx-auto max-w-5xl px-6 text-center">
|
|
121
|
-
<h1 className="animate-fade-up text-3xl leading-[1.1] font-extrabold tracking-tight text-stone-950 sm:text-5xl lg:text-7xl">
|
|
122
|
-
An AI financial advisor that runs on your machine
|
|
123
|
-
</h1>
|
|
124
|
-
<p className="animate-fade-up-delay-1 mx-auto mt-8 max-w-2xl text-lg leading-relaxed text-stone-500 sm:text-xl">
|
|
125
|
-
Connect your money accounts, ask anything, and get real answers
|
|
126
|
-
from your actual data. Open source. Local‑first.
|
|
127
|
-
Two minute set up.
|
|
128
|
-
</p>
|
|
129
|
-
<div className="animate-fade-up-delay-2 mt-10 flex flex-col items-center gap-5 sm:flex-row sm:justify-center">
|
|
130
|
-
<a
|
|
131
|
-
href="https://github.com/cdinnison/ray-finance"
|
|
132
|
-
className="inline-flex items-center gap-2.5 rounded-full bg-stone-900 px-6 py-3.5 text-sm font-semibold text-white shadow-lg shadow-stone-900/20 transition-colors hover:bg-stone-800"
|
|
133
|
-
>
|
|
134
|
-
<GitHubIcon />
|
|
135
|
-
Get started free
|
|
136
|
-
</a>
|
|
137
|
-
<CopyCommand
|
|
138
|
-
command="npx ray-finance"
|
|
139
|
-
className="rounded-full border border-stone-200 bg-white px-5 py-3 text-sm text-stone-700 shadow-sm transition-colors hover:border-stone-300"
|
|
140
|
-
/>
|
|
141
|
-
</div>
|
|
142
|
-
</div>
|
|
143
|
-
</section>
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/* ─── Terminal Demo ─── */
|
|
148
|
-
function Terminal() {
|
|
149
|
-
return (
|
|
150
|
-
<section className="px-6 py-16">
|
|
151
|
-
<div className="mx-auto max-w-3xl">
|
|
152
|
-
<div className="overflow-hidden rounded-2xl border border-stone-200 bg-stone-950 shadow-2xl shadow-stone-900/10">
|
|
153
|
-
{/* Title bar */}
|
|
154
|
-
<div className="flex items-center gap-2 border-b border-stone-800 px-4 py-3">
|
|
155
|
-
<div className="h-3 w-3 rounded-full bg-stone-700" />
|
|
156
|
-
<div className="h-3 w-3 rounded-full bg-stone-700" />
|
|
157
|
-
<div className="h-3 w-3 rounded-full bg-stone-700" />
|
|
158
|
-
<span className="ml-2 font-mono text-xs text-stone-500">
|
|
159
|
-
ray
|
|
160
|
-
</span>
|
|
161
|
-
</div>
|
|
162
|
-
{/* Content */}
|
|
163
|
-
<div className="overflow-x-auto p-5 font-mono text-[11px] leading-[1.7] sm:p-8 sm:text-[13px]">
|
|
164
|
-
{/* ── Briefing (shown on launch, before user types anything) ── */}
|
|
165
|
-
<p className="text-stone-600">Friday, Mar 28</p>
|
|
166
|
-
<Blank />
|
|
167
|
-
<p className="text-stone-300">
|
|
168
|
-
<D>net worth</D>{" "}<W>$45,230</W>{" "}<G>+$120</G>
|
|
169
|
-
</p>
|
|
170
|
-
<Blank />
|
|
171
|
-
<p className="text-stone-300">
|
|
172
|
-
<D>spending</D>{" "}<W>$2,340 this month</W>{" "}<D>·</D>{" "}<G>$340 less</G>{" "}<D>vs last month</D>
|
|
173
|
-
</p>
|
|
174
|
-
<p className="text-stone-300">
|
|
175
|
-
{" "}<D>Dining</D>{" "}<G>-$114</G>{" "}<D>·</D>{" "}<D>Shopping</D>{" "}<G>-$142</G>{" "}<D>·</D>{" "}<D>Groceries</D>{" "}<G>-$73</G>
|
|
176
|
-
</p>
|
|
177
|
-
<Blank />
|
|
178
|
-
<p className="text-stone-300 flex items-center gap-2">
|
|
179
|
-
{" "}<CSSBar pct={92} color="amber" />{" "}<Y>Dining 92%</Y>
|
|
180
|
-
</p>
|
|
181
|
-
<Blank />
|
|
182
|
-
<p className="text-stone-300 flex items-center gap-2">
|
|
183
|
-
{" "}<CSSBar pct={46} color="lime" />{" "}<W>Emergency fund</W>{" "}<D>$18,200/$40,000</D>
|
|
184
|
-
</p>
|
|
185
|
-
<Blank />
|
|
186
|
-
<p className="text-stone-300">
|
|
187
|
-
<D>upcoming</D>{" "}<D>Netflix $16 in 3d</D>{" "}<D>·</D>{" "}<D>Comcast $142 in 6d</D>
|
|
188
|
-
</p>
|
|
189
|
-
<Blank />
|
|
190
|
-
<p className="text-stone-300">
|
|
191
|
-
<D>score</D>{" "}<G>72</G><D>/100</D>{" "}<D>· 5d no dining · 3d on pace</D>
|
|
192
|
-
</p>
|
|
193
|
-
<Blank />
|
|
194
|
-
<div className="border-t border-stone-800 my-3" />
|
|
195
|
-
{/* ── Conversation ── */}
|
|
196
|
-
<p className="text-stone-300">
|
|
197
|
-
<span className="text-stone-600">{"❯ "}</span>
|
|
198
|
-
if I quit my job to freelance, how long can I survive?
|
|
199
|
-
</p>
|
|
200
|
-
<Blank />
|
|
201
|
-
<p className="text-stone-300">
|
|
202
|
-
Based on your last 3 months: you burn <W>$4,820/mo</W> after
|
|
203
|
-
</p>
|
|
204
|
-
<p className="text-stone-300">
|
|
205
|
-
fixed costs. With <G>$18,200</G> in savings, that's
|
|
206
|
-
</p>
|
|
207
|
-
<p className="text-stone-300">
|
|
208
|
-
<W>3.8 months</W> of runway at current spend.
|
|
209
|
-
</p>
|
|
210
|
-
<Blank />
|
|
211
|
-
<p className="text-stone-300">
|
|
212
|
-
Cut dining and shopping to last-month levels and
|
|
213
|
-
</p>
|
|
214
|
-
<p className="text-stone-300">
|
|
215
|
-
you stretch to <G>5.1 months</G>. Land one $8k contract
|
|
216
|
-
</p>
|
|
217
|
-
<p className="text-stone-300">
|
|
218
|
-
in that window and you never dip below $10k.
|
|
219
|
-
</p>
|
|
220
|
-
</div>
|
|
221
|
-
</div>
|
|
222
|
-
</div>
|
|
223
|
-
</section>
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/* ─── Trust Block ─── */
|
|
228
|
-
function TrustBlock() {
|
|
229
|
-
return (
|
|
230
|
-
<section className="py-16">
|
|
231
|
-
<div className="mx-auto grid max-w-3xl grid-cols-2 gap-y-6 px-6 sm:flex sm:flex-wrap sm:items-center sm:justify-center sm:gap-x-10 sm:gap-y-4">
|
|
232
|
-
<Metric value="Open Source" label="MIT License" />
|
|
233
|
-
<span className="hidden text-stone-200 sm:inline">|</span>
|
|
234
|
-
<Metric value="AES-256" label="Encrypted database" />
|
|
235
|
-
<span className="hidden text-stone-200 sm:inline">|</span>
|
|
236
|
-
<Metric value="0" label="Data stored in cloud" />
|
|
237
|
-
<span className="hidden text-stone-200 sm:inline">|</span>
|
|
238
|
-
<Metric value="5 min" label="Setup time" />
|
|
239
|
-
</div>
|
|
240
|
-
</section>
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function Metric({ value, label }: { value: string; label: string }) {
|
|
245
|
-
return (
|
|
246
|
-
<div className="text-center">
|
|
247
|
-
<p className="text-lg font-bold tracking-tight text-stone-900">
|
|
248
|
-
{value}
|
|
249
|
-
</p>
|
|
250
|
-
<p className="text-xs text-stone-400">{label}</p>
|
|
251
|
-
</div>
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/* ─── Story ─── */
|
|
256
|
-
function Story() {
|
|
257
|
-
return (
|
|
258
|
-
<section id="story" className="py-24 sm:py-32">
|
|
259
|
-
<div className="mx-auto max-w-3xl px-6">
|
|
260
|
-
<h2 className="text-3xl font-extrabold tracking-tight text-stone-950 sm:text-4xl">
|
|
261
|
-
You’ve tried everything else.
|
|
262
|
-
</h2>
|
|
263
|
-
|
|
264
|
-
<div className="mt-16 space-y-16">
|
|
265
|
-
<StoryBlock
|
|
266
|
-
label="The Apps"
|
|
267
|
-
title="Dashboards show you what happened."
|
|
268
|
-
body="Mint, Copilot, Monarch — they sort your transactions into
|
|
269
|
-
pie charts and send you notifications. They're good at showing
|
|
270
|
-
you what you spent. They never tell you what to do about it."
|
|
271
|
-
/>
|
|
272
|
-
|
|
273
|
-
<StoryBlock
|
|
274
|
-
label="The Spreadsheets"
|
|
275
|
-
title="Powerful when you keep them updated."
|
|
276
|
-
body="You built the perfect spreadsheet once. Formulas, projections,
|
|
277
|
-
a debt payoff timeline. But it only works when you do — and
|
|
278
|
-
manual data entry doesn't survive a busy month."
|
|
279
|
-
/>
|
|
280
|
-
|
|
281
|
-
<div className="pl-8">
|
|
282
|
-
<p className="font-mono text-sm tracking-wide text-lime-500 uppercase">
|
|
283
|
-
Then there’s Ray
|
|
284
|
-
</p>
|
|
285
|
-
<h3 className="mt-3 text-2xl font-bold tracking-tight text-stone-950">
|
|
286
|
-
A financial advisor that actually knows your numbers.
|
|
287
|
-
</h3>
|
|
288
|
-
<p className="mt-4 text-lg leading-relaxed text-stone-500">
|
|
289
|
-
Ray connects directly to your bank accounts. It sees every
|
|
290
|
-
transaction, every balance, every debt. When you ask “can
|
|
291
|
-
I afford this?” it doesn’t guess — it
|
|
292
|
-
queries your actual data, runs the math, and gives you a real
|
|
293
|
-
answer. It remembers your goals, tracks your progress, and
|
|
294
|
-
proactively flags problems before they become emergencies.
|
|
295
|
-
</p>
|
|
296
|
-
</div>
|
|
297
|
-
</div>
|
|
298
|
-
</div>
|
|
299
|
-
</section>
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function StoryBlock({
|
|
304
|
-
label,
|
|
305
|
-
title,
|
|
306
|
-
body,
|
|
307
|
-
}: {
|
|
308
|
-
label: string;
|
|
309
|
-
title: string;
|
|
310
|
-
body: string;
|
|
311
|
-
}) {
|
|
312
|
-
return (
|
|
313
|
-
<div className="pl-8">
|
|
314
|
-
<p className="font-mono text-sm tracking-wide text-stone-400 uppercase">
|
|
315
|
-
{label}
|
|
316
|
-
</p>
|
|
317
|
-
<h3 className="mt-3 text-2xl font-bold tracking-tight text-stone-950">
|
|
318
|
-
{title}
|
|
319
|
-
</h3>
|
|
320
|
-
<p className="mt-4 text-lg leading-relaxed text-stone-500">{body}</p>
|
|
321
|
-
</div>
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/* ─── How It Works ─── */
|
|
326
|
-
function HowItWorks() {
|
|
327
|
-
return (
|
|
328
|
-
<section id="how-it-works" className="bg-stone-950 py-24 sm:py-32">
|
|
329
|
-
<div className="mx-auto max-w-5xl px-6">
|
|
330
|
-
<p className="font-mono text-sm tracking-wide text-lime-400 uppercase">
|
|
331
|
-
How it works
|
|
332
|
-
</p>
|
|
333
|
-
<h2 className="mt-4 text-3xl font-extrabold tracking-tight text-white sm:text-4xl">
|
|
334
|
-
Three commands. Five minutes.
|
|
335
|
-
</h2>
|
|
336
|
-
|
|
337
|
-
<div className="mt-16 grid gap-12 sm:grid-cols-2">
|
|
338
|
-
{/* Quick Setup */}
|
|
339
|
-
<div className="rounded-2xl border border-stone-800 bg-stone-900/50 p-8">
|
|
340
|
-
<div className="mb-8 flex items-center gap-3">
|
|
341
|
-
<span className="rounded-full bg-lime-400/10 px-3 py-1 font-mono text-xs font-medium text-lime-400">
|
|
342
|
-
most popular
|
|
343
|
-
</span>
|
|
344
|
-
<h3 className="text-lg font-bold text-white">Quick Setup</h3>
|
|
345
|
-
</div>
|
|
346
|
-
<div className="space-y-8">
|
|
347
|
-
<Step
|
|
348
|
-
num="01"
|
|
349
|
-
title="Install"
|
|
350
|
-
code="npx ray-finance"
|
|
351
|
-
description="One command. No dependencies to manage."
|
|
352
|
-
/>
|
|
353
|
-
<Step
|
|
354
|
-
num="02"
|
|
355
|
-
title="Subscribe in the CLI"
|
|
356
|
-
code="ray setup"
|
|
357
|
-
description="Setup opens Stripe checkout in your browser. Paste the key back into the terminal."
|
|
358
|
-
/>
|
|
359
|
-
<Step
|
|
360
|
-
num="03"
|
|
361
|
-
title="Connect & chat"
|
|
362
|
-
code="ray link → ray"
|
|
363
|
-
description="Link your accounts, then start asking questions. Ray handles the rest."
|
|
364
|
-
/>
|
|
365
|
-
</div>
|
|
366
|
-
</div>
|
|
367
|
-
|
|
368
|
-
{/* Self-Hosted */}
|
|
369
|
-
<div className="rounded-2xl border border-stone-800/50 p-8">
|
|
370
|
-
<div className="mb-8">
|
|
371
|
-
<h3 className="text-lg font-bold text-white">Self-Hosted</h3>
|
|
372
|
-
</div>
|
|
373
|
-
<div className="space-y-8">
|
|
374
|
-
<Step
|
|
375
|
-
num="01"
|
|
376
|
-
title="Get your API keys"
|
|
377
|
-
code="anthropic.com + plaid.com"
|
|
378
|
-
description="Bring your own Anthropic API key and Plaid production credentials."
|
|
379
|
-
/>
|
|
380
|
-
<Step
|
|
381
|
-
num="02"
|
|
382
|
-
title="Install & configure"
|
|
383
|
-
code="npx ray-finance"
|
|
384
|
-
description="Enter each key during setup. Full control over which models and environments you use."
|
|
385
|
-
/>
|
|
386
|
-
<Step
|
|
387
|
-
num="03"
|
|
388
|
-
title="Connect & chat"
|
|
389
|
-
code="ray link → ray"
|
|
390
|
-
description="Same experience, your own infrastructure. No third-party proxy."
|
|
391
|
-
/>
|
|
392
|
-
</div>
|
|
393
|
-
</div>
|
|
394
|
-
</div>
|
|
395
|
-
</div>
|
|
396
|
-
</section>
|
|
397
|
-
);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
function Step({
|
|
401
|
-
num,
|
|
402
|
-
title,
|
|
403
|
-
code,
|
|
404
|
-
description,
|
|
405
|
-
}: {
|
|
406
|
-
num: string;
|
|
407
|
-
title: string;
|
|
408
|
-
code: string;
|
|
409
|
-
description: string;
|
|
410
|
-
}) {
|
|
411
|
-
return (
|
|
412
|
-
<div>
|
|
413
|
-
<span className="font-pixel text-sm text-stone-600">{num}</span>
|
|
414
|
-
<h3 className="mt-3 text-base font-bold text-white">{title}</h3>
|
|
415
|
-
<code className="mt-3 block whitespace-pre rounded-lg bg-stone-900 px-4 py-3 font-mono text-sm text-lime-400">
|
|
416
|
-
{code}
|
|
417
|
-
</code>
|
|
418
|
-
<p className="mt-3 text-sm leading-relaxed text-stone-400">
|
|
419
|
-
{description}
|
|
420
|
-
</p>
|
|
421
|
-
</div>
|
|
422
|
-
);
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
/* ─── Privacy ─── */
|
|
426
|
-
function Privacy() {
|
|
427
|
-
return (
|
|
428
|
-
<section id="privacy" className="py-24 sm:py-32">
|
|
429
|
-
<div className="mx-auto max-w-5xl px-6">
|
|
430
|
-
<div className="max-w-2xl">
|
|
431
|
-
<p className="font-mono text-sm tracking-wide text-lime-500 uppercase">
|
|
432
|
-
Privacy
|
|
433
|
-
</p>
|
|
434
|
-
<h2 className="mt-4 text-3xl font-extrabold tracking-tight text-stone-950 sm:text-4xl">
|
|
435
|
-
Your financial data is never stored outside your machine.
|
|
436
|
-
</h2>
|
|
437
|
-
<p className="mt-6 text-lg leading-relaxed text-stone-500">
|
|
438
|
-
Ray runs entirely on your computer. There’s no cloud, no
|
|
439
|
-
account, no server storing your data. Your financial history lives
|
|
440
|
-
in an encrypted database on your hard drive, and your name is
|
|
441
|
-
scrubbed before anything reaches the AI.
|
|
442
|
-
</p>
|
|
443
|
-
</div>
|
|
444
|
-
|
|
445
|
-
<div className="mt-16">
|
|
446
|
-
<PIIScramble />
|
|
447
|
-
</div>
|
|
448
|
-
|
|
449
|
-
<div className="mt-12 grid gap-8 sm:grid-cols-2 lg:grid-cols-4">
|
|
450
|
-
<PrivacyCard
|
|
451
|
-
title="Encrypted at rest"
|
|
452
|
-
description="AES-256 encrypted database with scrypt key derivation. File permissions locked to your user account."
|
|
453
|
-
href="https://github.com/cdinnison/ray-finance/blob/main/src/db/schema.ts"
|
|
454
|
-
/>
|
|
455
|
-
<PrivacyCard
|
|
456
|
-
title="No cloud storage"
|
|
457
|
-
description="Everything stays in ~/.ray on your machine. Even with a Ray API key, data is processed in-flight and never stored on our servers."
|
|
458
|
-
/>
|
|
459
|
-
<PrivacyCard
|
|
460
|
-
title="Fully auditable"
|
|
461
|
-
description="Every AI tool call is logged locally. You can see exactly what data was accessed and when."
|
|
462
|
-
href="https://github.com/cdinnison/ray-finance/blob/main/src/ai/agent.ts"
|
|
463
|
-
/>
|
|
464
|
-
<PrivacyCard
|
|
465
|
-
title="Three outbound calls"
|
|
466
|
-
description="Two outbound calls: Plaid for bank sync, Anthropic for AI chat (PII-masked). No telemetry. No analytics."
|
|
467
|
-
href="https://github.com/cdinnison/ray-finance/blob/main/src/plaid/client.ts"
|
|
468
|
-
/>
|
|
469
|
-
</div>
|
|
470
|
-
</div>
|
|
471
|
-
</section>
|
|
472
|
-
);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
function PrivacyCard({
|
|
476
|
-
title,
|
|
477
|
-
description,
|
|
478
|
-
href,
|
|
479
|
-
}: {
|
|
480
|
-
title: string;
|
|
481
|
-
description: string;
|
|
482
|
-
href?: string;
|
|
483
|
-
}) {
|
|
484
|
-
return (
|
|
485
|
-
<div className="rounded-xl border border-stone-200 bg-white p-6">
|
|
486
|
-
<h3 className="text-base font-semibold text-stone-900">{title}</h3>
|
|
487
|
-
<p className="mt-2 text-sm leading-relaxed text-stone-500">
|
|
488
|
-
{description}
|
|
489
|
-
</p>
|
|
490
|
-
{href && (
|
|
491
|
-
<a
|
|
492
|
-
href={href}
|
|
493
|
-
className="mt-3 inline-block py-2 font-mono text-xs text-stone-400 underline decoration-stone-300 underline-offset-4 transition-colors hover:text-stone-600"
|
|
494
|
-
>
|
|
495
|
-
view source
|
|
496
|
-
</a>
|
|
497
|
-
)}
|
|
498
|
-
</div>
|
|
499
|
-
);
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
/* ─── Features ─── */
|
|
503
|
-
function Features() {
|
|
504
|
-
return (
|
|
505
|
-
<section id="features" className="border-t border-stone-200 bg-stone-100/50 py-24 sm:py-32">
|
|
506
|
-
<div className="mx-auto max-w-5xl px-6">
|
|
507
|
-
<p className="font-mono text-sm tracking-wide text-lime-500 uppercase">
|
|
508
|
-
What Ray can do
|
|
509
|
-
</p>
|
|
510
|
-
<h2 className="mt-4 text-3xl font-extrabold tracking-tight text-stone-950 sm:text-4xl">
|
|
511
|
-
Ask a question. Get an answer from your actual numbers.
|
|
512
|
-
</h2>
|
|
513
|
-
<p className="mt-4 max-w-2xl text-lg text-stone-500">
|
|
514
|
-
Ray has 30+ tools that query your real financial data. It looks
|
|
515
|
-
things up, runs calculations, and takes action.
|
|
516
|
-
</p>
|
|
517
|
-
|
|
518
|
-
<div className="mt-16 grid gap-x-12 gap-y-10 sm:grid-cols-2 lg:grid-cols-3">
|
|
519
|
-
<Feature
|
|
520
|
-
question={`"Where is all my money going?"`}
|
|
521
|
-
description="Category breakdowns, period comparisons, and trend detection. Ray finds the patterns you miss in your own spending."
|
|
522
|
-
/>
|
|
523
|
-
<Feature
|
|
524
|
-
question={`"Should I pay off debt or invest?"`}
|
|
525
|
-
description="Ray simulates avalanche, snowball, and custom payoff strategies against your real balances and interest rates. Actual math, not rules of thumb."
|
|
526
|
-
/>
|
|
527
|
-
<Feature
|
|
528
|
-
question={`"Am I on track this month?"`}
|
|
529
|
-
description="Budget pacing that warns you before you overspend, not after. Set limits in conversation and Ray tracks against real transactions."
|
|
530
|
-
/>
|
|
531
|
-
<Feature
|
|
532
|
-
question={`"Can I afford to take this trip?"`}
|
|
533
|
-
description="Ray projects your balance forward based on actual income and spending patterns. See the impact before you commit."
|
|
534
|
-
/>
|
|
535
|
-
<Feature
|
|
536
|
-
question={`"How are my investments doing?"`}
|
|
537
|
-
description="Holdings, cost basis, gains and losses pulled directly from your brokerage. One question replaces logging into three apps."
|
|
538
|
-
/>
|
|
539
|
-
<Feature
|
|
540
|
-
question={`"How's my score today?"`}
|
|
541
|
-
description="A daily 0-100 behavior score with streaks and unlockable achievements. No restaurants for a week? That's Kitchen Hero. Five zero-spend days? Monk Mode. It turns financial discipline into a game you actually want to play."
|
|
542
|
-
/>
|
|
543
|
-
<Feature
|
|
544
|
-
question={`"What did we decide last time?"`}
|
|
545
|
-
description="Ray remembers your goals, preferences, life events, and past decisions. Every conversation builds on the last one."
|
|
546
|
-
/>
|
|
547
|
-
</div>
|
|
548
|
-
</div>
|
|
549
|
-
</section>
|
|
550
|
-
);
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
function Feature({
|
|
554
|
-
question,
|
|
555
|
-
description,
|
|
556
|
-
}: {
|
|
557
|
-
question: string;
|
|
558
|
-
description: string;
|
|
559
|
-
}) {
|
|
560
|
-
return (
|
|
561
|
-
<div>
|
|
562
|
-
<h3 className="font-mono text-base font-medium text-stone-900">
|
|
563
|
-
{question}
|
|
564
|
-
</h3>
|
|
565
|
-
<p className="mt-2 text-sm leading-relaxed text-stone-500">
|
|
566
|
-
{description}
|
|
567
|
-
</p>
|
|
568
|
-
</div>
|
|
569
|
-
);
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
/* ─── Pricing ─── */
|
|
573
|
-
function Pricing() {
|
|
574
|
-
return (
|
|
575
|
-
<section id="pricing" className="py-24 sm:py-32">
|
|
576
|
-
<div className="mx-auto max-w-5xl px-6">
|
|
577
|
-
<div className="text-center">
|
|
578
|
-
<p className="font-mono text-sm tracking-wide text-lime-500 uppercase">
|
|
579
|
-
Pricing
|
|
580
|
-
</p>
|
|
581
|
-
<h2 className="mt-4 text-3xl font-extrabold tracking-tight text-stone-950 sm:text-4xl">
|
|
582
|
-
Free forever. Or skip the setup.
|
|
583
|
-
</h2>
|
|
584
|
-
</div>
|
|
585
|
-
|
|
586
|
-
<div className="mx-auto mt-16 grid max-w-4xl gap-8 sm:grid-cols-2">
|
|
587
|
-
{/* Self-Hosted */}
|
|
588
|
-
<div className="rounded-2xl border-2 border-lime-400 bg-white p-8">
|
|
589
|
-
<div className="flex items-center gap-3">
|
|
590
|
-
<h3 className="text-lg font-bold text-stone-900">Self-Hosted</h3>
|
|
591
|
-
<span className="rounded-full bg-lime-400/10 px-2.5 py-0.5 text-xs font-medium text-lime-600">
|
|
592
|
-
full control
|
|
593
|
-
</span>
|
|
594
|
-
</div>
|
|
595
|
-
<p className="mt-1 text-sm text-stone-500">Bring your own keys</p>
|
|
596
|
-
<p className="mt-6">
|
|
597
|
-
<span className="text-4xl font-extrabold tracking-tight text-stone-900">
|
|
598
|
-
$0
|
|
599
|
-
</span>
|
|
600
|
-
<span className="text-sm text-stone-500">/forever</span>
|
|
601
|
-
</p>
|
|
602
|
-
<ul className="mt-8 space-y-3 text-sm text-stone-600">
|
|
603
|
-
<PricingItem>Open source, MIT licensed</PricingItem>
|
|
604
|
-
<PricingItem>Your own Anthropic API key</PricingItem>
|
|
605
|
-
<PricingItem>Your own Plaid credentials</PricingItem>
|
|
606
|
-
<PricingItem>Full model selection</PricingItem>
|
|
607
|
-
<PricingItem>All features included</PricingItem>
|
|
608
|
-
</ul>
|
|
609
|
-
<a
|
|
610
|
-
href="https://github.com/cdinnison/ray-finance"
|
|
611
|
-
className="mt-8 block rounded-full bg-stone-900 py-3 text-center text-sm font-semibold text-white transition-colors hover:bg-stone-800"
|
|
612
|
-
>
|
|
613
|
-
View on GitHub
|
|
614
|
-
</a>
|
|
615
|
-
</div>
|
|
616
|
-
|
|
617
|
-
{/* Ray API Key */}
|
|
618
|
-
<div className="rounded-2xl border border-stone-200 bg-white p-8">
|
|
619
|
-
<div className="flex items-center gap-3">
|
|
620
|
-
<h3 className="text-lg font-bold text-stone-900">
|
|
621
|
-
Ray Hosted Keys
|
|
622
|
-
</h3>
|
|
623
|
-
<span className="rounded-full bg-stone-100 px-2.5 py-0.5 text-xs font-medium text-stone-500">
|
|
624
|
-
skip the setup
|
|
625
|
-
</span>
|
|
626
|
-
</div>
|
|
627
|
-
<p className="mt-1 text-sm text-stone-500">
|
|
628
|
-
We handle everything
|
|
629
|
-
</p>
|
|
630
|
-
<p className="mt-6">
|
|
631
|
-
<span className="text-4xl font-extrabold tracking-tight text-stone-900">
|
|
632
|
-
$10
|
|
633
|
-
</span>
|
|
634
|
-
<span className="text-sm text-stone-500">/month</span>
|
|
635
|
-
</p>
|
|
636
|
-
<ul className="mt-8 space-y-3 text-sm text-stone-600">
|
|
637
|
-
<PricingItem>AI and bank access included</PricingItem>
|
|
638
|
-
<PricingItem>No Plaid application needed</PricingItem>
|
|
639
|
-
<PricingItem>Ready in 2 minutes</PricingItem>
|
|
640
|
-
<PricingItem>Same privacy guarantees</PricingItem>
|
|
641
|
-
<PricingItem>All features included</PricingItem>
|
|
642
|
-
</ul>
|
|
643
|
-
<CopyCommand
|
|
644
|
-
command="npx ray-finance"
|
|
645
|
-
className="mt-8 block rounded-full bg-stone-50 px-4 py-3 text-center text-sm text-stone-600 transition-colors hover:bg-stone-100"
|
|
646
|
-
/>
|
|
647
|
-
</div>
|
|
648
|
-
</div>
|
|
649
|
-
</div>
|
|
650
|
-
</section>
|
|
651
|
-
);
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
function PricingItem({ children }: { children: React.ReactNode }) {
|
|
655
|
-
return (
|
|
656
|
-
<li className="flex items-start gap-2">
|
|
657
|
-
<svg
|
|
658
|
-
className="mt-0.5 h-4 w-4 shrink-0 text-lime-500"
|
|
659
|
-
fill="none"
|
|
660
|
-
viewBox="0 0 24 24"
|
|
661
|
-
strokeWidth={2.5}
|
|
662
|
-
stroke="currentColor"
|
|
663
|
-
aria-hidden="true"
|
|
664
|
-
>
|
|
665
|
-
<path
|
|
666
|
-
strokeLinecap="round"
|
|
667
|
-
strokeLinejoin="round"
|
|
668
|
-
d="M4.5 12.75l6 6 9-13.5"
|
|
669
|
-
/>
|
|
670
|
-
</svg>
|
|
671
|
-
{children}
|
|
672
|
-
</li>
|
|
673
|
-
);
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
/* ─── CTA ─── */
|
|
677
|
-
function CTA() {
|
|
678
|
-
return (
|
|
679
|
-
<section className="bg-stone-950 py-24 sm:py-32">
|
|
680
|
-
<div className="mx-auto max-w-3xl px-6 text-center">
|
|
681
|
-
<h2 className="text-2xl leading-[1.3] font-extrabold tracking-tight text-white sm:text-3xl lg:text-5xl">
|
|
682
|
-
You’re already making financial decisions without the full picture.
|
|
683
|
-
</h2>
|
|
684
|
-
<p className="mx-auto mt-6 max-w-xl text-lg text-stone-400">
|
|
685
|
-
Ray is free, open source, and takes five minutes to set up.
|
|
686
|
-
Use a Ray API key for instant setup, or bring your own keys.
|
|
687
|
-
</p>
|
|
688
|
-
<div className="mt-10 flex flex-col items-center gap-6">
|
|
689
|
-
<CopyCommand
|
|
690
|
-
command="npx ray-finance"
|
|
691
|
-
className="rounded-full border border-stone-800 bg-stone-900 px-6 py-3.5 text-sm text-lime-400 [&>span:first-child]:text-stone-500"
|
|
692
|
-
/>
|
|
693
|
-
<div className="flex items-center gap-6">
|
|
694
|
-
<a
|
|
695
|
-
href="https://github.com/cdinnison/ray-finance"
|
|
696
|
-
className="inline-flex items-center gap-2 rounded-full bg-white px-5 py-2.5 text-sm font-semibold text-stone-900 transition-colors hover:bg-stone-100"
|
|
697
|
-
>
|
|
698
|
-
<GitHubIcon />
|
|
699
|
-
Star on GitHub
|
|
700
|
-
</a>
|
|
701
|
-
<a
|
|
702
|
-
href="https://github.com/cdinnison/ray-finance#readme"
|
|
703
|
-
className="text-sm font-medium text-stone-400 underline decoration-stone-700 underline-offset-4 transition-colors hover:text-white"
|
|
704
|
-
>
|
|
705
|
-
Read the docs
|
|
706
|
-
</a>
|
|
707
|
-
</div>
|
|
708
|
-
</div>
|
|
709
|
-
</div>
|
|
710
|
-
</section>
|
|
711
|
-
);
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
/* ─── Built By ─── */
|
|
715
|
-
function BuiltBy() {
|
|
716
|
-
return (
|
|
717
|
-
<section className="py-24 sm:py-32">
|
|
718
|
-
<div className="mx-auto max-w-2xl px-6 text-center">
|
|
719
|
-
<p className="font-mono text-sm tracking-wide text-stone-400 uppercase">
|
|
720
|
-
Why I built this
|
|
721
|
-
</p>
|
|
722
|
-
<p className="mt-6 text-lg leading-relaxed text-stone-500">
|
|
723
|
-
I tried every finance app, built every spreadsheet, and talked to
|
|
724
|
-
a financial advisor who charged $200/hr to tell me things I already
|
|
725
|
-
knew. Nothing actually helped me make better decisions with my own
|
|
726
|
-
money. So I built the thing I wanted — an advisor that
|
|
727
|
-
knows my real numbers, runs locally, and is honest enough to
|
|
728
|
-
open‑source.
|
|
729
|
-
</p>
|
|
730
|
-
<p className="mt-6 text-sm text-stone-400">
|
|
731
|
-
—{" "}
|
|
732
|
-
<a
|
|
733
|
-
href="https://github.com/cdinnison"
|
|
734
|
-
className="underline decoration-stone-300 underline-offset-4 transition-colors hover:text-stone-600"
|
|
735
|
-
>
|
|
736
|
-
Clark Dinnison
|
|
737
|
-
</a>
|
|
738
|
-
</p>
|
|
739
|
-
</div>
|
|
740
|
-
</section>
|
|
741
|
-
);
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
/* ─── Footer ─── */
|
|
745
|
-
function Footer() {
|
|
746
|
-
return (
|
|
747
|
-
<footer className="border-t border-stone-200 py-8">
|
|
748
|
-
<div className="mx-auto max-w-5xl px-6">
|
|
749
|
-
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
|
750
|
-
<span className="font-pixel text-sm text-stone-400">ray</span>
|
|
751
|
-
<p className="text-sm text-stone-400">
|
|
752
|
-
Open source under MIT.
|
|
753
|
-
</p>
|
|
754
|
-
</div>
|
|
755
|
-
<p className="mt-4 max-w-xl text-left text-xs leading-relaxed text-stone-400/70">
|
|
756
|
-
Ray is an AI tool, not a licensed financial advisor. Output is
|
|
757
|
-
informational, may be inaccurate, and does not constitute financial
|
|
758
|
-
advice. Consult a qualified professional before making financial
|
|
759
|
-
decisions.
|
|
760
|
-
</p>
|
|
761
|
-
</div>
|
|
762
|
-
</footer>
|
|
763
|
-
);
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
/* ─── Helpers ─── */
|
|
767
|
-
function Line({
|
|
768
|
-
children,
|
|
769
|
-
dim,
|
|
770
|
-
prompt,
|
|
771
|
-
}: {
|
|
772
|
-
children: React.ReactNode;
|
|
773
|
-
dim?: boolean;
|
|
774
|
-
prompt?: boolean;
|
|
775
|
-
}) {
|
|
776
|
-
return (
|
|
777
|
-
<p className={dim ? "text-stone-500" : "text-stone-300"}>
|
|
778
|
-
{prompt && <span className="text-stone-500" aria-hidden="true">{"❯ "}</span>}
|
|
779
|
-
{children}
|
|
780
|
-
</p>
|
|
781
|
-
);
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
function Blank() {
|
|
785
|
-
return <p className="h-5" />;
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
function G({ children }: { children: React.ReactNode }) {
|
|
789
|
-
return <span className="text-lime-400">{children}</span>;
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
function R({ children }: { children: React.ReactNode }) {
|
|
793
|
-
return <span className="text-red-400">{children}</span>;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
function Y({ children }: { children: React.ReactNode }) {
|
|
797
|
-
return <span className="text-amber-400">{children}</span>;
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
function W({ children }: { children: React.ReactNode }) {
|
|
801
|
-
return <span className="text-white">{children}</span>;
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
function D({ children }: { children: React.ReactNode }) {
|
|
805
|
-
return <span className="text-stone-500">{children}</span>;
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
function CSSBar({ pct, color }: { pct: number; color: "lime" | "amber" }) {
|
|
809
|
-
const total = 8;
|
|
810
|
-
const filled = Math.round((Math.min(pct, 100) / 100) * total);
|
|
811
|
-
const fillColor = color === "amber" ? "#fbbf24" : "#87da26";
|
|
812
|
-
const emptyColor = "#292524";
|
|
813
|
-
// Each block is a 6x10 rect with 1px gaps, mimicking terminal block chars
|
|
814
|
-
return (
|
|
815
|
-
<svg width={total * 7} height={10} className="inline-block align-middle" aria-hidden="true">
|
|
816
|
-
{Array.from({ length: total }, (_, i) => (
|
|
817
|
-
<rect
|
|
818
|
-
key={i}
|
|
819
|
-
x={i * 7}
|
|
820
|
-
y={0}
|
|
821
|
-
width={6}
|
|
822
|
-
height={10}
|
|
823
|
-
rx={1}
|
|
824
|
-
fill={i < filled ? fillColor : emptyColor}
|
|
825
|
-
/>
|
|
826
|
-
))}
|
|
827
|
-
</svg>
|
|
828
|
-
);
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
function GitHubIcon() {
|
|
832
|
-
return (
|
|
833
|
-
<svg className="h-4 w-4" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
834
|
-
<path
|
|
835
|
-
fillRule="evenodd"
|
|
836
|
-
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
|
|
837
|
-
clipRule="evenodd"
|
|
838
|
-
/>
|
|
839
|
-
</svg>
|
|
840
|
-
);
|
|
841
|
-
}
|