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,92 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ function Card({ className, ...props }: React.ComponentProps<"div">) {
6
+ return (
7
+ <div
8
+ data-slot="card"
9
+ className={cn(
10
+ "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
11
+ className
12
+ )}
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
19
+ return (
20
+ <div
21
+ data-slot="card-header"
22
+ className={cn(
23
+ "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
24
+ className
25
+ )}
26
+ {...props}
27
+ />
28
+ )
29
+ }
30
+
31
+ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
32
+ return (
33
+ <div
34
+ data-slot="card-title"
35
+ className={cn("leading-none font-semibold", className)}
36
+ {...props}
37
+ />
38
+ )
39
+ }
40
+
41
+ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
42
+ return (
43
+ <div
44
+ data-slot="card-description"
45
+ className={cn("text-muted-foreground text-sm", className)}
46
+ {...props}
47
+ />
48
+ )
49
+ }
50
+
51
+ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
52
+ return (
53
+ <div
54
+ data-slot="card-action"
55
+ className={cn(
56
+ "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
57
+ className
58
+ )}
59
+ {...props}
60
+ />
61
+ )
62
+ }
63
+
64
+ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
65
+ return (
66
+ <div
67
+ data-slot="card-content"
68
+ className={cn("px-6", className)}
69
+ {...props}
70
+ />
71
+ )
72
+ }
73
+
74
+ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
75
+ return (
76
+ <div
77
+ data-slot="card-footer"
78
+ className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
79
+ {...props}
80
+ />
81
+ )
82
+ }
83
+
84
+ export {
85
+ Card,
86
+ CardHeader,
87
+ CardFooter,
88
+ CardTitle,
89
+ CardAction,
90
+ CardDescription,
91
+ CardContent,
92
+ }
@@ -0,0 +1,385 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as RechartsPrimitive from "recharts"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ // Format: { THEME_NAME: CSS_SELECTOR }
9
+ const THEMES = { light: "", dark: ".dark" } as const
10
+
11
+ export type ChartConfig = {
12
+ [k in string]: {
13
+ label?: React.ReactNode
14
+ icon?: React.ComponentType
15
+ } & (
16
+ | { color?: string; theme?: never }
17
+ | { color?: never; theme: Record<keyof typeof THEMES, string> }
18
+ )
19
+ }
20
+
21
+ type ChartContextProps = {
22
+ config: ChartConfig
23
+ }
24
+
25
+ const ChartContext = React.createContext<ChartContextProps | null>(null)
26
+
27
+ function useChart() {
28
+ const context = React.useContext(ChartContext)
29
+
30
+ if (!context) {
31
+ throw new Error("useChart must be used within a <ChartContainer />")
32
+ }
33
+
34
+ return context
35
+ }
36
+
37
+ const ChartContainer = React.forwardRef<
38
+ HTMLDivElement,
39
+ React.ComponentProps<"div"> & {
40
+ config: ChartConfig
41
+ children: React.ComponentProps<
42
+ typeof RechartsPrimitive.ResponsiveContainer
43
+ >["children"]
44
+ }
45
+ >(({ id, className, children, config, ...props }, ref) => {
46
+ const uniqueId = React.useId()
47
+ const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
48
+
49
+ return (
50
+ <ChartContext.Provider value={{ config }}>
51
+ <div
52
+ data-chart={chartId}
53
+ ref={ref}
54
+ className={cn(
55
+ "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
56
+ className
57
+ )}
58
+ {...props}
59
+ >
60
+ <ChartStyle id={chartId} config={config} />
61
+ <RechartsPrimitive.ResponsiveContainer>
62
+ {children}
63
+ </RechartsPrimitive.ResponsiveContainer>
64
+ </div>
65
+ </ChartContext.Provider>
66
+ )
67
+ })
68
+ ChartContainer.displayName = "Chart"
69
+
70
+ const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
71
+ const colorConfig = Object.entries(config).filter(
72
+ ([, config]) => config.theme || config.color
73
+ )
74
+
75
+ if (!colorConfig.length) {
76
+ return null
77
+ }
78
+
79
+ return (
80
+ <style
81
+ dangerouslySetInnerHTML={{
82
+ __html: Object.entries(THEMES)
83
+ .map(
84
+ ([theme, prefix]) => `
85
+ ${prefix} [data-chart=${id}] {
86
+ ${colorConfig
87
+ .map(([key, itemConfig]) => {
88
+ const color =
89
+ itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
90
+ itemConfig.color
91
+ return color ? ` --color-${key}: ${color};` : null
92
+ })
93
+ .join("\n")}
94
+ }
95
+ `
96
+ )
97
+ .join("\n"),
98
+ }}
99
+ />
100
+ )
101
+ }
102
+
103
+ const ChartTooltip = RechartsPrimitive.Tooltip
104
+
105
+ interface PayloadItem {
106
+ dataKey?: string | number
107
+ name?: string
108
+ value?: number
109
+ color?: string
110
+ fill?: string
111
+ payload?: Record<string, unknown>
112
+ }
113
+
114
+ interface ChartTooltipContentProps extends React.ComponentProps<"div"> {
115
+ active?: boolean
116
+ payload?: PayloadItem[]
117
+ label?: string
118
+ labelFormatter?: (label: string, payload: PayloadItem[]) => React.ReactNode
119
+ labelClassName?: string
120
+ formatter?: (value: number, name: string, item: PayloadItem, index: number, payload: PayloadItem) => React.ReactNode
121
+ color?: string
122
+ hideLabel?: boolean
123
+ hideIndicator?: boolean
124
+ indicator?: "line" | "dot" | "dashed"
125
+ nameKey?: string
126
+ labelKey?: string
127
+ }
128
+
129
+ const ChartTooltipContent = React.forwardRef<HTMLDivElement, ChartTooltipContentProps>(
130
+ (
131
+ {
132
+ active,
133
+ payload,
134
+ className,
135
+ indicator = "dot",
136
+ hideLabel = false,
137
+ hideIndicator = false,
138
+ label,
139
+ labelFormatter,
140
+ labelClassName,
141
+ formatter,
142
+ color,
143
+ nameKey,
144
+ labelKey,
145
+ },
146
+ ref
147
+ ) => {
148
+ const { config } = useChart()
149
+
150
+ const tooltipLabel = React.useMemo(() => {
151
+ if (hideLabel || !payload?.length) {
152
+ return null
153
+ }
154
+
155
+ const [item] = payload
156
+ const key = `${labelKey || item.dataKey || item.name || "value"}`
157
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
158
+ const value =
159
+ !labelKey && typeof label === "string"
160
+ ? config[label as keyof typeof config]?.label || label
161
+ : itemConfig?.label
162
+
163
+ if (labelFormatter) {
164
+ return (
165
+ <div className={cn("font-medium", labelClassName)}>
166
+ {labelFormatter(String(value ?? ''), payload)}
167
+ </div>
168
+ )
169
+ }
170
+
171
+ if (!value) {
172
+ return null
173
+ }
174
+
175
+ return <div className={cn("font-medium", labelClassName)}>{value}</div>
176
+ }, [
177
+ label,
178
+ labelFormatter,
179
+ payload,
180
+ hideLabel,
181
+ labelClassName,
182
+ config,
183
+ labelKey,
184
+ ])
185
+
186
+ if (!active || !payload?.length) {
187
+ return null
188
+ }
189
+
190
+ const nestLabel = payload.length === 1 && indicator !== "dot"
191
+
192
+ return (
193
+ <div
194
+ ref={ref}
195
+ className={cn(
196
+ "grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
197
+ className
198
+ )}
199
+ >
200
+ {!nestLabel ? tooltipLabel : null}
201
+ <div className="grid gap-1.5">
202
+ {payload.map((item: PayloadItem, index: number) => {
203
+ const key = `${nameKey || item.name || item.dataKey || "value"}`
204
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
205
+ const indicatorColor = color || (item.payload?.fill as string) || item.color
206
+
207
+ return (
208
+ <div
209
+ key={String(item.dataKey)}
210
+ className={cn(
211
+ "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
212
+ indicator === "dot" && "items-center"
213
+ )}
214
+ >
215
+ {formatter && item?.value !== undefined && item.name ? (
216
+ formatter(item.value, item.name, item, index, item)
217
+ ) : (
218
+ <>
219
+ {itemConfig?.icon ? (
220
+ <itemConfig.icon />
221
+ ) : (
222
+ !hideIndicator && (
223
+ <div
224
+ className={cn(
225
+ "shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
226
+ {
227
+ "h-2.5 w-2.5": indicator === "dot",
228
+ "w-1": indicator === "line",
229
+ "w-0 border-[1.5px] border-dashed bg-transparent":
230
+ indicator === "dashed",
231
+ "my-0.5": nestLabel && indicator === "dashed",
232
+ }
233
+ )}
234
+ style={
235
+ {
236
+ "--color-bg": indicatorColor,
237
+ "--color-border": indicatorColor,
238
+ } as React.CSSProperties
239
+ }
240
+ />
241
+ )
242
+ )}
243
+ <div
244
+ className={cn(
245
+ "flex flex-1 justify-between leading-none",
246
+ nestLabel ? "items-end" : "items-center"
247
+ )}
248
+ >
249
+ <div className="grid gap-1.5">
250
+ {nestLabel ? tooltipLabel : null}
251
+ <span className="text-muted-foreground">
252
+ {itemConfig?.label || item.name}
253
+ </span>
254
+ </div>
255
+ {item.value && (
256
+ <span className="font-mono font-medium tabular-nums text-foreground">
257
+ {item.value.toLocaleString()}
258
+ </span>
259
+ )}
260
+ </div>
261
+ </>
262
+ )}
263
+ </div>
264
+ )
265
+ })}
266
+ </div>
267
+ </div>
268
+ )
269
+ }
270
+ )
271
+ ChartTooltipContent.displayName = "ChartTooltip"
272
+
273
+ const ChartLegend = RechartsPrimitive.Legend
274
+
275
+ interface LegendPayloadItem {
276
+ value?: string
277
+ dataKey?: string
278
+ color?: string
279
+ }
280
+
281
+ interface ChartLegendContentProps extends React.ComponentProps<"div"> {
282
+ payload?: LegendPayloadItem[]
283
+ verticalAlign?: "top" | "bottom"
284
+ hideIcon?: boolean
285
+ nameKey?: string
286
+ }
287
+
288
+ const ChartLegendContent = React.forwardRef<HTMLDivElement, ChartLegendContentProps>(
289
+ (
290
+ { className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
291
+ ref
292
+ ) => {
293
+ const { config } = useChart()
294
+
295
+ if (!payload?.length) {
296
+ return null
297
+ }
298
+
299
+ return (
300
+ <div
301
+ ref={ref}
302
+ className={cn(
303
+ "flex items-center justify-center gap-4",
304
+ verticalAlign === "top" ? "pb-3" : "pt-3",
305
+ className
306
+ )}
307
+ >
308
+ {payload.map((item: LegendPayloadItem) => {
309
+ const key = `${nameKey || item.dataKey || "value"}`
310
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
311
+
312
+ return (
313
+ <div
314
+ key={item.value}
315
+ className={cn(
316
+ "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
317
+ )}
318
+ >
319
+ {itemConfig?.icon && !hideIcon ? (
320
+ <itemConfig.icon />
321
+ ) : (
322
+ <div
323
+ className="h-2 w-2 shrink-0 rounded-[2px]"
324
+ style={{
325
+ backgroundColor: item.color,
326
+ }}
327
+ />
328
+ )}
329
+ {itemConfig?.label}
330
+ </div>
331
+ )
332
+ })}
333
+ </div>
334
+ )
335
+ }
336
+ )
337
+ ChartLegendContent.displayName = "ChartLegend"
338
+
339
+ // Helper to extract item config from a payload.
340
+ function getPayloadConfigFromPayload(
341
+ config: ChartConfig,
342
+ payload: unknown,
343
+ key: string
344
+ ) {
345
+ if (typeof payload !== "object" || payload === null) {
346
+ return undefined
347
+ }
348
+
349
+ const payloadPayload =
350
+ "payload" in payload &&
351
+ typeof payload.payload === "object" &&
352
+ payload.payload !== null
353
+ ? payload.payload
354
+ : undefined
355
+
356
+ let configLabelKey: string = key
357
+
358
+ if (
359
+ key in payload &&
360
+ typeof payload[key as keyof typeof payload] === "string"
361
+ ) {
362
+ configLabelKey = payload[key as keyof typeof payload] as string
363
+ } else if (
364
+ payloadPayload &&
365
+ key in payloadPayload &&
366
+ typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
367
+ ) {
368
+ configLabelKey = payloadPayload[
369
+ key as keyof typeof payloadPayload
370
+ ] as string
371
+ }
372
+
373
+ return configLabelKey in config
374
+ ? config[configLabelKey]
375
+ : config[key as keyof typeof config]
376
+ }
377
+
378
+ export {
379
+ ChartContainer,
380
+ ChartTooltip,
381
+ ChartTooltipContent,
382
+ ChartLegend,
383
+ ChartLegendContent,
384
+ ChartStyle,
385
+ }