个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证正在加载完整 README。 以下为已保存的摘录。
A small Flask app for tracking stocks, built with Claude Code. It has two tabs:
• Price vs SMA150 (/) — price + 150-day moving average + volume chart per ticker.
• Financials (/financials) — quarterly/annual revenue, profit, income, free cash flow, and EPS charts for a single ticker.
It's multi-user: sign in with Google and you get your own watch list, stored in Postgres (Neon). Every page requires login; a brand-new account starts with an empty list.查看完整 README ↗A small Flask app for tracking stocks, built with Claude Code. It has two tabs:
/) — price + 150-day moving average + volume chart per ticker./financials) — quarterly/annual revenue, profit, income, free cash flow, and EPS charts for a single ticker.It's multi-user: sign in with Google and you get your own watch list, stored in Postgres (Neon). Every page requires login; a brand-new account starts with an empty list.
The list is editable from the Price vs SMA150 tab: type a symbol in Add symbol to append it, or click the × on a chip to drop it. Changes save immediately and apply to both of your tabs. Clicking the chip itself (not the ×) still just toggles whether that ticker is charted right now.
app.py # entry point, registers blueprints, /healthz
auth.py # Google SSO (Authlib), session cookie, login gate
db.py # Postgres pool + app_config / app_user / user_config access
symbols.py # global config (cached 60s) + per-user watch lists (never cached)
init_db.py # creates the tables and seeds them from config.json
blueprints/
price.py # / and /api/stocks
financials.py # /financials and /api/financials
templates/
base.html # shared layout + tab nav + signed-in user / sign out
login.html
price.html
financials.html
public/
css/style.css
js/price.js
js/dashboard.js
config.json # seed values for the database only (not read at runtime)
.env # secrets (gitignored, see .env.example)
Google sign-in (OpenID Connect) handled server-side by Authlib. The session is a
signed cookie — permanent for 30 days, HttpOnly, SameSite=Lax, and Secure
whenever DEBUG is off — so returning users are already authenticated and no
session store is needed across gunicorn workers.
Login is enforced by a global before_request gate in auth.py, so new routes are
protected by default; only /login, the /auth/... routes, /healthz, and static
files are public. State-changing requests are additionally rejected if their
Origin header isn't this host.
To set it up, create an OAuth client (Google Cloud Console → APIs & Services → Credentials → OAuth client ID → Web application) and add these authorized redirect URIs:
http://127.0.0.1:5000/auth/google/callback
https://YOUR-DEPLOYED-HOST/auth/google/callback
Put the client ID/secret plus a random SECRET_KEY in .env. For local work
without Google credentials, set [email protected] — that adds a
one-click bypass button to the login page. It's ignored unless DEBUG=true, and
must never be set in production.
Three tables (db.py):
app_config(key text primary key, value jsonb, updated_at) -- global
app_user(id, email unique, provider, provider_sub, name, ...) -- accounts
user_config(user_id -> app_user, key, value jsonb, updated_at) -- per user
Global keys mirror the old config.json top level: default_range, alerts,
plus default_stocks/owner_email used only for seeding. Watch lists are
per-user in user_config; a new account gets default_stocks = [] and other
keys fall back to app_config.
The list is normally edited from the Price tab, which calls POST /api/symbols
({"symbol": "PLTR"}) and DELETE /api/symbols/<symbol> — both scoped to the
signed-in user. Direct SQL for one user:
UPDATE user_config SET value = '["AAPL","MSFT","NVDA"]', updated_at = now()
WHERE key = 'default_stocks'
AND user_id = (SELECT id FROM app_user WHERE email = '[email protected]');
pip install -r requirements.txt
cp .env.example .env # Neon URL, SECRET_KEY, Google client ID/secret
python init_db.py # create the tables and seed them from config.json
python app.py
Then open http://127.0.0.1:5000. Environment variables: DATABASE_URL and
SECRET_KEY (both required — the app refuses to start without a secret key),
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET (required for real login),
DEV_LOGIN_EMAIL (local bypass), plus HOST, PORT, DEBUG.
init_db.py also takes --show (dump global config, users, and each user's
list), --force (overwrite existing values), and --seed-user EMAIL (pre-create
an account with the seed watch list before its first login).
docker build -t investclaudecode:dev01 .
docker run -it --rm -p 5000:5000 --env-file .env -d --name invest investclaudecode:dev01
(see dockerstart.cmd)
Every target needs DATABASE_URL, SECRET_KEY, GOOGLE_CLIENT_ID, and
GOOGLE_CLIENT_SECRET in its environment, and its own callback URL registered
with Google. Health checks use /healthz because / now redirects to login.
app in app.py as one serverless function). Set the four values in the project settings and use Neon's pooled connection string for DATABASE_URL.invest-app-db secret — create it with kubectl create secret rather than committing values.gunicorn app:app; set all four as config vars.Stock prices and financial statements come from yfinance (Yahoo Finance), fetched on demand. The Financials tab caches responses in memory for 24 hours per symbol/period.
The running version is __version__ in app.py; it's also returned by
/healthz ({"status": "ok", "version": "..."}) and shown at the bottom of the
Help page. It matches the top section of CHANGELOG.md and the
vX.Y.Z git tag.
Versioning is MAJOR.MINOR.PATCH:
| Bump | When | e.g. |
|---|---|---|
| MAJOR | A deploy needs manual work | DB migration, renamed/removed env var, auth change, redesign |
| MINOR | New backward-compatible feature | a new tab or endpoint |
| PATCH | Fixes and polish only | bug fixes, copy, styling, refactors |
To cut a release: set __version__ in app.py to the new X.Y.Z and merge
dev → main. That's the only manual step.
The Release workflow then runs
git-cliff (config: cliff.toml) to build
the ## [X.Y.Z] section of CHANGELOG.md from the commit messages since the last
tag, commits the updated file back to main, tags vX.Y.Z, and publishes a
GitHub Release with that section as the notes. Merging to main without bumping
__version__ is a no-op.
Don't edit CHANGELOG.md by hand — it's generated. Just write commit
summaries that start with a verb (Add / Fix / Remove / Change) or a
Conventional Commits prefix (feat: / fix:); git-cliff groups them into
Added / Fixed / Removed / Changed. dev's copy of the file lags main between
releases, which is expected and merges cleanly.
git describe --tags gives an interim version between releases
(v2.1.0-7-gabc1234).