diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..b238b23 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "mcp__plugin_context-mode_context-mode__ctx_batch_execute", + "mcp__context-mode__ctx_batch_execute", + "mcp__context-mode__ctx_execute_file" + ] + } +} diff --git a/.claude/worktrees/agent-a0cb243b b/.claude/worktrees/agent-a0cb243b new file mode 160000 index 0000000..5b1600c --- /dev/null +++ b/.claude/worktrees/agent-a0cb243b @@ -0,0 +1 @@ +Subproject commit 5b1600c210a923cb33b0ae15c054cb4da64c62a5 diff --git a/.claude/worktrees/agent-a6424abe b/.claude/worktrees/agent-a6424abe new file mode 160000 index 0000000..5b1600c --- /dev/null +++ b/.claude/worktrees/agent-a6424abe @@ -0,0 +1 @@ +Subproject commit 5b1600c210a923cb33b0ae15c054cb4da64c62a5 diff --git a/.claude/worktrees/agent-a85e5c45 b/.claude/worktrees/agent-a85e5c45 new file mode 160000 index 0000000..5b1600c --- /dev/null +++ b/.claude/worktrees/agent-a85e5c45 @@ -0,0 +1 @@ +Subproject commit 5b1600c210a923cb33b0ae15c054cb4da64c62a5 diff --git a/.claude/worktrees/agent-aa2cb71d b/.claude/worktrees/agent-aa2cb71d new file mode 160000 index 0000000..5b1600c --- /dev/null +++ b/.claude/worktrees/agent-aa2cb71d @@ -0,0 +1 @@ +Subproject commit 5b1600c210a923cb33b0ae15c054cb4da64c62a5 diff --git a/.graphifyignore b/.graphifyignore new file mode 100644 index 0000000..d58fa88 --- /dev/null +++ b/.graphifyignore @@ -0,0 +1,21 @@ +node_modules/ +.next/ +dist/ +build/ +out/ +coverage/ +.git/ +.pnpm-store/ +.cache/ +*.log +.env +.env.* +__pycache__/ +*.pyc +.venv/ +venv/ +target/ +*.exe +*.dll +*.so +*.dylib diff --git a/.openclaude-profile.json b/.openclaude-profile.json new file mode 100644 index 0000000..6fc9980 --- /dev/null +++ b/.openclaude-profile.json @@ -0,0 +1,9 @@ +{ + "profile": "codex", + "env": { + "OPENAI_BASE_URL": "https://chatgpt.com/backend-api/codex", + "OPENAI_MODEL": "codexplan", + "CHATGPT_ACCOUNT_ID": "6dbec33c-c751-4383-b112-bfc776f39ed7" + }, + "createdAt": "2026-04-05T18:55:08.995Z" +} \ No newline at end of file diff --git a/package.json b/package.json index 156df23..80e66dc 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ "workspaces": [ "server", "web" - ] -} - - + ], + "packageManager": "pnpm@10.28.1" +} \ No newline at end of file diff --git a/web/src/App.css b/web/src/App.css index 92614dd..e69de29 100644 --- a/web/src/App.css +++ b/web/src/App.css @@ -1,290 +0,0 @@ -.app-root { - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 2.5rem 1.5rem 3rem; - gap: 2.5rem; - background: radial-gradient(circle at top, #27272a 0, #09090b 45%, #020617 100%); -} - -.app-header { - max-width: 640px; - text-align: center; -} - -.app-header h1 { - font-size: 2rem; - margin-bottom: 0.5rem; -} - -.app-header p { - font-size: 0.98rem; - color: #e4e4e7; -} - -.app-header-meta { - margin-top: 0.5rem; - display: flex; - justify-content: center; - gap: 1rem; - flex-wrap: wrap; - font-size: 0.8rem; - color: #a1a1aa; -} - -.app-main { - width: 100%; - display: flex; - justify-content: center; - align-items: flex-start; - gap: 2rem; - max-width: 1200px; - margin: 0 auto; -} - -.download-form { - width: 100%; - max-width: 560px; - display: flex; - flex-direction: column; - gap: 0.75rem; - padding: 1.5rem 1.75rem; - border-radius: 1rem; - border: 1px solid rgba(63, 63, 70, 0.9); - background: radial-gradient(circle at top left, #18181b 0, #020617 60%); - box-shadow: - 0 18px 40px rgba(0, 0, 0, 0.7), - 0 0 0 1px rgba(24, 24, 27, 0.9); -} - -.field-label { - font-size: 0.9rem; - font-weight: 500; -} - -.field-helper { - font-size: 0.78rem; - color: #a1a1aa; - margin-top: -0.1rem; -} - -.url-input { - padding: 0.75rem 0.9rem; - border-radius: 0.5rem; - border: 1px solid #27272a; - background-color: #09090b; - color: #f4f4f5; - font-size: 0.95rem; - resize: vertical; -} - -.url-input--multiline { - line-height: 1.4; -} - -.url-input:focus { - outline: 2px solid #4f46e5; - outline-offset: 1px; - border-color: transparent; -} - -.primary-button { - margin-top: 0.5rem; - padding: 0.75rem 1rem; - border-radius: 0.5rem; - border: none; - background: #4f46e5; - color: #f9fafb; - font-weight: 600; - font-size: 0.95rem; - cursor: pointer; - transition: background 180ms ease-out, transform 160ms ease-out; -} - -.primary-button:hover { - background: #4338ca; -} - -.primary-button:active { - transform: translateY(1px) scale(0.99); -} - -.primary-button:focus-visible { - outline: 2px solid #a5b4fc; - outline-offset: 2px; -} - -.error-text { - font-size: 0.85rem; - color: #f97373; -} - -.downloads-section { - width: 100%; - max-width: 620px; - margin-top: 0; - padding: 1.25rem 1.5rem; - border-radius: 1rem; - border: 1px solid rgba(63, 63, 70, 0.9); - background-color: rgba(9, 9, 11, 0.9); - backdrop-filter: blur(14px); -} - -.downloads-title { - font-size: 1rem; - margin-bottom: 0.5rem; -} - -.downloads-empty { - margin-top: 0.5rem; - font-size: 0.85rem; - color: #a1a1aa; -} - -.downloads-list { - list-style: none; - padding: 0; - margin: 0; - display: flex; - flex-direction: column; - gap: 0.75rem; -} - -.download-item { - padding: 0.6rem 0.1rem 0.7rem; - border-bottom: 1px solid rgba(39, 39, 42, 0.8); -} - -.download-item:last-of-type { - border-bottom: none; -} - -.download-main { - display: flex; - justify-content: space-between; - align-items: center; - gap: 0.75rem; - margin-bottom: 0.35rem; -} - -.download-url { - font-size: 0.8rem; - color: #e5e5e5; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.badge { - padding: 0.2rem 0.55rem; - border-radius: 999px; - font-size: 0.7rem; - font-weight: 500; -} - -.badge--pending { - background-color: rgba(39, 39, 42, 0.9); - color: #e5e5e5; -} - -.badge--downloading { - background-color: rgba(37, 99, 235, 0.18); - color: #bfdbfe; -} - -.badge--completed { - background-color: rgba(22, 163, 74, 0.25); - color: #bbf7d0; -} - -.badge--error { - background-color: rgba(239, 68, 68, 0.18); - color: #fecaca; -} - -.progress-wrapper { - display: flex; - align-items: center; - gap: 0.5rem; -} - -.progress-bar { - flex: 1; - height: 0.4rem; - border-radius: 999px; - background-color: #18181b; - overflow: hidden; -} - -.progress-bar__fill { - height: 100%; - border-radius: inherit; - background: linear-gradient(to right, #4f46e5, #22c55e); - transition: width 160ms ease-out; -} - -.progress-label { - font-size: 0.75rem; - color: #e4e4e7; - width: 3ch; - text-align: right; -} - -.download-error { - margin-top: 0.1rem; - font-size: 0.78rem; - color: #fecaca; -} - -.download-meta { - display: flex; - flex-wrap: wrap; - gap: 0.4rem; - margin-top: 0.25rem; - font-size: 0.8rem; - color: #d4d4d8; -} - -.download-footer { - margin-top: 0.35rem; - display: flex; - align-items: center; - justify-content: space-between; - gap: 0.75rem; -} - -.secondary-button { - padding: 0.35rem 0.75rem; - border-radius: 999px; - border: 1px solid #3f3f46; - background-color: transparent; - color: #e4e4e7; - font-size: 0.78rem; - font-weight: 500; - cursor: pointer; - transition: - background-color 160ms ease-out, - border-color 160ms ease-out, - transform 120ms ease-out; -} - -.secondary-button:hover { - background-color: rgba(63, 63, 70, 0.4); - border-color: #71717a; -} - -.secondary-button:active { - transform: translateY(1px) scale(0.99); -} - -@media (min-width: 768px) { - .app-header h1 { - font-size: 2.25rem; - } - - .download-form { - padding: 1.75rem 2rem; - } -} diff --git a/web/src/App.tsx b/web/src/App.tsx index 26d7981..2ac471b 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,26 +1,43 @@ -import "./App.css"; +import { AlertCircle, HardDrive, Server } from "lucide-react"; import { DownloadForm } from "./components/feature/DownloadForm"; import { DownloadItem } from "./components/feature/DownloadItem"; -import { Layout } from "./components/ui/Layout"; -import { Alert, AlertDescription, AlertTitle } from "./components/ui/Alert"; -import { AlertCircle } from "lucide-react"; -import { SessionProvider, useSession } from "./context/SessionContext"; import { SessionTimer } from "./components/feature/SessionTimer"; +import { Alert, AlertDescription, AlertTitle } from "./components/ui/Alert"; +import { Layout } from "./components/ui/Layout"; +import { SessionProvider } from "./context/SessionContext"; +import { useSession } from "./context/session-context"; function DownloadList() { const { tasks, expiresAt } = useSession(); return ( -
+ Transfer activity +
++ Monitor queue status, transfer speed and final files from a single workspace. +
+No downloads in session
-Files are removed after 1 hour of inactivity
++ Queue is empty +
++ Your queued, active and completed transfers will appear here with progress, speed and the final save action. +
- Paste a link to start. Files are downloaded to the server first. +
+ Paste a file URL, let the server fetch it first, then keep every transfer visible until the session window expires. +
+Server-side transfer
++ Downloads are fetched on the VPS first, then exposed as a final file in your session.
Ephemeral storage
++ Files stay available for one hour of activity and are removed automatically after expiration. +
+New transfer
++ Paste a direct file URL to add it to the active session queue. +
+