@open-press/create 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 (3) hide show
  1. package/README.md +13 -0
  2. package/dist/index.js +692 -0
  3. package/package.json +56 -0
package/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # @open-press/create
2
+
3
+ Bootstrap a new OpenPress workspace.
4
+
5
+ ```bash
6
+ npm create @open-press my-deck -- --type slides
7
+ cd my-deck
8
+ npm run dev
9
+ ```
10
+
11
+ This package writes the workspace `package.json`, `.gitignore`, and a minimal folder-per-slide Press under `press/<name>/`. It can install dependencies, sync OpenPress skills, and initialize git unless those steps are skipped with flags. The scaffold uses the OpenPress 2.0 slides folder contract and Tailwind-ready runtime.
12
+
13
+ Page-based scaffolding is intentionally handled by the OpenPress skills layer. The create package keeps the installable workspace bootstrap small and delegates richer document structure to agents that can read the current skills.
package/dist/index.js ADDED
@@ -0,0 +1,692 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import path3 from "path";
5
+ import process2 from "process";
6
+ import prompts from "prompts";
7
+
8
+ // src/slides-template.ts
9
+ import { mkdir, writeFile } from "fs/promises";
10
+ import path from "path";
11
+ async function writeSlidesPress(pressRoot, opts) {
12
+ const { pressName, title } = opts;
13
+ const folder = folderName(pressName);
14
+ const component = componentName(folder);
15
+ const escapedTitle = escapeJsxAttr(title);
16
+ await mkdir(path.join(pressRoot, "slides", "intro"), { recursive: true });
17
+ await mkdir(path.join(pressRoot, "components"), { recursive: true });
18
+ await mkdir(path.join(pressRoot, "layouts"), { recursive: true });
19
+ await mkdir(path.join(pressRoot, "themes"), { recursive: true });
20
+ await writeFile(
21
+ path.join(pressRoot, "press.tsx"),
22
+ `import { Press, Slide } from "@open-press/core";
23
+
24
+ export default function ${component}Press() {
25
+ return (
26
+ <Press
27
+ slug="${folder}"
28
+ title="${escapedTitle}"
29
+ type="slides"
30
+ page="slide-16-9"
31
+ componentsDir="./components"
32
+ >
33
+ <Slide id="intro" />
34
+ </Press>
35
+ );
36
+ }
37
+ `,
38
+ "utf8"
39
+ );
40
+ await writeFile(
41
+ path.join(pressRoot, "components", "DeckSlide.tsx"),
42
+ `import { PageFolio, Slide } from "@open-press/core";
43
+ import type { ReactNode } from "react";
44
+
45
+ export type DeckSlideVariant = "cover" | "agenda" | "content" | "process" | "closing";
46
+
47
+ const SLIDE_PAGE_CLASS = "op-slide-page bg-bg text-text [font-family:var(--font-body)]";
48
+ const SLIDE_SHELL_CLASS = "op-slide-shell relative h-full w-full overflow-hidden bg-bg";
49
+ const CHROME_HEADER_CLASS =
50
+ "op-slide-chrome-header absolute left-0 right-0 top-0 z-10 grid h-[66px] items-center border-b border-border bg-bg/95 pl-op-sm text-op-caption text-text-muted [grid-template-columns:minmax(0,1fr)_1px_218px]";
51
+ const CHROME_RULE_CLASS = "op-slide-chrome-rule h-[42px] w-px bg-border";
52
+ const WORDMARK_CLASS =
53
+ "op-slide-wordmark justify-self-center font-mono text-op-caption font-bold text-text";
54
+ const SLIDE_MAIN_CLASS = "op-slide-main absolute left-0 right-0 z-[1] top-[66px] bottom-[64px]";
55
+ const CHROME_FOOTER_CLASS =
56
+ "op-slide-chrome-footer absolute bottom-0 left-0 right-0 z-10 grid h-[64px] items-center border-t border-border bg-bg/95 pl-op-sm text-op-caption text-text-muted [grid-template-columns:minmax(0,1fr)_62px]";
57
+ const FOOTER_LABEL_CLASS =
58
+ "op-slide-footer-label overflow-hidden text-ellipsis whitespace-nowrap text-op-caption text-text-muted";
59
+ const FOOTER_NUMBER_CLASS =
60
+ "op-slide-footer-number grid h-[64px] place-items-center border-l border-border text-op-caption text-text-muted [font-variant-numeric:tabular-nums]";
61
+
62
+ // Edit this file to customise the chrome for your deck:
63
+ // - Change the header title / wordmark
64
+ // - Swap the footer label
65
+ // - Adjust header/footer height constants above
66
+ export function DeckSlide({
67
+ id,
68
+ variant = "content",
69
+ children,
70
+ }: {
71
+ id: string;
72
+ variant?: DeckSlideVariant;
73
+ children: ReactNode;
74
+ }) {
75
+ return (
76
+ <Slide id={id} className={SLIDE_PAGE_CLASS}>
77
+ <div className={SLIDE_SHELL_CLASS} data-variant={variant}>
78
+ <header className={CHROME_HEADER_CLASS}>
79
+ <span>${escapedTitle}</span>
80
+ <span className={CHROME_RULE_CLASS} />
81
+ <span className={WORDMARK_CLASS}>open-press</span>
82
+ </header>
83
+ <main className={SLIDE_MAIN_CLASS}>{children}</main>
84
+ <footer className={CHROME_FOOTER_CLASS}>
85
+ <span className={FOOTER_LABEL_CLASS}>${escapedTitle}</span>
86
+ <PageFolio currentFormat="plain" className={FOOTER_NUMBER_CLASS} />
87
+ </footer>
88
+ </div>
89
+ </Slide>
90
+ );
91
+ }
92
+
93
+ export default DeckSlide;
94
+ `,
95
+ "utf8"
96
+ );
97
+ await writeFile(
98
+ path.join(pressRoot, "layouts", "SlideProtocol.tsx"),
99
+ SLIDE_PROTOCOL_SOURCE,
100
+ "utf8"
101
+ );
102
+ await writeFile(
103
+ path.join(pressRoot, "slides", "intro", "slide.tsx"),
104
+ `import type { SlideMeta } from "@open-press/core";
105
+ import { BlankSlide } from "../layouts/SlideProtocol";
106
+
107
+ export const meta = {
108
+ layout: "blank",
109
+ description: "Intro slide \u2014 replace with TitleSlide or another layout.",
110
+ } satisfies SlideMeta;
111
+
112
+ export const notes = "First slide. Edit this file to get started.";
113
+
114
+ export default function Slide() {
115
+ return (
116
+ <BlankSlide id="intro">
117
+ <BlankSlide.Kicker>${escapedTitle}</BlankSlide.Kicker>
118
+ <BlankSlide.Title>Start here.</BlankSlide.Title>
119
+ <BlankSlide.Body>Replace this with your content.</BlankSlide.Body>
120
+ </BlankSlide>
121
+ );
122
+ }
123
+ `,
124
+ "utf8"
125
+ );
126
+ await writeFile(path.join(pressRoot, "themes", "default.css"), `/* ${folder} theme */
127
+ `, "utf8");
128
+ }
129
+ var SLIDE_PROTOCOL_SOURCE = `import { Text, type TextProps } from "@open-press/core";
130
+ import type { ComponentPropsWithoutRef, ReactNode } from "react";
131
+ import { DeckSlide, type DeckSlideVariant } from "../components/DeckSlide";
132
+
133
+ type SlideRootProps = {
134
+ id: string;
135
+ children: ReactNode;
136
+ } & Omit<ComponentPropsWithoutRef<"section">, "id" | "children">;
137
+
138
+ type SlotProps = TextProps & { children?: ReactNode };
139
+ type BoxEl = "div" | "ol" | "li" | "figure" | "figcaption" | "article";
140
+ type BoxProps<T extends BoxEl = "div"> = ComponentPropsWithoutRef<T> & { children?: ReactNode };
141
+
142
+ function cx(...parts: Array<string | false | null | undefined>) {
143
+ return parts.filter(Boolean).join(" ");
144
+ }
145
+
146
+ function SlotText({ as = "p", className, children, ...rest }: SlotProps) {
147
+ return <Text {...rest} as={as} className={className}>{children}</Text>;
148
+ }
149
+
150
+ function ProtocolSlide({ id, variant, children }: SlideRootProps & { variant: DeckSlideVariant }) {
151
+ return <DeckSlide id={id} variant={variant}>{children}</DeckSlide>;
152
+ }
153
+
154
+ // \u2500\u2500\u2500 TitleSlide \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
155
+
156
+ function TitleSlideRoot({ id, className, children, ...rest }: SlideRootProps) {
157
+ return (
158
+ <ProtocolSlide id={id} variant="cover">
159
+ <section {...rest} className={cx("op-slide-title-layout grid h-full items-end gap-op-xl px-op-xl pb-op-xl pt-op-lg [grid-template-columns:minmax(0,1fr)_500px]", className)}>
160
+ {children}
161
+ </section>
162
+ </ProtocolSlide>
163
+ );
164
+ }
165
+
166
+ function TitleSlideContent({ className, children, ...rest }: BoxProps) {
167
+ return <div {...rest} className={cx("op-slide-title-copy max-w-[960px] pb-op-xs", className)}>{children}</div>;
168
+ }
169
+
170
+ function TitleSlideKicker({ className, children, ...rest }: SlotProps) {
171
+ return <SlotText {...rest} as="p" className={cx("op-kicker mb-op-sm", className)}>{children}</SlotText>;
172
+ }
173
+
174
+ function TitleSlideTitle({ className, children, ...rest }: SlotProps) {
175
+ return <SlotText {...rest} as="h1" className={cx("op-display max-w-[960px]", className)}>{children}</SlotText>;
176
+ }
177
+
178
+ function TitleSlideSubtitle({ className, children, ...rest }: SlotProps) {
179
+ return <SlotText {...rest} as="p" className={cx("op-lead mt-op-sm max-w-[900px] font-semibold", className)}>{children}</SlotText>;
180
+ }
181
+
182
+ function TitleSlideMedia({ className, children, ...rest }: BoxProps<"figure">) {
183
+ return <figure {...rest} className={cx("op-slide-title-media relative h-[660px] w-[500px] self-center overflow-hidden rounded-op-panel border border-border bg-surface-muted shadow-op-card", className)}>{children}</figure>;
184
+ }
185
+
186
+ function TitleSlideImage({ className, ...rest }: ComponentPropsWithoutRef<"img">) {
187
+ return <img {...rest} className={cx("h-full w-full object-cover object-[58%_50%]", className)} />;
188
+ }
189
+
190
+ function TitleSlideMediaCaption({ className, children, ...rest }: BoxProps<"figcaption">) {
191
+ return <figcaption {...rest} className={cx("absolute bottom-op-sm left-op-sm inline-flex items-center rounded-op-pill border border-text-muted bg-surface-inverse px-op-sm py-op-xs text-op-caption font-medium text-text-inverse", className)}>{children}</figcaption>;
192
+ }
193
+
194
+ // \u2500\u2500\u2500 StatementSlide \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
195
+
196
+ function StatementSlideRoot({ id, className, children, ...rest }: SlideRootProps) {
197
+ return (
198
+ <ProtocolSlide id={id} variant="closing">
199
+ <section {...rest} className={cx("op-slide-statement-layout grid h-full content-center gap-op-xl px-op-lg py-op-lg [grid-template-columns:minmax(0,1fr)_520px]", className)}>
200
+ {children}
201
+ </section>
202
+ </ProtocolSlide>
203
+ );
204
+ }
205
+
206
+ function StatementSlideKicker({ className, children, ...rest }: SlotProps) {
207
+ return <SlotText {...rest} as="p" className={cx("op-kicker col-span-2", className)}>{children}</SlotText>;
208
+ }
209
+
210
+ function StatementSlideStatement({ className, children, ...rest }: SlotProps) {
211
+ return <SlotText {...rest} as="h2" className={cx("op-section max-w-[850px]", className)}>{children}</SlotText>;
212
+ }
213
+
214
+ function StatementSlideSupport({ className, children, ...rest }: BoxProps) {
215
+ return <div {...rest} className={cx("op-card-muted grid self-center gap-op-sm", className)}>{children}</div>;
216
+ }
217
+
218
+ function StatementSlideSupportText({ className, children, ...rest }: SlotProps) {
219
+ return <SlotText {...rest} as="p" className={cx("op-body font-bold", className)}>{children}</SlotText>;
220
+ }
221
+
222
+ // \u2500\u2500\u2500 BlankSlide \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
223
+
224
+ function BlankSlideRoot({ id, className, children, ...rest }: SlideRootProps) {
225
+ return (
226
+ <ProtocolSlide id={id} variant="content">
227
+ <section {...rest} className={cx("op-slide-blank-layout grid h-full place-items-center bg-bg px-op-xl py-op-xl text-center text-text", className)}>
228
+ <div className="op-slide-blank-copy max-w-[900px]">{children}</div>
229
+ </section>
230
+ </ProtocolSlide>
231
+ );
232
+ }
233
+
234
+ function BlankSlideKicker({ className, children, ...rest }: SlotProps) {
235
+ return <SlotText {...rest} as="p" className={cx("op-kicker mb-op-sm", className)}>{children}</SlotText>;
236
+ }
237
+
238
+ function BlankSlideTitle({ className, children, ...rest }: SlotProps) {
239
+ return <SlotText {...rest} as="h1" className={cx("op-display", className)}>{children}</SlotText>;
240
+ }
241
+
242
+ function BlankSlideBody({ className, children, ...rest }: SlotProps) {
243
+ return <SlotText {...rest} as="p" className={cx("op-lead mt-op-sm", className)}>{children}</SlotText>;
244
+ }
245
+
246
+ // \u2500\u2500\u2500 TwoColumnSlide \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
247
+
248
+ function TwoColumnSlideRoot({ id, className, children, ...rest }: SlideRootProps) {
249
+ return (
250
+ <ProtocolSlide id={id} variant="agenda">
251
+ <section {...rest} className={cx("op-slide-two-column-layout grid h-full items-start gap-op-xl px-op-xl py-op-xl [grid-template-columns:470px_minmax(0,1fr)]", className)}>
252
+ {children}
253
+ </section>
254
+ </ProtocolSlide>
255
+ );
256
+ }
257
+
258
+ function TwoColumnSlideKicker({ className, children, ...rest }: SlotProps) {
259
+ return <SlotText {...rest} as="p" className={cx("op-kicker mb-op-sm", className)}>{children}</SlotText>;
260
+ }
261
+
262
+ function TwoColumnSlideTitle({ className, children, ...rest }: SlotProps) {
263
+ return <SlotText {...rest} as="h2" className={cx("op-section", className)}>{children}</SlotText>;
264
+ }
265
+
266
+ function TwoColumnSlideLeft({ className, children, ...rest }: BoxProps) {
267
+ return <div {...rest} className={cx("min-w-0", className)}>{children}</div>;
268
+ }
269
+
270
+ function TwoColumnSlideRight({ className, children, ...rest }: BoxProps) {
271
+ return <div {...rest} className={cx("min-w-0", className)}>{children}</div>;
272
+ }
273
+
274
+ function TwoColumnSlideList({ className, children, ...rest }: BoxProps<"ol">) {
275
+ return <ol {...rest} className={cx("m-0 grid list-none gap-op-sm p-0", className)}>{children}</ol>;
276
+ }
277
+
278
+ function TwoColumnSlideItem({ className, children, ...rest }: BoxProps<"li">) {
279
+ return <li {...rest} className={cx("grid gap-op-sm border-t border-border py-op-sm [grid-template-columns:96px_minmax(0,1fr)]", className)}>{children}</li>;
280
+ }
281
+
282
+ function TwoColumnSlideItemNumber({ className, children, ...rest }: SlotProps) {
283
+ return <SlotText {...rest} as="span" className={cx("font-heading text-op-title text-accent", className)}>{children}</SlotText>;
284
+ }
285
+
286
+ function TwoColumnSlideItemCopy({ className, children, ...rest }: BoxProps) {
287
+ return <div {...rest} className={className}>{children}</div>;
288
+ }
289
+
290
+ function TwoColumnSlideItemTitle({ className, children, ...rest }: SlotProps) {
291
+ return <SlotText {...rest} as="h3" className={cx("op-lead font-bold text-text", className)}>{children}</SlotText>;
292
+ }
293
+
294
+ function TwoColumnSlideItemBody({ className, children, ...rest }: SlotProps) {
295
+ return <SlotText {...rest} as="p" className={cx("op-body mt-op-xs", className)}>{children}</SlotText>;
296
+ }
297
+
298
+ // \u2500\u2500\u2500 CardGridSlide \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
299
+
300
+ function CardGridSlideRoot({ id, className, children, ...rest }: SlideRootProps) {
301
+ return (
302
+ <ProtocolSlide id={id} variant="content">
303
+ <section {...rest} className={cx("h-full px-op-xl py-op-xl", className)}>{children}</section>
304
+ </ProtocolSlide>
305
+ );
306
+ }
307
+
308
+ function CardGridSlideHeading({ className, children, ...rest }: BoxProps) {
309
+ return <div {...rest} className={cx("max-w-[1120px]", className)}>{children}</div>;
310
+ }
311
+
312
+ function CardGridSlideKicker({ className, children, ...rest }: SlotProps) {
313
+ return <SlotText {...rest} as="p" className={cx("op-kicker mb-op-sm", className)}>{children}</SlotText>;
314
+ }
315
+
316
+ function CardGridSlideTitle({ className, children, ...rest }: SlotProps) {
317
+ return <SlotText {...rest} as="h2" className={cx("op-section", className)}>{children}</SlotText>;
318
+ }
319
+
320
+ function CardGridSlideGrid({ className, children, ...rest }: BoxProps) {
321
+ return <div {...rest} className={cx("mt-op-lg grid grid-cols-3 gap-op-sm", className)}>{children}</div>;
322
+ }
323
+
324
+ function CardGridSlideCard({ className, children, ...rest }: BoxProps<"article">) {
325
+ return <article {...rest} className={cx("op-card-muted min-h-[230px] border-t-4 border-t-text", className)}>{children}</article>;
326
+ }
327
+
328
+ function CardGridSlideLabel({ className, children, ...rest }: SlotProps) {
329
+ return <SlotText {...rest} as="span" className={cx("op-kicker mb-op-sm block", className)}>{children}</SlotText>;
330
+ }
331
+
332
+ function CardGridSlideCardTitle({ className, children, ...rest }: SlotProps) {
333
+ return <SlotText {...rest} as="h3" className={cx("op-lead font-bold text-text", className)}>{children}</SlotText>;
334
+ }
335
+
336
+ function CardGridSlideCardBody({ className, children, ...rest }: SlotProps) {
337
+ return <SlotText {...rest} as="p" className={cx("op-body mt-op-xs", className)}>{children}</SlotText>;
338
+ }
339
+
340
+ // \u2500\u2500\u2500 ProcessSlide \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
341
+
342
+ function ProcessSlideRoot({ id, className, children, ...rest }: SlideRootProps) {
343
+ return (
344
+ <ProtocolSlide id={id} variant="process">
345
+ <section {...rest} className={cx("grid h-full gap-op-lg px-op-lg py-op-lg [grid-template-rows:auto_minmax(0,1fr)]", className)}>
346
+ {children}
347
+ </section>
348
+ </ProtocolSlide>
349
+ );
350
+ }
351
+
352
+ function ProcessSlideHeading({ className, children, ...rest }: BoxProps) {
353
+ return <div {...rest} className={cx("max-w-[980px]", className)}>{children}</div>;
354
+ }
355
+
356
+ function ProcessSlideKicker({ className, children, ...rest }: SlotProps) {
357
+ return <SlotText {...rest} as="p" className={cx("op-kicker mb-op-sm", className)}>{children}</SlotText>;
358
+ }
359
+
360
+ function ProcessSlideTitle({ className, children, ...rest }: SlotProps) {
361
+ return <SlotText {...rest} as="h2" className={cx("op-section", className)}>{children}</SlotText>;
362
+ }
363
+
364
+ function ProcessSlideMap({ className, children, ...rest }: BoxProps) {
365
+ return <div {...rest} className={cx("grid grid-cols-4 items-start gap-op-sm bg-surface-muted p-op-lg", className)}>{children}</div>;
366
+ }
367
+
368
+ function ProcessSlideStep({ className, children, ...rest }: BoxProps<"article">) {
369
+ return <article {...rest} className={cx("min-h-[286px] rounded-op-card border border-border bg-surface-muted p-op-sm", className)}>{children}</article>;
370
+ }
371
+
372
+ function ProcessSlideStepNumber({ className, children, ...rest }: SlotProps) {
373
+ return <SlotText {...rest} as="span" className={cx("op-title text-accent italic", className)}>{children}</SlotText>;
374
+ }
375
+
376
+ function ProcessSlideStepTitle({ className, children, ...rest }: SlotProps) {
377
+ return <SlotText {...rest} as="h3" className={cx("op-lead mt-op-sm font-bold text-text", className)}>{children}</SlotText>;
378
+ }
379
+
380
+ function ProcessSlideStepBody({ className, children, ...rest }: SlotProps) {
381
+ return <SlotText {...rest} as="p" className={cx("op-body mt-op-xs", className)}>{children}</SlotText>;
382
+ }
383
+
384
+ // \u2500\u2500\u2500 Exports \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
385
+
386
+ export const TitleSlide = Object.assign(TitleSlideRoot, {
387
+ Content: TitleSlideContent, Image: TitleSlideImage, Kicker: TitleSlideKicker,
388
+ Media: TitleSlideMedia, MediaCaption: TitleSlideMediaCaption,
389
+ Subtitle: TitleSlideSubtitle, Title: TitleSlideTitle,
390
+ });
391
+
392
+ export const StatementSlide = Object.assign(StatementSlideRoot, {
393
+ Kicker: StatementSlideKicker, Statement: StatementSlideStatement,
394
+ Support: StatementSlideSupport, SupportText: StatementSlideSupportText,
395
+ });
396
+
397
+ export const BlankSlide = Object.assign(BlankSlideRoot, {
398
+ Body: BlankSlideBody, Kicker: BlankSlideKicker, Title: BlankSlideTitle,
399
+ });
400
+
401
+ export const TwoColumnSlide = Object.assign(TwoColumnSlideRoot, {
402
+ Item: TwoColumnSlideItem, ItemBody: TwoColumnSlideItemBody,
403
+ ItemCopy: TwoColumnSlideItemCopy, ItemNumber: TwoColumnSlideItemNumber,
404
+ ItemTitle: TwoColumnSlideItemTitle, Kicker: TwoColumnSlideKicker,
405
+ Left: TwoColumnSlideLeft, List: TwoColumnSlideList,
406
+ Right: TwoColumnSlideRight, Title: TwoColumnSlideTitle,
407
+ });
408
+
409
+ export const CardGridSlide = Object.assign(CardGridSlideRoot, {
410
+ Body: CardGridSlideCardBody, Card: CardGridSlideCard,
411
+ CardTitle: CardGridSlideCardTitle, Grid: CardGridSlideGrid,
412
+ Heading: CardGridSlideHeading, Kicker: CardGridSlideKicker,
413
+ Label: CardGridSlideLabel, Title: CardGridSlideTitle,
414
+ });
415
+
416
+ export const ProcessSlide = Object.assign(ProcessSlideRoot, {
417
+ Body: ProcessSlideStepBody, Heading: ProcessSlideHeading,
418
+ Kicker: ProcessSlideKicker, Map: ProcessSlideMap,
419
+ Step: ProcessSlideStep, StepNumber: ProcessSlideStepNumber,
420
+ StepTitle: ProcessSlideStepTitle, Title: ProcessSlideTitle,
421
+ });
422
+ `;
423
+ function folderName(name) {
424
+ const base = path.basename(name);
425
+ return base.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "press";
426
+ }
427
+ function componentName(folder) {
428
+ return folder.split(/[-_]+/).filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join("") || "OpenPress";
429
+ }
430
+ function escapeJsxAttr(value) {
431
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
432
+ }
433
+
434
+ // src/workspace.ts
435
+ import { spawn } from "child_process";
436
+ import { existsSync } from "fs";
437
+ import { mkdir as mkdir2, readFile, writeFile as writeFile2 } from "fs/promises";
438
+ import path2 from "path";
439
+ import process from "process";
440
+ import { fileURLToPath } from "url";
441
+
442
+ // src/path-is-empty.ts
443
+ import { readdir, stat } from "fs/promises";
444
+ var HARMLESS_TARGET_ENTRIES = /* @__PURE__ */ new Set([".git", ".gitignore", ".gitkeep", ".DS_Store"]);
445
+ async function pathIsEmpty(target, options = {}) {
446
+ try {
447
+ const s = await stat(target);
448
+ if (!s.isDirectory()) return false;
449
+ const entries = await readdir(target);
450
+ if (!options.ignoreHarmless) return entries.length === 0;
451
+ return entries.every((entry) => HARMLESS_TARGET_ENTRIES.has(entry));
452
+ } catch {
453
+ return true;
454
+ }
455
+ }
456
+
457
+ // src/workspace.ts
458
+ var __dirname = path2.dirname(fileURLToPath(import.meta.url));
459
+ var OWN_PACKAGE_JSON = path2.resolve(__dirname, "..", "package.json");
460
+ async function ensureTarget(target) {
461
+ if (existsSync(target)) {
462
+ const empty = await pathIsEmpty(target, { ignoreHarmless: true });
463
+ if (!empty) {
464
+ throw new Error(
465
+ `Target ${target} is not empty. Remove existing files first, or scaffold into a different directory.`
466
+ );
467
+ }
468
+ return;
469
+ }
470
+ await mkdir2(target, { recursive: true });
471
+ }
472
+ async function writeWorkspaceFiles(target, workspaceName) {
473
+ const version = await readOwnVersion();
474
+ await writeWorkspacePackageJson(target, workspaceName, version);
475
+ await writeWorkspaceGitignore(target);
476
+ await writeWorkspaceDesignDoc(target, workspaceName);
477
+ }
478
+ async function readOwnVersion() {
479
+ const pkg = JSON.parse(await readFile(OWN_PACKAGE_JSON, "utf8"));
480
+ return typeof pkg.version === "string" && pkg.version ? pkg.version : "latest";
481
+ }
482
+ async function writeWorkspacePackageJson(target, workspaceName, version) {
483
+ const pkg = {
484
+ name: workspaceName,
485
+ version: "0.0.0",
486
+ private: true,
487
+ type: "module",
488
+ description: `open-press workspace: ${workspaceName}`,
489
+ scripts: {
490
+ dev: "open-press dev . --renderer react",
491
+ build: "open-press render . --renderer react",
492
+ preview: "open-press preview . --renderer react",
493
+ typecheck: "open-press typecheck .",
494
+ "openpress:image": "open-press image .",
495
+ "openpress:pdf": "open-press pdf .",
496
+ "openpress:deploy": "open-press deploy .",
497
+ "openpress:deploy:dry-run": "open-press deploy . --confirm --dry-run",
498
+ "openpress:skills": "open-press skills:sync"
499
+ },
500
+ dependencies: {
501
+ "@open-press/core": version
502
+ },
503
+ devDependencies: {
504
+ "@open-press/cli": version,
505
+ "@types/node": "^25.8.0",
506
+ "@types/react": "^19.2.14",
507
+ "@types/react-dom": "^19.2.3",
508
+ typescript: "^6.0.3"
509
+ },
510
+ openpress: {
511
+ pdf: { filename: "document.pdf" },
512
+ deploy: {
513
+ adapter: "cloudflare-pages",
514
+ source: ".deploy/openpress",
515
+ projectName: null,
516
+ commitDirty: false,
517
+ requiresConfirmation: true
518
+ }
519
+ }
520
+ };
521
+ await writeFile2(path2.join(target, "package.json"), `${JSON.stringify(pkg, null, 2)}
522
+ `, "utf8");
523
+ }
524
+ async function writeWorkspaceGitignore(target) {
525
+ const content = [
526
+ "node_modules/",
527
+ ".DS_Store",
528
+ "*.log",
529
+ "",
530
+ "# OpenPress generated artifacts",
531
+ ".openpress/",
532
+ ".deploy/",
533
+ ".turbo/",
534
+ "dist/",
535
+ "dist-react/",
536
+ "public/openpress/",
537
+ "output/",
538
+ ""
539
+ ].join("\n");
540
+ await writeFile2(path2.join(target, ".gitignore"), content, "utf8");
541
+ }
542
+ async function writeWorkspaceDesignDoc(target, workspaceName) {
543
+ const pressRoot = path2.join(target, "press");
544
+ await mkdir2(pressRoot, { recursive: true });
545
+ const content = `# ${workspaceName} design
546
+
547
+ This workspace uses source-based slide authoring.
548
+
549
+ - Keep \`press.tsx\` as the ordered index of self-closing \`<Slide id />\` markers.
550
+ - Put slide content in \`press/${workspaceName}/slides/<id>/slide.tsx\`.
551
+ - Put reusable slide UI in \`press/${workspaceName}/components/\` or \`press/shared/\`.
552
+ - Update this file when visual rules, layout conventions, or agent constraints change.
553
+ `;
554
+ await writeFile2(path2.join(pressRoot, "design.md"), content, "utf8");
555
+ }
556
+ async function runInTarget(cwd, command, args, opts = {}) {
557
+ return new Promise((resolve, reject) => {
558
+ const child = spawn(command, args, {
559
+ cwd,
560
+ stdio: opts.silent ? ["ignore", "ignore", "ignore"] : "inherit",
561
+ shell: process.platform === "win32"
562
+ });
563
+ child.once("error", reject);
564
+ child.once("close", (code) => {
565
+ if (code === 0) resolve();
566
+ else reject(new Error(`${command} ${args.join(" ")} exited with code ${code}`));
567
+ });
568
+ });
569
+ }
570
+
571
+ // src/index.ts
572
+ var FRAMEWORK_SKILLS_SOURCE = "quan0715/open-press";
573
+ async function main(argv) {
574
+ if (argv.includes("--help") || argv.includes("-h")) {
575
+ printHelp();
576
+ return 0;
577
+ }
578
+ let target = argv.find((a) => !a.startsWith("--")) ?? "";
579
+ let type = parseFlag(argv, "--type");
580
+ const title = parseFlag(argv, "--title");
581
+ const noInstall = argv.includes("--no-install");
582
+ const noSkills = argv.includes("--no-skills");
583
+ const noGit = argv.includes("--no-git");
584
+ if (!target) {
585
+ const res = await prompts({
586
+ type: "text",
587
+ name: "target",
588
+ message: "Project name:",
589
+ validate: (value) => value.trim() ? true : "Name is required"
590
+ });
591
+ if (!res.target) return 1;
592
+ target = res.target;
593
+ }
594
+ if (!type) {
595
+ const res = await prompts({
596
+ type: "select",
597
+ name: "type",
598
+ message: "Press type:",
599
+ choices: [
600
+ { title: "Slides (16:9 deck)", value: "slides" },
601
+ { title: "Pages (not yet supported)", value: "pages", disabled: true }
602
+ ]
603
+ });
604
+ if (!res.type) return 1;
605
+ type = res.type;
606
+ }
607
+ if (type === "pages") {
608
+ process2.stderr.write("--type pages is not yet supported. Use --type slides.\n");
609
+ return 1;
610
+ }
611
+ const opts = {
612
+ target,
613
+ type,
614
+ title,
615
+ install: !noInstall,
616
+ skills: !noSkills,
617
+ git: !noGit
618
+ };
619
+ return run(opts);
620
+ }
621
+ async function run(opts) {
622
+ const targetPath = path3.resolve(process2.cwd(), opts.target);
623
+ const workspaceName = path3.basename(targetPath);
624
+ const pressRoot = path3.join(targetPath, "press", workspaceName);
625
+ log(`Creating open-press workspace at ${targetPath}`);
626
+ await ensureTarget(targetPath);
627
+ await writeWorkspaceFiles(targetPath, workspaceName);
628
+ await writeSlidesPress(pressRoot, {
629
+ pressName: workspaceName,
630
+ title: opts.title ?? workspaceName
631
+ });
632
+ if (opts.skills) {
633
+ log("Installing framework skills...");
634
+ try {
635
+ await runInTarget(targetPath, "npx", ["-y", "skills@latest", "add", FRAMEWORK_SKILLS_SOURCE]);
636
+ } catch (err) {
637
+ log("(skills install failed; retry later: open-press skills:sync)");
638
+ log(` ${err instanceof Error ? err.message : String(err)}`);
639
+ }
640
+ }
641
+ if (opts.install) {
642
+ log("Installing dependencies (npm install)...");
643
+ await runInTarget(targetPath, "npm", ["install"]);
644
+ }
645
+ if (opts.git) {
646
+ log("Initializing git repository...");
647
+ try {
648
+ await runInTarget(targetPath, "git", ["init"]);
649
+ await runInTarget(targetPath, "git", ["add", "-A"]);
650
+ await runInTarget(targetPath, "git", ["commit", "-m", "Initial commit from @open-press/create"], {
651
+ silent: true
652
+ });
653
+ } catch {
654
+ log("(git not available; skipping)");
655
+ }
656
+ }
657
+ printNextSteps(targetPath, opts);
658
+ return 0;
659
+ }
660
+ function parseFlag(argv, flag) {
661
+ const i = argv.indexOf(flag);
662
+ return i !== -1 ? argv[i + 1] : void 0;
663
+ }
664
+ function log(msg) {
665
+ process2.stdout.write(`> ${msg}
666
+ `);
667
+ }
668
+ function printHelp() {
669
+ process2.stdout.write(`npm create @open-press <target> -- --type slides [options]
670
+
671
+ Options:
672
+ --type slides Press type. The create package scaffolds slides; page projects are skill-authored.
673
+ --title <s> Document title
674
+ --no-install Skip npm install
675
+ --no-skills Skip agent skill installation
676
+ --no-git Skip git init
677
+ --help Show this help
678
+ `);
679
+ }
680
+ function printNextSteps(target, opts) {
681
+ const rel = path3.relative(process2.cwd(), target) || ".";
682
+ const lines = ["", "Done. Your open-press workspace is ready.", "", "Next steps:", ` cd ${rel}`];
683
+ if (!opts.install) lines.push(" npm install");
684
+ if (!opts.skills) lines.push(" open-press skills:sync");
685
+ lines.push("", " npm run dev", "", "Then open the local URL printed by Vite.", "");
686
+ process2.stdout.write(lines.join("\n"));
687
+ }
688
+ main(process2.argv.slice(2)).then((code) => process2.exit(code)).catch((err) => {
689
+ process2.stderr.write(`${err instanceof Error ? err.stack ?? err.message : String(err)}
690
+ `);
691
+ process2.exit(1);
692
+ });
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@open-press/create",
3
+ "version": "2.0.0",
4
+ "type": "module",
5
+ "description": "Bootstrap a new OpenPress workspace.",
6
+ "license": "MIT",
7
+ "author": "quan0715",
8
+ "homepage": "https://github.com/quan0715/open-press#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/quan0715/open-press.git",
12
+ "directory": "packages/create"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/quan0715/open-press/issues"
16
+ },
17
+ "keywords": [
18
+ "document",
19
+ "mdx",
20
+ "react",
21
+ "pdf",
22
+ "fixed-layout",
23
+ "ai-first",
24
+ "scaffold",
25
+ "create"
26
+ ],
27
+ "bin": {
28
+ "create-open-press": "./dist/index.js"
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "README.md"
33
+ ],
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "scripts": {
38
+ "build": "tsup",
39
+ "dev": "tsup --watch",
40
+ "typecheck": "tsc --noEmit",
41
+ "test": "pnpm build && node --test tests/*.test.mjs",
42
+ "prepack": "pnpm build"
43
+ },
44
+ "engines": {
45
+ "node": ">=20"
46
+ },
47
+ "dependencies": {
48
+ "prompts": "^2.4.2"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^25.8.0",
52
+ "@types/prompts": "^2.4.9",
53
+ "tsup": "^8.5.0",
54
+ "typescript": "^6.0.3"
55
+ }
56
+ }