prjct-cli 0.11.0 → 0.11.1

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.
Files changed (80) hide show
  1. package/package.json +11 -1
  2. package/packages/shared/dist/index.d.ts +615 -0
  3. package/packages/shared/dist/index.js +204 -0
  4. package/packages/shared/package.json +29 -0
  5. package/packages/shared/src/index.ts +9 -0
  6. package/packages/shared/src/schemas.ts +124 -0
  7. package/packages/shared/src/types.ts +187 -0
  8. package/packages/shared/src/utils.ts +148 -0
  9. package/packages/shared/tsconfig.json +18 -0
  10. package/packages/web/README.md +36 -0
  11. package/packages/web/app/api/claude/sessions/route.ts +44 -0
  12. package/packages/web/app/api/claude/status/route.ts +34 -0
  13. package/packages/web/app/api/projects/[id]/delete/route.ts +21 -0
  14. package/packages/web/app/api/projects/[id]/icon/route.ts +33 -0
  15. package/packages/web/app/api/projects/[id]/route.ts +29 -0
  16. package/packages/web/app/api/projects/[id]/stats/route.ts +36 -0
  17. package/packages/web/app/api/projects/[id]/status/route.ts +21 -0
  18. package/packages/web/app/api/projects/route.ts +16 -0
  19. package/packages/web/app/api/sessions/history/route.ts +122 -0
  20. package/packages/web/app/api/stats/route.ts +38 -0
  21. package/packages/web/app/error.tsx +34 -0
  22. package/packages/web/app/favicon.ico +0 -0
  23. package/packages/web/app/globals.css +155 -0
  24. package/packages/web/app/layout.tsx +43 -0
  25. package/packages/web/app/loading.tsx +7 -0
  26. package/packages/web/app/not-found.tsx +25 -0
  27. package/packages/web/app/page.tsx +227 -0
  28. package/packages/web/app/project/[id]/error.tsx +41 -0
  29. package/packages/web/app/project/[id]/loading.tsx +9 -0
  30. package/packages/web/app/project/[id]/not-found.tsx +27 -0
  31. package/packages/web/app/project/[id]/page.tsx +253 -0
  32. package/packages/web/app/project/[id]/stats/page.tsx +447 -0
  33. package/packages/web/app/sessions/page.tsx +165 -0
  34. package/packages/web/app/settings/page.tsx +150 -0
  35. package/packages/web/components/AppSidebar.tsx +113 -0
  36. package/packages/web/components/CommandButton.tsx +39 -0
  37. package/packages/web/components/ConnectionStatus.tsx +29 -0
  38. package/packages/web/components/Logo.tsx +65 -0
  39. package/packages/web/components/MarkdownContent.tsx +123 -0
  40. package/packages/web/components/ProjectAvatar.tsx +54 -0
  41. package/packages/web/components/TechStackBadges.tsx +20 -0
  42. package/packages/web/components/TerminalTab.tsx +84 -0
  43. package/packages/web/components/TerminalTabs.tsx +210 -0
  44. package/packages/web/components/charts/SessionsChart.tsx +172 -0
  45. package/packages/web/components/providers.tsx +45 -0
  46. package/packages/web/components/ui/alert-dialog.tsx +157 -0
  47. package/packages/web/components/ui/badge.tsx +46 -0
  48. package/packages/web/components/ui/button.tsx +60 -0
  49. package/packages/web/components/ui/card.tsx +92 -0
  50. package/packages/web/components/ui/chart.tsx +385 -0
  51. package/packages/web/components/ui/dropdown-menu.tsx +257 -0
  52. package/packages/web/components/ui/scroll-area.tsx +58 -0
  53. package/packages/web/components/ui/sheet.tsx +139 -0
  54. package/packages/web/components/ui/tabs.tsx +66 -0
  55. package/packages/web/components/ui/tooltip.tsx +61 -0
  56. package/packages/web/components.json +22 -0
  57. package/packages/web/context/TerminalContext.tsx +45 -0
  58. package/packages/web/context/TerminalTabsContext.tsx +136 -0
  59. package/packages/web/eslint.config.mjs +18 -0
  60. package/packages/web/hooks/useClaudeTerminal.ts +375 -0
  61. package/packages/web/hooks/useProjectStats.ts +38 -0
  62. package/packages/web/hooks/useProjects.ts +73 -0
  63. package/packages/web/hooks/useStats.ts +28 -0
  64. package/packages/web/lib/format.ts +23 -0
  65. package/packages/web/lib/parse-prjct-files.ts +1122 -0
  66. package/packages/web/lib/projects.ts +452 -0
  67. package/packages/web/lib/pty.ts +101 -0
  68. package/packages/web/lib/query-config.ts +44 -0
  69. package/packages/web/lib/utils.ts +6 -0
  70. package/packages/web/next-env.d.ts +6 -0
  71. package/packages/web/next.config.ts +7 -0
  72. package/packages/web/package.json +53 -0
  73. package/packages/web/postcss.config.mjs +7 -0
  74. package/packages/web/public/file.svg +1 -0
  75. package/packages/web/public/globe.svg +1 -0
  76. package/packages/web/public/next.svg +1 -0
  77. package/packages/web/public/vercel.svg +1 -0
  78. package/packages/web/public/window.svg +1 -0
  79. package/packages/web/server.ts +262 -0
  80. package/packages/web/tsconfig.json +34 -0
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022", "DOM"],
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "declaration": true,
12
+ "declarationMap": true,
13
+ "outDir": "./dist",
14
+ "rootDir": "./src"
15
+ },
16
+ "include": ["src/**/*"],
17
+ "exclude": ["node_modules", "dist"]
18
+ }
@@ -0,0 +1,36 @@
1
+ This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2
+
3
+ ## Getting Started
4
+
5
+ First, run the development server:
6
+
7
+ ```bash
8
+ npm run dev
9
+ # or
10
+ yarn dev
11
+ # or
12
+ pnpm dev
13
+ # or
14
+ bun dev
15
+ ```
16
+
17
+ Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18
+
19
+ You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20
+
21
+ This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22
+
23
+ ## Learn More
24
+
25
+ To learn more about Next.js, take a look at the following resources:
26
+
27
+ - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28
+ - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29
+
30
+ You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31
+
32
+ ## Deploy on Vercel
33
+
34
+ The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35
+
36
+ Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
@@ -0,0 +1,44 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { createClaudeSession, listSessions } from '@/lib/pty'
3
+
4
+ export const dynamic = 'force-dynamic'
5
+
6
+ export async function GET() {
7
+ try {
8
+ const sessions = listSessions()
9
+ return NextResponse.json({ success: true, data: sessions })
10
+ } catch (error) {
11
+ return NextResponse.json(
12
+ { success: false, error: 'Failed to list sessions' },
13
+ { status: 500 }
14
+ )
15
+ }
16
+ }
17
+
18
+ export async function POST(request: Request) {
19
+ try {
20
+ const body = await request.json()
21
+ const { sessionId, projectDir } = body
22
+
23
+ if (!sessionId || !projectDir) {
24
+ return NextResponse.json(
25
+ { success: false, error: 'sessionId and projectDir are required' },
26
+ { status: 400 }
27
+ )
28
+ }
29
+
30
+ // Create PTY session
31
+ createClaudeSession(sessionId, projectDir)
32
+
33
+ return NextResponse.json({
34
+ success: true,
35
+ data: { sessionId, projectDir }
36
+ })
37
+ } catch (error) {
38
+ console.error('Failed to create session:', error)
39
+ return NextResponse.json(
40
+ { success: false, error: 'Failed to create session' },
41
+ { status: 500 }
42
+ )
43
+ }
44
+ }
@@ -0,0 +1,34 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { exec } from 'child_process'
3
+ import { promisify } from 'util'
4
+
5
+ const execAsync = promisify(exec)
6
+
7
+ export const dynamic = 'force-dynamic'
8
+
9
+ export async function GET() {
10
+ try {
11
+ // Check if claude is available
12
+ const { stdout } = await execAsync('which claude && claude --version 2>/dev/null || echo "not found"')
13
+ const lines = stdout.trim().split('\n')
14
+
15
+ const available = !stdout.includes('not found') && lines.length > 0
16
+ const version = available ? lines[lines.length - 1] : null
17
+
18
+ return NextResponse.json({
19
+ success: true,
20
+ data: {
21
+ available,
22
+ version
23
+ }
24
+ })
25
+ } catch (error) {
26
+ return NextResponse.json({
27
+ success: true,
28
+ data: {
29
+ available: false,
30
+ version: null
31
+ }
32
+ })
33
+ }
34
+ }
@@ -0,0 +1,21 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { moveToTrash } from '@/lib/projects'
3
+
4
+ export const dynamic = 'force-dynamic'
5
+
6
+ export async function POST(
7
+ request: Request,
8
+ { params }: { params: Promise<{ id: string }> }
9
+ ) {
10
+ try {
11
+ const { id } = await params
12
+ const result = await moveToTrash(id)
13
+ return NextResponse.json({ success: true, ...result })
14
+ } catch (error) {
15
+ const message = error instanceof Error ? error.message : 'Failed to delete project'
16
+ return NextResponse.json(
17
+ { success: false, error: message },
18
+ { status: 500 }
19
+ )
20
+ }
21
+ }
@@ -0,0 +1,33 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { promises as fs } from 'fs'
3
+ import { getProjects } from '@/lib/projects'
4
+ import { lookup } from 'mime-types'
5
+
6
+ export const dynamic = 'force-dynamic'
7
+
8
+ export async function GET(
9
+ request: Request,
10
+ { params }: { params: Promise<{ id: string }> }
11
+ ) {
12
+ try {
13
+ const { id } = await params
14
+ const projects = await getProjects()
15
+ const project = projects.find(p => p.id === id)
16
+
17
+ if (!project?.iconPath) {
18
+ return new NextResponse(null, { status: 404 })
19
+ }
20
+
21
+ const file = await fs.readFile(project.iconPath)
22
+ const mimeType = lookup(project.iconPath) || 'application/octet-stream'
23
+
24
+ return new NextResponse(file, {
25
+ headers: {
26
+ 'Content-Type': mimeType,
27
+ 'Cache-Control': 'public, max-age=86400'
28
+ }
29
+ })
30
+ } catch {
31
+ return new NextResponse(null, { status: 404 })
32
+ }
33
+ }
@@ -0,0 +1,29 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { getProject } from '@/lib/projects'
3
+
4
+ export const dynamic = 'force-dynamic'
5
+
6
+ export async function GET(
7
+ request: Request,
8
+ { params }: { params: Promise<{ id: string }> }
9
+ ) {
10
+ const { id } = await params
11
+
12
+ try {
13
+ const project = await getProject(id)
14
+
15
+ if (!project) {
16
+ return NextResponse.json(
17
+ { success: false, error: 'Project not found' },
18
+ { status: 404 }
19
+ )
20
+ }
21
+
22
+ return NextResponse.json({ success: true, data: project })
23
+ } catch (error) {
24
+ return NextResponse.json(
25
+ { success: false, error: 'Failed to get project' },
26
+ { status: 500 }
27
+ )
28
+ }
29
+ }
@@ -0,0 +1,36 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import { getProjectStats, getRawProjectFiles } from '@/lib/parse-prjct-files'
3
+
4
+ export async function GET(
5
+ request: NextRequest,
6
+ { params }: { params: Promise<{ id: string }> }
7
+ ) {
8
+ const { id: projectId } = await params
9
+
10
+ if (!projectId) {
11
+ return NextResponse.json(
12
+ { success: false, error: 'Project ID required' },
13
+ { status: 400 }
14
+ )
15
+ }
16
+
17
+ try {
18
+ // Get both parsed stats AND raw files
19
+ const [stats, raw] = await Promise.all([
20
+ getProjectStats(projectId),
21
+ getRawProjectFiles(projectId)
22
+ ])
23
+
24
+ return NextResponse.json({
25
+ success: true,
26
+ data: stats,
27
+ raw // Raw markdown files for direct rendering
28
+ })
29
+ } catch (error) {
30
+ console.error('[API] Error getting project stats:', error)
31
+ return NextResponse.json(
32
+ { success: false, error: 'Failed to get project stats' },
33
+ { status: 500 }
34
+ )
35
+ }
36
+ }
@@ -0,0 +1,21 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { getProjectStatus } from '@/lib/projects'
3
+
4
+ export const dynamic = 'force-dynamic'
5
+
6
+ export async function GET(
7
+ request: Request,
8
+ { params }: { params: Promise<{ id: string }> }
9
+ ) {
10
+ const { id } = await params
11
+
12
+ try {
13
+ const status = await getProjectStatus(id)
14
+ return NextResponse.json({ success: true, data: status })
15
+ } catch (error) {
16
+ return NextResponse.json(
17
+ { success: false, error: 'Failed to get status' },
18
+ { status: 500 }
19
+ )
20
+ }
21
+ }
@@ -0,0 +1,16 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { getProjects } from '@/lib/projects'
3
+
4
+ export const dynamic = 'force-dynamic'
5
+
6
+ export async function GET() {
7
+ try {
8
+ const projects = await getProjects()
9
+ return NextResponse.json({ success: true, data: projects })
10
+ } catch (error) {
11
+ return NextResponse.json(
12
+ { success: false, error: 'Failed to list projects' },
13
+ { status: 500 }
14
+ )
15
+ }
16
+ }
@@ -0,0 +1,122 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { promises as fs } from 'fs'
3
+ import { join } from 'path'
4
+ import { homedir } from 'os'
5
+
6
+ export const dynamic = 'force-dynamic'
7
+
8
+ interface SessionEvent {
9
+ ts?: string
10
+ timestamp?: string
11
+ type?: string
12
+ action?: string
13
+ }
14
+
15
+ interface DailyStats {
16
+ date: string
17
+ tasks: number
18
+ ships: number
19
+ }
20
+
21
+ export async function GET() {
22
+ try {
23
+ const globalStorage = join(homedir(), '.prjct-cli', 'projects')
24
+
25
+ let projects: string[]
26
+ try {
27
+ projects = await fs.readdir(globalStorage)
28
+ } catch {
29
+ return NextResponse.json({
30
+ success: true,
31
+ data: {
32
+ chartData: [],
33
+ totals: { tasks: 0, ships: 0 },
34
+ dateRange: { start: '', end: '' }
35
+ }
36
+ })
37
+ }
38
+
39
+ // Aggregate by date
40
+ const dailyMap = new Map<string, { tasks: number; ships: number }>()
41
+
42
+ // Calculate date range (last 90 days)
43
+ const endDate = new Date()
44
+ const startDate = new Date()
45
+ startDate.setDate(startDate.getDate() - 90)
46
+
47
+ for (const projectId of projects) {
48
+ const contextPath = join(globalStorage, projectId, 'memory', 'context.jsonl')
49
+
50
+ try {
51
+ const content = await fs.readFile(contextPath, 'utf-8')
52
+ const lines = content.trim().split('\n').filter(Boolean)
53
+
54
+ for (const line of lines) {
55
+ try {
56
+ const event: SessionEvent = JSON.parse(line)
57
+ const timestamp = event.ts || event.timestamp
58
+ if (!timestamp) continue
59
+
60
+ const eventDate = new Date(timestamp)
61
+ if (isNaN(eventDate.getTime())) continue
62
+ if (eventDate < startDate || eventDate > endDate) continue
63
+
64
+ const dateKey = eventDate.toISOString().split('T')[0]
65
+ const current = dailyMap.get(dateKey) || { tasks: 0, ships: 0 }
66
+
67
+ const eventType = event.type || event.action
68
+
69
+ // Count tasks completed
70
+ if (eventType === 'task_complete' || eventType === 'task_completed') {
71
+ current.tasks++
72
+ }
73
+
74
+ // Count features shipped
75
+ if (eventType === 'feature_ship' || eventType === 'feature_shipped') {
76
+ current.ships++
77
+ }
78
+
79
+ dailyMap.set(dateKey, current)
80
+ } catch {
81
+ // Skip malformed lines
82
+ }
83
+ }
84
+ } catch {
85
+ // Skip projects without context.jsonl
86
+ }
87
+ }
88
+
89
+ // Convert to sorted array
90
+ const data: DailyStats[] = Array.from(dailyMap.entries())
91
+ .map(([date, stats]) => ({
92
+ date,
93
+ tasks: stats.tasks,
94
+ ships: stats.ships
95
+ }))
96
+ .sort((a, b) => a.date.localeCompare(b.date))
97
+
98
+ // Calculate totals for summary
99
+ const totals = {
100
+ tasks: data.reduce((sum, d) => sum + d.tasks, 0),
101
+ ships: data.reduce((sum, d) => sum + d.ships, 0)
102
+ }
103
+
104
+ return NextResponse.json({
105
+ success: true,
106
+ data: {
107
+ chartData: data,
108
+ totals,
109
+ dateRange: {
110
+ start: startDate.toISOString().split('T')[0],
111
+ end: endDate.toISOString().split('T')[0]
112
+ }
113
+ }
114
+ })
115
+ } catch (error) {
116
+ console.error('Sessions history error:', error)
117
+ return NextResponse.json(
118
+ { success: false, error: 'Failed to fetch session history' },
119
+ { status: 500 }
120
+ )
121
+ }
122
+ }
@@ -0,0 +1,38 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { getProjects } from '@/lib/projects'
3
+ import { exec } from 'child_process'
4
+ import { promisify } from 'util'
5
+
6
+ const execAsync = promisify(exec)
7
+
8
+ export const dynamic = 'force-dynamic'
9
+
10
+ async function getGitUserName(): Promise<string> {
11
+ try {
12
+ const { stdout } = await execAsync('git config user.name')
13
+ return stdout.trim() || 'Developer'
14
+ } catch {
15
+ return 'Developer'
16
+ }
17
+ }
18
+
19
+ export async function GET() {
20
+ try {
21
+ const [projects, userName] = await Promise.all([
22
+ getProjects(),
23
+ getGitUserName()
24
+ ])
25
+
26
+ const stats = {
27
+ userName,
28
+ totalProjects: projects.length
29
+ }
30
+
31
+ return NextResponse.json({ success: true, data: stats })
32
+ } catch {
33
+ return NextResponse.json(
34
+ { success: false, error: 'Failed to get stats' },
35
+ { status: 500 }
36
+ )
37
+ }
38
+ }
@@ -0,0 +1,34 @@
1
+ 'use client'
2
+
3
+ import { useEffect } from 'react'
4
+ import { Button } from '@/components/ui/button'
5
+ import { AlertTriangle } from 'lucide-react'
6
+
7
+ export default function Error({
8
+ error,
9
+ reset,
10
+ }: {
11
+ error: Error & { digest?: string }
12
+ reset: () => void
13
+ }) {
14
+ useEffect(() => {
15
+ console.error(error)
16
+ }, [error])
17
+
18
+ return (
19
+ <div className="flex items-center justify-center h-full">
20
+ <div className="text-center space-y-4">
21
+ <div className="w-16 h-16 rounded-full bg-destructive/10 flex items-center justify-center mx-auto">
22
+ <AlertTriangle className="w-8 h-8 text-destructive" />
23
+ </div>
24
+ <div>
25
+ <h2 className="text-lg font-medium">Something went wrong</h2>
26
+ <p className="text-sm text-muted-foreground mt-1">{error.message}</p>
27
+ </div>
28
+ <Button onClick={reset} variant="outline">
29
+ Try again
30
+ </Button>
31
+ </div>
32
+ </div>
33
+ )
34
+ }
Binary file
@@ -0,0 +1,155 @@
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+ @import "@xterm/xterm/css/xterm.css";
4
+
5
+ @custom-variant dark (&:is(.dark *));
6
+
7
+ @theme inline {
8
+ --color-background: var(--background);
9
+ --color-foreground: var(--foreground);
10
+ --font-sans: var(--font-geist-sans);
11
+ --font-mono: var(--font-geist-mono);
12
+ --color-sidebar-ring: var(--sidebar-ring);
13
+ --color-sidebar-border: var(--sidebar-border);
14
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
15
+ --color-sidebar-accent: var(--sidebar-accent);
16
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
17
+ --color-sidebar-primary: var(--sidebar-primary);
18
+ --color-sidebar-foreground: var(--sidebar-foreground);
19
+ --color-sidebar: var(--sidebar);
20
+ --color-chart-5: var(--chart-5);
21
+ --color-chart-4: var(--chart-4);
22
+ --color-chart-3: var(--chart-3);
23
+ --color-chart-2: var(--chart-2);
24
+ --color-chart-1: var(--chart-1);
25
+ --color-ring: var(--ring);
26
+ --color-input: var(--input);
27
+ --color-border: var(--border);
28
+ --color-destructive: var(--destructive);
29
+ --color-accent-foreground: var(--accent-foreground);
30
+ --color-accent: var(--accent);
31
+ --color-muted-foreground: var(--muted-foreground);
32
+ --color-muted: var(--muted);
33
+ --color-secondary-foreground: var(--secondary-foreground);
34
+ --color-secondary: var(--secondary);
35
+ --color-primary-foreground: var(--primary-foreground);
36
+ --color-primary: var(--primary);
37
+ --color-popover-foreground: var(--popover-foreground);
38
+ --color-popover: var(--popover);
39
+ --color-card-foreground: var(--card-foreground);
40
+ --color-card: var(--card);
41
+ --radius-sm: calc(var(--radius) - 4px);
42
+ --radius-md: calc(var(--radius) - 2px);
43
+ --radius-lg: var(--radius);
44
+ --radius-xl: calc(var(--radius) + 4px);
45
+ }
46
+
47
+ :root {
48
+ --radius: 0.625rem;
49
+ --background: oklch(1 0 0);
50
+ --foreground: oklch(0.145 0 0);
51
+ --card: oklch(1 0 0);
52
+ --card-foreground: oklch(0.145 0 0);
53
+ --popover: oklch(1 0 0);
54
+ --popover-foreground: oklch(0.145 0 0);
55
+ --primary: oklch(0.205 0 0);
56
+ --primary-foreground: oklch(0.985 0 0);
57
+ --secondary: oklch(0.97 0 0);
58
+ --secondary-foreground: oklch(0.205 0 0);
59
+ --muted: oklch(0.97 0 0);
60
+ --muted-foreground: oklch(0.556 0 0);
61
+ --accent: oklch(0.97 0 0);
62
+ --accent-foreground: oklch(0.205 0 0);
63
+ --destructive: oklch(0.577 0.245 27.325);
64
+ --border: oklch(0.922 0 0);
65
+ --input: oklch(0.922 0 0);
66
+ --ring: oklch(0.708 0 0);
67
+ --chart-1: oklch(0.646 0.222 41.116);
68
+ --chart-2: oklch(0.6 0.118 184.704);
69
+ --chart-3: oklch(0.398 0.07 227.392);
70
+ --chart-4: oklch(0.828 0.189 84.429);
71
+ --chart-5: oklch(0.769 0.188 70.08);
72
+ --sidebar: oklch(0.985 0 0);
73
+ --sidebar-foreground: oklch(0.145 0 0);
74
+ --sidebar-primary: oklch(0.205 0 0);
75
+ --sidebar-primary-foreground: oklch(0.985 0 0);
76
+ --sidebar-accent: oklch(0.97 0 0);
77
+ --sidebar-accent-foreground: oklch(0.205 0 0);
78
+ --sidebar-border: oklch(0.922 0 0);
79
+ --sidebar-ring: oklch(0.708 0 0);
80
+ }
81
+
82
+ .dark {
83
+ --background: oklch(0.145 0 0);
84
+ --foreground: oklch(0.985 0 0);
85
+ --card: oklch(0.205 0 0);
86
+ --card-foreground: oklch(0.985 0 0);
87
+ --popover: oklch(0.205 0 0);
88
+ --popover-foreground: oklch(0.985 0 0);
89
+ --primary: oklch(0.922 0 0);
90
+ --primary-foreground: oklch(0.205 0 0);
91
+ --secondary: oklch(0.269 0 0);
92
+ --secondary-foreground: oklch(0.985 0 0);
93
+ --muted: oklch(0.269 0 0);
94
+ --muted-foreground: oklch(0.708 0 0);
95
+ --accent: oklch(0.269 0 0);
96
+ --accent-foreground: oklch(0.985 0 0);
97
+ --destructive: oklch(0.704 0.191 22.216);
98
+ --border: oklch(1 0 0 / 10%);
99
+ --input: oklch(1 0 0 / 15%);
100
+ --ring: oklch(0.556 0 0);
101
+ --chart-1: oklch(0.488 0.243 264.376);
102
+ --chart-2: oklch(0.696 0.17 162.48);
103
+ --chart-3: oklch(0.769 0.188 70.08);
104
+ --chart-4: oklch(0.627 0.265 303.9);
105
+ --chart-5: oklch(0.645 0.246 16.439);
106
+ --sidebar: oklch(0.205 0 0);
107
+ --sidebar-foreground: oklch(0.985 0 0);
108
+ --sidebar-primary: oklch(0.488 0.243 264.376);
109
+ --sidebar-primary-foreground: oklch(0.985 0 0);
110
+ --sidebar-accent: oklch(0.269 0 0);
111
+ --sidebar-accent-foreground: oklch(0.985 0 0);
112
+ --sidebar-border: oklch(1 0 0 / 10%);
113
+ --sidebar-ring: oklch(0.556 0 0);
114
+ }
115
+
116
+ @layer base {
117
+ * {
118
+ @apply border-border outline-ring/50;
119
+ }
120
+ body {
121
+ @apply bg-background text-foreground;
122
+ }
123
+ }
124
+
125
+ /* Terminal styles */
126
+ .xterm {
127
+ padding: 8px 4px;
128
+ }
129
+
130
+ .xterm-viewport {
131
+ overflow-y: auto !important;
132
+ }
133
+
134
+ /* Logo - Fancy border animation */
135
+ @keyframes borderAnimation {
136
+ 0% { background-position: 0% 50%; }
137
+ 50% { background-position: 100% 50%; }
138
+ 100% { background-position: 0% 50%; }
139
+ }
140
+
141
+ .fancy-border {
142
+ position: absolute;
143
+ inset: -3px;
144
+ border-radius: 12px;
145
+ background: linear-gradient(45deg, #ff3d00, #00c6ff, #7a00ff, #09ff00, #ff3d00);
146
+ background-size: 400% 400%;
147
+ z-index: 0;
148
+ animation: borderAnimation 8s ease infinite;
149
+ filter: blur(4px);
150
+ opacity: 0.8;
151
+ }
152
+
153
+ .fancy-border.rounded-full {
154
+ border-radius: 9999px;
155
+ }
@@ -0,0 +1,43 @@
1
+ import type { Metadata } from 'next'
2
+ import { Geist, Geist_Mono } from 'next/font/google'
3
+ import './globals.css'
4
+ import { Providers } from '@/components/providers'
5
+ import { AppSidebar } from '@/components/AppSidebar'
6
+
7
+ const geistSans = Geist({
8
+ variable: '--font-geist-sans',
9
+ subsets: ['latin'],
10
+ })
11
+
12
+ const geistMono = Geist_Mono({
13
+ variable: '--font-geist-mono',
14
+ subsets: ['latin'],
15
+ })
16
+
17
+ export const metadata: Metadata = {
18
+ title: 'prjct - Developer Momentum',
19
+ description: 'Ship fast, track progress, stay focused.',
20
+ }
21
+
22
+ export default function RootLayout({
23
+ children,
24
+ }: Readonly<{
25
+ children: React.ReactNode
26
+ }>) {
27
+ return (
28
+ <html lang="en" suppressHydrationWarning>
29
+ <body
30
+ className={`${geistSans.variable} ${geistMono.variable} antialiased`}
31
+ >
32
+ <Providers>
33
+ <div className="flex h-screen bg-background">
34
+ <AppSidebar />
35
+ <main className="flex-1 overflow-hidden">
36
+ {children}
37
+ </main>
38
+ </div>
39
+ </Providers>
40
+ </body>
41
+ </html>
42
+ )
43
+ }