@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.
Files changed (204) hide show
  1. package/LICENSE +1 -1
  2. package/NOTICE +6 -6
  3. package/README.md +11 -18
  4. package/package.json +11 -11
  5. package/src/agents/loader.mjs +1 -1
  6. package/src/agents/workflow_loader.mjs +2 -2
  7. package/src/auth/{tarang-auth.mjs → bahulam-auth.mjs} +4 -3
  8. package/src/commands/agent.mjs +3 -3
  9. package/src/commands/device.mjs +2 -2
  10. package/src/commands/pair.mjs +2 -2
  11. package/src/commands/remote.mjs +3 -3
  12. package/src/commands/workflow.mjs +5 -5
  13. package/src/config/env.mjs +9 -2
  14. package/src/config/memory-loader.mjs +1 -1
  15. package/src/config/model-catalog-default.json +4 -3
  16. package/src/config/model-catalog.mjs +2 -0
  17. package/src/config/settings.mjs +2 -2
  18. package/src/context/prose-chunker.mjs +162 -5
  19. package/src/core/agent-loop.mjs +17 -0
  20. package/src/core/attachments.mjs +66 -4
  21. package/src/core/backend-url.mjs +11 -12
  22. package/src/core/bundled-runtime.mjs +1 -1
  23. package/src/core/callback-client.mjs +1 -1
  24. package/src/core/headless.mjs +18 -4
  25. package/src/core/jsonl-writer.mjs +18 -18
  26. package/src/core/lint-resolver.mjs +472 -0
  27. package/src/core/local-agent.mjs +40 -8
  28. package/src/core/local-store.mjs +5 -1
  29. package/src/core/mode-selector.mjs +1 -1
  30. package/src/core/output-filter.mjs +22 -26
  31. package/src/core/pricing.mjs +10 -3
  32. package/src/core/project-artifacts.mjs +3 -3
  33. package/src/core/project-context-loader.mjs +9 -9
  34. package/src/core/settings-sync.mjs +1 -1
  35. package/src/core/stagnation.mjs +61 -1
  36. package/src/core/stream-client.mjs +13 -8
  37. package/src/core/system-prompt.mjs +2 -2
  38. package/src/core/tool-executor.mjs +178 -84
  39. package/src/daemon/http-dashboard.mjs +400 -0
  40. package/src/daemon/session-core.mjs +1 -0
  41. package/src/local-service/agent-relay.mjs +868 -0
  42. package/src/local-service/approval-bridge.mjs +222 -0
  43. package/src/local-service/browser.mjs +24 -0
  44. package/src/local-service/command.mjs +155 -0
  45. package/src/local-service/file-access.mjs +393 -0
  46. package/src/local-service/machine.mjs +86 -0
  47. package/src/local-service/paths.mjs +29 -0
  48. package/src/local-service/preview-converters.mjs +260 -0
  49. package/src/local-service/server.mjs +2644 -0
  50. package/src/local-service/session-store.mjs +221 -0
  51. package/src/mcp/client.mjs +4 -4
  52. package/src/onboarding/preflight.mjs +2 -2
  53. package/src/terminal/agents.mjs +2 -2
  54. package/src/terminal/analytics.mjs +2 -6
  55. package/src/terminal/ansi.mjs +11 -3
  56. package/src/terminal/main.mjs +43 -17
  57. package/src/terminal/model-catalog-display.mjs +111 -0
  58. package/src/terminal/repl-format.mjs +4 -1
  59. package/src/terminal/repl-model-form.mjs +81 -14
  60. package/src/terminal/repl-render.mjs +37 -7
  61. package/src/terminal/repl-resume.mjs +2 -2
  62. package/src/terminal/repl-state.mjs +12 -0
  63. package/src/terminal/repl.mjs +248 -70
  64. package/src/terminal/tool-display.mjs +32 -0
  65. package/src/terminal/watch-state.mjs +118 -0
  66. package/src/tools/agent.mjs +2 -2
  67. package/src/tools/analyze-image.mjs +158 -0
  68. package/src/tools/bash.mjs +11 -9
  69. package/src/tools/generate-image.mjs +411 -0
  70. package/src/tools/lint.mjs +24 -43
  71. package/src/tools/project-overview.mjs +12 -12
  72. package/src/tools/registry.mjs +4 -0
  73. package/src/ui/formatter.mjs +2 -2
  74. package/src/ui/icons.mjs +2 -0
  75. package/src/ui/input-dock.mjs +7 -7
  76. package/src/ui/palette.mjs +1 -1
  77. package/src/ui/slash-commands.mjs +4 -6
  78. package/src/ui/spinner.mjs +1 -1
  79. package/src/ui/term.mjs +2 -6
  80. package/src/ui/tool-card.mjs +81 -6
  81. package/pulse/app/activity/page.tsx +0 -190
  82. package/pulse/app/api/activity/route.ts +0 -138
  83. package/pulse/app/api/benchmark/route.ts +0 -113
  84. package/pulse/app/api/benchmarks/route.ts +0 -195
  85. package/pulse/app/api/costs/route.ts +0 -88
  86. package/pulse/app/api/export/route.ts +0 -77
  87. package/pulse/app/api/history/route.ts +0 -11
  88. package/pulse/app/api/import/route.ts +0 -31
  89. package/pulse/app/api/memory/route.ts +0 -50
  90. package/pulse/app/api/plans/route.ts +0 -9
  91. package/pulse/app/api/projects/[slug]/route.ts +0 -96
  92. package/pulse/app/api/projects/route.ts +0 -121
  93. package/pulse/app/api/sessions/[id]/replay/route.ts +0 -20
  94. package/pulse/app/api/sessions/[id]/route.ts +0 -31
  95. package/pulse/app/api/sessions/route.ts +0 -112
  96. package/pulse/app/api/settings/route.ts +0 -14
  97. package/pulse/app/api/stats/route.ts +0 -143
  98. package/pulse/app/api/todos/route.ts +0 -9
  99. package/pulse/app/api/tools/route.ts +0 -160
  100. package/pulse/app/benchmarks/page.tsx +0 -224
  101. package/pulse/app/costs/page.tsx +0 -179
  102. package/pulse/app/export/page.tsx +0 -465
  103. package/pulse/app/favicon.ico +0 -0
  104. package/pulse/app/globals.css +0 -263
  105. package/pulse/app/help/page.tsx +0 -143
  106. package/pulse/app/history/page.tsx +0 -157
  107. package/pulse/app/layout.tsx +0 -46
  108. package/pulse/app/memory/page.tsx +0 -365
  109. package/pulse/app/overview-client.tsx +0 -393
  110. package/pulse/app/page.tsx +0 -14
  111. package/pulse/app/plans/page.tsx +0 -308
  112. package/pulse/app/projects/[slug]/page.tsx +0 -390
  113. package/pulse/app/projects/page.tsx +0 -110
  114. package/pulse/app/sessions/[id]/page.tsx +0 -243
  115. package/pulse/app/sessions/page.tsx +0 -39
  116. package/pulse/app/settings/page.tsx +0 -188
  117. package/pulse/app/todos/page.tsx +0 -211
  118. package/pulse/app/tools/page.tsx +0 -249
  119. package/pulse/cli.js +0 -164
  120. package/pulse/components/activity/day-of-week-chart.tsx +0 -35
  121. package/pulse/components/activity/streak-card.tsx +0 -36
  122. package/pulse/components/costs/cache-efficiency-panel.tsx +0 -76
  123. package/pulse/components/costs/cost-by-project-chart.tsx +0 -48
  124. package/pulse/components/costs/cost-over-time-chart.tsx +0 -95
  125. package/pulse/components/costs/model-token-table.tsx +0 -60
  126. package/pulse/components/global-search.tsx +0 -193
  127. package/pulse/components/keyboard-nav-provider.tsx +0 -23
  128. package/pulse/components/layout/bottom-nav.tsx +0 -53
  129. package/pulse/components/layout/client-layout.tsx +0 -31
  130. package/pulse/components/layout/sidebar-context.tsx +0 -50
  131. package/pulse/components/layout/sidebar.tsx +0 -183
  132. package/pulse/components/layout/top-bar.tsx +0 -121
  133. package/pulse/components/overview/activity-heatmap.tsx +0 -107
  134. package/pulse/components/overview/conversation-table.tsx +0 -148
  135. package/pulse/components/overview/model-breakdown-donut.tsx +0 -95
  136. package/pulse/components/overview/peak-hours-chart.tsx +0 -87
  137. package/pulse/components/overview/project-activity-donut.tsx +0 -96
  138. package/pulse/components/overview/stat-card.tsx +0 -102
  139. package/pulse/components/overview/usage-over-time-chart.tsx +0 -166
  140. package/pulse/components/projects/project-card.tsx +0 -175
  141. package/pulse/components/sessions/replay/assistant-markdown.tsx +0 -94
  142. package/pulse/components/sessions/replay/compaction-card.tsx +0 -25
  143. package/pulse/components/sessions/replay/session-sidebar.tsx +0 -231
  144. package/pulse/components/sessions/replay/token-accumulation-chart.tsx +0 -98
  145. package/pulse/components/sessions/replay/tool-call-badge.tsx +0 -127
  146. package/pulse/components/sessions/replay/turn-cards.tsx +0 -220
  147. package/pulse/components/sessions/replay/user-tool-result.tsx +0 -158
  148. package/pulse/components/sessions/session-badges.tsx +0 -49
  149. package/pulse/components/sessions/session-table.tsx +0 -299
  150. package/pulse/components/theme-provider.tsx +0 -44
  151. package/pulse/components/tools/feature-adoption-table.tsx +0 -58
  152. package/pulse/components/tools/mcp-server-panel.tsx +0 -45
  153. package/pulse/components/tools/tool-ranking-chart.tsx +0 -57
  154. package/pulse/components/tools/version-history-table.tsx +0 -32
  155. package/pulse/components/ui/alert.tsx +0 -66
  156. package/pulse/components/ui/badge.tsx +0 -48
  157. package/pulse/components/ui/breadcrumb.tsx +0 -109
  158. package/pulse/components/ui/button.tsx +0 -64
  159. package/pulse/components/ui/calendar.tsx +0 -220
  160. package/pulse/components/ui/card.tsx +0 -92
  161. package/pulse/components/ui/command.tsx +0 -158
  162. package/pulse/components/ui/dialog.tsx +0 -158
  163. package/pulse/components/ui/input.tsx +0 -21
  164. package/pulse/components/ui/popover.tsx +0 -89
  165. package/pulse/components/ui/progress.tsx +0 -31
  166. package/pulse/components/ui/select.tsx +0 -190
  167. package/pulse/components/ui/separator.tsx +0 -28
  168. package/pulse/components/ui/sheet.tsx +0 -143
  169. package/pulse/components/ui/skeleton.tsx +0 -13
  170. package/pulse/components/ui/table.tsx +0 -116
  171. package/pulse/components/ui/tabs.tsx +0 -91
  172. package/pulse/components/ui/tooltip.tsx +0 -57
  173. package/pulse/components/use-global-keyboard-nav.ts +0 -79
  174. package/pulse/components.json +0 -23
  175. package/pulse/eslint.config.mjs +0 -18
  176. package/pulse/lib/bahulam-paths.ts +0 -23
  177. package/pulse/lib/claude-reader.ts +0 -592
  178. package/pulse/lib/decode.ts +0 -129
  179. package/pulse/lib/pricing.ts +0 -102
  180. package/pulse/lib/replay-parser.ts +0 -165
  181. package/pulse/lib/tool-categories.ts +0 -140
  182. package/pulse/lib/utils.ts +0 -6
  183. package/pulse/next-env.d.ts +0 -6
  184. package/pulse/next.config.ts +0 -16
  185. package/pulse/package.json +0 -45
  186. package/pulse/postcss.config.mjs +0 -7
  187. package/pulse/public/activity.png +0 -0
  188. package/pulse/public/cc-lens.png +0 -0
  189. package/pulse/public/command-k.png +0 -0
  190. package/pulse/public/costs.png +0 -0
  191. package/pulse/public/dashboard-dark.png +0 -0
  192. package/pulse/public/dashboard-white.png +0 -0
  193. package/pulse/public/export.png +0 -0
  194. package/pulse/public/file.svg +0 -1
  195. package/pulse/public/globe.svg +0 -1
  196. package/pulse/public/next.svg +0 -1
  197. package/pulse/public/projects.png +0 -0
  198. package/pulse/public/session-chat.png +0 -0
  199. package/pulse/public/todos.png +0 -0
  200. package/pulse/public/tools.png +0 -0
  201. package/pulse/public/vercel.svg +0 -1
  202. package/pulse/public/window.svg +0 -1
  203. package/pulse/tsconfig.json +0 -34
  204. package/pulse/types/claude.ts +0 -294
@@ -1,308 +0,0 @@
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="Bahulam Pulse · plans" subtitle="~/.bahulam/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 ~/.bahulam/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
- }