@kywk/open-slide-core 0.0.0-stage → 2.3.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 (206) hide show
  1. package/README.md +107 -2
  2. package/bin.js +2 -0
  3. package/dist/build-CZ6Qtrng.js +19 -0
  4. package/dist/cli/bin.d.ts +1 -0
  5. package/dist/cli/bin.js +473 -0
  6. package/dist/config-B5LArqpp.js +3597 -0
  7. package/dist/config-CpmGqpQi.d.ts +24 -0
  8. package/dist/design-CoZ0cWDN.js +45 -0
  9. package/dist/dev-CCgdakbZ.js +118 -0
  10. package/dist/index.d.ts +133 -0
  11. package/dist/index.js +933 -0
  12. package/dist/locale/index.d.ts +21 -0
  13. package/dist/locale/index.js +13 -0
  14. package/dist/preview-AwAqvG17.js +24 -0
  15. package/dist/types-CvAqFqsY.d.ts +565 -0
  16. package/dist/update-C_RlnKu4.js +938 -0
  17. package/dist/vite/index.d.ts +11 -0
  18. package/dist/vite/index.js +2 -0
  19. package/dist/zh-tw-B7_Oqv2q.js +2606 -0
  20. package/env.d.ts +65 -0
  21. package/package.json +113 -4
  22. package/skills/apply-comments/SKILL.md +128 -0
  23. package/skills/create-slide/SKILL.md +103 -0
  24. package/skills/create-theme/SKILL.md +253 -0
  25. package/skills/current-slide/SKILL.md +111 -0
  26. package/skills/slide-authoring/SKILL.md +412 -0
  27. package/skills/slide-authoring/references/assets.md +72 -0
  28. package/skills/slide-authoring/references/design-system.md +48 -0
  29. package/skills/slide-authoring/references/diagrams.md +182 -0
  30. package/skills/slide-authoring/references/mermaid.md +191 -0
  31. package/skills/slide-authoring/references/morph.md +67 -0
  32. package/skills/slide-authoring/references/page-numbers.md +16 -0
  33. package/skills/slide-authoring/references/steps.md +43 -0
  34. package/skills/slide-authoring/references/transitions.md +176 -0
  35. package/skills/slide-authoring/references/webfonts.md +39 -0
  36. package/src/app/app.tsx +52 -0
  37. package/src/app/assets/open-slide.svg +39 -0
  38. package/src/app/components/asset-view.tsx +2120 -0
  39. package/src/app/components/command/command-menu.tsx +229 -0
  40. package/src/app/components/command/command.tsx +144 -0
  41. package/src/app/components/command/home-command-menu.tsx +96 -0
  42. package/src/app/components/command/slide-command-menu.tsx +181 -0
  43. package/src/app/components/export-progress-toast.tsx +80 -0
  44. package/src/app/components/history-provider.tsx +162 -0
  45. package/src/app/components/icon-switcher-indicator.tsx +10 -0
  46. package/src/app/components/icon-tooltip.tsx +43 -0
  47. package/src/app/components/image-placeholder.tsx +235 -0
  48. package/src/app/components/inline-svg.tsx +190 -0
  49. package/src/app/components/inspector/arrange-panel.tsx +449 -0
  50. package/src/app/components/inspector/asset-picker-dialog.tsx +188 -0
  51. package/src/app/components/inspector/comment-layer.tsx +992 -0
  52. package/src/app/components/inspector/image-crop-dialog.tsx +215 -0
  53. package/src/app/components/inspector/inline-text-editor.tsx +684 -0
  54. package/src/app/components/inspector/inspect-overlay.tsx +647 -0
  55. package/src/app/components/inspector/inspector-panel.tsx +1022 -0
  56. package/src/app/components/inspector/inspector-provider.tsx +1690 -0
  57. package/src/app/components/inspector/save-bar.tsx +84 -0
  58. package/src/app/components/language-toggle.tsx +45 -0
  59. package/src/app/components/mermaid.tsx +436 -0
  60. package/src/app/components/morph-element.tsx +43 -0
  61. package/src/app/components/note-markdown.test.tsx +42 -0
  62. package/src/app/components/note-markdown.tsx +85 -0
  63. package/src/app/components/notes-drawer.tsx +117 -0
  64. package/src/app/components/overview-grid.tsx +372 -0
  65. package/src/app/components/panel/editor-sidebar.tsx +50 -0
  66. package/src/app/components/panel/panel-fields.tsx +248 -0
  67. package/src/app/components/panel/panel-shell.tsx +63 -0
  68. package/src/app/components/panel/save-card.tsx +155 -0
  69. package/src/app/components/player.tsx +471 -0
  70. package/src/app/components/present/blackout-overlay.tsx +31 -0
  71. package/src/app/components/present/control-bar.tsx +326 -0
  72. package/src/app/components/present/help-overlay.tsx +57 -0
  73. package/src/app/components/present/jump-input.tsx +96 -0
  74. package/src/app/components/present/laser-pointer.tsx +62 -0
  75. package/src/app/components/present/progress-bar.tsx +26 -0
  76. package/src/app/components/present/use-idle.ts +46 -0
  77. package/src/app/components/present/use-pointer-near-bottom.ts +34 -0
  78. package/src/app/components/present/use-presenter-channel.ts +66 -0
  79. package/src/app/components/present/use-touch-swipe.ts +73 -0
  80. package/src/app/components/preview-step-host.tsx +16 -0
  81. package/src/app/components/sidebar/folder-item.tsx +296 -0
  82. package/src/app/components/sidebar/icon-picker.tsx +61 -0
  83. package/src/app/components/sidebar/sidebar-footer.tsx +152 -0
  84. package/src/app/components/sidebar/sidebar.tsx +329 -0
  85. package/src/app/components/slide-canvas.tsx +98 -0
  86. package/src/app/components/slide-preload-layer.tsx +185 -0
  87. package/src/app/components/slide-transition-layer.tsx +827 -0
  88. package/src/app/components/style-panel/design-provider.tsx +135 -0
  89. package/src/app/components/style-panel/style-panel.tsx +293 -0
  90. package/src/app/components/style-panel/use-design.ts +85 -0
  91. package/src/app/components/theme-toggle.tsx +65 -0
  92. package/src/app/components/themes/theme-detail.tsx +308 -0
  93. package/src/app/components/themes/themes-gallery.tsx +154 -0
  94. package/src/app/components/thumbnail-rail-scroll.test.ts +60 -0
  95. package/src/app/components/thumbnail-rail-scroll.ts +33 -0
  96. package/src/app/components/thumbnail-rail.tsx +1048 -0
  97. package/src/app/components/ui/badge.tsx +48 -0
  98. package/src/app/components/ui/button.tsx +93 -0
  99. package/src/app/components/ui/card.tsx +92 -0
  100. package/src/app/components/ui/context-menu.tsx +247 -0
  101. package/src/app/components/ui/dialog.tsx +155 -0
  102. package/src/app/components/ui/dropdown-menu.tsx +267 -0
  103. package/src/app/components/ui/input.tsx +25 -0
  104. package/src/app/components/ui/label.tsx +21 -0
  105. package/src/app/components/ui/popover.tsx +82 -0
  106. package/src/app/components/ui/progress.tsx +33 -0
  107. package/src/app/components/ui/scroll-area.tsx +53 -0
  108. package/src/app/components/ui/select.tsx +195 -0
  109. package/src/app/components/ui/separator.tsx +26 -0
  110. package/src/app/components/ui/slider.tsx +68 -0
  111. package/src/app/components/ui/sonner.tsx +48 -0
  112. package/src/app/components/ui/tabs.tsx +79 -0
  113. package/src/app/components/ui/textarea.tsx +22 -0
  114. package/src/app/components/ui/toggle-group.tsx +84 -0
  115. package/src/app/components/ui/toggle.tsx +45 -0
  116. package/src/app/components/ui/tooltip.tsx +75 -0
  117. package/src/app/favicon.ico +0 -0
  118. package/src/app/favicon.svg +39 -0
  119. package/src/app/index.html +35 -0
  120. package/src/app/lib/asset-filter.test.ts +176 -0
  121. package/src/app/lib/asset-filter.ts +80 -0
  122. package/src/app/lib/assets.ts +289 -0
  123. package/src/app/lib/design-presets.ts +94 -0
  124. package/src/app/lib/design.ts +69 -0
  125. package/src/app/lib/dom.ts +42 -0
  126. package/src/app/lib/export-html.ts +367 -0
  127. package/src/app/lib/export-pdf.ts +281 -0
  128. package/src/app/lib/export-pptx.ts +183 -0
  129. package/src/app/lib/folders.ts +244 -0
  130. package/src/app/lib/inspector/fiber.test.ts +154 -0
  131. package/src/app/lib/inspector/fiber.ts +85 -0
  132. package/src/app/lib/inspector/freeze-motion.ts +61 -0
  133. package/src/app/lib/inspector/geometry.test.ts +324 -0
  134. package/src/app/lib/inspector/geometry.ts +207 -0
  135. package/src/app/lib/inspector/inline-text.ts +31 -0
  136. package/src/app/lib/inspector/pick-target.ts +38 -0
  137. package/src/app/lib/inspector/text-edit-timeline.test.ts +121 -0
  138. package/src/app/lib/inspector/text-edit-timeline.ts +12 -0
  139. package/src/app/lib/inspector/text-selection.ts +117 -0
  140. package/src/app/lib/inspector/use-comments.ts +103 -0
  141. package/src/app/lib/inspector/use-editor.ts +73 -0
  142. package/src/app/lib/inspector/use-notes.ts +134 -0
  143. package/src/app/lib/inspector/use-visual-editor.ts +429 -0
  144. package/src/app/lib/inspector/visual-dom.ts +255 -0
  145. package/src/app/lib/keys.ts +30 -0
  146. package/src/app/lib/last-home-location.ts +18 -0
  147. package/src/app/lib/locale-store.ts +72 -0
  148. package/src/app/lib/note-markdown.test.ts +147 -0
  149. package/src/app/lib/note-markdown.ts +116 -0
  150. package/src/app/lib/page-context.tsx +38 -0
  151. package/src/app/lib/pptx/color.ts +49 -0
  152. package/src/app/lib/pptx/css.test.ts +205 -0
  153. package/src/app/lib/pptx/css.ts +462 -0
  154. package/src/app/lib/pptx/fonts.ts +145 -0
  155. package/src/app/lib/pptx/images.ts +116 -0
  156. package/src/app/lib/pptx/measure.ts +1281 -0
  157. package/src/app/lib/pptx/ooxml.test.ts +199 -0
  158. package/src/app/lib/pptx/ooxml.ts +470 -0
  159. package/src/app/lib/pptx/raster.ts +189 -0
  160. package/src/app/lib/pptx/render.ts +102 -0
  161. package/src/app/lib/pptx/scene.ts +116 -0
  162. package/src/app/lib/pptx/text-lines.test.ts +82 -0
  163. package/src/app/lib/pptx/text-lines.ts +109 -0
  164. package/src/app/lib/pptx/units.ts +22 -0
  165. package/src/app/lib/print-ready.test.ts +55 -0
  166. package/src/app/lib/print-ready.ts +52 -0
  167. package/src/app/lib/sdk.test.ts +13 -0
  168. package/src/app/lib/sdk.ts +37 -0
  169. package/src/app/lib/slides.ts +29 -0
  170. package/src/app/lib/step-context.tsx +269 -0
  171. package/src/app/lib/text-diff.ts +19 -0
  172. package/src/app/lib/themes.ts +22 -0
  173. package/src/app/lib/transition.test.ts +31 -0
  174. package/src/app/lib/transition.ts +43 -0
  175. package/src/app/lib/use-agent-socket.ts +18 -0
  176. package/src/app/lib/use-click-page-navigation.ts +56 -0
  177. package/src/app/lib/use-document-title.test.ts +14 -0
  178. package/src/app/lib/use-document-title.ts +16 -0
  179. package/src/app/lib/use-is-mobile.ts +9 -0
  180. package/src/app/lib/use-locale.ts +8 -0
  181. package/src/app/lib/use-media-query.ts +20 -0
  182. package/src/app/lib/use-prefers-reduced-motion.ts +11 -0
  183. package/src/app/lib/use-restart-server.ts +78 -0
  184. package/src/app/lib/use-slide-module.ts +48 -0
  185. package/src/app/lib/use-slide-titles.ts +29 -0
  186. package/src/app/lib/use-wheel-page-navigation.ts +99 -0
  187. package/src/app/lib/utils.test.ts +49 -0
  188. package/src/app/lib/utils.ts +14 -0
  189. package/src/app/main.tsx +14 -0
  190. package/src/app/routes/assets.tsx +9 -0
  191. package/src/app/routes/home-shell.tsx +261 -0
  192. package/src/app/routes/home.tsx +856 -0
  193. package/src/app/routes/presenter.tsx +692 -0
  194. package/src/app/routes/preview.tsx +169 -0
  195. package/src/app/routes/slide.tsx +1311 -0
  196. package/src/app/routes/themes.tsx +33 -0
  197. package/src/app/styles.css +385 -0
  198. package/src/app/virtual.d.ts +51 -0
  199. package/src/locale/en.ts +543 -0
  200. package/src/locale/es.ts +550 -0
  201. package/src/locale/format.ts +12 -0
  202. package/src/locale/index.ts +7 -0
  203. package/src/locale/ja.ts +550 -0
  204. package/src/locale/types.ts +578 -0
  205. package/src/locale/zh-cn.ts +540 -0
  206. package/src/locale/zh-tw.ts +540 -0
