@axplusb/kepler 0.0.1 → 1.0.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.
- package/README.md +82 -0
- package/package.json +36 -4
- package/pulse/app/activity/page.tsx +190 -0
- package/pulse/app/api/activity/route.ts +138 -0
- package/pulse/app/api/costs/route.ts +88 -0
- package/pulse/app/api/export/route.ts +77 -0
- package/pulse/app/api/history/route.ts +11 -0
- package/pulse/app/api/import/route.ts +31 -0
- package/pulse/app/api/memory/route.ts +52 -0
- package/pulse/app/api/plans/route.ts +9 -0
- package/pulse/app/api/projects/[slug]/route.ts +96 -0
- package/pulse/app/api/projects/route.ts +121 -0
- package/pulse/app/api/sessions/[id]/replay/route.ts +20 -0
- package/pulse/app/api/sessions/[id]/route.ts +31 -0
- package/pulse/app/api/sessions/route.ts +112 -0
- package/pulse/app/api/settings/route.ts +14 -0
- package/pulse/app/api/stats/route.ts +143 -0
- package/pulse/app/api/todos/route.ts +9 -0
- package/pulse/app/api/tools/route.ts +160 -0
- package/pulse/app/costs/page.tsx +179 -0
- package/pulse/app/export/page.tsx +465 -0
- package/pulse/app/favicon.ico +0 -0
- package/pulse/app/globals.css +263 -0
- package/pulse/app/help/page.tsx +142 -0
- package/pulse/app/history/page.tsx +157 -0
- package/pulse/app/layout.tsx +46 -0
- package/pulse/app/memory/page.tsx +365 -0
- package/pulse/app/overview-client.tsx +393 -0
- package/pulse/app/page.tsx +14 -0
- package/pulse/app/plans/page.tsx +308 -0
- package/pulse/app/projects/[slug]/page.tsx +390 -0
- package/pulse/app/projects/page.tsx +110 -0
- package/pulse/app/sessions/[id]/page.tsx +243 -0
- package/pulse/app/sessions/page.tsx +39 -0
- package/pulse/app/settings/page.tsx +188 -0
- package/pulse/app/todos/page.tsx +211 -0
- package/pulse/app/tools/page.tsx +249 -0
- package/pulse/cli.js +159 -0
- package/pulse/components/activity/day-of-week-chart.tsx +35 -0
- package/pulse/components/activity/streak-card.tsx +36 -0
- package/pulse/components/costs/cache-efficiency-panel.tsx +76 -0
- package/pulse/components/costs/cost-by-project-chart.tsx +48 -0
- package/pulse/components/costs/cost-over-time-chart.tsx +95 -0
- package/pulse/components/costs/model-token-table.tsx +60 -0
- package/pulse/components/global-search.tsx +193 -0
- package/pulse/components/keyboard-nav-provider.tsx +23 -0
- package/pulse/components/layout/bottom-nav.tsx +52 -0
- package/pulse/components/layout/client-layout.tsx +31 -0
- package/pulse/components/layout/sidebar-context.tsx +50 -0
- package/pulse/components/layout/sidebar.tsx +182 -0
- package/pulse/components/layout/top-bar.tsx +121 -0
- package/pulse/components/overview/activity-heatmap.tsx +107 -0
- package/pulse/components/overview/conversation-table.tsx +148 -0
- package/pulse/components/overview/model-breakdown-donut.tsx +95 -0
- package/pulse/components/overview/peak-hours-chart.tsx +87 -0
- package/pulse/components/overview/project-activity-donut.tsx +96 -0
- package/pulse/components/overview/stat-card.tsx +102 -0
- package/pulse/components/overview/usage-over-time-chart.tsx +166 -0
- package/pulse/components/projects/project-card.tsx +175 -0
- package/pulse/components/sessions/replay/assistant-markdown.tsx +94 -0
- package/pulse/components/sessions/replay/compaction-card.tsx +25 -0
- package/pulse/components/sessions/replay/session-sidebar.tsx +231 -0
- package/pulse/components/sessions/replay/token-accumulation-chart.tsx +98 -0
- package/pulse/components/sessions/replay/tool-call-badge.tsx +127 -0
- package/pulse/components/sessions/replay/turn-cards.tsx +220 -0
- package/pulse/components/sessions/replay/user-tool-result.tsx +158 -0
- package/pulse/components/sessions/session-badges.tsx +49 -0
- package/pulse/components/sessions/session-table.tsx +299 -0
- package/pulse/components/theme-provider.tsx +44 -0
- package/pulse/components/tools/feature-adoption-table.tsx +58 -0
- package/pulse/components/tools/mcp-server-panel.tsx +45 -0
- package/pulse/components/tools/tool-ranking-chart.tsx +57 -0
- package/pulse/components/tools/version-history-table.tsx +32 -0
- package/pulse/components/ui/alert.tsx +66 -0
- package/pulse/components/ui/badge.tsx +48 -0
- package/pulse/components/ui/breadcrumb.tsx +109 -0
- package/pulse/components/ui/button.tsx +64 -0
- package/pulse/components/ui/calendar.tsx +220 -0
- package/pulse/components/ui/card.tsx +92 -0
- package/pulse/components/ui/command.tsx +158 -0
- package/pulse/components/ui/dialog.tsx +158 -0
- package/pulse/components/ui/input.tsx +21 -0
- package/pulse/components/ui/popover.tsx +89 -0
- package/pulse/components/ui/progress.tsx +31 -0
- package/pulse/components/ui/select.tsx +190 -0
- package/pulse/components/ui/separator.tsx +28 -0
- package/pulse/components/ui/sheet.tsx +143 -0
- package/pulse/components/ui/skeleton.tsx +13 -0
- package/pulse/components/ui/table.tsx +116 -0
- package/pulse/components/ui/tabs.tsx +91 -0
- package/pulse/components/ui/tooltip.tsx +57 -0
- package/pulse/components/use-global-keyboard-nav.ts +79 -0
- package/pulse/components.json +23 -0
- package/pulse/eslint.config.mjs +18 -0
- package/pulse/lib/claude-reader.ts +594 -0
- package/pulse/lib/decode.ts +129 -0
- package/pulse/lib/pricing.ts +102 -0
- package/pulse/lib/replay-parser.ts +165 -0
- package/pulse/lib/tool-categories.ts +127 -0
- package/pulse/lib/utils.ts +6 -0
- package/pulse/next-env.d.ts +6 -0
- package/pulse/next.config.ts +16 -0
- package/pulse/package.json +45 -0
- package/pulse/postcss.config.mjs +7 -0
- 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 +1 -0
- package/pulse/public/globe.svg +1 -0
- package/pulse/public/next.svg +1 -0
- 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 +1 -0
- package/pulse/public/window.svg +1 -0
- package/pulse/tsconfig.json +34 -0
- package/pulse/types/claude.ts +294 -0
- package/src/agents/loader.mjs +89 -0
- package/src/agents/parser.mjs +98 -0
- package/src/agents/teams.mjs +123 -0
- package/src/auth/oauth.mjs +220 -0
- package/src/auth/tarang-auth.mjs +277 -0
- package/src/config/cli-args.mjs +173 -0
- package/src/config/env.mjs +263 -0
- package/src/config/settings.mjs +132 -0
- package/src/context/ast-parser.mjs +298 -0
- package/src/context/bm25.mjs +85 -0
- package/src/context/retriever.mjs +270 -0
- package/src/context/skeleton.mjs +134 -0
- package/src/core/agent-loop.mjs +480 -0
- package/src/core/approval.mjs +273 -0
- package/src/core/backend-url.mjs +57 -0
- package/src/core/cache.mjs +105 -0
- package/src/core/callback-client.mjs +149 -0
- package/src/core/checkpoints.mjs +142 -0
- package/src/core/context-manager.mjs +198 -0
- package/src/core/headless.mjs +168 -0
- package/src/core/hooks-manager.mjs +87 -0
- package/src/core/jsonl-writer.mjs +351 -0
- package/src/core/local-agent.mjs +429 -0
- package/src/core/local-store.mjs +325 -0
- package/src/core/mode-selector.mjs +51 -0
- package/src/core/output-filter.mjs +177 -0
- package/src/core/paths.mjs +101 -0
- package/src/core/pricing.mjs +314 -0
- package/src/core/providers.mjs +219 -0
- package/src/core/rate-limiter.mjs +119 -0
- package/src/core/safety.mjs +200 -0
- package/src/core/scheduler.mjs +173 -0
- package/src/core/session-manager.mjs +317 -0
- package/src/core/session.mjs +143 -0
- package/src/core/settings-sync.mjs +85 -0
- package/src/core/stagnation.mjs +57 -0
- package/src/core/stream-client.mjs +367 -0
- package/src/core/streaming.mjs +182 -0
- package/src/core/system-prompt.mjs +135 -0
- package/src/core/tool-executor.mjs +725 -0
- package/src/hooks/engine.mjs +162 -0
- package/src/index.mjs +370 -0
- package/src/mcp/client.mjs +253 -0
- package/src/mcp/transport-shttp.mjs +130 -0
- package/src/mcp/transport-sse.mjs +131 -0
- package/src/mcp/transport-ws.mjs +134 -0
- package/src/permissions/checker.mjs +57 -0
- package/src/permissions/command-classifier.mjs +573 -0
- package/src/permissions/injection-check.mjs +60 -0
- package/src/permissions/path-check.mjs +102 -0
- package/src/permissions/prompt.mjs +73 -0
- package/src/permissions/sandbox.mjs +112 -0
- package/src/plugins/loader.mjs +138 -0
- package/src/skills/loader.mjs +147 -0
- package/src/skills/runner.mjs +55 -0
- package/src/telemetry/index.mjs +96 -0
- package/src/terminal/agents.mjs +177 -0
- package/src/terminal/analytics.mjs +292 -0
- package/src/terminal/ansi.mjs +421 -0
- package/src/terminal/main.mjs +150 -0
- package/src/terminal/repl.mjs +1484 -0
- package/src/terminal/tool-display.mjs +58 -0
- package/src/tools/agent.mjs +137 -0
- package/src/tools/ask-user.mjs +61 -0
- package/src/tools/bash.mjs +148 -0
- package/src/tools/cron-create.mjs +120 -0
- package/src/tools/cron-delete.mjs +49 -0
- package/src/tools/cron-list.mjs +37 -0
- package/src/tools/edit.mjs +82 -0
- package/src/tools/enter-worktree.mjs +69 -0
- package/src/tools/exit-worktree.mjs +57 -0
- package/src/tools/glob.mjs +117 -0
- package/src/tools/grep.mjs +129 -0
- package/src/tools/lint.mjs +71 -0
- package/src/tools/ls.mjs +58 -0
- package/src/tools/lsp.mjs +115 -0
- package/src/tools/multi-edit.mjs +94 -0
- package/src/tools/notebook-edit.mjs +96 -0
- package/src/tools/read-mcp-resource.mjs +57 -0
- package/src/tools/read.mjs +138 -0
- package/src/tools/registry.mjs +132 -0
- package/src/tools/remote-trigger.mjs +84 -0
- package/src/tools/send-message.mjs +64 -0
- package/src/tools/skill.mjs +52 -0
- package/src/tools/test-runner.mjs +49 -0
- package/src/tools/todo-write.mjs +68 -0
- package/src/tools/tool-search.mjs +77 -0
- package/src/tools/web-fetch.mjs +65 -0
- package/src/tools/web-search.mjs +89 -0
- package/src/tools/write.mjs +55 -0
- package/src/ui/banner.mjs +237 -0
- package/src/ui/commands.mjs +499 -0
- package/src/ui/formatter.mjs +379 -0
- package/src/ui/markdown.mjs +278 -0
- package/src/ui/slash-commands.mjs +258 -0
- package/index.js +0 -1
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import useSWR from 'swr'
|
|
5
|
+
import { TopBar } from '@/components/layout/top-bar'
|
|
6
|
+
import { cn } from '@/lib/utils'
|
|
7
|
+
import { ChevronDown, ClipboardList } from 'lucide-react'
|
|
8
|
+
|
|
9
|
+
const fetcher = (url: string) =>
|
|
10
|
+
fetch(url).then(r => { if (!r.ok) throw new Error(`API error ${r.status}`); return r.json() })
|
|
11
|
+
|
|
12
|
+
interface PlanFile {
|
|
13
|
+
name: string
|
|
14
|
+
content: string
|
|
15
|
+
mtime: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function formatDate(iso: string) {
|
|
19
|
+
return new Date(iso).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function wordCount(text: string) {
|
|
23
|
+
return text.trim().split(/\s+/).filter(Boolean).length
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function extractTitle(content: string): string | null {
|
|
27
|
+
const firstLine = content.split('\n').find(l => l.trim())
|
|
28
|
+
if (firstLine?.startsWith('#')) return firstLine.replace(/^#+\s*/, '').trim()
|
|
29
|
+
return null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Minimal markdown renderer — handles headings, bold, code, lists, hr, plain text
|
|
33
|
+
function MarkdownLine({ line }: { line: string }) {
|
|
34
|
+
// h1
|
|
35
|
+
if (/^#\s/.test(line))
|
|
36
|
+
return (
|
|
37
|
+
<p className="text-foreground font-bold text-base font-mono mt-4 mb-1 first:mt-0 leading-snug tracking-tight">
|
|
38
|
+
{line.replace(/^#\s/, '')}
|
|
39
|
+
</p>
|
|
40
|
+
)
|
|
41
|
+
// h2
|
|
42
|
+
if (/^##\s/.test(line))
|
|
43
|
+
return (
|
|
44
|
+
<p className="text-primary font-bold text-sm font-mono mt-3 mb-1 leading-snug">
|
|
45
|
+
{line.replace(/^##\s/, '')}
|
|
46
|
+
</p>
|
|
47
|
+
)
|
|
48
|
+
// h3
|
|
49
|
+
if (/^###\s/.test(line))
|
|
50
|
+
return (
|
|
51
|
+
<p className="text-foreground font-semibold text-sm font-mono mt-2 mb-0.5 leading-snug">
|
|
52
|
+
{line.replace(/^###\s/, '')}
|
|
53
|
+
</p>
|
|
54
|
+
)
|
|
55
|
+
// hr
|
|
56
|
+
if (/^---+$/.test(line.trim()))
|
|
57
|
+
return <hr className="border-border my-3" />
|
|
58
|
+
// empty
|
|
59
|
+
if (!line.trim())
|
|
60
|
+
return <div className="h-2" />
|
|
61
|
+
// bullet list
|
|
62
|
+
if (/^[-*]\s/.test(line))
|
|
63
|
+
return (
|
|
64
|
+
<p className="text-foreground/95 text-sm font-mono leading-relaxed flex gap-2">
|
|
65
|
+
<span className="text-primary flex-shrink-0">›</span>
|
|
66
|
+
<InlineMarkdown text={line.replace(/^[-*]\s/, '')} />
|
|
67
|
+
</p>
|
|
68
|
+
)
|
|
69
|
+
// numbered list
|
|
70
|
+
if (/^\d+\.\s/.test(line)) {
|
|
71
|
+
const [num, ...rest] = line.split(/\.\s(.+)/)
|
|
72
|
+
return (
|
|
73
|
+
<p className="text-foreground/95 text-sm font-mono leading-relaxed flex gap-2">
|
|
74
|
+
<span className="text-primary flex-shrink-0 w-5 text-right">{num}.</span>
|
|
75
|
+
<InlineMarkdown text={rest.join('. ')} />
|
|
76
|
+
</p>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
// code block marker — handled at block level, fallback
|
|
80
|
+
if (line.startsWith('```'))
|
|
81
|
+
return <span className="text-muted-foreground/60 text-xs font-mono">{line}</span>
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<p className="text-foreground/95 text-sm font-mono leading-relaxed">
|
|
85
|
+
<InlineMarkdown text={line} />
|
|
86
|
+
</p>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function InlineMarkdown({ text }: { text: string }) {
|
|
91
|
+
// Bold + italic: ***text***
|
|
92
|
+
// Bold: **text**
|
|
93
|
+
// Inline code: `text`
|
|
94
|
+
const parts = text.split(/(`[^`]+`|\*\*\*[^*]+\*\*\*|\*\*[^*]+\*\*|\*[^*]+\*)/)
|
|
95
|
+
return (
|
|
96
|
+
<>
|
|
97
|
+
{parts.map((p, i) => {
|
|
98
|
+
if (p.startsWith('***') && p.endsWith('***'))
|
|
99
|
+
return <strong key={i} className="text-primary italic">{p.slice(3, -3)}</strong>
|
|
100
|
+
if (p.startsWith('**') && p.endsWith('**'))
|
|
101
|
+
return <strong key={i} className="text-foreground font-bold">{p.slice(2, -2)}</strong>
|
|
102
|
+
if (p.startsWith('*') && p.endsWith('*'))
|
|
103
|
+
return <em key={i} className="text-muted-foreground italic">{p.slice(1, -1)}</em>
|
|
104
|
+
if (p.startsWith('`') && p.endsWith('`'))
|
|
105
|
+
return (
|
|
106
|
+
<code
|
|
107
|
+
key={i}
|
|
108
|
+
className="text-emerald-800 dark:text-emerald-300 bg-emerald-950/10 dark:bg-emerald-950/40 px-1 py-0.5 rounded text-xs font-mono"
|
|
109
|
+
>
|
|
110
|
+
{p.slice(1, -1)}
|
|
111
|
+
</code>
|
|
112
|
+
)
|
|
113
|
+
return <span key={i}>{p}</span>
|
|
114
|
+
})}
|
|
115
|
+
</>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function MarkdownContent({ content }: { content: string }) {
|
|
120
|
+
const lines = content.split('\n')
|
|
121
|
+
const result: React.ReactNode[] = []
|
|
122
|
+
let inCodeBlock = false
|
|
123
|
+
let codeLines: string[] = []
|
|
124
|
+
let codeLang = ''
|
|
125
|
+
|
|
126
|
+
for (let i = 0; i < lines.length; i++) {
|
|
127
|
+
const line = lines[i]
|
|
128
|
+
if (line.startsWith('```')) {
|
|
129
|
+
if (!inCodeBlock) {
|
|
130
|
+
inCodeBlock = true
|
|
131
|
+
codeLang = line.slice(3).trim()
|
|
132
|
+
codeLines = []
|
|
133
|
+
} else {
|
|
134
|
+
inCodeBlock = false
|
|
135
|
+
result.push(
|
|
136
|
+
<div
|
|
137
|
+
key={i}
|
|
138
|
+
className="my-2 rounded-lg border border-border bg-muted/80 dark:bg-muted overflow-x-auto shadow-sm"
|
|
139
|
+
>
|
|
140
|
+
{codeLang && (
|
|
141
|
+
<div className="px-3 py-1.5 border-b border-border text-[10px] font-mono text-muted-foreground uppercase tracking-wide">
|
|
142
|
+
{codeLang}
|
|
143
|
+
</div>
|
|
144
|
+
)}
|
|
145
|
+
<pre className="px-3 py-2.5 text-xs font-mono text-foreground leading-relaxed">{codeLines.join('\n')}</pre>
|
|
146
|
+
</div>
|
|
147
|
+
)
|
|
148
|
+
codeLines = []
|
|
149
|
+
codeLang = ''
|
|
150
|
+
}
|
|
151
|
+
continue
|
|
152
|
+
}
|
|
153
|
+
if (inCodeBlock) {
|
|
154
|
+
codeLines.push(line)
|
|
155
|
+
continue
|
|
156
|
+
}
|
|
157
|
+
result.push(<MarkdownLine key={i} line={line} />)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return <div className="space-y-0.5">{result}</div>
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function PlanCard({ plan }: { plan: PlanFile }) {
|
|
164
|
+
const [expanded, setExpanded] = useState(false)
|
|
165
|
+
const lines = plan.content.split('\n').filter(l => l.trim())
|
|
166
|
+
const title = extractTitle(plan.content)
|
|
167
|
+
const words = wordCount(plan.content)
|
|
168
|
+
|
|
169
|
+
// Preview: first 8 non-empty lines
|
|
170
|
+
const previewContent = plan.content.split('\n').slice(0, 12).join('\n')
|
|
171
|
+
const hasMore = plan.content.split('\n').length > 12
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<div
|
|
175
|
+
className={[
|
|
176
|
+
'border rounded-xl transition-all duration-200 shadow-sm',
|
|
177
|
+
expanded ? 'border-primary/40 bg-card ring-1 ring-primary/10' : 'border-border bg-card hover:border-primary/25',
|
|
178
|
+
].join(' ')}
|
|
179
|
+
>
|
|
180
|
+
|
|
181
|
+
{/* Header — always visible */}
|
|
182
|
+
<button
|
|
183
|
+
type="button"
|
|
184
|
+
aria-expanded={expanded}
|
|
185
|
+
className="flex w-full items-start justify-between gap-4 rounded-t-xl px-5 py-4 text-left md:px-6"
|
|
186
|
+
onClick={() => setExpanded(e => !e)}
|
|
187
|
+
>
|
|
188
|
+
<div className="min-w-0 flex-1">
|
|
189
|
+
<div className="mb-2 flex items-center gap-2.5">
|
|
190
|
+
<ClipboardList className="h-4 w-4 shrink-0 text-[#d97706]" aria-hidden />
|
|
191
|
+
<span className="truncate font-mono text-sm font-bold text-foreground">
|
|
192
|
+
{title ?? plan.name}
|
|
193
|
+
</span>
|
|
194
|
+
</div>
|
|
195
|
+
{title && title !== plan.name && (
|
|
196
|
+
<p className="mb-2 pl-7 font-mono text-xs text-muted-foreground/60">{plan.name}</p>
|
|
197
|
+
)}
|
|
198
|
+
<div className="flex flex-wrap items-center gap-4 pl-7">
|
|
199
|
+
<span className="font-mono text-xs text-muted-foreground/60">{formatDate(plan.mtime)}</span>
|
|
200
|
+
<span className="font-mono text-xs text-muted-foreground/60">{lines.length} lines</span>
|
|
201
|
+
<span className="font-mono text-xs text-muted-foreground/60">{words} words</span>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
<ChevronDown
|
|
205
|
+
className={cn(
|
|
206
|
+
'mt-0.5 h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200',
|
|
207
|
+
expanded && 'rotate-180'
|
|
208
|
+
)}
|
|
209
|
+
aria-hidden
|
|
210
|
+
/>
|
|
211
|
+
</button>
|
|
212
|
+
|
|
213
|
+
{/* Preview (collapsed) */}
|
|
214
|
+
{!expanded && (
|
|
215
|
+
<div className="px-5 pb-5 md:px-6 border-t border-border/60 bg-muted/25 dark:bg-muted/10">
|
|
216
|
+
<div className="pt-4 rounded-lg">
|
|
217
|
+
<MarkdownContent content={previewContent} />
|
|
218
|
+
{hasMore && (
|
|
219
|
+
<p className="text-muted-foreground text-xs font-mono mt-3 pl-0.5">
|
|
220
|
+
— {plan.content.split('\n').length - 12} more lines, click to expand —
|
|
221
|
+
</p>
|
|
222
|
+
)}
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
)}
|
|
226
|
+
|
|
227
|
+
{/* Full content (expanded) */}
|
|
228
|
+
{expanded && (
|
|
229
|
+
<div className="px-5 pb-6 md:px-6 border-t border-primary/15 bg-muted/25 dark:bg-muted/10">
|
|
230
|
+
<div className="pt-5 max-h-[min(70vh,56rem)] overflow-y-auto rounded-lg pr-1">
|
|
231
|
+
<MarkdownContent content={plan.content} />
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
)}
|
|
235
|
+
</div>
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export default function PlansPage() {
|
|
240
|
+
const { data, error, isLoading } = useSWR<{ plans: PlanFile[] }>(
|
|
241
|
+
'/api/plans', fetcher, { refreshInterval: 30_000 }
|
|
242
|
+
)
|
|
243
|
+
const [search, setSearch] = useState('')
|
|
244
|
+
|
|
245
|
+
const plans = data?.plans ?? []
|
|
246
|
+
const filtered = plans.filter(p =>
|
|
247
|
+
!search ||
|
|
248
|
+
p.name.toLowerCase().includes(search.toLowerCase()) ||
|
|
249
|
+
p.content.toLowerCase().includes(search.toLowerCase())
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
return (
|
|
253
|
+
<div className="flex flex-col min-h-screen">
|
|
254
|
+
<TopBar title="orca-pulse · plans" subtitle="~/.orca/plans/" />
|
|
255
|
+
<div className="p-4 md:p-6 space-y-5">
|
|
256
|
+
|
|
257
|
+
{error && <p className="text-[#f87171] text-sm font-mono">Error: {String(error)}</p>}
|
|
258
|
+
|
|
259
|
+
{isLoading && (
|
|
260
|
+
<div className="space-y-3">
|
|
261
|
+
{Array.from({ length: 4 }).map((_, i) => (
|
|
262
|
+
<div key={i} className="h-28 bg-muted rounded-lg animate-pulse" />
|
|
263
|
+
))}
|
|
264
|
+
</div>
|
|
265
|
+
)}
|
|
266
|
+
|
|
267
|
+
{data && (
|
|
268
|
+
<>
|
|
269
|
+
{/* Search + count */}
|
|
270
|
+
<div className="flex flex-col sm:flex-row gap-3 items-start sm:items-center">
|
|
271
|
+
<div className="flex-1 border border-border rounded-lg bg-card w-full focus-within:border-primary/40 transition-colors">
|
|
272
|
+
<input
|
|
273
|
+
className="w-full bg-transparent px-4 py-2.5 text-sm font-mono text-foreground placeholder-muted-foreground/50 outline-none"
|
|
274
|
+
placeholder="search plans by name or content..."
|
|
275
|
+
value={search}
|
|
276
|
+
onChange={e => setSearch(e.target.value)}
|
|
277
|
+
/>
|
|
278
|
+
</div>
|
|
279
|
+
<p className="text-sm font-mono text-muted-foreground whitespace-nowrap">
|
|
280
|
+
<span className="text-[#fbbf24] font-bold">{filtered.length}</span>
|
|
281
|
+
{filtered.length !== plans.length && (
|
|
282
|
+
<span className="text-muted-foreground/60"> of {plans.length}</span>
|
|
283
|
+
)} plans
|
|
284
|
+
</p>
|
|
285
|
+
</div>
|
|
286
|
+
|
|
287
|
+
{filtered.length === 0 ? (
|
|
288
|
+
<div className="text-center py-16">
|
|
289
|
+
<p className="text-[#d97706] text-2xl mb-3">📋</p>
|
|
290
|
+
<p className="text-muted-foreground/60 text-sm font-mono">
|
|
291
|
+
{plans.length === 0
|
|
292
|
+
? 'No plans found in ~/.orca/plans/'
|
|
293
|
+
: 'No plans match your search.'}
|
|
294
|
+
</p>
|
|
295
|
+
</div>
|
|
296
|
+
) : (
|
|
297
|
+
<div className="space-y-3">
|
|
298
|
+
{filtered.map(plan => (
|
|
299
|
+
<PlanCard key={plan.name} plan={plan} />
|
|
300
|
+
))}
|
|
301
|
+
</div>
|
|
302
|
+
)}
|
|
303
|
+
</>
|
|
304
|
+
)}
|
|
305
|
+
</div>
|
|
306
|
+
</div>
|
|
307
|
+
)
|
|
308
|
+
}
|