promptslide 0.2.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 (39) hide show
  1. package/dist/index.d.ts +377 -0
  2. package/dist/index.js +963 -0
  3. package/dist/index.js.map +1 -0
  4. package/package.json +65 -0
  5. package/src/commands/build.mjs +73 -0
  6. package/src/commands/create.mjs +197 -0
  7. package/src/commands/preview.mjs +22 -0
  8. package/src/commands/studio.mjs +27 -0
  9. package/src/core/animated.tsx +153 -0
  10. package/src/core/animation-config.ts +98 -0
  11. package/src/core/animation-context.tsx +54 -0
  12. package/src/core/index.ts +73 -0
  13. package/src/core/layouts/shared-footer.tsx +43 -0
  14. package/src/core/morph.tsx +153 -0
  15. package/src/core/slide-deck.tsx +430 -0
  16. package/src/core/slide-error-boundary.tsx +50 -0
  17. package/src/core/theme-context.tsx +48 -0
  18. package/src/core/transitions.ts +200 -0
  19. package/src/core/types.ts +136 -0
  20. package/src/core/use-slide-navigation.ts +142 -0
  21. package/src/core/utils.ts +8 -0
  22. package/src/index.mjs +70 -0
  23. package/src/utils/ansi.mjs +5 -0
  24. package/src/utils/colors.mjs +44 -0
  25. package/src/utils/prompts.mjs +50 -0
  26. package/src/utils/tsconfig.mjs +35 -0
  27. package/src/vite/config.mjs +40 -0
  28. package/src/vite/plugin.mjs +66 -0
  29. package/templates/default/AGENTS.md +453 -0
  30. package/templates/default/README.md +35 -0
  31. package/templates/default/package.json +26 -0
  32. package/templates/default/public/logo.svg +7 -0
  33. package/templates/default/src/App.tsx +11 -0
  34. package/templates/default/src/deck-config.ts +8 -0
  35. package/templates/default/src/globals.css +157 -0
  36. package/templates/default/src/layouts/slide-layout-centered.tsx +59 -0
  37. package/templates/default/src/slides/slide-example.tsx +53 -0
  38. package/templates/default/src/slides/slide-title.tsx +27 -0
  39. package/templates/default/src/theme.ts +8 -0
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Slide transition variants for the slide deck framework.
3
+ * These define how slides enter and exit during navigation.
4
+ */
5
+
6
+ import type { Variants } from "framer-motion"
7
+
8
+ import {
9
+ EASE_MORPH,
10
+ MORPH_DURATION,
11
+ SLIDE_DISTANCE,
12
+ SLIDE_TRANSITION,
13
+ SLIDE_TRANSITION_DURATION
14
+ } from "./animation-config"
15
+
16
+ // =============================================================================
17
+ // TYPES
18
+ // =============================================================================
19
+
20
+ export type SlideTransitionType =
21
+ | "fade"
22
+ | "slide-left"
23
+ | "slide-right"
24
+ | "slide-up"
25
+ | "slide-down"
26
+ | "zoom"
27
+ | "zoom-fade"
28
+ | "morph"
29
+ | "none"
30
+
31
+ export interface SlideTransitionConfig {
32
+ /** The transition type */
33
+ type: SlideTransitionType
34
+ /** Optional custom duration (overrides default) */
35
+ duration?: number
36
+ /** Whether to use directional transitions based on navigation direction */
37
+ directional?: boolean
38
+ }
39
+
40
+ // =============================================================================
41
+ // STATIC VARIANTS (non-directional)
42
+ // =============================================================================
43
+
44
+ const fadeVariants: Variants = {
45
+ enter: { opacity: 0 },
46
+ center: { opacity: 1 },
47
+ exit: { opacity: 0 }
48
+ }
49
+
50
+ const slideLeftVariants: Variants = {
51
+ enter: { x: SLIDE_DISTANCE, opacity: 0 },
52
+ center: { x: 0, opacity: 1 },
53
+ exit: { x: -SLIDE_DISTANCE, opacity: 0 }
54
+ }
55
+
56
+ const slideRightVariants: Variants = {
57
+ enter: { x: -SLIDE_DISTANCE, opacity: 0 },
58
+ center: { x: 0, opacity: 1 },
59
+ exit: { x: SLIDE_DISTANCE, opacity: 0 }
60
+ }
61
+
62
+ const slideUpVariants: Variants = {
63
+ enter: { y: SLIDE_DISTANCE, opacity: 0 },
64
+ center: { y: 0, opacity: 1 },
65
+ exit: { y: -SLIDE_DISTANCE, opacity: 0 }
66
+ }
67
+
68
+ const slideDownVariants: Variants = {
69
+ enter: { y: -SLIDE_DISTANCE, opacity: 0 },
70
+ center: { y: 0, opacity: 1 },
71
+ exit: { y: SLIDE_DISTANCE, opacity: 0 }
72
+ }
73
+
74
+ const zoomVariants: Variants = {
75
+ enter: { scale: 0.8, opacity: 0 },
76
+ center: { scale: 1, opacity: 1 },
77
+ exit: { scale: 1.2, opacity: 0 }
78
+ }
79
+
80
+ const zoomFadeVariants: Variants = {
81
+ enter: { scale: 0.95, opacity: 0 },
82
+ center: { scale: 1, opacity: 1 },
83
+ exit: { scale: 1.05, opacity: 0 }
84
+ }
85
+
86
+ const morphVariants: Variants = {
87
+ enter: {
88
+ opacity: 0,
89
+ zIndex: 1
90
+ },
91
+ center: {
92
+ opacity: 1,
93
+ zIndex: 1,
94
+ transition: {
95
+ opacity: { delay: 0.05, duration: 0.25 }
96
+ }
97
+ },
98
+ exit: {
99
+ opacity: 0,
100
+ zIndex: 0,
101
+ transition: {
102
+ opacity: { duration: MORPH_DURATION, ease: EASE_MORPH }
103
+ }
104
+ }
105
+ }
106
+
107
+ const noneVariants: Variants = {
108
+ enter: {},
109
+ center: {},
110
+ exit: {}
111
+ }
112
+
113
+ // =============================================================================
114
+ // VARIANT REGISTRY
115
+ // =============================================================================
116
+
117
+ export const SLIDE_VARIANTS: Record<SlideTransitionType, Variants> = {
118
+ fade: fadeVariants,
119
+ "slide-left": slideLeftVariants,
120
+ "slide-right": slideRightVariants,
121
+ "slide-up": slideUpVariants,
122
+ "slide-down": slideDownVariants,
123
+ zoom: zoomVariants,
124
+ "zoom-fade": zoomFadeVariants,
125
+ morph: morphVariants,
126
+ none: noneVariants
127
+ }
128
+
129
+ // =============================================================================
130
+ // DIRECTIONAL VARIANTS (based on navigation direction)
131
+ // =============================================================================
132
+
133
+ export function createDirectionalVariants(axis: "x" | "y" = "x"): (direction: number) => Variants {
134
+ return (direction: number) => ({
135
+ enter: {
136
+ [axis]: direction > 0 ? SLIDE_DISTANCE : -SLIDE_DISTANCE,
137
+ opacity: 0
138
+ },
139
+ center: {
140
+ [axis]: 0,
141
+ opacity: 1
142
+ },
143
+ exit: {
144
+ [axis]: direction < 0 ? SLIDE_DISTANCE : -SLIDE_DISTANCE,
145
+ opacity: 0
146
+ }
147
+ })
148
+ }
149
+
150
+ /** Horizontal directional slide (left/right based on direction) */
151
+ export const directionalSlideX = createDirectionalVariants("x")
152
+
153
+ /** Vertical directional slide (up/down based on direction) */
154
+ export const directionalSlideY = createDirectionalVariants("y")
155
+
156
+ // =============================================================================
157
+ // HELPER FUNCTIONS
158
+ // =============================================================================
159
+
160
+ export function getSlideVariants(
161
+ config: SlideTransitionConfig | SlideTransitionType,
162
+ direction: number = 1
163
+ ): Variants {
164
+ const type = typeof config === "string" ? config : config.type
165
+ const directional = typeof config === "object" ? config.directional : false
166
+
167
+ if (directional && (type === "slide-left" || type === "slide-right")) {
168
+ return directionalSlideX(direction)
169
+ }
170
+
171
+ if (directional && (type === "slide-up" || type === "slide-down")) {
172
+ return directionalSlideY(direction)
173
+ }
174
+
175
+ return SLIDE_VARIANTS[type]
176
+ }
177
+
178
+ export function getSlideTransition(config?: SlideTransitionConfig | SlideTransitionType): {
179
+ duration: number
180
+ ease: typeof SLIDE_TRANSITION.ease
181
+ } {
182
+ if (!config) return SLIDE_TRANSITION
183
+
184
+ const type = typeof config === "string" ? config : config.type
185
+
186
+ const defaultDuration = type === "morph" ? MORPH_DURATION : SLIDE_TRANSITION_DURATION
187
+
188
+ const duration = typeof config === "object" && config.duration ? config.duration : defaultDuration
189
+
190
+ return {
191
+ ...SLIDE_TRANSITION,
192
+ duration
193
+ }
194
+ }
195
+
196
+ // =============================================================================
197
+ // DEFAULT EXPORT
198
+ // =============================================================================
199
+
200
+ export const DEFAULT_SLIDE_TRANSITION: SlideTransitionType = "fade"
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Shared types for the slide deck framework.
3
+ */
4
+
5
+ import type { SlideTransitionType } from "./transitions"
6
+
7
+ // =============================================================================
8
+ // SLIDE CONFIGURATION TYPES
9
+ // =============================================================================
10
+
11
+ /**
12
+ * Props that all slide components receive.
13
+ */
14
+ export interface SlideProps {
15
+ slideNumber: number
16
+ totalSlides: number
17
+ }
18
+
19
+ /**
20
+ * A React component that renders a slide.
21
+ */
22
+ export type SlideComponent = React.ComponentType<SlideProps>
23
+
24
+ /**
25
+ * Configuration for a single slide including its animation step count.
26
+ * The `steps` property declares how many animation steps the slide has,
27
+ * eliminating the need for runtime step discovery.
28
+ */
29
+ export interface SlideConfig {
30
+ /** The slide component to render */
31
+ component: SlideComponent
32
+ /** Number of animation steps (0 = no animations). This is the max step number used in <Animated step={N}> */
33
+ steps: number
34
+ /** Display name for grid view thumbnails, navigation, and accessibility */
35
+ title?: string
36
+ /** Speaker notes (not rendered on slide, shown in notes panel) */
37
+ notes?: string
38
+ /** Per-slide transition override (falls back to deck-level transition) */
39
+ transition?: SlideTransitionType
40
+ /** Section/chapter name for grouping slides in grid view */
41
+ section?: string
42
+ }
43
+
44
+ // =============================================================================
45
+ // NAVIGATION TYPES
46
+ // =============================================================================
47
+
48
+ /**
49
+ * Direction of slide navigation.
50
+ * - 1: Forward (next slide)
51
+ * - -1: Backward (previous slide)
52
+ * - 0: Direct jump (no direction)
53
+ */
54
+ export type NavigationDirection = -1 | 0 | 1
55
+
56
+ // =============================================================================
57
+ // THEME CONFIGURATION TYPES
58
+ // =============================================================================
59
+
60
+ /**
61
+ * Logo variants for different contexts.
62
+ */
63
+ export interface ThemeLogos {
64
+ /** Full logo (default, used in footers) */
65
+ full?: string
66
+ /** Icon-only mark (used in compact spaces) */
67
+ icon?: string
68
+ /** Light version for dark backgrounds */
69
+ fullLight?: string
70
+ /** Light icon for dark backgrounds */
71
+ iconLight?: string
72
+ }
73
+
74
+ /**
75
+ * Brand color overrides using OKLCH strings.
76
+ * Injected as CSS custom properties at runtime.
77
+ * If omitted, the existing globals.css values apply.
78
+ */
79
+ export interface ThemeColors {
80
+ primary?: string
81
+ primaryForeground?: string
82
+ secondary?: string
83
+ secondaryForeground?: string
84
+ accent?: string
85
+ accentForeground?: string
86
+ }
87
+
88
+ /**
89
+ * Corporate visual assets (paths relative to public/).
90
+ */
91
+ export interface ThemeAssets {
92
+ /** Background image URL (e.g. for title slides) */
93
+ backgroundImage?: string
94
+ /** Subtle pattern overlay URL */
95
+ patternImage?: string
96
+ }
97
+
98
+ /**
99
+ * Typography preferences. Font names must be loaded via
100
+ * <link> in index.html or @font-face in globals.css.
101
+ */
102
+ export interface ThemeFonts {
103
+ heading?: string
104
+ body?: string
105
+ }
106
+
107
+ /**
108
+ * Full theme configuration.
109
+ */
110
+ export interface ThemeConfig {
111
+ /** Company/product name (shown in footers) */
112
+ name: string
113
+ /** Optional tagline */
114
+ tagline?: string
115
+ /** Logo configuration */
116
+ logo?: ThemeLogos
117
+ /** Brand color overrides (OKLCH strings) */
118
+ colors?: ThemeColors
119
+ /** Corporate visual assets */
120
+ assets?: ThemeAssets
121
+ /** Typography preferences */
122
+ fonts?: ThemeFonts
123
+ }
124
+
125
+ // =============================================================================
126
+ // LAYOUT BASE PROPS
127
+ // =============================================================================
128
+
129
+ /**
130
+ * Base props shared by all layout components.
131
+ */
132
+ export interface LayoutBaseProps extends SlideProps {
133
+ children?: React.ReactNode
134
+ hideFooter?: boolean
135
+ className?: string
136
+ }
@@ -0,0 +1,142 @@
1
+ import { useCallback, useEffect, useState } from "react"
2
+
3
+ import type { NavigationDirection, SlideConfig } from "./types"
4
+
5
+ // =============================================================================
6
+ // TYPES
7
+ // =============================================================================
8
+
9
+ type NavigationStatus = "idle" | "transitioning"
10
+
11
+ type QueuedAction = "advance" | "goBack" | null
12
+
13
+ interface NavigationState {
14
+ status: NavigationStatus
15
+ direction: NavigationDirection
16
+ }
17
+
18
+ export interface UseSlideNavigationOptions {
19
+ slides: SlideConfig[]
20
+ initialSlide?: number
21
+ onSlideChange?: (slideIndex: number) => void
22
+ }
23
+
24
+ export interface UseSlideNavigationReturn {
25
+ currentSlide: number
26
+ animationStep: number
27
+ totalSteps: number
28
+ direction: NavigationDirection
29
+ isTransitioning: boolean
30
+ showAllAnimations: boolean
31
+ advance: () => void
32
+ goBack: () => void
33
+ goToSlide: (index: number) => void
34
+ onTransitionComplete: () => void
35
+ }
36
+
37
+ // =============================================================================
38
+ // HOOK
39
+ // =============================================================================
40
+
41
+ export function useSlideNavigation({
42
+ slides,
43
+ initialSlide = 0,
44
+ onSlideChange
45
+ }: UseSlideNavigationOptions): UseSlideNavigationReturn {
46
+ const [currentSlide, setCurrentSlide] = useState(initialSlide)
47
+ const [animationStep, setAnimationStep] = useState(0)
48
+
49
+ const [navState, setNavState] = useState<NavigationState>({
50
+ status: "idle",
51
+ direction: 0
52
+ })
53
+
54
+ const [queuedAction, setQueuedAction] = useState<QueuedAction>(null)
55
+
56
+ const totalSteps = slides[currentSlide]?.steps ?? 0
57
+
58
+ const onTransitionComplete = useCallback(() => {
59
+ setNavState(prev => {
60
+ if (prev.status === "transitioning") {
61
+ return { status: "idle", direction: 0 }
62
+ }
63
+ return prev
64
+ })
65
+ }, [])
66
+
67
+ const advance = useCallback(() => {
68
+ if (navState.status === "transitioning") {
69
+ setQueuedAction("advance")
70
+ return
71
+ }
72
+
73
+ const currentTotalSteps = slides[currentSlide]?.steps ?? 0
74
+
75
+ if (animationStep >= currentTotalSteps) {
76
+ const nextSlide = (currentSlide + 1) % slides.length
77
+ setNavState({ status: "transitioning", direction: 1 })
78
+ setAnimationStep(0)
79
+ setCurrentSlide(nextSlide)
80
+ onSlideChange?.(nextSlide)
81
+ } else {
82
+ setAnimationStep(prev => prev + 1)
83
+ }
84
+ }, [navState.status, animationStep, currentSlide, slides, onSlideChange])
85
+
86
+ const goBack = useCallback(() => {
87
+ if (navState.status === "transitioning") {
88
+ setQueuedAction("goBack")
89
+ return
90
+ }
91
+
92
+ if (animationStep <= 0) {
93
+ const prevSlide = (currentSlide - 1 + slides.length) % slides.length
94
+ const prevSlideSteps = slides[prevSlide]?.steps ?? 0
95
+ setNavState({ status: "transitioning", direction: -1 })
96
+ setAnimationStep(prevSlideSteps)
97
+ setCurrentSlide(prevSlide)
98
+ onSlideChange?.(prevSlide)
99
+ } else {
100
+ setAnimationStep(prev => prev - 1)
101
+ }
102
+ }, [navState.status, animationStep, currentSlide, slides, onSlideChange])
103
+
104
+ const goToSlide = useCallback(
105
+ (index: number) => {
106
+ if (index < 0 || index >= slides.length || index === currentSlide) {
107
+ return
108
+ }
109
+
110
+ const direction = index > currentSlide ? 1 : -1
111
+ setNavState({ status: "transitioning", direction })
112
+ setAnimationStep(0)
113
+ setCurrentSlide(index)
114
+ onSlideChange?.(index)
115
+ },
116
+ [currentSlide, slides.length, onSlideChange]
117
+ )
118
+
119
+ useEffect(() => {
120
+ if (navState.status === "idle" && queuedAction !== null) {
121
+ setQueuedAction(null)
122
+ if (queuedAction === "advance") {
123
+ advance()
124
+ } else if (queuedAction === "goBack") {
125
+ goBack()
126
+ }
127
+ }
128
+ }, [navState.status, queuedAction, advance, goBack])
129
+
130
+ return {
131
+ currentSlide,
132
+ animationStep,
133
+ totalSteps,
134
+ direction: navState.direction,
135
+ isTransitioning: navState.status !== "idle",
136
+ showAllAnimations: navState.direction === -1 && navState.status === "transitioning",
137
+ advance,
138
+ goBack,
139
+ goToSlide,
140
+ onTransitionComplete
141
+ }
142
+ }
@@ -0,0 +1,8 @@
1
+ import type { ClassValue } from "clsx"
2
+
3
+ import { clsx } from "clsx"
4
+ import { twMerge } from "tailwind-merge"
5
+
6
+ export function cn(...inputs: ClassValue[]) {
7
+ return twMerge(clsx(inputs))
8
+ }
package/src/index.mjs ADDED
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync } from "node:fs"
4
+ import { join, dirname } from "node:path"
5
+ import { fileURLToPath } from "node:url"
6
+
7
+ import { bold, dim, red } from "./utils/ansi.mjs"
8
+
9
+ const __filename = fileURLToPath(import.meta.url)
10
+ const __dirname = dirname(__filename)
11
+
12
+ function getVersion() {
13
+ const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"))
14
+ return pkg.version
15
+ }
16
+
17
+ function printHelp() {
18
+ console.log()
19
+ console.log(` ${bold("promptslide")} ${dim(`v${getVersion()}`)}`)
20
+ console.log()
21
+ console.log(` ${bold("Commands:")}`)
22
+ console.log(` create ${dim("<dir>")} Scaffold a new slide deck project`)
23
+ console.log(` studio Start the development studio`)
24
+ console.log(` build Build for production`)
25
+ console.log(` preview Preview the production build`)
26
+ console.log()
27
+ console.log(` ${bold("Options:")}`)
28
+ console.log(` --help, -h Show this help message`)
29
+ console.log(` --version, -v Show version number`)
30
+ console.log()
31
+ }
32
+
33
+ const command = process.argv[2]
34
+ const args = process.argv.slice(3)
35
+
36
+ switch (command) {
37
+ case "create": {
38
+ const { create } = await import("./commands/create.mjs")
39
+ await create(args)
40
+ break
41
+ }
42
+ case "studio": {
43
+ const { studio } = await import("./commands/studio.mjs")
44
+ await studio(args)
45
+ break
46
+ }
47
+ case "build": {
48
+ const { build } = await import("./commands/build.mjs")
49
+ await build(args)
50
+ break
51
+ }
52
+ case "preview": {
53
+ const { preview } = await import("./commands/preview.mjs")
54
+ await preview(args)
55
+ break
56
+ }
57
+ case "--help":
58
+ case "-h":
59
+ case undefined:
60
+ printHelp()
61
+ break
62
+ case "--version":
63
+ case "-v":
64
+ console.log(getVersion())
65
+ break
66
+ default:
67
+ console.error(` ${red("Error:")} Unknown command "${command}"`)
68
+ printHelp()
69
+ process.exit(1)
70
+ }
@@ -0,0 +1,5 @@
1
+ export const bold = s => `\x1b[1m${s}\x1b[0m`
2
+ export const green = s => `\x1b[32m${s}\x1b[0m`
3
+ export const cyan = s => `\x1b[36m${s}\x1b[0m`
4
+ export const red = s => `\x1b[31m${s}\x1b[0m`
5
+ export const dim = s => `\x1b[2m${s}\x1b[0m`
@@ -0,0 +1,44 @@
1
+ export function hexToOklch(hex) {
2
+ hex = hex.replace("#", "")
3
+ const r = parseInt(hex.slice(0, 2), 16) / 255
4
+ const g = parseInt(hex.slice(2, 4), 16) / 255
5
+ const b = parseInt(hex.slice(4, 6), 16) / 255
6
+
7
+ // sRGB → linear RGB
8
+ const toLinear = c => (c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4))
9
+ const lr = toLinear(r)
10
+ const lg = toLinear(g)
11
+ const lb = toLinear(b)
12
+
13
+ // Linear RGB → LMS
14
+ const l = 0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb
15
+ const m = 0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb
16
+ const s = 0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb
17
+
18
+ // LMS → OKLAB
19
+ const l_ = Math.cbrt(l)
20
+ const m_ = Math.cbrt(m)
21
+ const s_ = Math.cbrt(s)
22
+ const L = 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_
23
+ const a = 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_
24
+ const bv = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_
25
+
26
+ // OKLAB → OKLCH
27
+ const C = Math.sqrt(a * a + bv * bv)
28
+ let H = Math.atan2(bv, a) * (180 / Math.PI)
29
+ if (H < 0) H += 360
30
+
31
+ const round = (n, d = 3) => +n.toFixed(d)
32
+ return `oklch(${round(L)} ${round(C)} ${round(H)})`
33
+ }
34
+
35
+ export function hexToOklchDark(hex) {
36
+ const oklch = hexToOklch(hex)
37
+ const match = oklch.match(/oklch\(([\d.]+) ([\d.]+) ([\d.]+)\)/)
38
+ const L = Math.min(1, parseFloat(match[1]) + 0.05)
39
+ return `oklch(${+L.toFixed(3)} ${match[2]} ${match[3]})`
40
+ }
41
+
42
+ export function isValidHex(hex) {
43
+ return /^#?[0-9a-fA-F]{6}$/.test(hex)
44
+ }
@@ -0,0 +1,50 @@
1
+ import { createInterface } from "node:readline"
2
+
3
+ import { dim } from "./ansi.mjs"
4
+
5
+ let _rl = null
6
+ let _closed = false
7
+
8
+ function getRL() {
9
+ if (!_rl && !_closed) {
10
+ _rl = createInterface({ input: process.stdin, output: process.stdout })
11
+ _rl.on("close", () => {
12
+ _rl = null
13
+ _closed = true
14
+ })
15
+ }
16
+ return _rl
17
+ }
18
+
19
+ export function closePrompts() {
20
+ if (_rl) {
21
+ _rl.close()
22
+ _rl = null
23
+ }
24
+ }
25
+
26
+ export function prompt(question, defaultValue) {
27
+ const rl = getRL()
28
+ if (!rl) return Promise.resolve(defaultValue || "")
29
+ return new Promise(resolve => {
30
+ const suffix = defaultValue ? ` ${dim(`(${defaultValue})`)} ` : " "
31
+ rl.question(` ${question}${suffix}`, answer => {
32
+ resolve(answer.trim() || defaultValue || "")
33
+ })
34
+ rl.once("close", () => resolve(defaultValue || ""))
35
+ })
36
+ }
37
+
38
+ export function confirm(question, defaultYes = true) {
39
+ const rl = getRL()
40
+ if (!rl) return Promise.resolve(defaultYes)
41
+ return new Promise(resolve => {
42
+ const hint = defaultYes ? "Y/n" : "y/N"
43
+ rl.question(` ${question} ${dim(`(${hint})`)} `, answer => {
44
+ const a = answer.trim().toLowerCase()
45
+ if (!a) return resolve(defaultYes)
46
+ resolve(a === "y" || a === "yes")
47
+ })
48
+ rl.once("close", () => resolve(defaultYes))
49
+ })
50
+ }
@@ -0,0 +1,35 @@
1
+ import { writeFileSync } from "node:fs"
2
+ import { join } from "node:path"
3
+
4
+ export function ensureTsConfig(cwd) {
5
+ const tsconfigPath = join(cwd, "tsconfig.json")
6
+
7
+ const tsconfig = {
8
+ compilerOptions: {
9
+ target: "ES2020",
10
+ useDefineForClassFields: true,
11
+ lib: ["ES2020", "DOM", "DOM.Iterable"],
12
+ module: "ESNext",
13
+ skipLibCheck: true,
14
+ moduleResolution: "bundler",
15
+ allowImportingTsExtensions: true,
16
+ isolatedModules: true,
17
+ moduleDetection: "force",
18
+ noEmit: true,
19
+ jsx: "react-jsx",
20
+ strict: true,
21
+ noUnusedLocals: true,
22
+ noUnusedParameters: true,
23
+ noFallthroughCasesInSwitch: true,
24
+ noUncheckedIndexedAccess: true,
25
+ baseUrl: ".",
26
+ paths: {
27
+ "@/*": ["./src/*"],
28
+ "promptslide": ["./node_modules/promptslide/src/core/index.ts"]
29
+ }
30
+ },
31
+ include: ["src"]
32
+ }
33
+
34
+ writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2) + "\n")
35
+ }