codedna 0.8.2__tar.gz → 0.8.4__tar.gz
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.
- {codedna-0.8.2 → codedna-0.8.4}/PKG-INFO +1 -1
- {codedna-0.8.2 → codedna-0.8.4}/codedna/__init__.py +1 -1
- {codedna-0.8.2 → codedna-0.8.4}/codedna/cli.py +44 -11
- {codedna-0.8.2 → codedna-0.8.4}/codedna/db.py +19 -9
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/app/ai-compare/page.tsx +1 -2
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/app/api/billing/checkout/route.ts +1 -1
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/app/billing/page.tsx +3 -3
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/app/layout.tsx +1 -1
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/app/protected/page.tsx +1 -1
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/components/ErrorBanner.tsx +4 -5
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/components/NavClient.tsx +9 -9
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/lib/api.ts +0 -4
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/lib/auth.ts +0 -41
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/lib/plan.ts +14 -9
- {codedna-0.8.2 → codedna-0.8.4}/pyproject.toml +1 -1
- {codedna-0.8.2 → codedna-0.8.4}/.gitignore +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/LICENSE +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/README.md +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/codedna/ai.py +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/codedna/ai_box.py +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/codedna/ai_fingerprint.py +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/codedna/analyzer.py +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/codedna/api.py +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/codedna/auth.py +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/codedna/bus_factor.py +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/codedna/demo.py +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/codedna/git_hook.py +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/codedna/integrations/__init__.py +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/codedna/integrations/github_bot.py +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/codedna/integrations/jira.py +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/codedna/integrations/lemonsqueezy.py +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/codedna/interview.py +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/codedna/onboarding.py +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/codedna/plan.py +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/codedna/protection.py +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/codedna/rate_limit.py +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/codedna/scorer.py +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/codedna/sprint_health.py +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/codedna/survey.py +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/codedna/tech_debt.py +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/.env.local.example +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/.gitignore +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/README.md +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/app/api/auth/login/route.ts +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/app/api/auth/logout/route.ts +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/app/api/auth/me/route.ts +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/app/api/auth/register/route.ts +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/app/bus-factor/page.tsx +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/app/commits/page.tsx +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/app/debt/page.tsx +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/app/favicon.ico +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/app/files/page.tsx +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/app/fonts/GeistMonoVF.woff +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/app/fonts/GeistVF.woff +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/app/globals.css +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/app/interview/page.tsx +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/app/login/page.tsx +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/app/onboarding/page.tsx +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/app/page.tsx +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/app/pricing/page.tsx +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/app/register/page.tsx +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/app/settings/integrations/page.tsx +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/app/sprints/page.tsx +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/components/AuthProvider.tsx +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/components/ClientProviders.tsx +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/components/CommitTable.tsx +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/components/CostInfoTooltip.tsx +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/components/FeatureGate.tsx +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/components/FileHeatmap.tsx +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/components/LanguageSwitcher.tsx +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/components/OverviewTitles.tsx +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/components/QuickInsights.tsx +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/components/RiskBadge.tsx +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/components/SummaryCards.tsx +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/components/UnderstandingChart.tsx +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/components/UpgradeBanner.tsx +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/lib/i18n/en.ts +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/lib/i18n/index.tsx +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/lib/i18n/tr.ts +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/next-env.d.ts +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/next.config.mjs +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/package-lock.json +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/package.json +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/postcss.config.mjs +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/tailwind.config.ts +0 -0
- {codedna-0.8.2 → codedna-0.8.4}/dashboard/tsconfig.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codedna
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.4
|
|
4
4
|
Summary: AI Code Transparency Tool - detect AI-written code and measure developer understanding
|
|
5
5
|
Project-URL: Homepage, https://codedna.dev
|
|
6
6
|
Project-URL: Repository, https://github.com/natureco-official/codedna
|
|
@@ -217,6 +217,19 @@ def scan(
|
|
|
217
217
|
f"Risk: [bold {risk_color}]{risk_label}[/bold {risk_color}]\n"
|
|
218
218
|
)
|
|
219
219
|
|
|
220
|
+
# AI analysis box
|
|
221
|
+
try:
|
|
222
|
+
from codedna.ai_box import print_ai_box
|
|
223
|
+
summary = (
|
|
224
|
+
f"CodeDNA scan: {len(results)} files\n"
|
|
225
|
+
f"Avg AI probability: {avg_ai:.0f}%\n"
|
|
226
|
+
f"Risk level: {risk_label}\n"
|
|
227
|
+
f"Files: {', '.join(str(root / s.file_path) for s in results[:10])}"
|
|
228
|
+
)
|
|
229
|
+
print_ai_box("codedna scan", summary)
|
|
230
|
+
except Exception:
|
|
231
|
+
pass
|
|
232
|
+
|
|
220
233
|
|
|
221
234
|
# ---------------------------------------------------------------------------
|
|
222
235
|
# codedna status
|
|
@@ -283,8 +296,8 @@ def status(
|
|
|
283
296
|
)
|
|
284
297
|
|
|
285
298
|
# Show summary
|
|
299
|
+
avg_ai = sum(s.ai_probability for s in results) / len(results) if results else 0.0
|
|
286
300
|
if results:
|
|
287
|
-
avg_ai = sum(s.ai_probability for s in results) / len(results)
|
|
288
301
|
risk_label, risk_color = _risk_label(avg_ai * 100)
|
|
289
302
|
|
|
290
303
|
understanding_display = (
|
|
@@ -316,13 +329,32 @@ def status(
|
|
|
316
329
|
console.print(f"[bold red]{warning}[/bold red]")
|
|
317
330
|
except Exception:
|
|
318
331
|
pass # Never breaks the hook
|
|
332
|
+
return
|
|
319
333
|
|
|
320
|
-
|
|
334
|
+
if not results:
|
|
321
335
|
console.print(
|
|
322
336
|
f"[bold]Commit:[/bold] [dim]{commit_hash[:8]}[/dim]\n"
|
|
323
337
|
"[dim]No supported code files found in this commit.[/dim]"
|
|
324
338
|
)
|
|
325
339
|
|
|
340
|
+
# AI analysis box
|
|
341
|
+
try:
|
|
342
|
+
from codedna.ai_box import print_ai_box
|
|
343
|
+
if results:
|
|
344
|
+
status_summary = (
|
|
345
|
+
f"Commit: {commit_hash[:8]}\n"
|
|
346
|
+
f"Author: {author}\n"
|
|
347
|
+
f"Message: {message}\n"
|
|
348
|
+
f"Files changed: {len(results)}\n"
|
|
349
|
+
f"Avg AI probability: {avg_ai*100:.0f}%\n"
|
|
350
|
+
f"Files: {', '.join(s.file_path for s in results[:5])}"
|
|
351
|
+
)
|
|
352
|
+
else:
|
|
353
|
+
status_summary = f"Commit: {commit_hash[:8]}\nNo supported code files."
|
|
354
|
+
print_ai_box("codedna status", status_summary)
|
|
355
|
+
except Exception:
|
|
356
|
+
pass
|
|
357
|
+
|
|
326
358
|
|
|
327
359
|
# ---------------------------------------------------------------------------
|
|
328
360
|
# codedna history
|
|
@@ -1093,8 +1125,7 @@ def bus_factor(
|
|
|
1093
1125
|
if not is_feature_available("bus_factor"):
|
|
1094
1126
|
console.print(
|
|
1095
1127
|
Panel(
|
|
1096
|
-
"[bold yellow]🔒 This feature is available on Team plan.[/bold yellow]\n"
|
|
1097
|
-
"[dim]This feature is available on Team plan.[/dim]\n\n"
|
|
1128
|
+
"[bold yellow]🔒 This feature is available on Team plan.[/bold yellow]\n\n"
|
|
1098
1129
|
"[dim]To upgrade:[/dim] [cyan]codedna plan activate <LICENSE_KEY>[/cyan]",
|
|
1099
1130
|
border_style="yellow",
|
|
1100
1131
|
padding=(1, 2),
|
|
@@ -1311,8 +1342,7 @@ def sprint_create(
|
|
|
1311
1342
|
if not is_feature_available("sprint_health"):
|
|
1312
1343
|
console.print(
|
|
1313
1344
|
Panel(
|
|
1314
|
-
"[bold yellow]🔒 This feature is available on Team plan.[/bold yellow]\n"
|
|
1315
|
-
"[dim]This feature is available on Team plan.[/dim]\n\n"
|
|
1345
|
+
"[bold yellow]🔒 This feature is available on Team plan.[/bold yellow]\n\n"
|
|
1316
1346
|
"[dim]To upgrade:[/dim] [cyan]codedna plan activate <LICENSE_KEY>[/cyan]",
|
|
1317
1347
|
border_style="yellow",
|
|
1318
1348
|
padding=(1, 2),
|
|
@@ -1719,7 +1749,7 @@ def uninstall(
|
|
|
1719
1749
|
"""Remove CodeDNA hook."""
|
|
1720
1750
|
root = repo or find_git_root()
|
|
1721
1751
|
if uninstall_hook(root):
|
|
1722
|
-
console.print("[green]✓[/green] CodeDNA
|
|
1752
|
+
console.print("[green]✓[/green] CodeDNA hook removed.")
|
|
1723
1753
|
else:
|
|
1724
1754
|
raise typer.Exit(1)
|
|
1725
1755
|
|
|
@@ -1850,11 +1880,11 @@ def doctor(
|
|
|
1850
1880
|
deps = [
|
|
1851
1881
|
("typer", "CLI framework"),
|
|
1852
1882
|
("rich", "Terminal UI"),
|
|
1853
|
-
("
|
|
1883
|
+
("git", "Git integration"),
|
|
1854
1884
|
("fastapi", "REST API"),
|
|
1855
1885
|
("uvicorn", "ASGI server"),
|
|
1856
1886
|
("pydantic", "Data validation"),
|
|
1857
|
-
("
|
|
1887
|
+
("jwt", "JWT auth"),
|
|
1858
1888
|
("bcrypt", "Password hashing"),
|
|
1859
1889
|
]
|
|
1860
1890
|
for mod_name, desc in deps:
|
|
@@ -2086,12 +2116,14 @@ def update(
|
|
|
2086
2116
|
def demo(
|
|
2087
2117
|
reset: bool = typer.Option(False, "--reset", help="Clear all demo data"),
|
|
2088
2118
|
data_only: bool = typer.Option(False, "--data-only", help="Seed data only, don't start dashboard"),
|
|
2119
|
+
repo: Optional[Path] = typer.Option(None, "--repo", "-r", help="Git repo directory"),
|
|
2089
2120
|
) -> None:
|
|
2090
2121
|
"""Load demo data into the database (idempotent) and open the dashboard."""
|
|
2091
2122
|
from codedna.demo import seed_demo_data, clear_demo_data, is_demo_active
|
|
2092
2123
|
from codedna.db import get_db_path
|
|
2093
2124
|
|
|
2094
|
-
|
|
2125
|
+
root = repo or find_git_root() or Path.cwd()
|
|
2126
|
+
db_path = get_db_path(root)
|
|
2095
2127
|
|
|
2096
2128
|
console.print()
|
|
2097
2129
|
if reset:
|
|
@@ -2175,6 +2207,7 @@ def demo(
|
|
|
2175
2207
|
@app.command(name="security-check")
|
|
2176
2208
|
def security_check(
|
|
2177
2209
|
path: Optional[Path] = typer.Option(None, "--path", "-p", help="Project root to scan (defaults to cwd)"),
|
|
2210
|
+
repo: Optional[Path] = typer.Option(None, "--repo", "-r", help="Git repo directory"),
|
|
2178
2211
|
strict: bool = typer.Option(False, "--strict", help="Exit 1 on any warning"),
|
|
2179
2212
|
) -> None:
|
|
2180
2213
|
"""Scan the project for personal paths, secrets, and missing .gitignore rules.
|
|
@@ -2188,7 +2221,7 @@ def security_check(
|
|
|
2188
2221
|
from codedna.git_hook import find_git_root
|
|
2189
2222
|
import re
|
|
2190
2223
|
|
|
2191
|
-
root = path or find_git_root() or Path.cwd()
|
|
2224
|
+
root = repo or path or find_git_root() or Path.cwd()
|
|
2192
2225
|
if not root.exists():
|
|
2193
2226
|
console.print(f" [red]✗[/red] Path not found: {root}")
|
|
2194
2227
|
raise typer.Exit(1)
|
|
@@ -119,15 +119,25 @@ def save_commit(
|
|
|
119
119
|
) -> None:
|
|
120
120
|
"""Save or update a commit record."""
|
|
121
121
|
with get_connection(db_path) as conn:
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
122
|
+
if understanding_score is not None:
|
|
123
|
+
conn.execute(
|
|
124
|
+
"""
|
|
125
|
+
INSERT INTO commits (commit_hash, author, timestamp, files_changed, understanding_score)
|
|
126
|
+
VALUES (?, ?, ?, ?, ?)
|
|
127
|
+
ON CONFLICT(commit_hash) DO UPDATE SET
|
|
128
|
+
understanding_score = excluded.understanding_score
|
|
129
|
+
""",
|
|
130
|
+
(commit_hash, author, timestamp, files_changed, understanding_score),
|
|
131
|
+
)
|
|
132
|
+
else:
|
|
133
|
+
conn.execute(
|
|
134
|
+
"""
|
|
135
|
+
INSERT INTO commits (commit_hash, author, timestamp, files_changed, understanding_score)
|
|
136
|
+
VALUES (?, ?, ?, ?, NULL)
|
|
137
|
+
ON CONFLICT(commit_hash) DO NOTHING
|
|
138
|
+
""",
|
|
139
|
+
(commit_hash, author, timestamp, files_changed),
|
|
140
|
+
)
|
|
131
141
|
|
|
132
142
|
|
|
133
143
|
def save_file_score(
|
|
@@ -6,7 +6,7 @@ import { useTranslation } from "@/lib/i18n";
|
|
|
6
6
|
import { getCurrentPlan } from "@/lib/plan";
|
|
7
7
|
import { FeatureGate } from "@/components/FeatureGate";
|
|
8
8
|
import { ErrorBanner } from "@/components/ErrorBanner";
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
|
|
11
11
|
const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
|
|
12
12
|
|
|
@@ -77,7 +77,6 @@ export default function AIComparePage() {
|
|
|
77
77
|
<div className="flex items-start gap-2 bg-amber-500/10 border border-amber-500/20 rounded-xl px-4 py-3">
|
|
78
78
|
<span className="text-amber-400 mt-0.5">⚠️</span>
|
|
79
79
|
<p className="text-xs text-amber-400/80">{t("ai_compare_disclaimer")}</p>
|
|
80
|
-
<CostInfoTooltip />
|
|
81
80
|
</div>
|
|
82
81
|
|
|
83
82
|
{error && <ErrorBanner />}
|
|
@@ -107,7 +107,7 @@ export default function BillingPage() {
|
|
|
107
107
|
.then((d) => {
|
|
108
108
|
if (d) setSubscription({ plan: d.plan, subscription_status: d.subscription_status, lemonsqueezy_customer_id: null });
|
|
109
109
|
})
|
|
110
|
-
.catch(() =>
|
|
110
|
+
.catch(() => setCheckoutError("Could not load subscription data."));
|
|
111
111
|
}, [user]);
|
|
112
112
|
|
|
113
113
|
const handleUpgrade = async (plan: Plan) => {
|
|
@@ -145,8 +145,8 @@ export default function BillingPage() {
|
|
|
145
145
|
} else {
|
|
146
146
|
alert("Customer portal coming soon. For now, use: codedna plan demo");
|
|
147
147
|
}
|
|
148
|
-
} catch
|
|
149
|
-
alert("Error:
|
|
148
|
+
} catch {
|
|
149
|
+
alert("Error: Could not connect to customer portal.");
|
|
150
150
|
} finally {
|
|
151
151
|
setCancelLoading(false);
|
|
152
152
|
}
|
|
@@ -42,7 +42,7 @@ export default function RootLayout({
|
|
|
42
42
|
<div className="max-w-7xl mx-auto px-6 py-4 text-xs text-gray-700 flex items-center justify-between">
|
|
43
43
|
<span>🧬 CodeDNA</span>
|
|
44
44
|
<a
|
|
45
|
-
href="http://localhost:8000/docs
|
|
45
|
+
href={`${process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000"}/docs`}
|
|
46
46
|
target="_blank"
|
|
47
47
|
rel="noopener noreferrer"
|
|
48
48
|
className="hover:text-gray-500 transition-colors"
|
|
@@ -114,7 +114,7 @@ export default function ProtectedPage() {
|
|
|
114
114
|
const violations = modules.filter((m) => m.status === "VIOLATION");
|
|
115
115
|
|
|
116
116
|
return (
|
|
117
|
-
<FeatureGate feature="
|
|
117
|
+
<FeatureGate feature="protect_modules" plan={plan}>
|
|
118
118
|
<div className="space-y-6">
|
|
119
119
|
<div>
|
|
120
120
|
<h1 className="text-2xl font-bold text-white">🛡️ {t("protect_title")}</h1>
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import { useTranslation } from "@/lib/i18n";
|
|
4
4
|
|
|
5
|
-
/** Banner shown when API connection fails */
|
|
6
5
|
export function ErrorBanner({ message }: { message?: string }) {
|
|
7
6
|
const { t } = useTranslation();
|
|
8
7
|
|
|
@@ -11,11 +10,11 @@ export function ErrorBanner({ message }: { message?: string }) {
|
|
|
11
10
|
<span className="text-red-400 text-lg mt-0.5">⚠️</span>
|
|
12
11
|
<div>
|
|
13
12
|
<p className="text-red-400 font-semibold text-sm">
|
|
14
|
-
{t("error_api")
|
|
15
|
-
</p>
|
|
16
|
-
<p className="text-red-300/70 text-xs mt-0.5">
|
|
17
|
-
{message ?? t("error_api")}
|
|
13
|
+
{t("error_api")}
|
|
18
14
|
</p>
|
|
15
|
+
{message && (
|
|
16
|
+
<p className="text-red-300/70 text-xs mt-0.5">{message}</p>
|
|
17
|
+
)}
|
|
19
18
|
</div>
|
|
20
19
|
</div>
|
|
21
20
|
);
|
|
@@ -21,16 +21,16 @@ export function NavClient() {
|
|
|
21
21
|
<Link href="/" className="text-gray-400 hover:text-cyan-400 transition-colors text-sm font-medium">{t("nav_dashboard")}</Link>
|
|
22
22
|
<Link href="/files" className="text-gray-400 hover:text-cyan-400 transition-colors text-sm font-medium">{t("nav_files")}</Link>
|
|
23
23
|
<Link href="/commits" className="text-gray-400 hover:text-cyan-400 transition-colors text-sm font-medium">{t("nav_commits")}</Link>
|
|
24
|
-
<Link href="/bus-factor" className="text-gray-400 hover:text-cyan-400 transition-colors text-sm font-medium">🚌</Link>
|
|
25
|
-
<Link href="/debt" className="text-gray-400 hover:text-cyan-400 transition-colors text-sm font-medium">💰</Link>
|
|
26
|
-
<Link href="/sprints" className="text-gray-400 hover:text-cyan-400 transition-colors text-sm font-medium">🏃</Link>
|
|
27
|
-
<Link href="/ai-compare" className="text-gray-400 hover:text-cyan-400 transition-colors text-sm font-medium">🤖</Link>
|
|
28
|
-
<Link href="/onboarding" className="text-gray-400 hover:text-cyan-400 transition-colors text-sm font-medium">🚀</Link>
|
|
29
|
-
<Link href="/settings/integrations" className="text-gray-400 hover:text-cyan-400 transition-colors text-sm font-medium">⚙️</Link>
|
|
30
|
-
<Link href="/protected" className="text-gray-400 hover:text-cyan-400 transition-colors text-sm font-medium">🛡️</Link>
|
|
31
|
-
<Link href="/interview" className="text-gray-400 hover:text-cyan-400 transition-colors text-sm font-medium">🎯</Link>
|
|
24
|
+
<Link href="/bus-factor" className="text-gray-400 hover:text-cyan-400 transition-colors text-sm font-medium" aria-label="Bus Factor">🚌</Link>
|
|
25
|
+
<Link href="/debt" className="text-gray-400 hover:text-cyan-400 transition-colors text-sm font-medium" aria-label="Technical Debt">💰</Link>
|
|
26
|
+
<Link href="/sprints" className="text-gray-400 hover:text-cyan-400 transition-colors text-sm font-medium" aria-label="Sprints">🏃</Link>
|
|
27
|
+
<Link href="/ai-compare" className="text-gray-400 hover:text-cyan-400 transition-colors text-sm font-medium" aria-label="AI Comparison">🤖</Link>
|
|
28
|
+
<Link href="/onboarding" className="text-gray-400 hover:text-cyan-400 transition-colors text-sm font-medium" aria-label="Onboarding">🚀</Link>
|
|
29
|
+
<Link href="/settings/integrations" className="text-gray-400 hover:text-cyan-400 transition-colors text-sm font-medium" aria-label="Integrations">⚙️</Link>
|
|
30
|
+
<Link href="/protected" className="text-gray-400 hover:text-cyan-400 transition-colors text-sm font-medium" aria-label="Protected Modules">🛡️</Link>
|
|
31
|
+
<Link href="/interview" className="text-gray-400 hover:text-cyan-400 transition-colors text-sm font-medium" aria-label="Interview">🎯</Link>
|
|
32
32
|
<Link href="/pricing" className="text-gray-400 hover:text-cyan-400 transition-colors text-sm font-medium">{t("nav_pricing")}</Link>
|
|
33
|
-
<a href="http://localhost:8000/docs
|
|
33
|
+
<a href={`${process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000"}/docs`} target="_blank" rel="noopener noreferrer" className="text-gray-600 hover:text-gray-400 transition-colors text-xs">API ↗</a>
|
|
34
34
|
|
|
35
35
|
{/* Auth state */}
|
|
36
36
|
{user ? (
|
|
@@ -95,10 +95,6 @@ async function apiFetch<T>(path: string): Promise<T> {
|
|
|
95
95
|
return res.json() as Promise<T>;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
export async function getHealth(): Promise<HealthResponse> {
|
|
99
|
-
return apiFetch<HealthResponse>("/health");
|
|
100
|
-
}
|
|
101
|
-
|
|
102
98
|
export async function getRepoSummary(): Promise<RepoSummary> {
|
|
103
99
|
return apiFetch<RepoSummary>("/repo/summary");
|
|
104
100
|
}
|
|
@@ -9,8 +9,6 @@
|
|
|
9
9
|
* - The client side only knows whether the user is logged in
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
|
|
13
|
-
|
|
14
12
|
export interface UserData {
|
|
15
13
|
user_id: number;
|
|
16
14
|
email: string;
|
|
@@ -18,12 +16,6 @@ export interface UserData {
|
|
|
18
16
|
subscription_status: string;
|
|
19
17
|
}
|
|
20
18
|
|
|
21
|
-
export interface SubscriptionData {
|
|
22
|
-
plan: string;
|
|
23
|
-
subscription_status: string;
|
|
24
|
-
lemonsqueezy_customer_id: string | null;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
19
|
/** Log in — via Next.js API route (sets httpOnly cookie) */
|
|
28
20
|
export async function login(
|
|
29
21
|
email: string,
|
|
@@ -95,37 +87,4 @@ export async function getCurrentUser(): Promise<UserData | null> {
|
|
|
95
87
|
}
|
|
96
88
|
}
|
|
97
89
|
|
|
98
|
-
/** Request checkout URL directly from FastAPI */
|
|
99
|
-
export async function getCheckoutUrl(
|
|
100
|
-
plan: string,
|
|
101
|
-
token: string
|
|
102
|
-
): Promise<string | null> {
|
|
103
|
-
try {
|
|
104
|
-
const res = await fetch(`${API_URL}/billing/checkout`, {
|
|
105
|
-
method: "POST",
|
|
106
|
-
headers: {
|
|
107
|
-
"Content-Type": "application/json",
|
|
108
|
-
Authorization: `Bearer ${token}`,
|
|
109
|
-
},
|
|
110
|
-
body: JSON.stringify({ plan }),
|
|
111
|
-
});
|
|
112
|
-
if (!res.ok) return null;
|
|
113
|
-
const data = await res.json();
|
|
114
|
-
return data.checkout_url ?? null;
|
|
115
|
-
} catch {
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
90
|
|
|
120
|
-
/** Fetch subscription status */
|
|
121
|
-
export async function getSubscriptionStatus(token: string): Promise<SubscriptionData | null> {
|
|
122
|
-
try {
|
|
123
|
-
const res = await fetch(`${API_URL}/billing/subscription`, {
|
|
124
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
125
|
-
});
|
|
126
|
-
if (!res.ok) return null;
|
|
127
|
-
return res.json();
|
|
128
|
-
} catch {
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
@@ -5,17 +5,18 @@
|
|
|
5
5
|
export type Plan = "free" | "pro" | "team" | "enterprise";
|
|
6
6
|
|
|
7
7
|
export interface PlanLimits {
|
|
8
|
-
max_repos: number;
|
|
9
|
-
max_files_scan: number;
|
|
10
|
-
history_days: number;
|
|
8
|
+
max_repos: number;
|
|
9
|
+
max_files_scan: number;
|
|
10
|
+
history_days: number;
|
|
11
11
|
dashboard_access: boolean;
|
|
12
12
|
github_actions: boolean;
|
|
13
13
|
slack_notify: boolean;
|
|
14
|
-
bus_factor: boolean;
|
|
15
|
-
sprint_health: boolean;
|
|
16
|
-
ai_comparison: boolean;
|
|
17
|
-
team_members: number;
|
|
18
|
-
interview_tool: boolean;
|
|
14
|
+
bus_factor: boolean;
|
|
15
|
+
sprint_health: boolean;
|
|
16
|
+
ai_comparison: boolean;
|
|
17
|
+
team_members: number;
|
|
18
|
+
interview_tool: boolean;
|
|
19
|
+
protect_modules: boolean;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export const PLAN_LIMITS: Record<Plan, PlanLimits> = {
|
|
@@ -23,7 +24,7 @@ export const PLAN_LIMITS: Record<Plan, PlanLimits> = {
|
|
|
23
24
|
max_repos: 1,
|
|
24
25
|
max_files_scan: 50,
|
|
25
26
|
history_days: 7,
|
|
26
|
-
dashboard_access:
|
|
27
|
+
dashboard_access: true,
|
|
27
28
|
github_actions: false,
|
|
28
29
|
slack_notify: false,
|
|
29
30
|
bus_factor: false,
|
|
@@ -31,6 +32,7 @@ export const PLAN_LIMITS: Record<Plan, PlanLimits> = {
|
|
|
31
32
|
ai_comparison: false,
|
|
32
33
|
team_members: 1,
|
|
33
34
|
interview_tool: false,
|
|
35
|
+
protect_modules: false,
|
|
34
36
|
},
|
|
35
37
|
pro: {
|
|
36
38
|
max_repos: -1,
|
|
@@ -44,6 +46,7 @@ export const PLAN_LIMITS: Record<Plan, PlanLimits> = {
|
|
|
44
46
|
ai_comparison: false,
|
|
45
47
|
team_members: 1,
|
|
46
48
|
interview_tool: false,
|
|
49
|
+
protect_modules: false,
|
|
47
50
|
},
|
|
48
51
|
team: {
|
|
49
52
|
max_repos: -1,
|
|
@@ -57,6 +60,7 @@ export const PLAN_LIMITS: Record<Plan, PlanLimits> = {
|
|
|
57
60
|
ai_comparison: false,
|
|
58
61
|
team_members: 10,
|
|
59
62
|
interview_tool: false,
|
|
63
|
+
protect_modules: true,
|
|
60
64
|
},
|
|
61
65
|
enterprise: {
|
|
62
66
|
max_repos: -1,
|
|
@@ -70,6 +74,7 @@ export const PLAN_LIMITS: Record<Plan, PlanLimits> = {
|
|
|
70
74
|
ai_comparison: true,
|
|
71
75
|
team_members: -1,
|
|
72
76
|
interview_tool: true,
|
|
77
|
+
protect_modules: true,
|
|
73
78
|
},
|
|
74
79
|
};
|
|
75
80
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|