create-manifest 1.3.4 → 2.0.0

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 (57) hide show
  1. package/README.md +40 -21
  2. package/index.js +51 -0
  3. package/package.json +11 -89
  4. package/starter/.claude/settings.local.json +21 -0
  5. package/starter/.env.example +1 -0
  6. package/starter/@/components/table.tsx +478 -0
  7. package/starter/@/components/ui/button.tsx +62 -0
  8. package/starter/@/components/ui/checkbox.tsx +30 -0
  9. package/starter/README-DEV.md +167 -0
  10. package/starter/components.json +24 -0
  11. package/starter/package.json +42 -0
  12. package/starter/src/flows/list-pokemons.flow.ts +131 -0
  13. package/starter/src/server.ts +165 -0
  14. package/starter/src/web/PokemonList.tsx +125 -0
  15. package/starter/src/web/components/blog-post-card.tsx +286 -0
  16. package/starter/src/web/components/blog-post-list.tsx +291 -0
  17. package/starter/src/web/components/ui/.gitkeep +0 -0
  18. package/starter/src/web/components/ui/button.tsx +62 -0
  19. package/starter/src/web/globals.css +98 -0
  20. package/starter/src/web/hooks/.gitkeep +0 -0
  21. package/starter/src/web/lib/utils.ts +6 -0
  22. package/starter/src/web/root.tsx +36 -0
  23. package/starter/src/web/tsconfig.json +3 -0
  24. package/starter/tsconfig.json +21 -0
  25. package/starter/tsconfig.web.json +24 -0
  26. package/starter/vite.config.ts +36 -0
  27. package/assets/monorepo/README.md +0 -52
  28. package/assets/monorepo/api-package.json +0 -9
  29. package/assets/monorepo/api-readme.md +0 -50
  30. package/assets/monorepo/manifest.yml +0 -34
  31. package/assets/monorepo/root-package.json +0 -15
  32. package/assets/monorepo/web-package.json +0 -10
  33. package/assets/monorepo/web-readme.md +0 -9
  34. package/assets/standalone/README.md +0 -50
  35. package/assets/standalone/api-package.json +0 -9
  36. package/assets/standalone/manifest.yml +0 -34
  37. package/bin/dev.cmd +0 -3
  38. package/bin/dev.js +0 -5
  39. package/bin/run.cmd +0 -3
  40. package/bin/run.js +0 -5
  41. package/dist/commands/index.d.ts +0 -65
  42. package/dist/commands/index.js +0 -480
  43. package/dist/index.d.ts +0 -1
  44. package/dist/index.js +0 -1
  45. package/dist/utils/GetBackendFileContent.d.ts +0 -1
  46. package/dist/utils/GetBackendFileContent.js +0 -21
  47. package/dist/utils/GetLatestPackageVersion.d.ts +0 -1
  48. package/dist/utils/GetLatestPackageVersion.js +0 -5
  49. package/dist/utils/UpdateExtensionJsonFile.d.ts +0 -6
  50. package/dist/utils/UpdateExtensionJsonFile.js +0 -8
  51. package/dist/utils/UpdatePackageJsonFile.d.ts +0 -18
  52. package/dist/utils/UpdatePackageJsonFile.js +0 -21
  53. package/dist/utils/UpdateSettingsJsonFile.d.ts +0 -4
  54. package/dist/utils/UpdateSettingsJsonFile.js +0 -6
  55. package/dist/utils/helpers.d.ts +0 -1
  56. package/dist/utils/helpers.js +0 -11
  57. package/oclif.manifest.json +0 -47