package/README.md CHANGED
@@ -1,3 +1,108 @@
1
- # Temporary Holding Version
1
+ # @kywk/open-slide-core
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Runtime and CLI for [open-slide](https://github.com/open-slide/open-slide) — a React-based slide framework where you write slides and the framework handles the Vite/React stack, layout, navigation, hot reload, and fullscreen play mode.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @kywk/open-slide-core
9
+ ```
10
+
11
+ Most users get this installed automatically by running `npx @kywk/open-slide-cli init`. Use this package directly only if you're wiring up an existing workspace by hand.
12
+
13
+ ## What's inside
14
+
15
+ - **Runtime** — home page, slide viewer, thumbnail rail, keyboard navigation, and fullscreen presenter mode. Every slide renders into a fixed **1920×1080** canvas; the framework scales it.
16
+ - **Vite plugin** — discovers `slides/<id>/index.{tsx,jsx,ts,js}`, exposes them via virtual modules, and reloads when slides are added or removed.
17
+ - **CLI** — `open-slide dev | build | preview` so workspaces never need to touch Vite, React, or tsconfig directly.
18
+
19
+ ## CLI
20
+
21
+ Once installed, the `open-slide` bin is available in the workspace:
22
+
23
+ | Command | Description |
24
+ | --- | --- |
25
+ | `open-slide dev` | Start the dev server. Flags: `-p, --port <port>`, `--host [host]`, `--open`. |
26
+ | `open-slide build` | Build a static site. Flags: `--out-dir <dir>` (defaults to `dist`). |
27
+ | `open-slide preview` | Preview the production build. Flags: `-p, --port <port>`, `--host [host]`, `--open`. |
28
+
29
+ ## Config
30
+
31
+ Create `open-slide.config.ts` in the workspace root (all fields optional):
32
+
33
+ ```ts
34
+ import type { OpenSlideConfig } from '@kywk/open-slide-core';
35
+
36
+ const openSlideConfig: OpenSlideConfig = {
37
+ slidesDir: 'slides',
38
+ port: 5173,
39
+ };
40
+
41
+ export default openSlideConfig;
42
+ ```
43
+
44
+ ### Hosting under a subpath
45
+
46
+ Set `base` to deploy the built site under a sub-directory (intranet folders, GitHub Pages project sites, reverse proxies). Use a leading and trailing slash:
47
+
48
+ ```ts
49
+ const openSlideConfig: OpenSlideConfig = {
50
+ base: '/my-slides/',
51
+ };
52
+ ```
53
+
54
+ The value is passed straight to Vite's `base` and to React Router's `basename`, so client-side navigation matches the deployed path.
55
+
56
+ ## Authoring slides
57
+
58
+ Slides live under `slides/<kebab-case-id>/index.tsx` and default-export an array of `Page` components:
59
+
60
+ ```tsx
61
+ import type { Page } from '@kywk/open-slide-core';
62
+
63
+ const Cover: Page = () => (
64
+ <div className="flex h-full w-full items-center justify-center">
65
+ <h1 className="text-[120px] font-bold">Hello, open-slide</h1>
66
+ </div>
67
+ );
68
+
69
+ const pages: Page[] = [Cover];
70
+ export default pages;
71
+
72
+ export const meta = { title: 'Hello' };
73
+ ```
74
+
75
+ ## Exports
76
+
77
+ ```ts
78
+ import {
79
+ CANVAS_WIDTH, // 1920
80
+ CANVAS_HEIGHT, // 1080
81
+ Mermaid, // render flow, sequence, state, and other Mermaid diagrams
82
+ MorphElement, // match or fade objects across pages for morph transitions
83
+ type Page,
84
+ type SlideMeta,
85
+ type SlideModule,
86
+ type SlideTransition,
87
+ type OpenSlideConfig,
88
+ } from '@kywk/open-slide-core';
89
+ ```
90
+
91
+ Render relational or sequential information directly in a TSX page:
92
+
93
+ ```tsx
94
+ <Mermaid chart={`flowchart LR
95
+ Source --> Transform --> Result`} style={{ width: 1200, height: 650 }} />
96
+ ```
97
+
98
+ Lightbox is enabled by default — click any diagram to expand it near-fullscreen. Pass `lightbox={false}` to disable.
99
+
100
+ The Vite plugin is exposed under a subpath for advanced setups:
101
+
102
+ ```ts
103
+ import { createViteConfig } from '@kywk/open-slide-core/vite';
104
+ ```
105
+
106
+ ## License
107
+
108
+ MIT
package/bin.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import './dist/cli/bin.js';
@@ -0,0 +1,19 @@
1
+ import { r as printHeader, t as createCliLogger } from "./cli/bin.js";
2
+ import { t as createViteConfig } from "./config-B5LArqpp.js";
3
+ import path from "node:path";
4
+ import { build as build$1, mergeConfig } from "vite";
5
+ //#region src/cli/build.ts
6
+ async function build(opts = {}) {
7
+ printHeader("building for production");
8
+ const base = await createViteConfig({
9
+ userCwd: process.cwd(),
10
+ mode: "build"
11
+ });
12
+ const config = mergeConfig(base, {
13
+ customLogger: createCliLogger(),
14
+ build: { ...opts.outDir !== void 0 ? { outDir: path.resolve(process.cwd(), opts.outDir) } : {} }
15
+ });
16
+ await build$1(config);
17
+ }
18
+ //#endregion
19
+ export { build };
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,473 @@
1
+ #!/usr/bin/env node
2
+ import { A as ownPackage, j as ownPackageName, t as detectPackageManager } from "../update-C_RlnKu4.js";
3
+ import path from "node:path";
4
+ import * as readline from "node:readline/promises";
5
+ import { fileURLToPath } from "node:url";
6
+ import chalk from "chalk";
7
+ import { Command, Option } from "commander";
8
+ import { existsSync, readFileSync, realpathSync } from "node:fs";
9
+ import { cp, lstat, mkdir, readFile, readdir, readlink, rm, symlink } from "node:fs/promises";
10
+ import { createHash } from "node:crypto";
11
+ import * as readline$1 from "node:readline";
12
+ import { stripVTControlCharacters } from "node:util";
13
+ //#region src/cli/preflight.ts
14
+ const MIGRATION_URL = "https://open-slide.dev/docs/migrate-to-v2";
15
+ const VITE_CONSUMERS = ["@vitejs/plugin-react", "@tailwindcss/vite"];
16
+ const DEPENDENCY_FIELDS = [
17
+ "dependencies",
18
+ "devDependencies",
19
+ "optionalDependencies",
20
+ "peerDependencies"
21
+ ];
22
+ const PROJECT_ROOT_MARKERS = [
23
+ "pnpm-workspace.yaml",
24
+ "pnpm-lock.yaml",
25
+ "yarn.lock",
26
+ "bun.lock",
27
+ "bun.lockb",
28
+ "package-lock.json",
29
+ ".git"
30
+ ];
31
+ function locatePackage(name, fromDir) {
32
+ let dir = fromDir;
33
+ while (true) {
34
+ const candidate = path.join(dir, "node_modules", name);
35
+ const pkgPath = path.join(candidate, "package.json");
36
+ if (existsSync(pkgPath)) {
37
+ let version = "unknown";
38
+ try {
39
+ version = JSON.parse(readFileSync(pkgPath, "utf8")).version ?? version;
40
+ } catch {}
41
+ return {
42
+ dir: realpathSync(candidate),
43
+ root: dir,
44
+ version
45
+ };
46
+ }
47
+ const parent = path.dirname(dir);
48
+ if (parent === dir) return null;
49
+ dir = parent;
50
+ }
51
+ }
52
+ function findViteMismatch(coreDir) {
53
+ const coreVite = locatePackage("vite", coreDir);
54
+ if (!coreVite) return null;
55
+ for (const consumer of VITE_CONSUMERS) {
56
+ const consumerPkg = locatePackage(consumer, coreDir);
57
+ if (!consumerPkg) continue;
58
+ const consumerVite = locatePackage("vite", consumerPkg.dir);
59
+ if (!consumerVite || consumerVite.version === coreVite.version) continue;
60
+ return {
61
+ consumer,
62
+ coreVite,
63
+ consumerVite
64
+ };
65
+ }
66
+ return null;
67
+ }
68
+ function declarationIn(dir) {
69
+ const file = path.join(dir, "package.json");
70
+ if (!existsSync(file)) return null;
71
+ try {
72
+ const pkg = JSON.parse(readFileSync(file, "utf8"));
73
+ for (const field of DEPENDENCY_FIELDS) {
74
+ const deps = pkg[field];
75
+ if (deps && Object.hasOwn(deps, "vite")) return {
76
+ file,
77
+ field
78
+ };
79
+ }
80
+ } catch {}
81
+ return null;
82
+ }
83
+ function isProjectRoot(dir) {
84
+ return PROJECT_ROOT_MARKERS.some((marker) => existsSync(path.join(dir, marker)));
85
+ }
86
+ function isAncestorOrSelf(ancestor, dir) {
87
+ const rel = path.relative(ancestor, dir);
88
+ return !rel.startsWith("..") && !path.isAbsolute(rel);
89
+ }
90
+ function findViteDeclaration(cwd, installRoot) {
91
+ if (installRoot && (isAncestorOrSelf(installRoot, cwd) || isAncestorOrSelf(cwd, installRoot))) {
92
+ const owner = declarationIn(installRoot);
93
+ if (owner) return owner;
94
+ }
95
+ const stopAt = installRoot && isAncestorOrSelf(installRoot, cwd) ? installRoot : null;
96
+ let dir = cwd;
97
+ while (dir !== stopAt) {
98
+ const found = declarationIn(dir);
99
+ if (found) return found;
100
+ const parent = path.dirname(dir);
101
+ if (parent === dir || !stopAt && isProjectRoot(dir)) return null;
102
+ dir = parent;
103
+ }
104
+ return null;
105
+ }
106
+ function removeCommand(pm) {
107
+ return pm === "npm" ? "npm uninstall vite" : `${pm} remove vite`;
108
+ }
109
+ function formatViteMismatch(mismatch, { cwd = process.cwd(), packageManager = "npm", declaration = null } = {}) {
110
+ const rel = (p) => path.relative(cwd, p) || p;
111
+ const want = chalk.bold(`vite@${mismatch.coreVite.version}`);
112
+ const got = chalk.bold(`vite@${mismatch.consumerVite.version}`);
113
+ const from = chalk.dim(rel(mismatch.consumerVite.dir));
114
+ const command = (cmd) => [
115
+ "",
116
+ ` ${chalk.dim("$")} ${chalk.cyan(cmd)}`,
117
+ ""
118
+ ];
119
+ const footer = `${chalk.dim("Migration guide")} ${chalk.cyan.underline(MIGRATION_URL)}`;
120
+ if (!declaration) return [
121
+ chalk.bold("Conflicting vite versions in node_modules"),
122
+ "",
123
+ `open-slide ships ${want}, but ${mismatch.consumer} is loading ${got}`,
124
+ `from ${from}. Make sure no package.json lists vite, then reinstall:`,
125
+ ...command(`${packageManager} install`),
126
+ footer
127
+ ].join("\n");
128
+ const dir = path.dirname(declaration.file);
129
+ const cd = path.resolve(dir) === path.resolve(cwd) ? "" : `cd ${rel(dir)} && `;
130
+ return [
131
+ chalk.bold(`Remove ${chalk.yellow("vite")} from your package.json`),
132
+ "",
133
+ `${chalk.bold(declaration.field)} in ${chalk.dim(rel(declaration.file))} still lists vite, a leftover from v1.`,
134
+ `It shadows the ${want} bundled with open-slide: ${mismatch.consumer}`,
135
+ `is loading ${got} from ${from} instead.`,
136
+ ...command(cd + removeCommand(packageManager)),
137
+ footer
138
+ ].join("\n");
139
+ }
140
+ async function assertViteResolvesToCore(cwd = process.cwd()) {
141
+ const mismatch = findViteMismatch(realpathSync(path.dirname(fileURLToPath(import.meta.url))));
142
+ if (!mismatch) return;
143
+ const declaration = findViteDeclaration(cwd, mismatch.consumerVite.root);
144
+ const packageManager = await detectPackageManager(declaration ? path.dirname(declaration.file) : cwd);
145
+ throw new Error(formatViteMismatch(mismatch, {
146
+ cwd,
147
+ packageManager,
148
+ declaration
149
+ }));
150
+ }
151
+ //#endregion
152
+ //#region src/cli/sync.ts
153
+ async function detectSkillsDrift(skillsDir) {
154
+ if (!existsSync(skillsDir)) return [];
155
+ const cwd = process.cwd();
156
+ const agentsSkillsDir = path.join(cwd, ".agents", "skills");
157
+ const skillNames = (await readdir(skillsDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name).sort();
158
+ const results = [];
159
+ for (const name of skillNames) {
160
+ const src = path.join(skillsDir, name);
161
+ const dst = path.join(agentsSkillsDir, name);
162
+ const srcHash = await hashDir(src);
163
+ const dstHash = existsSync(dst) ? await hashDir(dst) : null;
164
+ let status;
165
+ if (dstHash === null) status = "added";
166
+ else if (dstHash !== srcHash) status = "updated";
167
+ else status = "unchanged";
168
+ results.push({
169
+ name,
170
+ status
171
+ });
172
+ }
173
+ return results;
174
+ }
175
+ async function syncSkills(skillsDir, opts = {}) {
176
+ const { dryRun = false } = opts;
177
+ if (!existsSync(skillsDir)) throw new Error(`Built-in skills directory missing at ${skillsDir}. The ${ownPackageName()} package may be corrupt — try reinstalling.`);
178
+ const cwd = process.cwd();
179
+ const agentsSkillsDir = path.join(cwd, ".agents", "skills");
180
+ const claudeSkillsDir = path.join(cwd, ".claude", "skills");
181
+ const results = await detectSkillsDrift(skillsDir);
182
+ if (results.length === 0) {
183
+ process.stdout.write(chalk.yellow("No skills found to sync.\n"));
184
+ return;
185
+ }
186
+ for (const { name, status } of results) {
187
+ const src = path.join(skillsDir, name);
188
+ const dst = path.join(agentsSkillsDir, name);
189
+ if (dryRun) continue;
190
+ if (status === "unchanged") {
191
+ await ensureClaudeSymlink(claudeSkillsDir, name);
192
+ continue;
193
+ }
194
+ await mkdir(path.dirname(dst), { recursive: true });
195
+ if (existsSync(dst)) await rm(dst, {
196
+ recursive: true,
197
+ force: true
198
+ });
199
+ await cp(src, dst, { recursive: true });
200
+ await ensureClaudeSymlink(claudeSkillsDir, name);
201
+ }
202
+ printSummary(results, dryRun);
203
+ }
204
+ async function ensureClaudeSymlink(claudeSkillsDir, name) {
205
+ await mkdir(claudeSkillsDir, { recursive: true });
206
+ const linkPath = path.join(claudeSkillsDir, name);
207
+ const target = path.join("..", "..", ".agents", "skills", name);
208
+ if (existsSync(linkPath)) try {
209
+ if ((await lstat(linkPath)).isSymbolicLink()) {
210
+ if (await readlink(linkPath) === target) return;
211
+ }
212
+ await rm(linkPath, {
213
+ recursive: true,
214
+ force: true
215
+ });
216
+ } catch {
217
+ await rm(linkPath, {
218
+ recursive: true,
219
+ force: true
220
+ });
221
+ }
222
+ try {
223
+ await symlink(target, linkPath, "dir");
224
+ } catch (err) {
225
+ const code = err.code;
226
+ if (code === "EPERM" || code === "EEXIST") {
227
+ const absoluteTarget = path.resolve(claudeSkillsDir, target);
228
+ await cp(absoluteTarget, linkPath, { recursive: true });
229
+ } else throw err;
230
+ }
231
+ }
232
+ async function hashDir(dir) {
233
+ const hash = createHash("sha256");
234
+ const files = await collectFiles(dir);
235
+ files.sort();
236
+ for (const rel of files) {
237
+ const abs = path.join(dir, rel);
238
+ const data = await readFile(abs);
239
+ hash.update(rel);
240
+ hash.update("\0");
241
+ hash.update(data);
242
+ hash.update("\0");
243
+ }
244
+ return hash.digest("hex");
245
+ }
246
+ async function collectFiles(dir, prefix = "") {
247
+ const out = [];
248
+ const entries = await readdir(dir, { withFileTypes: true });
249
+ for (const entry of entries) {
250
+ const rel = prefix ? path.join(prefix, entry.name) : entry.name;
251
+ if (entry.isDirectory()) out.push(...await collectFiles(path.join(dir, entry.name), rel));
252
+ else if (entry.isFile()) out.push(rel);
253
+ }
254
+ return out;
255
+ }
256
+ function printSummary(results, dryRun) {
257
+ const symbols = {
258
+ added: chalk.green("+"),
259
+ updated: chalk.yellow("~"),
260
+ unchanged: chalk.dim("=")
261
+ };
262
+ const labels = {
263
+ added: chalk.green("added"),
264
+ updated: chalk.yellow("updated"),
265
+ unchanged: chalk.dim("unchanged")
266
+ };
267
+ const header = dryRun ? chalk.bold("Dry run — no files written:") : chalk.bold("Synced built-in skills:");
268
+ process.stdout.write(`${header}\n`);
269
+ for (const { name, status } of results) process.stdout.write(` ${symbols[status]} ${name} ${chalk.dim(`(${labels[status]})`)}\n`);
270
+ const counts = results.reduce((acc, { status }) => {
271
+ acc[status] += 1;
272
+ return acc;
273
+ }, {
274
+ added: 0,
275
+ updated: 0,
276
+ unchanged: 0
277
+ });
278
+ process.stdout.write(chalk.dim(`\n${counts.added} added, ${counts.updated} updated, ${counts.unchanged} unchanged.\n`));
279
+ }
280
+ //#endregion
281
+ //#region src/cli/ui.ts
282
+ const UNICODE = process.platform !== "win32" || Boolean(process.env.WT_SESSION) || process.env.TERM_PROGRAM === "vscode";
283
+ const glyph = {
284
+ bar: UNICODE ? "┃" : "|",
285
+ warn: UNICODE ? "▲" : "!",
286
+ cross: UNICODE ? "✖" : "x"
287
+ };
288
+ function readVersion() {
289
+ return ownPackage().version;
290
+ }
291
+ function brand() {
292
+ return chalk.inverse.bold(" open-slide ");
293
+ }
294
+ function printHeader(status) {
295
+ const parts = [brand(), chalk.dim(`v${readVersion()}`)];
296
+ if (status) parts.push(chalk.dim(status));
297
+ process.stdout.write(`\n ${parts.join(" ")}\n\n`);
298
+ }
299
+ function startupDuration() {
300
+ return `ready in ${Math.round(process.uptime() * 1e3)} ms`;
301
+ }
302
+ function printUrls(urls, host) {
303
+ if (!urls) return;
304
+ const rows = [];
305
+ for (const url of urls.local) rows.push(urlRow("Local", chalk.cyan(url)));
306
+ urls.network.forEach((url, index) => {
307
+ const iface = urls.networkInterfaceNames?.[index];
308
+ rows.push(urlRow("Network", chalk.cyan(url) + (iface ? ` ${chalk.dim(iface)}` : "")));
309
+ });
310
+ if (urls.network.length === 0 && host === void 0) rows.push(urlRow("Network", chalk.dim("use --host to expose")));
311
+ process.stdout.write(`${rows.join("\n")}\n`);
312
+ }
313
+ function urlRow(label, value) {
314
+ return ` ${chalk.dim(glyph.bar)} ${chalk.bold(label.padEnd(8))} ${value}`;
315
+ }
316
+ function shortcutsEnabled() {
317
+ return Boolean(process.stdin.isTTY) && !process.env.CI;
318
+ }
319
+ function printShortcutsHint() {
320
+ process.stdout.write(`\n ${chalk.dim("press")} ${chalk.bold("h + enter")} ${chalk.dim("for shortcuts")}\n`);
321
+ }
322
+ function formatError(message) {
323
+ const [first, ...rest] = message.split("\n");
324
+ const body = rest.map((line) => line ? `\n ${line}` : "\n").join("");
325
+ return `\n ${chalk.red(glyph.cross)} ${first}${body}\n\n`;
326
+ }
327
+ const DROPPED = [/^vite v\d+\.\d+\.\d+ building /];
328
+ const REWRITES = [
329
+ [/hmr update /, "updated "],
330
+ [/hmr invalidate /, "invalidated "],
331
+ [/trigger page reload /, "full reload "],
332
+ [/page reload/, "full reload"],
333
+ [/server restarted\./, "server restarted"],
334
+ [/optimized dependencies changed\. reloading/, "dependencies changed, reloading"],
335
+ [/Re-optimizing dependencies because vite config has changed/, "config changed, re-bundling"],
336
+ [/Re-optimizing dependencies because lockfile has changed/, "lockfile changed, re-bundling"],
337
+ [/Forced re-optimization of dependencies/, "re-bundling dependencies"],
338
+ [/\[plugin builtin:vite-[a-z-]+\] ?\n?/, ""],
339
+ [/\[vite\] ?/g, ""]
340
+ ];
341
+ function rewriteViteMessage(msg) {
342
+ const plain = stripVTControlCharacters(msg);
343
+ if (DROPPED.some((re) => re.test(plain))) return null;
344
+ let out = msg;
345
+ for (const [re, replacement] of REWRITES) out = out.replace(re, replacement);
346
+ return out;
347
+ }
348
+ function timestamp() {
349
+ return (/* @__PURE__ */ new Date()).toLocaleTimeString([], { hourCycle: "h23" });
350
+ }
351
+ function formatLine(type, text, opts) {
352
+ if (!opts?.timestamp) return text;
353
+ const mark = type === "warn" ? `${chalk.yellow(glyph.warn)} ` : type === "error" ? `${chalk.red(glyph.cross)} ` : "";
354
+ return ` ${chalk.dim(timestamp())} ${mark}${text}`;
355
+ }
356
+ function clearTerminal() {
357
+ if (!process.stdout.isTTY || process.env.CI) return;
358
+ const blank = "\n".repeat(Math.max(process.stdout.rows - 2, 0));
359
+ console.log(blank);
360
+ readline$1.cursorTo(process.stdout, 0, 0);
361
+ readline$1.clearScreenDown(process.stdout);
362
+ }
363
+ function createCliLogger() {
364
+ const loggedErrors = /* @__PURE__ */ new WeakSet();
365
+ const warnedOnce = /* @__PURE__ */ new Set();
366
+ const write = (type, msg, opts) => {
367
+ const text = rewriteViteMessage(msg);
368
+ if (text === null) return;
369
+ console[type === "info" ? "log" : type](formatLine(type, text, opts));
370
+ };
371
+ const logger = {
372
+ hasWarned: false,
373
+ info(msg, opts) {
374
+ write("info", msg, opts);
375
+ },
376
+ warn(msg, opts) {
377
+ logger.hasWarned = true;
378
+ write("warn", msg, opts);
379
+ },
380
+ warnOnce(msg, opts) {
381
+ if (warnedOnce.has(msg)) return;
382
+ warnedOnce.add(msg);
383
+ logger.warn(msg, opts);
384
+ },
385
+ error(msg, opts) {
386
+ logger.hasWarned = true;
387
+ if (opts?.error) loggedErrors.add(opts.error);
388
+ write("error", msg, opts);
389
+ },
390
+ clearScreen() {
391
+ clearTerminal();
392
+ },
393
+ hasErrorLogged(error) {
394
+ return loggedErrors.has(error);
395
+ }
396
+ };
397
+ return logger;
398
+ }
399
+ //#endregion
400
+ //#region src/cli/run.ts
401
+ function parsePort(value) {
402
+ const n = Number(value);
403
+ if (!Number.isInteger(n) || n < 0 || n > 65535) throw new Error(`Invalid port: ${value}`);
404
+ return n;
405
+ }
406
+ async function runSkillsDriftCheck(skillsDir) {
407
+ if (process.env.OPEN_SLIDE_SKIP_SKILLS_CHECK === "1") return;
408
+ let drift;
409
+ try {
410
+ drift = await detectSkillsDrift(skillsDir);
411
+ } catch {
412
+ return;
413
+ }
414
+ const stale = drift.filter((d) => d.status !== "unchanged");
415
+ if (stale.length === 0) return;
416
+ const names = stale.map((d) => d.name).join(", ");
417
+ const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
418
+ const notice = `${chalk.yellow(glyph.warn)} Built-in skills are out of date: ${chalk.bold(names)}`;
419
+ if (!interactive) {
420
+ process.stderr.write(`\n ${notice}\n ${chalk.dim("Run `open-slide sync:skills` to update.")}\n`);
421
+ return;
422
+ }
423
+ const rl = readline.createInterface({
424
+ input: process.stdin,
425
+ output: process.stdout
426
+ });
427
+ try {
428
+ const answer = (await rl.question(`\n ${notice}\n Sync now? ${chalk.dim("(Y/n)")} `)).trim().toLowerCase();
429
+ if (answer === "" || answer === "y" || answer === "yes") {
430
+ process.stdout.write("\n");
431
+ await syncSkills(skillsDir);
432
+ } else process.stdout.write(chalk.dim(" Skipped. Run `open-slide sync:skills` later to update.\n"));
433
+ } finally {
434
+ rl.close();
435
+ }
436
+ }
437
+ function resolveBuiltinSkillsDir() {
438
+ const here = path.dirname(fileURLToPath(import.meta.url));
439
+ return path.resolve(here, "..", "..", "skills");
440
+ }
441
+ async function run(argv) {
442
+ const program = new Command();
443
+ program.name("open-slide").description("Author slides in React — open-slide runs the rest.").version(readVersion(), "-v, --version", "print version").helpOption("-h, --help", "show help").showHelpAfterError(chalk.dim("(run `open-slide --help` for usage)"));
444
+ program.command("dev").description("Start the dev server").addOption(new Option("-p, --port <port>", "port to listen on").argParser(parsePort)).addOption(new Option("--host [host]", "expose on the network (optional host)")).option("--open", "open the browser on start").option("--no-skills-check", "skip the built-in skills drift check").action(async (flags) => {
445
+ if (flags.skillsCheck !== false) await runSkillsDriftCheck(resolveBuiltinSkillsDir());
446
+ await assertViteResolvesToCore();
447
+ const { dev } = await import("../dev-CCgdakbZ.js");
448
+ await dev(flags);
449
+ });
450
+ program.command("build").description("Build a static site").option("--out-dir <dir>", "output directory (defaults to `dist`)").action(async (flags) => {
451
+ await assertViteResolvesToCore();
452
+ const { build } = await import("../build-CZ6Qtrng.js");
453
+ await build(flags);
454
+ });
455
+ program.command("preview").description("Preview the production build").addOption(new Option("-p, --port <port>", "port to listen on").argParser(parsePort)).addOption(new Option("--host [host]", "expose on the network (optional host)")).option("--open", "open the browser on start").action(async (flags) => {
456
+ await assertViteResolvesToCore();
457
+ const { preview } = await import("../preview-AwAqvG17.js");
458
+ await preview(flags);
459
+ });
460
+ program.command("sync:skills").description(`Sync built-in skills from ${ownPackageName()} into this workspace`).option("--dry-run", "show what would change without writing").action(async (flags) => {
461
+ await syncSkills(resolveBuiltinSkillsDir(), flags);
462
+ });
463
+ await program.parseAsync(argv, { from: "user" });
464
+ }
465
+ //#endregion
466
+ //#region src/cli/bin.ts
467
+ run(process.argv.slice(2)).catch((err) => {
468
+ const message = err instanceof Error ? err.message : String(err);
469
+ process.stderr.write(formatError(message));
470
+ process.exit(1);
471
+ });
472
+ //#endregion
473
+ export { printUrls as a, printShortcutsHint as i, formatError as n, shortcutsEnabled as o, printHeader as r, startupDuration as s, createCliLogger as t };