@open-press/cli 1.2.1 → 1.3.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 (3) hide show
  1. package/README.md +6 -3
  2. package/dist/cli.js +406 -79
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -26,7 +26,8 @@ npx @open-press/cli init <target> [flags]
26
26
 
27
27
  | Flag | Description |
28
28
  | -------------------- | --------------------------------------------------------------------------- |
29
- | `--title <s>` | Document title (written to workspace config) |
29
+ | `--title <s>` | Document title |
30
+ | `--type <pages|slides>` | Scaffold a folder-convention Press under `press/<target-name>/` |
30
31
  | `--no-git` | Skip `git init` + initial commit (use when scaffolding inside an existing repo) |
31
32
  | `--no-install` | Skip `npm install` (offline, or you'll run pnpm/bun yourself) |
32
33
  | `--skills` | Install OpenPress agent skills after scaffolding |
@@ -46,8 +47,10 @@ npx -y skills@latest add quan0715/openpress-social-card-skill
46
47
  A self-contained workspace with:
47
48
 
48
49
  - `package.json` with `@open-press/core`, `@open-press/cli`, and `open-press ...` scripts
49
- - `press/index.tsx` the workspace document entry
50
- - `press/theme/`, `press/media/`, `press/components/` — user-owned authoring files
50
+ - `press/<target-name>/press.tsx` for folder-convention Press entries
51
+ - `press/<target-name>/theme/`, `press/<target-name>/media/`, `press/<target-name>/components/` — artifact-owned authoring files
52
+ - optional `press/shared/` for assets, media, components, or theme used by multiple Press folders
53
+ - `press/<target-name>/ui/` and `press/<target-name>/layouts/` for slide starters created with `--type slides`
51
54
  - `press/design.md` — working design notes for agents and maintainers
52
55
 
53
56
  It does **not** create `engine/`, `src/openpress/`, `index.html`, or `vite.config.ts` in your project. Those are package-owned runtime internals.
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ import process2 from "process";
9
9
  // src/init.ts
10
10
  import { spawn } from "child_process";
11
11
  import { existsSync } from "fs";
12
- import { mkdir, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
12
+ import { mkdir, readFile, writeFile } from "fs/promises";
13
13
  import path from "path";
14
14
  import process from "process";
15
15
  import { fileURLToPath } from "url";
@@ -29,24 +29,6 @@ async function pathIsEmpty(target, options = {}) {
29
29
  }
30
30
  }
31
31
 
32
- // src/metadata.ts
33
- import { readFile, writeFile } from "fs/promises";
34
- async function patchPressTitle(entryPath, title) {
35
- const source = await readFile(entryPath, "utf8");
36
- const escaped = escapeStringForJs(title);
37
- const existing = /(<Press\b[^>]*\btitle\s*=\s*)("[^"]*"|'[^']*'|\{`[^`]*`\})/;
38
- let next;
39
- if (existing.test(source)) {
40
- next = source.replace(existing, `$1"${escaped}"`);
41
- } else {
42
- next = source.replace(/<Press\b/, `<Press title="${escaped}"`);
43
- }
44
- await writeFile(entryPath, next);
45
- }
46
- function escapeStringForJs(value) {
47
- return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
48
- }
49
-
50
32
  // src/init.ts
51
33
  var FRAMEWORK_SKILLS_SOURCE = "quan0715/open-press";
52
34
  var __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -58,14 +40,12 @@ async function init(options) {
58
40
  const title = options.title ?? "OpenPress Document";
59
41
  const version = await readCliVersion();
60
42
  log(`Creating open-press workspace at ${target}`);
43
+ if (!options.type) {
44
+ throw new Error("open-press init requires --type pages or --type slides.");
45
+ }
61
46
  await writeWorkspacePackageJson(target, workspaceName, version);
62
47
  await writeWorkspaceGitignore(target);
63
- await writeStarterPress(target, title);
64
- if (options.title) {
65
- const pressEntry = path.join(target, "press", "index.tsx");
66
- log("Writing title into <Press> in press/index.tsx");
67
- await patchPressTitle(pressEntry, options.title);
68
- }
48
+ await writeTypedStarterPress(target, { pressName: workspaceName, title, type: options.type });
69
49
  if (options.skills) {
70
50
  log(`Installing framework skills via \`open-press skills add\`\u2026`);
71
51
  try {
@@ -110,7 +90,7 @@ async function ensureTarget(target) {
110
90
  await mkdir(target, { recursive: true });
111
91
  }
112
92
  async function readCliVersion() {
113
- const pkg = JSON.parse(await readFile2(CLI_PACKAGE_JSON, "utf8"));
93
+ const pkg = JSON.parse(await readFile(CLI_PACKAGE_JSON, "utf8"));
114
94
  return typeof pkg.version === "string" && pkg.version ? pkg.version : "latest";
115
95
  }
116
96
  async function writeWorkspacePackageJson(target, workspaceName, version) {
@@ -154,7 +134,7 @@ async function writeWorkspacePackageJson(target, workspaceName, version) {
154
134
  }
155
135
  }
156
136
  };
157
- await writeFile2(path.join(target, "package.json"), `${JSON.stringify(pkg, null, 2)}
137
+ await writeFile(path.join(target, "package.json"), `${JSON.stringify(pkg, null, 2)}
158
138
  `, "utf8");
159
139
  }
160
140
  async function writeWorkspaceGitignore(target) {
@@ -173,21 +153,45 @@ async function writeWorkspaceGitignore(target) {
173
153
  "output/",
174
154
  ""
175
155
  ].join("\n");
176
- await writeFile2(path.join(target, ".gitignore"), content, "utf8");
156
+ await writeFile(path.join(target, ".gitignore"), content, "utf8");
177
157
  }
178
- async function writeStarterPress(target, title) {
158
+ async function writeTypedStarterPress(target, options) {
159
+ const folder = folderNameFromPressName(options.pressName);
160
+ const title = options.title;
179
161
  const pressRoot = path.join(target, "press");
180
- await mkdir(path.join(pressRoot, "components"), { recursive: true });
181
- await mkdir(path.join(pressRoot, "media"), { recursive: true });
162
+ const sharedRoot = path.join(pressRoot, "shared");
163
+ const typedRoot = path.join(pressRoot, folder);
164
+ await writeWorkspaceThemeSkeleton(sharedRoot);
165
+ await mkdir(path.join(sharedRoot, "media"), { recursive: true });
166
+ await mkdir(path.join(typedRoot, "components"), { recursive: true });
167
+ await mkdir(path.join(typedRoot, "theme"), { recursive: true });
168
+ await mkdir(path.join(typedRoot, "media"), { recursive: true });
169
+ await writeFile(path.join(pressRoot, "design.md"), `# ${title}
170
+
171
+ Starter OpenPress ${options.type} workspace.
172
+ `, "utf8");
173
+ await writeFile(path.join(sharedRoot, "media", "README.md"), "# Shared Media\n\nPlace workspace-wide media here.\n", "utf8");
174
+ await writeFile(path.join(typedRoot, "media", "README.md"), "# Media\n\nPlace Press-specific media here.\n", "utf8");
175
+ if (options.type === "pages") {
176
+ await writePagesPress(typedRoot, { folder, title });
177
+ } else {
178
+ await writeSlidesPress(typedRoot, { folder, title });
179
+ }
180
+ }
181
+ async function writeWorkspaceThemeSkeleton(pressRoot) {
182
182
  await mkdir(path.join(pressRoot, "theme", "base"), { recursive: true });
183
183
  await mkdir(path.join(pressRoot, "theme", "page-surfaces"), { recursive: true });
184
184
  await mkdir(path.join(pressRoot, "theme", "shell"), { recursive: true });
185
- await writeFile2(path.join(pressRoot, "design.md"), `# ${title}
186
-
187
- Starter OpenPress workspace.
188
- `, "utf8");
189
- await writeFile2(path.join(pressRoot, "media", "README.md"), "# Media\n\nPlace project media here.\n", "utf8");
190
- await writeFile2(
185
+ await writeFile(
186
+ path.join(pressRoot, "theme", "tokens.css"),
187
+ `:root {
188
+ --openpress-font-body: system-ui, sans-serif;
189
+ --openpress-font-serif: Georgia, serif;
190
+ }
191
+ `,
192
+ "utf8"
193
+ );
194
+ await writeFile(
191
195
  path.join(pressRoot, "theme", "base", "page-contract.css"),
192
196
  `* {
193
197
  box-sizing: border-box;
@@ -212,7 +216,7 @@ body {
212
216
  `,
213
217
  "utf8"
214
218
  );
215
- await writeFile2(
219
+ await writeFile(
216
220
  path.join(pressRoot, "theme", "base", "typography.css"),
217
221
  `h1,
218
222
  h2,
@@ -221,25 +225,13 @@ p {
221
225
  margin: 0;
222
226
  }
223
227
 
224
- h1 {
225
- font-family: var(--openpress-font-serif, Georgia, serif);
226
- font-size: clamp(42px, 8cqw, 72px);
227
- line-height: 1;
228
- font-weight: 500;
229
- }
230
-
231
- p {
232
- font-size: clamp(16px, 2cqw, 22px);
233
- line-height: 1.5;
234
- }
235
-
236
228
  code {
237
229
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
238
230
  }
239
231
  `,
240
232
  "utf8"
241
233
  );
242
- await writeFile2(
234
+ await writeFile(
243
235
  path.join(pressRoot, "theme", "base", "print.css"),
244
236
  `@media print {
245
237
  html,
@@ -250,41 +242,363 @@ code {
250
242
  `,
251
243
  "utf8"
252
244
  );
253
- await writeFile2(path.join(pressRoot, "theme", "page-surfaces", "cover.css"), "/* Starter cover surface. */\n", "utf8");
254
- await writeFile2(path.join(pressRoot, "theme", "page-surfaces", "back-cover.css"), "/* Starter back-cover surface. */\n", "utf8");
255
- await writeFile2(path.join(pressRoot, "theme", "page-surfaces", "toc.css"), "/* Starter TOC surface. */\n", "utf8");
256
- await writeFile2(path.join(pressRoot, "theme", "shell", "reader-controls.css"), "/* Starter reader controls surface. */\n", "utf8");
257
- await writeFile2(
258
- path.join(pressRoot, "theme", "tokens.css"),
259
- `:root {
260
- --openpress-font-body: system-ui, sans-serif;
261
- --openpress-font-serif: Georgia, serif;
245
+ await writeFile(path.join(pressRoot, "theme", "page-surfaces", "cover.css"), "/* Starter cover surface. */\n", "utf8");
246
+ await writeFile(path.join(pressRoot, "theme", "page-surfaces", "back-cover.css"), "/* Starter back-cover surface. */\n", "utf8");
247
+ await writeFile(path.join(pressRoot, "theme", "page-surfaces", "toc.css"), "/* Starter TOC surface. */\n", "utf8");
248
+ await writeFile(path.join(pressRoot, "theme", "shell", "reader-controls.css"), "/* Starter reader controls surface. */\n", "utf8");
249
+ }
250
+ async function writePagesPress(typedRoot, { folder, title }) {
251
+ await mkdir(path.join(typedRoot, "chapters", "01-intro", "content"), { recursive: true });
252
+ await writeFile(
253
+ path.join(typedRoot, "press.tsx"),
254
+ `import { Press } from "@open-press/core";
255
+ import { mdxSource } from "@open-press/core/mdx";
256
+ import { Sections, Toc } from "@open-press/core/manuscript";
257
+
258
+ export default function ${componentNameFromFolder(folder)}Press() {
259
+ return (
260
+ <Press
261
+ slug="${escapeJsxAttribute(folder)}"
262
+ title="${escapeJsxAttribute(title)}"
263
+ type="pages"
264
+ page="a4"
265
+ componentsDir="./components"
266
+ mediaDir="./media"
267
+ sources={[mdxSource({ id: "${escapeJsxAttribute(folder)}", preset: "section-folders", root: "${escapeJsxAttribute(folder)}/chapters" })]}
268
+ >
269
+ <Toc source="${escapeJsxAttribute(folder)}" maxLevel={2} />
270
+ <Sections source="${escapeJsxAttribute(folder)}" />
271
+ </Press>
272
+ );
273
+ }
274
+ `,
275
+ "utf8"
276
+ );
277
+ await writeFile(
278
+ path.join(typedRoot, "chapters", "01-intro", "content", "01-intro.mdx"),
279
+ `# ${escapeText(title)}
280
+
281
+ Start writing this OpenPress document here.
282
+ `,
283
+ "utf8"
284
+ );
285
+ }
286
+ async function writeSlidesPress(typedRoot, { folder, title }) {
287
+ await mkdir(path.join(typedRoot, "ui"), { recursive: true });
288
+ await mkdir(path.join(typedRoot, "layouts"), { recursive: true });
289
+ await writeFile(
290
+ path.join(typedRoot, "press.tsx"),
291
+ `import { Press, Text } from "@open-press/core";
292
+ import { TitleSlide } from "./layouts/title-slide";
293
+ import { TitledContentSlide } from "./layouts/titled-content-slide";
294
+ import { Timeline } from "./ui/timeline";
295
+ import "./theme/slides.css";
296
+
297
+ export default function ${componentNameFromFolder(folder)}Press() {
298
+ return (
299
+ <Press
300
+ slug="${escapeJsxAttribute(folder)}"
301
+ title="${escapeJsxAttribute(title)}"
302
+ type="slides"
303
+ page="slide-16-9"
304
+ componentsDir="./components"
305
+ mediaDir="./media"
306
+ >
307
+ <TitleSlide id="cover">
308
+ <TitleSlide.Title objectId="title">${escapeText(title)}</TitleSlide.Title>
309
+ <TitleSlide.Description objectId="description">
310
+ A concise deck authored as editable OpenPress source.
311
+ </TitleSlide.Description>
312
+ </TitleSlide>
313
+
314
+ <TitledContentSlide id="problem-context">
315
+ <TitledContentSlide.Eyebrow objectId="eyebrow">Context</TitledContentSlide.Eyebrow>
316
+ <TitledContentSlide.Title objectId="title">Problem Context</TitledContentSlide.Title>
317
+ <TitledContentSlide.Content>
318
+ <Text as="p" objectId="summary">Write visible slide content directly in JSX.</Text>
319
+ </TitledContentSlide.Content>
320
+ </TitledContentSlide>
321
+
322
+ <TitledContentSlide id="workflow">
323
+ <TitledContentSlide.Eyebrow objectId="eyebrow">Process</TitledContentSlide.Eyebrow>
324
+ <TitledContentSlide.Title objectId="title">Workflow</TitledContentSlide.Title>
325
+ <TitledContentSlide.Content>
326
+ <Timeline>
327
+ <Timeline.Item id="draft" marker="01" title="Draft">Create the first structure.</Timeline.Item>
328
+ <Timeline.Item id="review" marker="02" title="Review">Tighten content and layout.</Timeline.Item>
329
+ <Timeline.Item id="export" marker="03" title="Export">Render PDF or images.</Timeline.Item>
330
+ </Timeline>
331
+ </TitledContentSlide.Content>
332
+ </TitledContentSlide>
333
+ </Press>
334
+ );
335
+ }
336
+ `,
337
+ "utf8"
338
+ );
339
+ await writeFile(
340
+ path.join(typedRoot, "components", "DeckSlide.tsx"),
341
+ `import { PageFolio, Slide } from "@open-press/core";
342
+ import type { ReactNode } from "react";
343
+
344
+ export function DeckSlide({
345
+ id,
346
+ variant = "default",
347
+ children,
348
+ }: {
349
+ id: string;
350
+ variant?: "default" | "cover";
351
+ children: ReactNode;
352
+ }) {
353
+ return (
354
+ <Slide id={id} className={\`op-slide op-slide--\${variant}\`}>
355
+ <div className="op-slide__surface">
356
+ <main className="op-slide__content">{children}</main>
357
+ <footer className="op-slide__footer">
358
+ <PageFolio variant="slash" currentFormat="2-digit" totalFormat="2-digit" />
359
+ </footer>
360
+ </div>
361
+ </Slide>
362
+ );
363
+ }
364
+ `,
365
+ "utf8"
366
+ );
367
+ await writeFile(
368
+ path.join(typedRoot, "layouts", "title-slide.tsx"),
369
+ `import { Text, type TextProps } from "@open-press/core";
370
+ import type { ReactNode } from "react";
371
+ import { DeckSlide } from "../components/DeckSlide";
372
+
373
+ function TitleSlideRoot({ id, children }: { id: string; children: ReactNode }) {
374
+ return (
375
+ <DeckSlide id={id} variant="cover">
376
+ <section className="op-title-slide">{children}</section>
377
+ </DeckSlide>
378
+ );
379
+ }
380
+
381
+ function TitleSlideTitle(props: Omit<TextProps, "as" | "label">) {
382
+ return <Text as="h1" label="Title" className="op-slide-title" {...props} />;
383
+ }
384
+
385
+ function TitleSlideDescription(props: Omit<TextProps, "as" | "label">) {
386
+ return <Text as="p" label="Description" className="op-slide-description" {...props} />;
387
+ }
388
+
389
+ export const TitleSlide = Object.assign(TitleSlideRoot, {
390
+ Title: TitleSlideTitle,
391
+ Description: TitleSlideDescription,
392
+ });
393
+ `,
394
+ "utf8"
395
+ );
396
+ await writeFile(
397
+ path.join(typedRoot, "layouts", "titled-content-slide.tsx"),
398
+ `import { Text, type TextProps } from "@open-press/core";
399
+ import type { ReactNode } from "react";
400
+ import { DeckSlide } from "../components/DeckSlide";
401
+
402
+ function TitledContentSlideRoot({ id, children }: { id: string; children: ReactNode }) {
403
+ return (
404
+ <DeckSlide id={id}>
405
+ <section className="op-titled-content-slide">{children}</section>
406
+ </DeckSlide>
407
+ );
408
+ }
409
+
410
+ function TitledContentSlideEyebrow(props: Omit<TextProps, "as" | "label">) {
411
+ return <Text as="p" label="Eyebrow" className="op-slide-eyebrow" {...props} />;
412
+ }
413
+
414
+ function TitledContentSlideTitle(props: Omit<TextProps, "as" | "label">) {
415
+ return <Text as="h2" label="Title" className="op-slide-heading" {...props} />;
416
+ }
417
+
418
+ function TitledContentSlideDescription(props: Omit<TextProps, "as" | "label">) {
419
+ return <Text as="p" label="Description" className="op-slide-description" {...props} />;
420
+ }
421
+
422
+ function TitledContentSlideContent({ children }: { children: ReactNode }) {
423
+ return <div className="op-slide-body">{children}</div>;
262
424
  }
425
+
426
+ export const TitledContentSlide = Object.assign(TitledContentSlideRoot, {
427
+ Eyebrow: TitledContentSlideEyebrow,
428
+ Title: TitledContentSlideTitle,
429
+ Description: TitledContentSlideDescription,
430
+ Desc: TitledContentSlideDescription,
431
+ Content: TitledContentSlideContent,
432
+ });
263
433
  `,
264
434
  "utf8"
265
435
  );
266
- await writeFile2(
267
- path.join(pressRoot, "index.tsx"),
268
- `import { Frame, Press, Workspace } from "@open-press/core";
436
+ await writeFile(
437
+ path.join(typedRoot, "ui", "timeline.tsx"),
438
+ `import { Text } from "@open-press/core";
439
+ import type { ReactNode } from "react";
440
+
441
+ function TimelineRoot({ children }: { children: ReactNode }) {
442
+ return <ol className="op-timeline">{children}</ol>;
443
+ }
269
444
 
270
- export default function OpenPressDocument() {
445
+ function TimelineItem({
446
+ id,
447
+ marker,
448
+ title,
449
+ children,
450
+ }: {
451
+ id: string;
452
+ marker: string;
453
+ title: string;
454
+ children: ReactNode;
455
+ }) {
271
456
  return (
272
- <Workspace name="${escapeJsxAttribute(title)}">
273
- <Press title="${escapeJsxAttribute(title)}">
274
- <Frame frameKey="cover" role="manuscript.cover">
275
- <main style={{ padding: "72px", fontFamily: "var(--openpress-font-serif, Georgia, serif)" }}>
276
- <p style={{ letterSpacing: "0.12em", textTransform: "uppercase" }}>OpenPress</p>
277
- <h1>${escapeText(title)}</h1>
278
- <p>Edit <code>press/index.tsx</code>, then run <code>npm run dev</code>.</p>
279
- </main>
280
- </Frame>
281
- </Press>
282
- </Workspace>
457
+ <li className="op-timeline__item">
458
+ <Text as="span" objectId={\`\${id}-marker\`} label="Timeline marker" className="op-timeline__marker">{marker}</Text>
459
+ <div>
460
+ <Text as="h3" objectId={\`\${id}-title\`} label="Timeline title" className="op-timeline__title">{title}</Text>
461
+ <Text as="p" objectId={\`\${id}-body\`} label="Timeline body" className="op-timeline__body">{children}</Text>
462
+ </div>
463
+ </li>
283
464
  );
284
465
  }
466
+
467
+ export const Timeline = Object.assign(TimelineRoot, {
468
+ Item: TimelineItem,
469
+ });
470
+ `,
471
+ "utf8"
472
+ );
473
+ await writeFile(
474
+ path.join(typedRoot, "ui", "text.tsx"),
475
+ `export { Text } from "@open-press/core";
476
+ export type { TextProps } from "@open-press/core";
285
477
  `,
286
478
  "utf8"
287
479
  );
480
+ await writeFile(path.join(typedRoot, "ui", "card.tsx"), `import type { ReactNode } from "react";
481
+
482
+ export function Card({ children }: { children: ReactNode }) {
483
+ return <article className="op-card">{children}</article>;
484
+ }
485
+ `, "utf8");
486
+ await writeFile(path.join(typedRoot, "theme", "tokens.css"), `:root {
487
+ --op-slide-bg: #f8fafc;
488
+ --op-slide-fg: #17202a;
489
+ --op-slide-muted: #607084;
490
+ --op-slide-accent: #2563eb;
491
+ }
492
+ `, "utf8");
493
+ await writeFile(
494
+ path.join(typedRoot, "theme", "slides.css"),
495
+ `@import "./tokens.css";
496
+
497
+ .op-slide {
498
+ background: var(--op-slide-bg);
499
+ color: var(--op-slide-fg);
500
+ font-family: Inter, ui-sans-serif, system-ui, sans-serif;
501
+ }
502
+
503
+ .op-slide__surface {
504
+ width: 100%;
505
+ height: 100%;
506
+ padding: 92px 112px;
507
+ display: grid;
508
+ grid-template-rows: 1fr auto;
509
+ }
510
+
511
+ .op-slide__content {
512
+ min-width: 0;
513
+ }
514
+
515
+ .op-slide__footer {
516
+ color: var(--op-slide-muted);
517
+ display: flex;
518
+ justify-content: flex-end;
519
+ font-size: 22px;
520
+ }
521
+
522
+ .op-title-slide,
523
+ .op-titled-content-slide {
524
+ display: grid;
525
+ gap: 28px;
526
+ }
527
+
528
+ .op-title-slide {
529
+ align-content: center;
530
+ max-width: 1180px;
531
+ }
532
+
533
+ .op-slide-title {
534
+ font-size: 112px;
535
+ line-height: 1;
536
+ }
537
+
538
+ .op-slide-heading {
539
+ font-size: 72px;
540
+ line-height: 1.08;
541
+ }
542
+
543
+ .op-slide-eyebrow {
544
+ color: var(--op-slide-accent);
545
+ font-size: 24px;
546
+ font-weight: 700;
547
+ text-transform: uppercase;
548
+ }
549
+
550
+ .op-slide-description,
551
+ .op-slide-body {
552
+ color: var(--op-slide-muted);
553
+ font-size: 34px;
554
+ line-height: 1.35;
555
+ }
556
+
557
+ .op-timeline {
558
+ display: grid;
559
+ grid-template-columns: repeat(3, minmax(0, 1fr));
560
+ gap: 24px;
561
+ padding: 0;
562
+ margin: 24px 0 0;
563
+ list-style: none;
564
+ }
565
+
566
+ .op-timeline__item {
567
+ background: #ffffff;
568
+ border: 1px solid #dbe3ef;
569
+ display: grid;
570
+ gap: 18px;
571
+ padding: 28px;
572
+ }
573
+
574
+ .op-timeline__marker {
575
+ color: var(--op-slide-accent);
576
+ font-size: 24px;
577
+ font-weight: 800;
578
+ }
579
+
580
+ .op-timeline__title {
581
+ font-size: 34px;
582
+ }
583
+
584
+ .op-timeline__body {
585
+ color: var(--op-slide-muted);
586
+ font-size: 26px;
587
+ line-height: 1.35;
588
+ }
589
+ `,
590
+ "utf8"
591
+ );
592
+ }
593
+ function folderNameFromPressName(value) {
594
+ const base = path.basename(value);
595
+ const folder = base.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
596
+ return folder || "press";
597
+ }
598
+ function componentNameFromFolder(folder) {
599
+ const words = folder.split(/[-_]+/).filter(Boolean);
600
+ const name = words.map((word) => word.slice(0, 1).toUpperCase() + word.slice(1)).join("");
601
+ return name || "OpenPress";
288
602
  }
289
603
  function escapeJsxAttribute(value) {
290
604
  return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
@@ -332,7 +646,7 @@ function printNextSteps(target, options) {
332
646
  "",
333
647
  "Then open the local URL printed by Vite (typically http://127.0.0.1:5173/workspace).",
334
648
  "",
335
- "Use an OpenPress-ready skill to add or adapt the press source tree.",
649
+ "Use openpress-create-pages for a page-based artifact or openpress-create-slide for a deck.",
336
650
  ""
337
651
  );
338
652
  process.stdout.write(lines.join("\n"));
@@ -348,7 +662,8 @@ Usage:
348
662
  open-press skills <add|update> [--source <owner/repo>]
349
663
 
350
664
  Init flags:
351
- --title <s> Document title (written into <Press title="..."> in press/index.tsx)
665
+ --title <s> Document title (written into <Press title="...">)
666
+ --type <pages|slides> Required. Scaffold a Press under press/<target-name>/
352
667
  --no-git Skip git init
353
668
  --no-install Skip npm install
354
669
  --skills Install OpenPress agent skills after scaffolding
@@ -371,8 +686,8 @@ Runtime commands:
371
686
  skills Install or update OpenPress agent skills
372
687
 
373
688
  Examples:
374
- npx @open-press/cli init my-doc
375
- npx @open-press/cli init my-brief --title "Q2 Brief"
689
+ npx @open-press/cli init my-doc --type pages
690
+ npx @open-press/cli init my-brief --type slides --title "Q2 Brief"
376
691
  npx open-press dev .
377
692
  npx open-press image .
378
693
  `;
@@ -411,6 +726,7 @@ function parseInitArgs(args) {
411
726
  const options = {
412
727
  target: "",
413
728
  title: void 0,
729
+ type: void 0,
414
730
  git: true,
415
731
  install: true,
416
732
  skills: false
@@ -421,6 +737,17 @@ function parseInitArgs(args) {
421
737
  case "--title":
422
738
  options.title = args[++i];
423
739
  break;
740
+ case "--type": {
741
+ const type = args[++i];
742
+ if (type !== "pages" && type !== "slides") {
743
+ process2.stderr.write(`Invalid --type: ${type}. Expected "pages" or "slides".
744
+
745
+ `);
746
+ return null;
747
+ }
748
+ options.type = type;
749
+ break;
750
+ }
424
751
  case "--no-git":
425
752
  options.git = false;
426
753
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-press/cli",
3
- "version": "1.2.1",
3
+ "version": "1.3.1",
4
4
  "type": "module",
5
5
  "description": "Command line interface for OpenPress workspaces.",
6
6
  "license": "MIT",
@@ -42,7 +42,7 @@
42
42
  "node": ">=20"
43
43
  },
44
44
  "dependencies": {
45
- "@open-press/core": "1.2.1"
45
+ "@open-press/core": "1.3.1"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/node": "^25.8.0",