@programinglive/dev-workflow-mcp-server 1.8.1 → 1.10.0
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/.env.example +21 -1
- package/AGENTS.md +25 -0
- package/README.md +372 -649
- package/factory-client.js +554 -0
- package/git-facade.js +35 -0
- package/index.js +9 -34
- package/package.json +21 -20
- package/scripts/bootstrap-unix.sh +55 -0
- package/scripts/bootstrap-windows.ps1 +75 -0
- package/scripts/install-husky.js +43 -0
- package/scripts/setup-stack.js +436 -0
- package/scripts/setup-test-dbs.sh +61 -0
- package/scripts/test-db-adapters.sh +71 -0
- package/scripts/verify-bundle.mjs +92 -0
- package/tools/definitions.js +143 -0
- package/tools/handler-modules/commit-handlers.js +2 -1
- package/tools/handler-modules/factory-handlers.js +276 -0
- package/tools/handler-modules/index.js +24 -7
- package/tools/handler-modules/shared.js +7 -41
- package/tools/handler-modules/status-handlers.js +25 -3
- package/tools/handler-modules/workflow-handlers.js +57 -10
- package/vite.config.js +6 -0
- package/workflow-state.js +176 -2
- package/.agent/workflows/dev-workflow-bugfix.md +0 -250
- package/.agent/workflows/dev-workflow-feature.md +0 -201
- package/.agent/workflows/dev-workflow-full-cycle.md +0 -400
- package/.agent/workflows/dev-workflow-refactor.md +0 -238
- package/.env.production +0 -16
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -34
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -25
- package/.github/pull_request_template.md +0 -22
- package/.github/workflows/github-release.yml +0 -131
- package/.husky/commit-msg +0 -3
- package/.husky/pre-commit +0 -2
- package/CHANGELOG.md +0 -1145
- package/docs/GCP_DEPLOYMENT.md +0 -298
- package/docs/NETLIFY_DEPLOYMENT.md +0 -125
- package/docs/PLESK_DEPLOYMENT.md +0 -170
- package/docs/PRD.md +0 -86
- package/docs/WORKFLOW-BEST-PRACTICES.md +0 -58
- package/docs/antigravity/EXAMPLES.md +0 -535
- package/docs/antigravity/GETTING-STARTED.md +0 -292
- package/docs/antigravity/TROUBLESHOOTING.md +0 -742
- package/docs/antigravity/WALKTHROUGH.md +0 -25
- package/docs/antigravity/WORKFLOW-TEMPLATES.md +0 -670
- package/docs/release-notes/RELEASE_NOTES.md +0 -524
- package/docs/web-dashboard.md +0 -175
- package/scripts/test-auth-flow.js +0 -75
- package/tailwind.config.js +0 -8
- package/tests/build.test.js +0 -74
- package/tests/db-adapters.test.js +0 -108
- package/tests/env-support.test.js +0 -46
- package/tests/exec.test.js +0 -35
- package/tests/handlers-commit.test.js +0 -174
- package/tests/handlers-release.test.js +0 -212
- package/tests/handlers-workflow.test.js +0 -68
- package/tests/handlers.test.js +0 -1142
- package/tests/hello-world.test.js +0 -13
- package/tests/mcpRegistry.test.js +0 -65
- package/tests/patch-release.test.js +0 -7
- package/tests/release-wrapper.test.js +0 -107
- package/tests/startup-resilience.test.js +0 -73
- package/tests/test-helpers.js +0 -50
- package/tests/workflowState.test.js +0 -520
- package/web/Dockerfile.api +0 -25
- package/web/README.md +0 -133
- package/web/__tests__/api/workflow-history.test.js +0 -63
- package/web/api-server.js +0 -211
- package/web/app/docs/page.tsx +0 -202
- package/web/app/favicon.ico +0 -0
- package/web/app/globals.css +0 -91
- package/web/app/history/page.tsx +0 -220
- package/web/app/layout.tsx +0 -39
- package/web/app/login/page.tsx +0 -142
- package/web/app/page.tsx +0 -13
- package/web/components/ApiReference.tsx +0 -20
- package/web/components/Features.tsx +0 -61
- package/web/components/Footer.tsx +0 -9
- package/web/components/Hero.tsx +0 -96
- package/web/components/History.tsx +0 -175
- package/web/components/Navbar.tsx +0 -208
- package/web/components/Terminal.tsx +0 -114
- package/web/components/ThemeProvider.tsx +0 -58
- package/web/eslint.config.mjs +0 -18
- package/web/lib/api-client.ts +0 -123
- package/web/lib/auth.js +0 -75
- package/web/lib/db.js +0 -131
- package/web/lib/docs.js +0 -73
- package/web/middleware/auth.js +0 -26
- package/web/netlify.toml +0 -8
- package/web/next.config.ts +0 -19
- package/web/package-lock.json +0 -10911
- package/web/package.json +0 -42
- package/web/postcss.config.mjs +0 -9
- package/web/public/assets/android-chrome-192x192.png +0 -0
- package/web/public/assets/android-chrome-512x512.png +0 -0
- package/web/public/assets/apple-touch-icon.png +0 -0
- package/web/public/assets/favicon-16x16.png +0 -0
- package/web/public/assets/favicon-32x32.png +0 -0
- package/web/public/assets/favicon.ico +0 -0
- package/web/public/assets/favicon.png +0 -0
- package/web/public/assets/logo.png +0 -0
- package/web/public/assets/og-preview.png +0 -0
- package/web/public/assets/site.webmanifest +0 -1
- package/web/public/file.svg +0 -1
- package/web/public/globe.svg +0 -1
- package/web/public/next.svg +0 -1
- package/web/public/vercel.svg +0 -1
- package/web/public/window.svg +0 -1
- package/web/scripts/db-query-history.js +0 -120
- package/web/scripts/smoke-test.js +0 -30
- package/web/server.js +0 -184
- package/web/tailwind.config.ts +0 -20
- package/web/tsconfig.json +0 -42
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useState, useEffect } from "react";
|
|
4
|
-
import { apiClient } from "@/lib/api-client";
|
|
5
|
-
|
|
6
|
-
type HistoryItem = {
|
|
7
|
-
id: number;
|
|
8
|
-
task_type: string;
|
|
9
|
-
description: string;
|
|
10
|
-
commit_message: string;
|
|
11
|
-
completed_at: string;
|
|
12
|
-
tests_passed: boolean;
|
|
13
|
-
documentation_type: string;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
type SummaryData = {
|
|
17
|
-
summary: {
|
|
18
|
-
totalTasks: number;
|
|
19
|
-
};
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export default function History() {
|
|
23
|
-
const [history, setHistory] = useState<HistoryItem[]>([]);
|
|
24
|
-
const [summary, setSummary] = useState<SummaryData | null>(null);
|
|
25
|
-
const [page, setPage] = useState(1);
|
|
26
|
-
const [totalPages, setTotalPages] = useState(1);
|
|
27
|
-
const [filters, setFilters] = useState({
|
|
28
|
-
userId: "",
|
|
29
|
-
startDate: "",
|
|
30
|
-
endDate: ""
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
const loadData = async () => {
|
|
34
|
-
try {
|
|
35
|
-
// Fetch workflow history from API via apiClient
|
|
36
|
-
const data = await apiClient.getHistory();
|
|
37
|
-
|
|
38
|
-
if (data?.history) {
|
|
39
|
-
setHistory(data.history || []);
|
|
40
|
-
setTotalPages(Math.ceil((data.history?.length || 0) / 10));
|
|
41
|
-
setSummary({
|
|
42
|
-
summary: {
|
|
43
|
-
totalTasks: data.history?.length || 0,
|
|
44
|
-
},
|
|
45
|
-
});
|
|
46
|
-
} else {
|
|
47
|
-
// Not authenticated or error - show empty state
|
|
48
|
-
setHistory([]);
|
|
49
|
-
setTotalPages(1);
|
|
50
|
-
setSummary({
|
|
51
|
-
summary: {
|
|
52
|
-
totalTasks: 0,
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
} catch (error) {
|
|
57
|
-
// Silently handle errors - show empty state
|
|
58
|
-
setHistory([]);
|
|
59
|
-
setTotalPages(1);
|
|
60
|
-
setSummary({
|
|
61
|
-
summary: {
|
|
62
|
-
totalTasks: 0,
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
// Only load if we're likely authenticated (reduces 401 errors)
|
|
70
|
-
// History component should only be on authenticated pages anyway
|
|
71
|
-
if (typeof window !== 'undefined' && window.location.pathname === '/history') {
|
|
72
|
-
loadData();
|
|
73
|
-
}
|
|
74
|
-
}, [page]); // Reload when page changes
|
|
75
|
-
|
|
76
|
-
const handleApplyFilters = () => {
|
|
77
|
-
setPage(1);
|
|
78
|
-
loadData();
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
return (
|
|
82
|
-
<section id="history" className="py-12 sm:py-16 lg:py-20 px-3 sm:px-4 bg-gray-50 dark:bg-white/5 transition-colors duration-300">
|
|
83
|
-
<div className="max-w-7xl mx-auto">
|
|
84
|
-
<h2 className="text-2xl sm:text-3xl md:text-4xl font-bold mb-4 sm:mb-8 text-gray-900 dark:text-white">Workflow History</h2>
|
|
85
|
-
<p className="text-sm sm:text-base text-gray-600 dark:text-gray-400 mb-8 sm:mb-12">Track your development progress and analyze your workflow patterns.</p>
|
|
86
|
-
<div className="bg-white dark:bg-[#0a0a0f] rounded-xl border border-gray-200 dark:border-white/10 p-4 sm:p-6 transition-colors duration-300">
|
|
87
|
-
<div className="grid gap-3 sm:gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 mb-6">
|
|
88
|
-
<input
|
|
89
|
-
type="text"
|
|
90
|
-
placeholder="User ID"
|
|
91
|
-
value={filters.userId}
|
|
92
|
-
onChange={(e) => setFilters({ ...filters, userId: e.target.value })}
|
|
93
|
-
className="px-4 py-2 bg-gray-100 dark:bg-white/5 border border-gray-300 dark:border-white/10 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400"
|
|
94
|
-
/>
|
|
95
|
-
<input
|
|
96
|
-
type="date"
|
|
97
|
-
value={filters.startDate}
|
|
98
|
-
onChange={(e) => setFilters({ ...filters, startDate: e.target.value })}
|
|
99
|
-
className="px-4 py-2 bg-gray-100 dark:bg-white/5 border border-gray-300 dark:border-white/10 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 text-gray-900 dark:text-white [color-scheme:light] dark:[color-scheme:dark]"
|
|
100
|
-
/>
|
|
101
|
-
<input
|
|
102
|
-
type="date"
|
|
103
|
-
value={filters.endDate}
|
|
104
|
-
onChange={(e) => setFilters({ ...filters, endDate: e.target.value })}
|
|
105
|
-
className="px-4 py-2 bg-gray-100 dark:bg-white/5 border border-gray-300 dark:border-white/10 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 text-gray-900 dark:text-white [color-scheme:light] dark:[color-scheme:dark]"
|
|
106
|
-
/>
|
|
107
|
-
<button
|
|
108
|
-
onClick={handleApplyFilters}
|
|
109
|
-
className="px-4 py-2 bg-gradient-to-r from-indigo-600 to-purple-600 rounded-lg font-semibold hover:shadow-lg hover:shadow-indigo-500/50 transition text-white"
|
|
110
|
-
>
|
|
111
|
-
Apply Filters
|
|
112
|
-
</button>
|
|
113
|
-
</div>
|
|
114
|
-
|
|
115
|
-
{summary && (
|
|
116
|
-
<div className="mb-6">
|
|
117
|
-
<h3 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">Project Summary</h3>
|
|
118
|
-
<div className="text-gray-600 dark:text-gray-400">
|
|
119
|
-
<p>Total tasks: {summary.summary?.totalTasks || 0}</p>
|
|
120
|
-
</div>
|
|
121
|
-
</div>
|
|
122
|
-
)}
|
|
123
|
-
|
|
124
|
-
<div>
|
|
125
|
-
<h3 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">Recent History</h3>
|
|
126
|
-
<div className="space-y-3">
|
|
127
|
-
{history.length > 0 ? (
|
|
128
|
-
history.map((row) => (
|
|
129
|
-
<div key={row.id} className="p-4 rounded-lg bg-gray-100 dark:bg-white/5 border border-gray-200 dark:border-white/10">
|
|
130
|
-
<div className="flex items-center gap-2 mb-2">
|
|
131
|
-
<span className={`px-2 py-1 rounded text-xs font-medium ${row.task_type === 'feature' ? 'bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200' :
|
|
132
|
-
row.task_type === 'bugfix' ? 'bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200' :
|
|
133
|
-
'bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200'
|
|
134
|
-
}`}>
|
|
135
|
-
{row.task_type}
|
|
136
|
-
</span>
|
|
137
|
-
<span className={`px-2 py-1 rounded text-xs font-medium ${row.tests_passed ? 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200' :
|
|
138
|
-
'bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200'
|
|
139
|
-
}`}>
|
|
140
|
-
{row.tests_passed ? '✓ Tests' : '⚠ Skipped'}
|
|
141
|
-
</span>
|
|
142
|
-
</div>
|
|
143
|
-
<div className="font-semibold text-gray-900 dark:text-white">{row.description}</div>
|
|
144
|
-
<div className="text-sm text-gray-700 dark:text-gray-300 mt-1">{row.commit_message}</div>
|
|
145
|
-
<div className="text-sm text-gray-600 dark:text-gray-400 mt-1">{new Date(row.completed_at).toLocaleString()}</div>
|
|
146
|
-
</div>
|
|
147
|
-
))
|
|
148
|
-
) : (
|
|
149
|
-
<p className="text-gray-500 dark:text-gray-400">No history yet.</p>
|
|
150
|
-
)}
|
|
151
|
-
</div>
|
|
152
|
-
|
|
153
|
-
<div className="flex items-center justify-between mt-6">
|
|
154
|
-
<button
|
|
155
|
-
onClick={() => setPage(p => Math.max(1, p - 1))}
|
|
156
|
-
disabled={page <= 1}
|
|
157
|
-
className="px-4 py-2 bg-gray-100 dark:bg-white/5 border border-gray-300 dark:border-white/10 rounded-lg hover:bg-gray-200 dark:hover:bg-white/10 disabled:opacity-50 text-gray-900 dark:text-white transition"
|
|
158
|
-
>
|
|
159
|
-
Previous
|
|
160
|
-
</button>
|
|
161
|
-
<span className="text-sm text-gray-600 dark:text-gray-400">Page {page} of {totalPages}</span>
|
|
162
|
-
<button
|
|
163
|
-
onClick={() => setPage(p => Math.min(totalPages, p + 1))}
|
|
164
|
-
disabled={page >= totalPages}
|
|
165
|
-
className="px-4 py-2 bg-gray-100 dark:bg-white/5 border border-gray-300 dark:border-white/10 rounded-lg hover:bg-gray-200 dark:hover:bg-white/10 disabled:opacity-50 text-gray-900 dark:text-white transition"
|
|
166
|
-
>
|
|
167
|
-
Next
|
|
168
|
-
</button>
|
|
169
|
-
</div>
|
|
170
|
-
</div>
|
|
171
|
-
</div>
|
|
172
|
-
</div>
|
|
173
|
-
</section>
|
|
174
|
-
);
|
|
175
|
-
}
|
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useState, useEffect } from "react";
|
|
4
|
-
import Link from "next/link";
|
|
5
|
-
import { Github, Menu, X, Moon, Sun, LogOut } from "lucide-react";
|
|
6
|
-
import Image from "next/image";
|
|
7
|
-
import { apiClient } from "@/lib/api-client";
|
|
8
|
-
|
|
9
|
-
export default function Navbar() {
|
|
10
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
11
|
-
const [theme, setTheme] = useState<"light" | "dark">("dark");
|
|
12
|
-
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
13
|
-
const [username, setUsername] = useState<string | null>(null);
|
|
14
|
-
|
|
15
|
-
useEffect(() => {
|
|
16
|
-
// Get theme from localStorage or system preference
|
|
17
|
-
const savedTheme = localStorage.getItem("theme") as "light" | "dark" | null;
|
|
18
|
-
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
19
|
-
|
|
20
|
-
const initialTheme = savedTheme || (prefersDark ? "dark" : "light");
|
|
21
|
-
setTheme(initialTheme);
|
|
22
|
-
|
|
23
|
-
// Apply the class immediately
|
|
24
|
-
document.documentElement.classList.toggle("dark", initialTheme === "dark");
|
|
25
|
-
|
|
26
|
-
// Check authentication status
|
|
27
|
-
const checkAuth = () => {
|
|
28
|
-
// Check authentication status ONLY if we think we're logged in OR if we just received an update event
|
|
29
|
-
// This prevents 401 errors from cluttering the console for guests
|
|
30
|
-
const mightBeLoggedIn = localStorage.getItem('isLoggedIn') === 'true';
|
|
31
|
-
|
|
32
|
-
if (mightBeLoggedIn) {
|
|
33
|
-
apiClient.getCurrentUser()
|
|
34
|
-
.then(data => {
|
|
35
|
-
if (data) {
|
|
36
|
-
setIsAuthenticated(true);
|
|
37
|
-
setUsername(data.username || 'User');
|
|
38
|
-
// Ensure local storage is in sync
|
|
39
|
-
localStorage.setItem('isLoggedIn', 'true');
|
|
40
|
-
} else {
|
|
41
|
-
throw new Error('Not authenticated');
|
|
42
|
-
}
|
|
43
|
-
})
|
|
44
|
-
.catch(() => {
|
|
45
|
-
// Session invalid/expired
|
|
46
|
-
localStorage.removeItem('isLoggedIn');
|
|
47
|
-
setIsAuthenticated(false);
|
|
48
|
-
setUsername(null);
|
|
49
|
-
});
|
|
50
|
-
} else {
|
|
51
|
-
setIsAuthenticated(false);
|
|
52
|
-
setUsername(null);
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
// Initial check
|
|
57
|
-
checkAuth();
|
|
58
|
-
|
|
59
|
-
// Listen for auth updates (login/logout/validated session)
|
|
60
|
-
window.addEventListener('authUpdate', checkAuth);
|
|
61
|
-
|
|
62
|
-
return () => {
|
|
63
|
-
window.removeEventListener('authUpdate', checkAuth);
|
|
64
|
-
};
|
|
65
|
-
}, []);
|
|
66
|
-
|
|
67
|
-
const toggleTheme = () => {
|
|
68
|
-
const newTheme = theme === "dark" ? "light" : "dark";
|
|
69
|
-
setTheme(newTheme);
|
|
70
|
-
localStorage.setItem("theme", newTheme);
|
|
71
|
-
document.documentElement.classList.toggle("dark", newTheme === "dark");
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const handleLogout = async () => {
|
|
75
|
-
try {
|
|
76
|
-
await apiClient.logout();
|
|
77
|
-
localStorage.removeItem('isLoggedIn');
|
|
78
|
-
setIsAuthenticated(false);
|
|
79
|
-
setUsername(null);
|
|
80
|
-
window.dispatchEvent(new Event('authUpdate'));
|
|
81
|
-
window.location.href = '/login';
|
|
82
|
-
} catch (error) {
|
|
83
|
-
console.error('Logout error:', error);
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
return (
|
|
88
|
-
<nav className="fixed top-0 left-0 right-0 z-50 bg-white/80 dark:bg-[#0a0a0f]/80 backdrop-blur-lg border-b border-gray-200 dark:border-white/10 transition-colors duration-300">
|
|
89
|
-
<div className="max-w-7xl mx-auto px-3 sm:px-4 lg:px-8">
|
|
90
|
-
<div className="flex items-center justify-between h-14 sm:h-16">
|
|
91
|
-
<Link href="/" className="flex items-center gap-2">
|
|
92
|
-
<Image src="/assets/logo.png" alt="Dev Workflow" width={28} height={28} className="rounded-lg w-7 h-7 sm:w-8 sm:h-8" />
|
|
93
|
-
<span className="text-sm sm:text-lg font-bold text-gray-900 dark:text-white">Dev Workflow</span>
|
|
94
|
-
</Link>
|
|
95
|
-
<div className="hidden md:flex items-center gap-3 lg:gap-6">
|
|
96
|
-
<Link href="/#features" className="text-xs lg:text-sm text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition">Features</Link>
|
|
97
|
-
<Link href="/history" className="text-xs lg:text-sm text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition">History</Link>
|
|
98
|
-
<Link href="/#api" className="text-xs lg:text-sm text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition">API</Link>
|
|
99
|
-
<Link href="/docs" className="text-xs lg:text-sm text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition">Docs</Link>
|
|
100
|
-
<button
|
|
101
|
-
onClick={toggleTheme}
|
|
102
|
-
className="flex items-center justify-center w-8 h-8 rounded-lg bg-gray-100 dark:bg-white/10 hover:bg-gray-200 dark:hover:bg-white/20 transition text-gray-900 dark:text-white"
|
|
103
|
-
aria-label="Toggle theme"
|
|
104
|
-
>
|
|
105
|
-
{theme === "dark" ? (
|
|
106
|
-
<Sun className="w-4 h-4" />
|
|
107
|
-
) : (
|
|
108
|
-
<Moon className="w-4 h-4" />
|
|
109
|
-
)}
|
|
110
|
-
</button>
|
|
111
|
-
<a
|
|
112
|
-
href="https://github.com/programinglive/dev-workflow-mcp-server"
|
|
113
|
-
target="_blank"
|
|
114
|
-
rel="noopener noreferrer"
|
|
115
|
-
className="flex items-center gap-1.5 px-3 py-1.5 lg:px-4 lg:py-2 bg-gray-100 dark:bg-white/5 hover:bg-gray-200 dark:hover:bg-white/10 rounded-lg transition text-xs lg:text-sm text-gray-900 dark:text-white"
|
|
116
|
-
>
|
|
117
|
-
<Github className="w-3.5 h-3.5 lg:w-4 lg:h-4" />
|
|
118
|
-
GitHub
|
|
119
|
-
</a>
|
|
120
|
-
{isAuthenticated && (
|
|
121
|
-
<div className="flex items-center gap-4 pl-4 border-l border-gray-200 dark:border-white/10">
|
|
122
|
-
<span className="text-sm font-medium text-gray-900 dark:text-white">
|
|
123
|
-
{username}
|
|
124
|
-
</span>
|
|
125
|
-
<button
|
|
126
|
-
onClick={handleLogout}
|
|
127
|
-
className="flex items-center gap-1.5 px-3 py-1.5 lg:px-4 lg:py-2 bg-red-100 dark:bg-red-900/50 hover:bg-red-200 dark:hover:bg-red-800/50 rounded-lg transition text-xs lg:text-sm text-red-700 dark:text-red-300"
|
|
128
|
-
>
|
|
129
|
-
<LogOut className="w-3.5 h-3.5 lg:w-4 lg:h-4" />
|
|
130
|
-
Logout
|
|
131
|
-
</button>
|
|
132
|
-
</div>
|
|
133
|
-
)}
|
|
134
|
-
{!isAuthenticated && (
|
|
135
|
-
<Link
|
|
136
|
-
href="/login"
|
|
137
|
-
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg transition"
|
|
138
|
-
>
|
|
139
|
-
Login
|
|
140
|
-
</Link>
|
|
141
|
-
)}
|
|
142
|
-
</div>
|
|
143
|
-
<div className="md:hidden flex items-center gap-2">
|
|
144
|
-
<button
|
|
145
|
-
onClick={toggleTheme}
|
|
146
|
-
className="p-2 rounded-lg bg-gray-100 dark:bg-white/10 hover:bg-gray-200 dark:hover:bg-white/20 transition text-gray-900 dark:text-white"
|
|
147
|
-
aria-label="Toggle theme"
|
|
148
|
-
>
|
|
149
|
-
{theme === "dark" ? (
|
|
150
|
-
<Sun className="w-5 h-5" />
|
|
151
|
-
) : (
|
|
152
|
-
<Moon className="w-5 h-5" />
|
|
153
|
-
)}
|
|
154
|
-
</button>
|
|
155
|
-
<button
|
|
156
|
-
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-white/5 transition text-gray-900 dark:text-white"
|
|
157
|
-
onClick={() => setIsOpen(!isOpen)}
|
|
158
|
-
>
|
|
159
|
-
{isOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
|
|
160
|
-
</button>
|
|
161
|
-
</div>
|
|
162
|
-
</div>
|
|
163
|
-
</div>
|
|
164
|
-
{/* Mobile Menu */}
|
|
165
|
-
{isOpen && (
|
|
166
|
-
<div className="md:hidden border-t border-gray-200 dark:border-white/10 bg-white dark:bg-[#0a0a0f] transition-colors duration-300">
|
|
167
|
-
<div className="px-4 py-4 space-y-3">
|
|
168
|
-
{isAuthenticated && (
|
|
169
|
-
<div className="px-3 py-2 text-sm font-medium text-gray-900 dark:text-white border-b border-gray-200 dark:border-white/10 mb-2">
|
|
170
|
-
Signed in as {username}
|
|
171
|
-
</div>
|
|
172
|
-
)}
|
|
173
|
-
<Link href="/#features" className="block text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition" onClick={() => setIsOpen(false)}>Features</Link>
|
|
174
|
-
<Link href="/history" className="block text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition" onClick={() => setIsOpen(false)}>History</Link>
|
|
175
|
-
<Link href="/#api" className="block text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition" onClick={() => setIsOpen(false)}>API</Link>
|
|
176
|
-
<Link href="/docs" className="block text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition" onClick={() => setIsOpen(false)}>Docs</Link>
|
|
177
|
-
<a
|
|
178
|
-
href="https://github.com/programinglive/dev-workflow-mcp-server"
|
|
179
|
-
target="_blank"
|
|
180
|
-
rel="noopener noreferrer"
|
|
181
|
-
className="flex items-center gap-2 text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition"
|
|
182
|
-
>
|
|
183
|
-
<Github className="w-4 h-4" />
|
|
184
|
-
GitHub
|
|
185
|
-
</a>
|
|
186
|
-
{isAuthenticated ? (
|
|
187
|
-
<button
|
|
188
|
-
onClick={() => { setIsOpen(false); handleLogout(); }}
|
|
189
|
-
className="w-full text-left flex items-center gap-2 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 px-2 py-1.5 rounded transition"
|
|
190
|
-
>
|
|
191
|
-
<LogOut className="w-4 h-4" />
|
|
192
|
-
Logout
|
|
193
|
-
</button>
|
|
194
|
-
) : (
|
|
195
|
-
<Link
|
|
196
|
-
href="/login"
|
|
197
|
-
className="block w-full text-center px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg transition"
|
|
198
|
-
onClick={() => setIsOpen(false)}
|
|
199
|
-
>
|
|
200
|
-
Login
|
|
201
|
-
</Link>
|
|
202
|
-
)}
|
|
203
|
-
</div>
|
|
204
|
-
</div>
|
|
205
|
-
)}
|
|
206
|
-
</nav>
|
|
207
|
-
);
|
|
208
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useEffect, useState, useRef } from "react";
|
|
4
|
-
import { Rocket } from "lucide-react";
|
|
5
|
-
|
|
6
|
-
type TerminalLine = {
|
|
7
|
-
type: 'command' | 'success' | 'comment' | 'info' | 'final';
|
|
8
|
-
text: string;
|
|
9
|
-
delay: number;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
const terminalLines: TerminalLine[] = [
|
|
13
|
-
{ type: 'command', text: 'start_task', delay: 500 },
|
|
14
|
-
{ type: 'success', text: '✓ Task started: Implement user auth', delay: 1500 },
|
|
15
|
-
{ type: 'comment', text: '# Write code...', delay: 2500 },
|
|
16
|
-
{ type: 'command', text: 'mark_bug_fixed', delay: 4000 },
|
|
17
|
-
{ type: 'info', text: '✨ Feature complete! Now create tests...', delay: 5000 },
|
|
18
|
-
{ type: 'command', text: 'create_tests', delay: 6500 },
|
|
19
|
-
{ type: 'success', text: '✓ Tests created', delay: 7500 },
|
|
20
|
-
{ type: 'command', text: 'run_tests', delay: 8500 },
|
|
21
|
-
{ type: 'success', text: '✓ All tests passed!', delay: 9500 },
|
|
22
|
-
{ type: 'command', text: 'create_documentation', delay: 10500 },
|
|
23
|
-
{ type: 'success', text: '✓ Documentation updated', delay: 11500 },
|
|
24
|
-
{ type: 'command', text: 'commit_and_push', delay: 12500 },
|
|
25
|
-
{ type: 'final', text: 'Ready to release!', delay: 13500 }
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
export default function Terminal() {
|
|
29
|
-
const [lines, setLines] = useState<React.ReactElement[]>([]);
|
|
30
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
31
|
-
const hasStarted = useRef(false);
|
|
32
|
-
|
|
33
|
-
useEffect(() => {
|
|
34
|
-
const observer = new IntersectionObserver((entries) => {
|
|
35
|
-
entries.forEach(entry => {
|
|
36
|
-
if (entry.isIntersecting && !hasStarted.current) {
|
|
37
|
-
hasStarted.current = true;
|
|
38
|
-
runAnimation();
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
if (containerRef.current) {
|
|
44
|
-
observer.observe(containerRef.current);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return () => observer.disconnect();
|
|
48
|
-
}, []);
|
|
49
|
-
|
|
50
|
-
const runAnimation = async () => {
|
|
51
|
-
for (const line of terminalLines) {
|
|
52
|
-
await new Promise(r => setTimeout(r, line.delay - (terminalLines[terminalLines.indexOf(line) - 1]?.delay || 0)));
|
|
53
|
-
|
|
54
|
-
if (line.type === 'command') {
|
|
55
|
-
await typeCommand(line.text);
|
|
56
|
-
} else {
|
|
57
|
-
addLine(line);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const typeCommand = async (text: string) => {
|
|
63
|
-
const id = Date.now();
|
|
64
|
-
setLines(prev => [...prev, (
|
|
65
|
-
<div key={id} className="terminal-line visible">
|
|
66
|
-
<span className="text-gray-500 mr-2">$</span>
|
|
67
|
-
<span className="text-white typing-cursor" id={`cmd-${id}`}></span>
|
|
68
|
-
</div>
|
|
69
|
-
)]);
|
|
70
|
-
|
|
71
|
-
for (let i = 0; i < text.length; i++) {
|
|
72
|
-
await new Promise(r => setTimeout(r, 50 + Math.random() * 50));
|
|
73
|
-
const el = document.getElementById(`cmd-${id}`);
|
|
74
|
-
if (el) el.textContent += text[i];
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const el = document.getElementById(`cmd-${id}`);
|
|
78
|
-
if (el) el.classList.remove('typing-cursor');
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const addLine = (line: TerminalLine) => {
|
|
82
|
-
let content: React.ReactElement;
|
|
83
|
-
let className = "terminal-line visible";
|
|
84
|
-
|
|
85
|
-
switch (line.type) {
|
|
86
|
-
case 'success':
|
|
87
|
-
className += " text-emerald-400";
|
|
88
|
-
content = <>{line.text}</>;
|
|
89
|
-
break;
|
|
90
|
-
case 'comment':
|
|
91
|
-
className += " mt-3";
|
|
92
|
-
content = <><span className="text-gray-500 mr-2">$</span><span className="text-white">{line.text}</span></>;
|
|
93
|
-
break;
|
|
94
|
-
case 'info':
|
|
95
|
-
className += " text-purple-400";
|
|
96
|
-
content = <>{line.text}</>;
|
|
97
|
-
break;
|
|
98
|
-
case 'final':
|
|
99
|
-
className += " text-pink-400 flex items-center gap-2";
|
|
100
|
-
content = <><Rocket className="w-4 h-4" />{line.text}</>;
|
|
101
|
-
break;
|
|
102
|
-
default:
|
|
103
|
-
content = <>{line.text}</>;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
setLines(prev => [...prev, <div key={Date.now()} className={className}>{content}</div>]);
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
return (
|
|
110
|
-
<div ref={containerRef} className="font-mono text-xs sm:text-sm space-y-1.5 sm:space-y-2 min-h-[300px] sm:min-h-[400px]">
|
|
111
|
-
{lines}
|
|
112
|
-
</div>
|
|
113
|
-
);
|
|
114
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { createContext, useContext, useEffect, useState } from "react";
|
|
4
|
-
|
|
5
|
-
type Theme = "light" | "dark";
|
|
6
|
-
|
|
7
|
-
interface ThemeContextType {
|
|
8
|
-
theme: Theme;
|
|
9
|
-
toggleTheme: () => void;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
|
13
|
-
|
|
14
|
-
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
|
15
|
-
const [theme, setTheme] = useState<Theme>("dark");
|
|
16
|
-
const [mounted, setMounted] = useState(false);
|
|
17
|
-
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
setMounted(true);
|
|
20
|
-
// Check localStorage for saved theme preference
|
|
21
|
-
const savedTheme = localStorage.getItem("theme") as Theme | null;
|
|
22
|
-
if (savedTheme) {
|
|
23
|
-
setTheme(savedTheme);
|
|
24
|
-
document.documentElement.classList.toggle("dark", savedTheme === "dark");
|
|
25
|
-
} else {
|
|
26
|
-
// Check system preference
|
|
27
|
-
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
28
|
-
const initialTheme = prefersDark ? "dark" : "light";
|
|
29
|
-
setTheme(initialTheme);
|
|
30
|
-
document.documentElement.classList.toggle("dark", prefersDark);
|
|
31
|
-
}
|
|
32
|
-
}, []);
|
|
33
|
-
|
|
34
|
-
const toggleTheme = () => {
|
|
35
|
-
const newTheme = theme === "dark" ? "light" : "dark";
|
|
36
|
-
setTheme(newTheme);
|
|
37
|
-
localStorage.setItem("theme", newTheme);
|
|
38
|
-
document.documentElement.classList.toggle("dark", newTheme === "dark");
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
if (!mounted) {
|
|
42
|
-
return <>{children}</>;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return (
|
|
46
|
-
<ThemeContext.Provider value={{ theme, toggleTheme }}>
|
|
47
|
-
{children}
|
|
48
|
-
</ThemeContext.Provider>
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function useTheme() {
|
|
53
|
-
const context = useContext(ThemeContext);
|
|
54
|
-
if (context === undefined) {
|
|
55
|
-
throw new Error("useTheme must be used within a ThemeProvider");
|
|
56
|
-
}
|
|
57
|
-
return context;
|
|
58
|
-
}
|
package/web/eslint.config.mjs
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { defineConfig, globalIgnores } from "eslint/config";
|
|
2
|
-
import nextVitals from "eslint-config-next/core-web-vitals";
|
|
3
|
-
import nextTs from "eslint-config-next/typescript";
|
|
4
|
-
|
|
5
|
-
const eslintConfig = defineConfig([
|
|
6
|
-
...nextVitals,
|
|
7
|
-
...nextTs,
|
|
8
|
-
// Override default ignores of eslint-config-next.
|
|
9
|
-
globalIgnores([
|
|
10
|
-
// Default ignores of eslint-config-next:
|
|
11
|
-
".next/**",
|
|
12
|
-
"out/**",
|
|
13
|
-
"build/**",
|
|
14
|
-
"next-env.d.ts",
|
|
15
|
-
]),
|
|
16
|
-
]);
|
|
17
|
-
|
|
18
|
-
export default eslintConfig;
|