@bahulam/code 0.1.6 → 0.1.8
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/LICENSE +1 -1
- package/NOTICE +6 -6
- package/README.md +11 -18
- package/package.json +11 -11
- package/src/agents/loader.mjs +1 -1
- package/src/agents/workflow_loader.mjs +2 -2
- package/src/auth/{tarang-auth.mjs → bahulam-auth.mjs} +4 -3
- package/src/commands/agent.mjs +3 -3
- package/src/commands/device.mjs +2 -2
- package/src/commands/pair.mjs +2 -2
- package/src/commands/remote.mjs +3 -3
- package/src/commands/workflow.mjs +5 -5
- package/src/config/env.mjs +9 -2
- package/src/config/memory-loader.mjs +1 -1
- package/src/config/model-catalog-default.json +4 -3
- package/src/config/model-catalog.mjs +2 -0
- package/src/config/settings.mjs +2 -2
- package/src/context/prose-chunker.mjs +162 -5
- package/src/core/agent-loop.mjs +17 -0
- package/src/core/attachments.mjs +66 -4
- package/src/core/backend-url.mjs +11 -12
- package/src/core/bundled-runtime.mjs +1 -1
- package/src/core/callback-client.mjs +1 -1
- package/src/core/headless.mjs +18 -4
- package/src/core/jsonl-writer.mjs +18 -18
- package/src/core/lint-resolver.mjs +472 -0
- package/src/core/local-agent.mjs +40 -8
- package/src/core/local-store.mjs +5 -1
- package/src/core/mode-selector.mjs +1 -1
- package/src/core/output-filter.mjs +22 -26
- package/src/core/pricing.mjs +10 -3
- package/src/core/project-artifacts.mjs +3 -3
- package/src/core/project-context-loader.mjs +9 -9
- package/src/core/settings-sync.mjs +1 -1
- package/src/core/stagnation.mjs +61 -1
- package/src/core/stream-client.mjs +13 -8
- package/src/core/system-prompt.mjs +2 -2
- package/src/core/tool-executor.mjs +178 -84
- package/src/daemon/http-dashboard.mjs +400 -0
- package/src/daemon/session-core.mjs +1 -0
- package/src/local-service/agent-relay.mjs +868 -0
- package/src/local-service/approval-bridge.mjs +222 -0
- package/src/local-service/browser.mjs +24 -0
- package/src/local-service/command.mjs +155 -0
- package/src/local-service/file-access.mjs +393 -0
- package/src/local-service/machine.mjs +86 -0
- package/src/local-service/paths.mjs +29 -0
- package/src/local-service/preview-converters.mjs +260 -0
- package/src/local-service/server.mjs +2644 -0
- package/src/local-service/session-store.mjs +221 -0
- package/src/mcp/client.mjs +4 -4
- package/src/onboarding/preflight.mjs +2 -2
- package/src/terminal/agents.mjs +2 -2
- package/src/terminal/analytics.mjs +2 -6
- package/src/terminal/ansi.mjs +11 -3
- package/src/terminal/main.mjs +43 -17
- package/src/terminal/model-catalog-display.mjs +111 -0
- package/src/terminal/repl-format.mjs +4 -1
- package/src/terminal/repl-model-form.mjs +81 -14
- package/src/terminal/repl-render.mjs +37 -7
- package/src/terminal/repl-resume.mjs +2 -2
- package/src/terminal/repl-state.mjs +12 -0
- package/src/terminal/repl.mjs +248 -70
- package/src/terminal/tool-display.mjs +32 -0
- package/src/terminal/watch-state.mjs +118 -0
- package/src/tools/agent.mjs +2 -2
- package/src/tools/analyze-image.mjs +158 -0
- package/src/tools/bash.mjs +11 -9
- package/src/tools/generate-image.mjs +411 -0
- package/src/tools/lint.mjs +24 -43
- package/src/tools/project-overview.mjs +12 -12
- package/src/tools/registry.mjs +4 -0
- package/src/ui/formatter.mjs +2 -2
- package/src/ui/icons.mjs +2 -0
- package/src/ui/input-dock.mjs +7 -7
- package/src/ui/palette.mjs +1 -1
- package/src/ui/slash-commands.mjs +4 -6
- package/src/ui/spinner.mjs +1 -1
- package/src/ui/term.mjs +2 -6
- package/src/ui/tool-card.mjs +81 -6
- package/pulse/app/activity/page.tsx +0 -190
- package/pulse/app/api/activity/route.ts +0 -138
- package/pulse/app/api/benchmark/route.ts +0 -113
- package/pulse/app/api/benchmarks/route.ts +0 -195
- package/pulse/app/api/costs/route.ts +0 -88
- package/pulse/app/api/export/route.ts +0 -77
- package/pulse/app/api/history/route.ts +0 -11
- package/pulse/app/api/import/route.ts +0 -31
- package/pulse/app/api/memory/route.ts +0 -50
- package/pulse/app/api/plans/route.ts +0 -9
- package/pulse/app/api/projects/[slug]/route.ts +0 -96
- package/pulse/app/api/projects/route.ts +0 -121
- package/pulse/app/api/sessions/[id]/replay/route.ts +0 -20
- package/pulse/app/api/sessions/[id]/route.ts +0 -31
- package/pulse/app/api/sessions/route.ts +0 -112
- package/pulse/app/api/settings/route.ts +0 -14
- package/pulse/app/api/stats/route.ts +0 -143
- package/pulse/app/api/todos/route.ts +0 -9
- package/pulse/app/api/tools/route.ts +0 -160
- package/pulse/app/benchmarks/page.tsx +0 -224
- package/pulse/app/costs/page.tsx +0 -179
- package/pulse/app/export/page.tsx +0 -465
- package/pulse/app/favicon.ico +0 -0
- package/pulse/app/globals.css +0 -263
- package/pulse/app/help/page.tsx +0 -143
- package/pulse/app/history/page.tsx +0 -157
- package/pulse/app/layout.tsx +0 -46
- package/pulse/app/memory/page.tsx +0 -365
- package/pulse/app/overview-client.tsx +0 -393
- package/pulse/app/page.tsx +0 -14
- package/pulse/app/plans/page.tsx +0 -308
- package/pulse/app/projects/[slug]/page.tsx +0 -390
- package/pulse/app/projects/page.tsx +0 -110
- package/pulse/app/sessions/[id]/page.tsx +0 -243
- package/pulse/app/sessions/page.tsx +0 -39
- package/pulse/app/settings/page.tsx +0 -188
- package/pulse/app/todos/page.tsx +0 -211
- package/pulse/app/tools/page.tsx +0 -249
- package/pulse/cli.js +0 -164
- package/pulse/components/activity/day-of-week-chart.tsx +0 -35
- package/pulse/components/activity/streak-card.tsx +0 -36
- package/pulse/components/costs/cache-efficiency-panel.tsx +0 -76
- package/pulse/components/costs/cost-by-project-chart.tsx +0 -48
- package/pulse/components/costs/cost-over-time-chart.tsx +0 -95
- package/pulse/components/costs/model-token-table.tsx +0 -60
- package/pulse/components/global-search.tsx +0 -193
- package/pulse/components/keyboard-nav-provider.tsx +0 -23
- package/pulse/components/layout/bottom-nav.tsx +0 -53
- package/pulse/components/layout/client-layout.tsx +0 -31
- package/pulse/components/layout/sidebar-context.tsx +0 -50
- package/pulse/components/layout/sidebar.tsx +0 -183
- package/pulse/components/layout/top-bar.tsx +0 -121
- package/pulse/components/overview/activity-heatmap.tsx +0 -107
- package/pulse/components/overview/conversation-table.tsx +0 -148
- package/pulse/components/overview/model-breakdown-donut.tsx +0 -95
- package/pulse/components/overview/peak-hours-chart.tsx +0 -87
- package/pulse/components/overview/project-activity-donut.tsx +0 -96
- package/pulse/components/overview/stat-card.tsx +0 -102
- package/pulse/components/overview/usage-over-time-chart.tsx +0 -166
- package/pulse/components/projects/project-card.tsx +0 -175
- package/pulse/components/sessions/replay/assistant-markdown.tsx +0 -94
- package/pulse/components/sessions/replay/compaction-card.tsx +0 -25
- package/pulse/components/sessions/replay/session-sidebar.tsx +0 -231
- package/pulse/components/sessions/replay/token-accumulation-chart.tsx +0 -98
- package/pulse/components/sessions/replay/tool-call-badge.tsx +0 -127
- package/pulse/components/sessions/replay/turn-cards.tsx +0 -220
- package/pulse/components/sessions/replay/user-tool-result.tsx +0 -158
- package/pulse/components/sessions/session-badges.tsx +0 -49
- package/pulse/components/sessions/session-table.tsx +0 -299
- package/pulse/components/theme-provider.tsx +0 -44
- package/pulse/components/tools/feature-adoption-table.tsx +0 -58
- package/pulse/components/tools/mcp-server-panel.tsx +0 -45
- package/pulse/components/tools/tool-ranking-chart.tsx +0 -57
- package/pulse/components/tools/version-history-table.tsx +0 -32
- package/pulse/components/ui/alert.tsx +0 -66
- package/pulse/components/ui/badge.tsx +0 -48
- package/pulse/components/ui/breadcrumb.tsx +0 -109
- package/pulse/components/ui/button.tsx +0 -64
- package/pulse/components/ui/calendar.tsx +0 -220
- package/pulse/components/ui/card.tsx +0 -92
- package/pulse/components/ui/command.tsx +0 -158
- package/pulse/components/ui/dialog.tsx +0 -158
- package/pulse/components/ui/input.tsx +0 -21
- package/pulse/components/ui/popover.tsx +0 -89
- package/pulse/components/ui/progress.tsx +0 -31
- package/pulse/components/ui/select.tsx +0 -190
- package/pulse/components/ui/separator.tsx +0 -28
- package/pulse/components/ui/sheet.tsx +0 -143
- package/pulse/components/ui/skeleton.tsx +0 -13
- package/pulse/components/ui/table.tsx +0 -116
- package/pulse/components/ui/tabs.tsx +0 -91
- package/pulse/components/ui/tooltip.tsx +0 -57
- package/pulse/components/use-global-keyboard-nav.ts +0 -79
- package/pulse/components.json +0 -23
- package/pulse/eslint.config.mjs +0 -18
- package/pulse/lib/bahulam-paths.ts +0 -23
- package/pulse/lib/claude-reader.ts +0 -592
- package/pulse/lib/decode.ts +0 -129
- package/pulse/lib/pricing.ts +0 -102
- package/pulse/lib/replay-parser.ts +0 -165
- package/pulse/lib/tool-categories.ts +0 -140
- package/pulse/lib/utils.ts +0 -6
- package/pulse/next-env.d.ts +0 -6
- package/pulse/next.config.ts +0 -16
- package/pulse/package.json +0 -45
- package/pulse/postcss.config.mjs +0 -7
- package/pulse/public/activity.png +0 -0
- package/pulse/public/cc-lens.png +0 -0
- package/pulse/public/command-k.png +0 -0
- package/pulse/public/costs.png +0 -0
- package/pulse/public/dashboard-dark.png +0 -0
- package/pulse/public/dashboard-white.png +0 -0
- package/pulse/public/export.png +0 -0
- package/pulse/public/file.svg +0 -1
- package/pulse/public/globe.svg +0 -1
- package/pulse/public/next.svg +0 -1
- package/pulse/public/projects.png +0 -0
- package/pulse/public/session-chat.png +0 -0
- package/pulse/public/todos.png +0 -0
- package/pulse/public/tools.png +0 -0
- package/pulse/public/vercel.svg +0 -1
- package/pulse/public/window.svg +0 -1
- package/pulse/tsconfig.json +0 -34
- package/pulse/types/claude.ts +0 -294
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useMemo, useState } from 'react'
|
|
4
|
-
import { AreaChart, Area, XAxis, YAxis, Tooltip, CartesianGrid, ResponsiveContainer, Legend } from 'recharts'
|
|
5
|
-
import { formatCost } from '@/lib/decode'
|
|
6
|
-
import type { DailyCost } from '@/types/claude'
|
|
7
|
-
|
|
8
|
-
const MODEL_COLORS: Record<string, string> = {
|
|
9
|
-
'claude-opus-4-6': '#d97706',
|
|
10
|
-
'claude-opus-4-5-20251101': '#a78bfa',
|
|
11
|
-
'claude-sonnet-4-6': 'var(--viz-sky)',
|
|
12
|
-
'claude-haiku-4-5': '#34d399',
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function colorForModel(m: string): string {
|
|
16
|
-
for (const [key, col] of Object.entries(MODEL_COLORS)) {
|
|
17
|
-
if (m.includes(key.split('-').slice(2).join('-'))) return col
|
|
18
|
-
}
|
|
19
|
-
return '#7a8494'
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function shortModel(m: string): string {
|
|
23
|
-
if (m.includes('opus-4-6')) return 'Opus 4.6'
|
|
24
|
-
if (m.includes('opus-4-5')) return 'Opus 4.5'
|
|
25
|
-
if (m.includes('sonnet-4-6')) return 'Sonnet 4.6'
|
|
26
|
-
if (m.includes('haiku-4-5')) return 'Haiku 4.5'
|
|
27
|
-
return m
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface Props {
|
|
31
|
-
daily: DailyCost[]
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
type Window = 30 | 90 | 365
|
|
35
|
-
|
|
36
|
-
export function CostOverTimeChart({ daily }: Props) {
|
|
37
|
-
const [window, setWindow] = useState<Window>(90)
|
|
38
|
-
|
|
39
|
-
const { data, models } = useMemo(() => {
|
|
40
|
-
const sorted = [...daily].sort((a, b) => a.date.localeCompare(b.date))
|
|
41
|
-
const sliced = sorted.slice(-window)
|
|
42
|
-
const modelSet = new Set<string>()
|
|
43
|
-
for (const d of sliced) Object.keys(d.costs ?? {}).forEach(m => modelSet.add(m))
|
|
44
|
-
const models = [...modelSet]
|
|
45
|
-
return {
|
|
46
|
-
data: sliced.map(d => ({
|
|
47
|
-
date: d.date.slice(5), // MM-DD
|
|
48
|
-
...Object.fromEntries(models.map(m => [m, d.costs[m] ?? 0])),
|
|
49
|
-
total: d.total,
|
|
50
|
-
})),
|
|
51
|
-
models,
|
|
52
|
-
}
|
|
53
|
-
}, [daily, window])
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<div>
|
|
57
|
-
<div className="flex items-center justify-between mb-3">
|
|
58
|
-
<h3 className="text-[13px] font-bold text-muted-foreground uppercase tracking-widest">Cost Over Time</h3>
|
|
59
|
-
<div className="flex gap-1">
|
|
60
|
-
{([30, 90, 365] as Window[]).map(w => (
|
|
61
|
-
<button
|
|
62
|
-
key={w}
|
|
63
|
-
onClick={() => setWindow(w)}
|
|
64
|
-
className={`px-2 py-0.5 rounded text-[12px] transition-colors ${window === w ? 'bg-primary text-black font-bold' : 'text-muted-foreground hover:text-foreground border border-border'}`}
|
|
65
|
-
>
|
|
66
|
-
{w === 365 ? 'All' : `${w}d`}
|
|
67
|
-
</button>
|
|
68
|
-
))}
|
|
69
|
-
</div>
|
|
70
|
-
</div>
|
|
71
|
-
<ResponsiveContainer width="100%" height={200}>
|
|
72
|
-
<AreaChart data={data} margin={{ top: 4, right: 8, bottom: 0, left: 0 }}>
|
|
73
|
-
<CartesianGrid strokeDasharray="3 3" stroke="var(--border)" />
|
|
74
|
-
<XAxis dataKey="date" tick={{ fontSize: 11, fill: 'var(--muted-foreground)' }} tickLine={false} axisLine={false} interval="preserveStartEnd" />
|
|
75
|
-
<YAxis tick={{ fontSize: 11, fill: 'var(--muted-foreground)' }} tickLine={false} axisLine={false} tickFormatter={v => `$${v.toFixed(2)}`} width={48} />
|
|
76
|
-
<Tooltip
|
|
77
|
-
contentStyle={{ background: 'var(--card)', border: '1px solid var(--border)', borderRadius: 4, fontSize: 12 }}
|
|
78
|
-
formatter={(val: number | undefined, name?: string) => [formatCost(val ?? 0), shortModel(name ?? '')]}
|
|
79
|
-
/>
|
|
80
|
-
{models.map(m => (
|
|
81
|
-
<Area
|
|
82
|
-
key={m}
|
|
83
|
-
type="monotone"
|
|
84
|
-
dataKey={m}
|
|
85
|
-
stackId="1"
|
|
86
|
-
stroke={colorForModel(m)}
|
|
87
|
-
fill={colorForModel(m) + '30'}
|
|
88
|
-
strokeWidth={1.5}
|
|
89
|
-
/>
|
|
90
|
-
))}
|
|
91
|
-
</AreaChart>
|
|
92
|
-
</ResponsiveContainer>
|
|
93
|
-
</div>
|
|
94
|
-
)
|
|
95
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { formatTokens, formatCost } from '@/lib/decode'
|
|
2
|
-
import type { ModelCostBreakdown } from '@/types/claude'
|
|
3
|
-
|
|
4
|
-
function shortModel(m: string): string {
|
|
5
|
-
if (m.includes('opus-4-6')) return 'claude-opus-4.6'
|
|
6
|
-
if (m.includes('opus-4-5')) return 'claude-opus-4.5'
|
|
7
|
-
if (m.includes('sonnet-4-6')) return 'claude-sonnet-4.6'
|
|
8
|
-
if (m.includes('haiku-4-5')) return 'claude-haiku-4.5'
|
|
9
|
-
return m
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface Props {
|
|
13
|
-
models: ModelCostBreakdown[]
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function ModelTokenTable({ models }: Props) {
|
|
17
|
-
const totals = models.reduce((acc, m) => ({
|
|
18
|
-
input: acc.input + m.input_tokens,
|
|
19
|
-
output: acc.output + m.output_tokens,
|
|
20
|
-
cacheWrite: acc.cacheWrite + m.cache_write_tokens,
|
|
21
|
-
cacheRead: acc.cacheRead + m.cache_read_tokens,
|
|
22
|
-
cost: acc.cost + m.estimated_cost,
|
|
23
|
-
}), { input: 0, output: 0, cacheWrite: 0, cacheRead: 0, cost: 0 })
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<div className="overflow-x-auto">
|
|
27
|
-
<table className="w-full text-[13px] font-mono">
|
|
28
|
-
<thead>
|
|
29
|
-
<tr className="border-b border-border">
|
|
30
|
-
{['Model', 'Input', 'Output', 'Cache W', 'Cache R', 'Cost'].map(h => (
|
|
31
|
-
<th key={h} className={`py-2 text-[12px] font-bold text-muted-foreground uppercase tracking-wider ${h === 'Model' ? 'text-left' : 'text-right'}`}>
|
|
32
|
-
{h}
|
|
33
|
-
</th>
|
|
34
|
-
))}
|
|
35
|
-
</tr>
|
|
36
|
-
</thead>
|
|
37
|
-
<tbody>
|
|
38
|
-
{models.map(m => (
|
|
39
|
-
<tr key={m.model} className="border-b border-border/30 hover:bg-muted/50 transition-colors">
|
|
40
|
-
<td className="py-2 text-foreground/80">{shortModel(m.model)}</td>
|
|
41
|
-
<td className="py-2 text-right text-blue-700 dark:text-[#60a5fa]">{formatTokens(m.input_tokens)}</td>
|
|
42
|
-
<td className="py-2 text-right text-[#d97706]">{formatTokens(m.output_tokens)}</td>
|
|
43
|
-
<td className="py-2 text-right text-[#a78bfa]">{formatTokens(m.cache_write_tokens)}</td>
|
|
44
|
-
<td className="py-2 text-right text-[#34d399]">{formatTokens(m.cache_read_tokens)}</td>
|
|
45
|
-
<td className="py-2 text-right text-[#d97706] font-bold">{formatCost(m.estimated_cost)}</td>
|
|
46
|
-
</tr>
|
|
47
|
-
))}
|
|
48
|
-
<tr className="border-t border-border font-bold">
|
|
49
|
-
<td className="py-2 text-muted-foreground">TOTAL</td>
|
|
50
|
-
<td className="py-2 text-right text-blue-700 dark:text-[#60a5fa]">{formatTokens(totals.input)}</td>
|
|
51
|
-
<td className="py-2 text-right text-[#d97706]">{formatTokens(totals.output)}</td>
|
|
52
|
-
<td className="py-2 text-right text-[#a78bfa]">{formatTokens(totals.cacheWrite)}</td>
|
|
53
|
-
<td className="py-2 text-right text-[#34d399]">{formatTokens(totals.cacheRead)}</td>
|
|
54
|
-
<td className="py-2 text-right text-[#d97706]">{formatCost(totals.cost)}</td>
|
|
55
|
-
</tr>
|
|
56
|
-
</tbody>
|
|
57
|
-
</table>
|
|
58
|
-
</div>
|
|
59
|
-
)
|
|
60
|
-
}
|
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useState, useEffect } from 'react'
|
|
4
|
-
import { useRouter } from 'next/navigation'
|
|
5
|
-
import { LayoutDashboard, FileText, FolderOpen, Layers, Brain } from 'lucide-react'
|
|
6
|
-
import {
|
|
7
|
-
CommandDialog,
|
|
8
|
-
CommandEmpty,
|
|
9
|
-
CommandGroup,
|
|
10
|
-
CommandInput,
|
|
11
|
-
CommandItem,
|
|
12
|
-
CommandList,
|
|
13
|
-
CommandSeparator,
|
|
14
|
-
CommandShortcut,
|
|
15
|
-
} from '@/components/ui/command'
|
|
16
|
-
import { projectDisplayName } from '@/lib/decode'
|
|
17
|
-
|
|
18
|
-
const PAGES = [
|
|
19
|
-
{ label: 'Overview', href: '/' },
|
|
20
|
-
{ label: 'Projects', href: '/projects' },
|
|
21
|
-
{ label: 'Sessions', href: '/sessions' },
|
|
22
|
-
{ label: 'Costs', href: '/costs' },
|
|
23
|
-
{ label: 'Tools', href: '/tools' },
|
|
24
|
-
{ label: 'Activity', href: '/activity' },
|
|
25
|
-
{ label: 'History', href: '/history' },
|
|
26
|
-
{ label: 'Todos', href: '/todos' },
|
|
27
|
-
{ label: 'Plans', href: '/plans' },
|
|
28
|
-
{ label: 'Memory', href: '/memory' },
|
|
29
|
-
{ label: 'Settings', href: '/settings' },
|
|
30
|
-
{ label: 'Export', href: '/export' },
|
|
31
|
-
]
|
|
32
|
-
|
|
33
|
-
export function GlobalSearch() {
|
|
34
|
-
const [open, setOpen] = useState(false)
|
|
35
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
|
-
const [sessions, setSessions] = useState<any[]>([])
|
|
37
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
-
const [projects, setProjects] = useState<any[]>([])
|
|
39
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
|
-
const [plans, setPlans] = useState<any[]>([])
|
|
41
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
42
|
-
const [memories, setMemories] = useState<any[]>([])
|
|
43
|
-
const [loaded, setLoaded] = useState(false)
|
|
44
|
-
const router = useRouter()
|
|
45
|
-
|
|
46
|
-
// Lazy-load data on first open
|
|
47
|
-
useEffect(() => {
|
|
48
|
-
if (!open || loaded) return
|
|
49
|
-
Promise.all([
|
|
50
|
-
fetch('/api/sessions').then(r => r.json()),
|
|
51
|
-
fetch('/api/projects').then(r => r.json()),
|
|
52
|
-
fetch('/api/plans').then(r => r.json()),
|
|
53
|
-
fetch('/api/memory').then(r => r.json()),
|
|
54
|
-
]).then(([s, p, pl, m]) => {
|
|
55
|
-
setSessions(s.sessions ?? [])
|
|
56
|
-
setProjects(p.projects ?? [])
|
|
57
|
-
setPlans(pl.plans ?? [])
|
|
58
|
-
setMemories(m.memories ?? [])
|
|
59
|
-
setLoaded(true)
|
|
60
|
-
}).catch(() => setLoaded(true))
|
|
61
|
-
}, [open, loaded])
|
|
62
|
-
|
|
63
|
-
// Listen for open-search custom event (fired by TopBar button)
|
|
64
|
-
useEffect(() => {
|
|
65
|
-
const handler = () => setOpen(true)
|
|
66
|
-
window.addEventListener('open-search', handler)
|
|
67
|
-
return () => window.removeEventListener('open-search', handler)
|
|
68
|
-
}, [])
|
|
69
|
-
|
|
70
|
-
// Cmd+K / Ctrl+K shortcut
|
|
71
|
-
useEffect(() => {
|
|
72
|
-
const handler = (e: KeyboardEvent) => {
|
|
73
|
-
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
|
74
|
-
e.preventDefault()
|
|
75
|
-
setOpen(o => !o)
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
window.addEventListener('keydown', handler)
|
|
79
|
-
return () => window.removeEventListener('keydown', handler)
|
|
80
|
-
}, [])
|
|
81
|
-
|
|
82
|
-
function navigate(href: string) {
|
|
83
|
-
router.push(href)
|
|
84
|
-
setOpen(false)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return (
|
|
88
|
-
<CommandDialog open={open} onOpenChange={setOpen}>
|
|
89
|
-
<CommandInput placeholder="Search pages, sessions, projects..." />
|
|
90
|
-
<CommandList>
|
|
91
|
-
<CommandEmpty>No results found.</CommandEmpty>
|
|
92
|
-
|
|
93
|
-
<CommandGroup heading="Pages">
|
|
94
|
-
{PAGES.map(p => (
|
|
95
|
-
<CommandItem key={p.href} value={p.label} onSelect={() => navigate(p.href)}>
|
|
96
|
-
<LayoutDashboard className="w-3.5 h-3.5 text-muted-foreground shrink-0" />
|
|
97
|
-
{p.label}
|
|
98
|
-
<CommandShortcut>page</CommandShortcut>
|
|
99
|
-
</CommandItem>
|
|
100
|
-
))}
|
|
101
|
-
</CommandGroup>
|
|
102
|
-
|
|
103
|
-
{sessions.length > 0 && (
|
|
104
|
-
<>
|
|
105
|
-
<CommandSeparator />
|
|
106
|
-
<CommandGroup heading="Sessions">
|
|
107
|
-
{sessions.slice(0, 80).map(s => (
|
|
108
|
-
<CommandItem
|
|
109
|
-
key={s.session_id}
|
|
110
|
-
value={`${s.slug ?? ''} ${s.first_prompt ?? ''} ${projectDisplayName(s.project_path ?? '')}`}
|
|
111
|
-
onSelect={() => navigate(`/sessions/${s.session_id}`)}
|
|
112
|
-
>
|
|
113
|
-
<FileText className="w-3.5 h-3.5 text-blue-700 dark:text-blue-400 shrink-0" />
|
|
114
|
-
<div className="flex-1 min-w-0">
|
|
115
|
-
<span className="text-sm">{s.slug ?? s.session_id.slice(0, 12)}</span>
|
|
116
|
-
{s.first_prompt && (
|
|
117
|
-
<p className="text-xs text-muted-foreground truncate">{s.first_prompt.slice(0, 60)}</p>
|
|
118
|
-
)}
|
|
119
|
-
</div>
|
|
120
|
-
<CommandShortcut>session</CommandShortcut>
|
|
121
|
-
</CommandItem>
|
|
122
|
-
))}
|
|
123
|
-
</CommandGroup>
|
|
124
|
-
</>
|
|
125
|
-
)}
|
|
126
|
-
|
|
127
|
-
{projects.length > 0 && (
|
|
128
|
-
<>
|
|
129
|
-
<CommandSeparator />
|
|
130
|
-
<CommandGroup heading="Projects">
|
|
131
|
-
{projects.map(p => (
|
|
132
|
-
<CommandItem
|
|
133
|
-
key={p.slug}
|
|
134
|
-
value={`${p.display_name} ${p.slug}`}
|
|
135
|
-
onSelect={() => navigate(`/projects/${p.slug}`)}
|
|
136
|
-
>
|
|
137
|
-
<FolderOpen className="w-3.5 h-3.5 text-green-400 shrink-0" />
|
|
138
|
-
<span className="flex-1 truncate">{p.display_name}</span>
|
|
139
|
-
<CommandShortcut>{p.session_count} sessions</CommandShortcut>
|
|
140
|
-
</CommandItem>
|
|
141
|
-
))}
|
|
142
|
-
</CommandGroup>
|
|
143
|
-
</>
|
|
144
|
-
)}
|
|
145
|
-
|
|
146
|
-
{plans.length > 0 && (
|
|
147
|
-
<>
|
|
148
|
-
<CommandSeparator />
|
|
149
|
-
<CommandGroup heading="Plans">
|
|
150
|
-
{plans.map((p, i) => (
|
|
151
|
-
<CommandItem
|
|
152
|
-
key={`plan-${i}`}
|
|
153
|
-
value={`${p.name} ${(p.content ?? '').slice(0, 200)}`}
|
|
154
|
-
onSelect={() => navigate('/plans')}
|
|
155
|
-
>
|
|
156
|
-
<Layers className="w-3.5 h-3.5 text-purple-400 shrink-0" />
|
|
157
|
-
<span className="flex-1 truncate">{p.name}</span>
|
|
158
|
-
<CommandShortcut>plan</CommandShortcut>
|
|
159
|
-
</CommandItem>
|
|
160
|
-
))}
|
|
161
|
-
</CommandGroup>
|
|
162
|
-
</>
|
|
163
|
-
)}
|
|
164
|
-
|
|
165
|
-
{memories.length > 0 && (
|
|
166
|
-
<>
|
|
167
|
-
<CommandSeparator />
|
|
168
|
-
<CommandGroup heading="Memory">
|
|
169
|
-
{memories.slice(0, 40).map((m, i) => (
|
|
170
|
-
<CommandItem
|
|
171
|
-
key={`mem-${i}`}
|
|
172
|
-
value={`${m.name ?? ''} ${m.description ?? ''} ${(m.body ?? '').slice(0, 100)}`}
|
|
173
|
-
onSelect={() => navigate('/memory')}
|
|
174
|
-
>
|
|
175
|
-
<Brain className="w-3.5 h-3.5 text-amber-400 shrink-0" />
|
|
176
|
-
<span className="flex-1 truncate">{m.name}</span>
|
|
177
|
-
<CommandShortcut>{m.type}</CommandShortcut>
|
|
178
|
-
</CommandItem>
|
|
179
|
-
))}
|
|
180
|
-
</CommandGroup>
|
|
181
|
-
</>
|
|
182
|
-
)}
|
|
183
|
-
</CommandList>
|
|
184
|
-
|
|
185
|
-
<div className="border-t border-border px-3 py-2 flex items-center gap-4 text-[10px] text-muted-foreground/40 font-mono">
|
|
186
|
-
<span>↑↓ navigate</span>
|
|
187
|
-
<span>↵ open</span>
|
|
188
|
-
<span>esc close</span>
|
|
189
|
-
<span className="ml-auto">⌘K toggle</span>
|
|
190
|
-
</div>
|
|
191
|
-
</CommandDialog>
|
|
192
|
-
)
|
|
193
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { GlobalSearch } from './global-search'
|
|
4
|
-
import { useGlobalKeyboardNav } from './use-global-keyboard-nav'
|
|
5
|
-
|
|
6
|
-
function GModeIndicator() {
|
|
7
|
-
const gMode = useGlobalKeyboardNav()
|
|
8
|
-
if (!gMode) return null
|
|
9
|
-
return (
|
|
10
|
-
<div className="fixed bottom-20 right-4 md:bottom-4 z-50 px-3 py-1.5 bg-background border border-primary/60 rounded text-sm font-mono text-primary shadow-lg animate-in fade-in-0 duration-100 pointer-events-none">
|
|
11
|
-
g —
|
|
12
|
-
</div>
|
|
13
|
-
)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function KeyboardNavProvider() {
|
|
17
|
-
return (
|
|
18
|
-
<>
|
|
19
|
-
<GlobalSearch />
|
|
20
|
-
<GModeIndicator />
|
|
21
|
-
</>
|
|
22
|
-
)
|
|
23
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import Link from 'next/link'
|
|
4
|
-
import { usePathname } from 'next/navigation'
|
|
5
|
-
import {
|
|
6
|
-
LayoutDashboard, MessageSquare, DollarSign,
|
|
7
|
-
FolderOpen, Activity, Moon, Sun, Zap,
|
|
8
|
-
} from 'lucide-react'
|
|
9
|
-
import { useTheme } from '@/components/theme-provider'
|
|
10
|
-
import { cn } from '@/lib/utils'
|
|
11
|
-
|
|
12
|
-
const NAV = [
|
|
13
|
-
{ href: '/', label: 'Overview', icon: LayoutDashboard },
|
|
14
|
-
{ href: '/sessions', label: 'Sessions', icon: MessageSquare },
|
|
15
|
-
{ href: '/costs', label: 'Costs', icon: DollarSign },
|
|
16
|
-
{ href: '/projects', label: 'Projects', icon: FolderOpen },
|
|
17
|
-
{ href: '/activity', label: 'Activity', icon: Activity },
|
|
18
|
-
{ href: '/benchmarks', label: 'Benchmarks', icon: Zap },
|
|
19
|
-
]
|
|
20
|
-
|
|
21
|
-
export function BottomNav() {
|
|
22
|
-
const pathname = usePathname()
|
|
23
|
-
const { theme, toggle } = useTheme()
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<nav className="md:hidden fixed bottom-0 left-0 right-0 z-50 bg-sidebar border-t border-sidebar-border flex">
|
|
27
|
-
{NAV.map(({ href, label, icon: Icon }) => {
|
|
28
|
-
const active = pathname === href
|
|
29
|
-
return (
|
|
30
|
-
<Link
|
|
31
|
-
key={href}
|
|
32
|
-
href={href}
|
|
33
|
-
className={cn(
|
|
34
|
-
'flex-1 flex flex-col items-center justify-center py-2.5 gap-1 transition-colors',
|
|
35
|
-
active ? 'text-sidebar-primary' : 'text-sidebar-foreground/40 hover:text-sidebar-foreground',
|
|
36
|
-
)}
|
|
37
|
-
>
|
|
38
|
-
<Icon className="w-4 h-4" />
|
|
39
|
-
<span className="text-[10px] font-medium leading-none">{label}</span>
|
|
40
|
-
</Link>
|
|
41
|
-
)
|
|
42
|
-
})}
|
|
43
|
-
<button
|
|
44
|
-
onClick={toggle}
|
|
45
|
-
aria-label="Toggle theme"
|
|
46
|
-
className="flex-1 flex flex-col items-center justify-center py-2.5 gap-1 transition-colors text-sidebar-foreground/40 hover:text-sidebar-foreground cursor-pointer"
|
|
47
|
-
>
|
|
48
|
-
{theme === 'dark' ? <Sun className="w-4 h-4" /> : <Moon className="w-4 h-4" />}
|
|
49
|
-
<span className="text-[10px] font-medium leading-none">Theme</span>
|
|
50
|
-
</button>
|
|
51
|
-
</nav>
|
|
52
|
-
)
|
|
53
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useSidebar } from '@/components/layout/sidebar-context'
|
|
4
|
-
|
|
5
|
-
export function ClientLayout({ children }: { children: React.ReactNode }) {
|
|
6
|
-
const { collapsed } = useSidebar()
|
|
7
|
-
return (
|
|
8
|
-
<main
|
|
9
|
-
className={[
|
|
10
|
-
'flex-1 min-h-screen overflow-x-hidden bg-background pb-16 md:pb-0',
|
|
11
|
-
'transition-[margin] duration-300',
|
|
12
|
-
collapsed ? 'md:ml-14' : 'md:ml-56',
|
|
13
|
-
].join(' ')}
|
|
14
|
-
>
|
|
15
|
-
{children}
|
|
16
|
-
<footer className="border-t border-border/50 py-3 px-6 flex items-center justify-center mb-16 md:mb-0">
|
|
17
|
-
<p className="text-xs text-muted-foreground">
|
|
18
|
-
Made by{' '}
|
|
19
|
-
<a
|
|
20
|
-
href="https://github.com/Arindam200"
|
|
21
|
-
target="_blank"
|
|
22
|
-
rel="noopener noreferrer"
|
|
23
|
-
className="text-muted-foreground hover:text-foreground underline underline-offset-2 transition-colors"
|
|
24
|
-
>
|
|
25
|
-
Arindam
|
|
26
|
-
</a>
|
|
27
|
-
</p>
|
|
28
|
-
</footer>
|
|
29
|
-
</main>
|
|
30
|
-
)
|
|
31
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { createContext, useContext, useEffect, useState } from 'react'
|
|
4
|
-
|
|
5
|
-
interface SidebarContextType {
|
|
6
|
-
collapsed: boolean
|
|
7
|
-
toggle: () => void
|
|
8
|
-
mobileOpen: boolean
|
|
9
|
-
setMobileOpen: (v: boolean) => void
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const SidebarContext = createContext<SidebarContextType>({
|
|
13
|
-
collapsed: false,
|
|
14
|
-
toggle: () => {},
|
|
15
|
-
mobileOpen: false,
|
|
16
|
-
setMobileOpen: () => {},
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
export function SidebarProvider({ children }: { children: React.ReactNode }) {
|
|
20
|
-
const [collapsed, setCollapsed] = useState(false)
|
|
21
|
-
const [mobileOpen, setMobileOpen] = useState(false)
|
|
22
|
-
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
const id = window.setTimeout(() => {
|
|
25
|
-
try {
|
|
26
|
-
setCollapsed(localStorage.getItem('cc-sidebar-collapsed') === 'true')
|
|
27
|
-
} catch {}
|
|
28
|
-
}, 0)
|
|
29
|
-
|
|
30
|
-
return () => window.clearTimeout(id)
|
|
31
|
-
}, [])
|
|
32
|
-
|
|
33
|
-
function toggle() {
|
|
34
|
-
setCollapsed(prev => {
|
|
35
|
-
const next = !prev
|
|
36
|
-
try { localStorage.setItem('cc-sidebar-collapsed', String(next)) } catch {}
|
|
37
|
-
return next
|
|
38
|
-
})
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return (
|
|
42
|
-
<SidebarContext.Provider value={{ collapsed, toggle, mobileOpen, setMobileOpen }}>
|
|
43
|
-
{children}
|
|
44
|
-
</SidebarContext.Provider>
|
|
45
|
-
)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function useSidebar() {
|
|
49
|
-
return useContext(SidebarContext)
|
|
50
|
-
}
|