@@ -0,0 +1,478 @@
1
+ 'use client'
2
+
3
+ import { Button } from '@/components/ui/button'
4
+ import { cn } from '@/lib/utils'
5
+ import { Check, ChevronDown, ChevronUp, Download, Minus, Send } from 'lucide-react'
6
+ import { useCallback, useMemo, useState } from 'react'
7
+
8
+ export interface TableColumn<T = Record<string, unknown>> {
9
+ header: string
10
+ accessor: keyof T | string
11
+ sortable?: boolean
12
+ width?: string
13
+ align?: 'left' | 'center' | 'right'
14
+ render?: (value: unknown, row: T, index: number) => React.ReactNode
15
+ }
16
+
17
+ export interface TableProps<T = Record<string, unknown>> {
18
+ columns?: TableColumn<T>[]
19
+ data?: T[]
20
+ selectable?: 'none' | 'single' | 'multi'
21
+ onSelectionChange?: (selectedRows: T[]) => void
22
+ loading?: boolean
23
+ emptyMessage?: string
24
+ stickyHeader?: boolean
25
+ compact?: boolean
26
+ selectedRows?: T[]
27
+ showActions?: boolean
28
+ onDownload?: (selectedRows: T[]) => void
29
+ onSend?: (selectedRows: T[]) => void
30
+ }
31
+
32
+ // Default demo data for the table
33
+ const defaultColumns: TableColumn[] = [
34
+ { header: 'Model', accessor: 'model', sortable: true },
35
+ {
36
+ header: 'Input (w/ Cache)',
37
+ accessor: 'inputCache',
38
+ sortable: true,
39
+ align: 'right'
40
+ },
41
+ { header: 'Output', accessor: 'output', sortable: true, align: 'right' },
42
+ {
43
+ header: 'Total Tokens',
44
+ accessor: 'totalTokens',
45
+ sortable: true,
46
+ align: 'right'
47
+ },
48
+ {
49
+ header: 'API Cost',
50
+ accessor: 'apiCost',
51
+ sortable: true,
52
+ align: 'right',
53
+ render: (value) => `$${(value as number).toFixed(2)}`
54
+ }
55
+ ]
56
+
57
+ const defaultData = [
58
+ {
59
+ model: 'gpt-5',
60
+ inputCache: 0,
61
+ output: 103271,
62
+ totalTokens: 2267482,
63
+ apiCost: 0.0
64
+ },
65
+ {
66
+ model: 'claude-3.5-sonnet',
67
+ inputCache: 176177,
68
+ output: 8326,
69
+ totalTokens: 647528,
70
+ apiCost: 1.0
71
+ },
72
+ {
73
+ model: 'gemini-2.0-flash-exp',
74
+ inputCache: 176100,
75
+ output: 8326,
76
+ totalTokens: 647528,
77
+ apiCost: 0.0
78
+ },
79
+ {
80
+ model: 'gemini-2.5-pro',
81
+ inputCache: 176177,
82
+ output: 7000,
83
+ totalTokens: 647528,
84
+ apiCost: 0.0
85
+ },
86
+ {
87
+ model: 'claude-4-sonnet',
88
+ inputCache: 68415,
89
+ output: 12769,
90
+ totalTokens: 946536,
91
+ apiCost: 0.71
92
+ }
93
+ ]
94
+
95
+ function SkeletonRow({
96
+ columns,
97
+ compact
98
+ }: {
99
+ columns: number
100
+ compact?: boolean
101
+ }) {
102
+ return (
103
+ <tr className="border-b border-border">
104
+ {Array.from({ length: columns }).map((_, i) => (
105
+ <td key={i} className={cn('px-3', compact ? 'py-2' : 'py-3')}>
106
+ <div className="h-4 bg-muted animate-pulse rounded" />
107
+ </td>
108
+ ))}
109
+ </tr>
110
+ )
111
+ }
112
+
113
+ export function Table<T extends Record<string, unknown>>({
114
+ columns = defaultColumns as unknown as TableColumn<T>[],
115
+ data = defaultData as unknown as T[],
116
+ selectable = 'none',
117
+ onSelectionChange,
118
+ loading = false,
119
+ emptyMessage = 'No data available',
120
+ stickyHeader = false,
121
+ compact = false,
122
+ selectedRows: controlledSelectedRows,
123
+ showActions = false,
124
+ onDownload,
125
+ onSend
126
+ }: TableProps<T>) {
127
+ const [sortConfig, setSortConfig] = useState<{
128
+ key: string
129
+ direction: 'asc' | 'desc'
130
+ } | null>(null)
131
+ const [internalSelectedRows, setInternalSelectedRows] = useState<Set<number>>(
132
+ new Set()
133
+ )
134
+
135
+ const selectedRowsSet = controlledSelectedRows
136
+ ? new Set(controlledSelectedRows.map((row) => data.indexOf(row)))
137
+ : internalSelectedRows
138
+
139
+ const handleSort = useCallback((accessor: string) => {
140
+ setSortConfig((current) => {
141
+ if (current?.key === accessor) {
142
+ if (current.direction === 'asc') {
143
+ return { key: accessor, direction: 'desc' }
144
+ }
145
+ return null
146
+ }
147
+ return { key: accessor, direction: 'asc' }
148
+ })
149
+ }, [])
150
+
151
+ const sortedData = useMemo(() => {
152
+ if (!sortConfig) return data
153
+
154
+ return [...data].sort((a, b) => {
155
+ const aValue = a[sortConfig.key as keyof T]
156
+ const bValue = b[sortConfig.key as keyof T]
157
+
158
+ if (aValue === bValue) return 0
159
+
160
+ let comparison = 0
161
+ if (typeof aValue === 'number' && typeof bValue === 'number') {
162
+ comparison = aValue - bValue
163
+ } else {
164
+ comparison = String(aValue).localeCompare(String(bValue))
165
+ }
166
+
167
+ return sortConfig.direction === 'asc' ? comparison : -comparison
168
+ })
169
+ }, [data, sortConfig])
170
+
171
+ const handleRowSelect = useCallback(
172
+ (index: number) => {
173
+ if (selectable === 'none') return
174
+
175
+ const newSelected = new Set(selectedRowsSet)
176
+
177
+ if (selectable === 'single') {
178
+ if (newSelected.has(index)) {
179
+ newSelected.clear()
180
+ } else {
181
+ newSelected.clear()
182
+ newSelected.add(index)
183
+ }
184
+ } else {
185
+ if (newSelected.has(index)) {
186
+ newSelected.delete(index)
187
+ } else {
188
+ newSelected.add(index)
189
+ }
190
+ }
191
+
192
+ setInternalSelectedRows(newSelected)
193
+ onSelectionChange?.(sortedData.filter((_, i) => newSelected.has(i)))
194
+ },
195
+ [selectable, selectedRowsSet, sortedData, onSelectionChange]
196
+ )
197
+
198
+ const handleSelectAll = useCallback(() => {
199
+ if (selectable !== 'multi') return
200
+
201
+ const allSelected = selectedRowsSet.size === sortedData.length
202
+ const newSelected = allSelected
203
+ ? new Set<number>()
204
+ : new Set(sortedData.map((_, i) => i))
205
+
206
+ setInternalSelectedRows(newSelected)
207
+ onSelectionChange?.(allSelected ? [] : sortedData)
208
+ }, [selectable, selectedRowsSet.size, sortedData, onSelectionChange])
209
+
210
+ const getValue = (row: T, accessor: string): unknown => {
211
+ const keys = accessor.split('.')
212
+ let value: unknown = row
213
+ for (const key of keys) {
214
+ value = (value as Record<string, unknown>)?.[key]
215
+ }
216
+ return value
217
+ }
218
+
219
+ const formatNumber = (value: unknown): string => {
220
+ if (typeof value === 'number') {
221
+ return new Intl.NumberFormat('en-US').format(value)
222
+ }
223
+ return String(value ?? '')
224
+ }
225
+
226
+ const getSortIcon = (accessor: string) => {
227
+ if (sortConfig?.key !== accessor) {
228
+ return <Minus className="h-3 w-3 opacity-0 group-hover:opacity-30" />
229
+ }
230
+ return sortConfig.direction === 'asc' ? (
231
+ <ChevronUp className="h-3 w-3" />
232
+ ) : (
233
+ <ChevronDown className="h-3 w-3" />
234
+ )
235
+ }
236
+
237
+ return (
238
+ <div className="w-full">
239
+ {/* Mobile: Card view */}
240
+ <div className="sm:hidden space-y-2">
241
+ {loading ? (
242
+ Array.from({ length: 3 }).map((_, i) => (
243
+ <div key={i} className="rounded-md sm:rounded-lg border bg-card p-3 space-y-2">
244
+ {columns.slice(0, 4).map((_, j) => (
245
+ <div
246
+ key={j}
247
+ className="h-4 bg-muted animate-pulse rounded w-3/4"
248
+ />
249
+ ))}
250
+ </div>
251
+ ))
252
+ ) : sortedData.length === 0 ? (
253
+ <div className="rounded-md sm:rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground">
254
+ {emptyMessage}
255
+ </div>
256
+ ) : (
257
+ sortedData.map((row, rowIndex) => (
258
+ <button
259
+ key={rowIndex}
260
+ type="button"
261
+ onClick={() => handleRowSelect(rowIndex)}
262
+ disabled={selectable === 'none'}
263
+ className={cn(
264
+ 'w-full rounded-md sm:rounded-lg border bg-card p-3 text-left transition-all',
265
+ selectable !== 'none' &&
266
+ 'cursor-pointer hover:border-foreground/30',
267
+ selectedRowsSet.has(rowIndex) &&
268
+ 'border-foreground ring-1 ring-foreground'
269
+ )}
270
+ >
271
+ <div className="space-y-1.5">
272
+ {columns.map((column, colIndex) => {
273
+ const value = getValue(row, column.accessor as string)
274
+ const displayValue = column.render
275
+ ? column.render(value, row, rowIndex)
276
+ : formatNumber(value)
277
+
278
+ return (
279
+ <div
280
+ key={colIndex}
281
+ className="flex justify-between items-center"
282
+ >
283
+ <span className="text-xs text-muted-foreground">
284
+ {column.header}
285
+ </span>
286
+ <span
287
+ className={cn(
288
+ 'text-xs font-medium',
289
+ colIndex === 0 && 'font-semibold'
290
+ )}
291
+ >
292
+ {displayValue}
293
+ </span>
294
+ </div>
295
+ )
296
+ })}
297
+ </div>
298
+ </button>
299
+ ))
300
+ )}
301
+ </div>
302
+
303
+ {/* Desktop: Table view */}
304
+ <div className="hidden sm:block overflow-x-auto rounded-md sm:rounded-lg">
305
+ <table className="w-full text-sm" role="grid">
306
+ <thead
307
+ className={cn(
308
+ 'border-b bg-muted/50',
309
+ stickyHeader && 'sticky top-0 z-10'
310
+ )}
311
+ >
312
+ <tr>
313
+ {selectable === 'multi' && (
314
+ <th className={cn('w-10 px-3', compact ? 'py-2' : 'py-3')}>
315
+ <button
316
+ type="button"
317
+ onClick={handleSelectAll}
318
+ className={cn(
319
+ 'flex h-4 w-4 items-center justify-center rounded border transition-colors',
320
+ selectedRowsSet.size === sortedData.length &&
321
+ sortedData.length > 0
322
+ ? 'bg-foreground border-foreground text-background'
323
+ : 'border-border hover:border-foreground/50'
324
+ )}
325
+ aria-label="Select all rows"
326
+ >
327
+ {selectedRowsSet.size === sortedData.length &&
328
+ sortedData.length > 0 && <Check className="h-3 w-3" />}
329
+ </button>
330
+ </th>
331
+ )}
332
+ {selectable === 'single' && (
333
+ <th className={cn('w-10 px-3', compact ? 'py-2' : 'py-3')} />
334
+ )}
335
+ {columns.map((column, index) => (
336
+ <th
337
+ key={index}
338
+ className={cn(
339
+ 'px-3 font-medium text-muted-foreground group text-left',
340
+ compact ? 'py-2' : 'py-3',
341
+ column.align === 'right' && 'text-right',
342
+ column.sortable &&
343
+ 'cursor-pointer select-none hover:text-foreground'
344
+ )}
345
+ style={{ width: column.width }}
346
+ onClick={() =>
347
+ column.sortable && handleSort(column.accessor as string)
348
+ }
349
+ role={
350
+ column.sortable ? 'columnheader button' : 'columnheader'
351
+ }
352
+ aria-sort={
353
+ sortConfig?.key === column.accessor
354
+ ? sortConfig.direction === 'asc'
355
+ ? 'ascending'
356
+ : 'descending'
357
+ : undefined
358
+ }
359
+ >
360
+ <span
361
+ className={cn(
362
+ 'inline-flex items-center gap-1',
363
+ column.align === 'right' && 'justify-end'
364
+ )}
365
+ >
366
+ {column.header}
367
+ {column.sortable && getSortIcon(column.accessor as string)}
368
+ </span>
369
+ </th>
370
+ ))}
371
+ </tr>
372
+ </thead>
373
+ <tbody>
374
+ {loading ? (
375
+ Array.from({ length: 5 }).map((_, i) => (
376
+ <SkeletonRow
377
+ key={i}
378
+ columns={columns.length + (selectable !== 'none' ? 1 : 0)}
379
+ compact={compact}
380
+ />
381
+ ))
382
+ ) : sortedData.length === 0 ? (
383
+ <tr>
384
+ <td
385
+ colSpan={columns.length + (selectable !== 'none' ? 1 : 0)}
386
+ className="px-3 py-8 text-center text-muted-foreground"
387
+ >
388
+ {emptyMessage}
389
+ </td>
390
+ </tr>
391
+ ) : (
392
+ sortedData.map((row, rowIndex) => (
393
+ <tr
394
+ key={rowIndex}
395
+ onClick={() => handleRowSelect(rowIndex)}
396
+ className={cn(
397
+ 'border-b border-border last:border-0 transition-colors',
398
+ selectable !== 'none' && 'cursor-pointer hover:bg-muted/30'
399
+ )}
400
+ role="row"
401
+ aria-selected={selectedRowsSet.has(rowIndex)}
402
+ >
403
+ {selectable !== 'none' && (
404
+ <td className={cn('px-3', compact ? 'py-2' : 'py-3')}>
405
+ <div
406
+ className={cn(
407
+ 'flex h-4 w-4 items-center justify-center rounded border transition-colors',
408
+ selectedRowsSet.has(rowIndex)
409
+ ? 'bg-foreground border-foreground text-background'
410
+ : 'border-border'
411
+ )}
412
+ >
413
+ {selectedRowsSet.has(rowIndex) && (
414
+ <Check className="h-3 w-3" />
415
+ )}
416
+ </div>
417
+ </td>
418
+ )}
419
+ {columns.map((column, colIndex) => {
420
+ const value = getValue(row, column.accessor as string)
421
+ const displayValue = column.render
422
+ ? column.render(value, row, rowIndex)
423
+ : formatNumber(value)
424
+
425
+ return (
426
+ <td
427
+ key={colIndex}
428
+ className={cn(
429
+ 'px-3',
430
+ compact ? 'py-2' : 'py-3',
431
+ column.align === 'center' && 'text-center',
432
+ column.align === 'right' && 'text-right',
433
+ colIndex === 0 && 'font-medium'
434
+ )}
435
+ >
436
+ {displayValue}
437
+ </td>
438
+ )
439
+ })}
440
+ </tr>
441
+ ))
442
+ )}
443
+ </tbody>
444
+ </table>
445
+ </div>
446
+
447
+ {/* Action buttons for multi-select */}
448
+ {showActions && selectable === 'multi' && (
449
+ <div className="mt-3 flex items-center justify-between border-t pt-3">
450
+ <span className="text-sm text-muted-foreground">
451
+ {selectedRowsSet.size > 0
452
+ ? `${selectedRowsSet.size} item${selectedRowsSet.size > 1 ? 's' : ''} selected`
453
+ : 'Select items'}
454
+ </span>
455
+ <div className="flex gap-2">
456
+ <Button
457
+ variant="outline"
458
+ size="sm"
459
+ disabled={selectedRowsSet.size === 0}
460
+ onClick={() => onDownload?.(sortedData.filter((_, i) => selectedRowsSet.has(i)))}
461
+ >
462
+ <Download className="mr-1.5 h-3.5 w-3.5" />
463
+ Download
464
+ </Button>
465
+ <Button
466
+ size="sm"
467
+ disabled={selectedRowsSet.size === 0}
468
+ onClick={() => onSend?.(sortedData.filter((_, i) => selectedRowsSet.has(i)))}
469
+ >
470
+ <Send className="mr-1.5 h-3.5 w-3.5" />
471
+ Send
472
+ </Button>
473
+ </div>
474
+ </div>
475
+ )}
476
+ </div>
477
+ )
478
+ }
@@ -0,0 +1,62 @@
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
13
+ destructive:
14
+ "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
15
+ outline:
16
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
17
+ secondary:
18
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19
+ ghost:
20
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
21
+ link: "text-primary underline-offset-4 hover:underline",
22
+ },
23
+ size: {
24
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
25
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
26
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
27
+ icon: "size-9",
28
+ "icon-sm": "size-8",
29
+ "icon-lg": "size-10",
30
+ },
31
+ },
32
+ defaultVariants: {
33
+ variant: "default",
34
+ size: "default",
35
+ },
36
+ }
37
+ )
38
+
39
+ function Button({
40
+ className,
41
+ variant = "default",
42
+ size = "default",
43
+ asChild = false,
44
+ ...props
45
+ }: React.ComponentProps<"button"> &
46
+ VariantProps<typeof buttonVariants> & {
47
+ asChild?: boolean
48
+ }) {
49
+ const Comp = asChild ? Slot : "button"
50
+
51
+ return (
52
+ <Comp
53
+ data-slot="button"
54
+ data-variant={variant}
55
+ data-size={size}
56
+ className={cn(buttonVariants({ variant, size, className }))}
57
+ {...props}
58
+ />
59
+ )
60
+ }
61
+
62
+ export { Button, buttonVariants }
@@ -0,0 +1,30 @@
1
+ import * as React from "react"
2
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
3
+ import { CheckIcon } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ function Checkbox({
8
+ className,
9
+ ...props
10
+ }: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
11
+ return (
12
+ <CheckboxPrimitive.Root
13
+ data-slot="checkbox"
14
+ className={cn(
15
+ "peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
16
+ className
17
+ )}
18
+ {...props}
19
+ >
20
+ <CheckboxPrimitive.Indicator
21
+ data-slot="checkbox-indicator"
22
+ className="grid place-content-center text-current transition-none"
23
+ >
24
+ <CheckIcon className="size-3.5" />
25
+ </CheckboxPrimitive.Indicator>
26
+ </CheckboxPrimitive.Root>
27
+ )
28
+ }
29
+
30
+ export { Checkbox }