@open-press/cli 0.3.0 → 0.6.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 (124) hide show
  1. package/dist/cli.js +125 -51
  2. package/package.json +1 -1
  3. package/template/core/CHANGELOG.md +73 -0
  4. package/template/core/engine/cli.mjs +6 -0
  5. package/template/core/engine/commands/_shared.mjs +9 -2
  6. package/template/core/engine/commands/deploy.mjs +3 -3
  7. package/template/core/engine/commands/dev.mjs +25 -2
  8. package/template/core/engine/commands/doctor.mjs +229 -0
  9. package/template/core/engine/commands/pdf.mjs +3 -3
  10. package/template/core/engine/commands/preview.mjs +4 -4
  11. package/template/core/engine/commands/upgrade.mjs +117 -0
  12. package/template/core/package.json +3 -1
  13. package/template/core/vite.config.ts +26 -11
  14. package/template/packs/academic-paper/document/chapters/01-introduction/content/01-introduction.mdx +21 -0
  15. package/template/packs/academic-paper/document/chapters/02-methods/content/01-methods.mdx +30 -0
  16. package/template/packs/academic-paper/document/chapters/03-results-and-discussion/content/01-results.mdx +29 -0
  17. package/template/packs/academic-paper/document/chapters/04-acknowledgment/content/01-acknowledgment.mdx +12 -0
  18. package/template/packs/academic-paper/document/chapters/05-references/content/01-references.mdx +27 -0
  19. package/template/packs/academic-paper/document/index.tsx +107 -0
  20. package/template/packs/academic-paper/document/openpress.config.mjs +26 -0
  21. package/template/packs/academic-paper/document/theme/page-surfaces/cover.css +267 -0
  22. package/template/{skills/editorial-monograph/starter → packs/editorial-monograph}/document/chapters/03-agent-skills-contributors/content/01-agent-skills-contributors.mdx +2 -3
  23. package/template/packs/editorial-monograph/document/components/ChapterOpenerVisual/index.tsx +76 -0
  24. package/template/packs/editorial-monograph/document/components/Page.tsx +27 -0
  25. package/template/packs/editorial-monograph/document/components/TokenSwatchGrid/index.tsx +46 -0
  26. package/template/packs/editorial-monograph/document/components/TokenSwatchGrid/style.css +63 -0
  27. package/template/packs/editorial-monograph/document/components/TypeSpecimen/index.tsx +38 -0
  28. package/template/packs/editorial-monograph/document/components/TypeSpecimen/style.css +111 -0
  29. package/template/packs/editorial-monograph/document/design.md +279 -0
  30. package/template/packs/editorial-monograph/document/media/README.md +13 -0
  31. package/template/packs/editorial-monograph/document/theme/README.md +11 -0
  32. package/template/packs/editorial-monograph/document/theme/base/page-contract.css +505 -0
  33. package/template/packs/editorial-monograph/document/theme/base/print.css +93 -0
  34. package/template/packs/editorial-monograph/document/theme/base/typography.css +336 -0
  35. package/template/packs/editorial-monograph/document/theme/fonts.css +3 -0
  36. package/template/packs/editorial-monograph/document/theme/page-surfaces/back-cover.css +43 -0
  37. package/template/packs/editorial-monograph/document/theme/page-surfaces/chapter-opener.css +205 -0
  38. package/template/packs/editorial-monograph/document/theme/page-surfaces/toc.css +139 -0
  39. package/template/packs/editorial-monograph/document/theme/patterns/_chart-frame.css +49 -0
  40. package/template/packs/editorial-monograph/document/theme/patterns/figure-grid.css +68 -0
  41. package/template/packs/editorial-monograph/document/theme/patterns/table-utilities.css +66 -0
  42. package/template/packs/editorial-monograph/document/theme/shell/reader-controls.css +761 -0
  43. package/template/packs/editorial-monograph/document/theme/tokens.css +80 -0
  44. package/template/packs/editorial-monograph/openpress.config.mjs +5 -0
  45. package/template/core/.turbo/turbo-test.log +0 -341
  46. package/template/skills/chinese-ai-writing-polish/SKILL.md +0 -195
  47. package/template/skills/claude-document/SKILL.md +0 -66
  48. package/template/skills/editorial-monograph/SKILL.md +0 -73
  49. package/template/skills/openpress/SKILL.md +0 -114
  50. package/template/skills/openpress/references/cli-commands.md +0 -31
  51. package/template/skills/openpress/references/local-review.md +0 -43
  52. package/template/skills/openpress-deploy/SKILL.md +0 -69
  53. package/template/skills/openpress-deploy/references/cloudflare-pages.md +0 -51
  54. package/template/skills/openpress-design/SKILL.md +0 -51
  55. package/template/skills/openpress-design/references/pdf-safe-css.md +0 -29
  56. package/template/skills/openpress-design/references/responsive-fixed-layout.md +0 -48
  57. package/template/skills/openpress-design/references/theme-and-components.md +0 -77
  58. package/template/skills/openpress-diagram-drawing/SKILL.md +0 -44
  59. package/template/skills/openpress-diagram-drawing/references/diagram-patterns.md +0 -93
  60. package/template/skills/openpress-document-hierarchy/SKILL.md +0 -81
  61. package/template/skills/openpress-document-hierarchy/agents/openai.yaml +0 -4
  62. package/template/skills/openpress-document-hierarchy/references/data-structures-outline.md +0 -115
  63. package/template/skills/openpress-init/SKILL.md +0 -84
  64. package/template/skills/openpress-style-pack-contributor/SKILL.md +0 -62
  65. package/template/skills/openpress-style-pack-contributor/references/starter-contract.md +0 -49
  66. package/template/skills/openpress-update/SKILL.md +0 -88
  67. package/template/skills/openpress-writing/SKILL.md +0 -68
  68. package/template/skills/openpress-writing/references/source-and-writing-rules.md +0 -120
  69. package/template/skills/teaching-notes-writing/SKILL.md +0 -54
  70. package/template/skills/teaching-notes-writing/references/programming.md +0 -65
  71. package/template/skills/teaching-notes-writing/references/teaching-patterns.md +0 -60
  72. /package/template/{skills/editorial-monograph/starter → packs/academic-paper}/document/components/ChapterOpenerVisual/index.tsx +0 -0
  73. /package/template/{skills/claude-document/starter → packs/academic-paper}/document/components/Page.tsx +0 -0
  74. /package/template/{skills/editorial-monograph/starter → packs/academic-paper}/document/components/TokenSwatchGrid/index.tsx +0 -0
  75. /package/template/{skills/editorial-monograph/starter → packs/academic-paper}/document/components/TokenSwatchGrid/style.css +0 -0
  76. /package/template/{skills/editorial-monograph/starter → packs/academic-paper}/document/components/TypeSpecimen/index.tsx +0 -0
  77. /package/template/{skills/editorial-monograph/starter → packs/academic-paper}/document/components/TypeSpecimen/style.css +0 -0
  78. /package/template/{skills/editorial-monograph/starter → packs/academic-paper}/document/design.md +0 -0
  79. /package/template/{skills/claude-document/starter → packs/academic-paper}/document/media/README.md +0 -0
  80. /package/template/{skills/editorial-monograph/starter → packs/academic-paper}/document/theme/README.md +0 -0
  81. /package/template/{skills/editorial-monograph/starter → packs/academic-paper}/document/theme/base/page-contract.css +0 -0
  82. /package/template/{skills/editorial-monograph/starter → packs/academic-paper}/document/theme/base/print.css +0 -0
  83. /package/template/{skills/editorial-monograph/starter → packs/academic-paper}/document/theme/base/typography.css +0 -0
  84. /package/template/{skills/editorial-monograph/starter → packs/academic-paper}/document/theme/fonts.css +0 -0
  85. /package/template/{skills/editorial-monograph/starter → packs/academic-paper}/document/theme/page-surfaces/back-cover.css +0 -0
  86. /package/template/{skills/editorial-monograph/starter → packs/academic-paper}/document/theme/page-surfaces/chapter-opener.css +0 -0
  87. /package/template/{skills/editorial-monograph/starter → packs/academic-paper}/document/theme/page-surfaces/toc.css +0 -0
  88. /package/template/{skills/editorial-monograph/starter → packs/academic-paper}/document/theme/patterns/_chart-frame.css +0 -0
  89. /package/template/{skills/claude-document/starter → packs/academic-paper}/document/theme/patterns/figure-grid.css +0 -0
  90. /package/template/{skills/editorial-monograph/starter → packs/academic-paper}/document/theme/patterns/table-utilities.css +0 -0
  91. /package/template/{skills/editorial-monograph/starter → packs/academic-paper}/document/theme/shell/reader-controls.css +0 -0
  92. /package/template/{skills/editorial-monograph/starter → packs/academic-paper}/document/theme/tokens.css +0 -0
  93. /package/template/{skills/editorial-monograph/starter → packs/academic-paper}/openpress.config.mjs +0 -0
  94. /package/template/{skills/claude-document/starter → packs/claude-document}/document/chapters/01-document-shape/chapter.tsx +0 -0
  95. /package/template/{skills/claude-document/starter → packs/claude-document}/document/chapters/01-document-shape/content/01-document-shape.mdx +0 -0
  96. /package/template/{skills/claude-document/starter → packs/claude-document}/document/chapters/02-review-loop/chapter.tsx +0 -0
  97. /package/template/{skills/claude-document/starter → packs/claude-document}/document/chapters/02-review-loop/content/01-review-loop.mdx +0 -0
  98. /package/template/{skills/claude-document/starter → packs/claude-document}/document/components/ChapterOpenerVisual.tsx +0 -0
  99. /package/template/{skills/editorial-monograph/starter → packs/claude-document}/document/components/Page.tsx +0 -0
  100. /package/template/{skills/claude-document/starter → packs/claude-document}/document/design.md +0 -0
  101. /package/template/{skills/claude-document/starter → packs/claude-document}/document/index.tsx +0 -0
  102. /package/template/{skills/editorial-monograph/starter → packs/claude-document}/document/media/README.md +0 -0
  103. /package/template/{skills/claude-document/starter → packs/claude-document}/document/openpress.config.mjs +0 -0
  104. /package/template/{skills/claude-document/starter → packs/claude-document}/document/theme/README.md +0 -0
  105. /package/template/{skills/claude-document/starter → packs/claude-document}/document/theme/base/page-contract.css +0 -0
  106. /package/template/{skills/claude-document/starter → packs/claude-document}/document/theme/base/print.css +0 -0
  107. /package/template/{skills/claude-document/starter → packs/claude-document}/document/theme/base/typography.css +0 -0
  108. /package/template/{skills/claude-document/starter → packs/claude-document}/document/theme/fonts.css +0 -0
  109. /package/template/{skills/claude-document/starter → packs/claude-document}/document/theme/page-surfaces/back-cover.css +0 -0
  110. /package/template/{skills/claude-document/starter → packs/claude-document}/document/theme/page-surfaces/chapter-opener.css +0 -0
  111. /package/template/{skills/claude-document/starter → packs/claude-document}/document/theme/page-surfaces/cover.css +0 -0
  112. /package/template/{skills/claude-document/starter → packs/claude-document}/document/theme/page-surfaces/toc.css +0 -0
  113. /package/template/{skills/claude-document/starter → packs/claude-document}/document/theme/patterns/_chart-frame.css +0 -0
  114. /package/template/{skills/editorial-monograph/starter → packs/claude-document}/document/theme/patterns/figure-grid.css +0 -0
  115. /package/template/{skills/claude-document/starter → packs/claude-document}/document/theme/patterns/table-utilities.css +0 -0
  116. /package/template/{skills/claude-document/starter → packs/claude-document}/document/theme/shell/reader-controls.css +0 -0
  117. /package/template/{skills/claude-document/starter → packs/claude-document}/document/theme/tokens.css +0 -0
  118. /package/template/{skills/claude-document/starter → packs/claude-document}/openpress.config.mjs +0 -0
  119. /package/template/{skills/editorial-monograph/starter → packs/editorial-monograph}/document/chapters/01-product-and-use-cases/content/01-product-and-use-cases.mdx +0 -0
  120. /package/template/{skills/editorial-monograph/starter → packs/editorial-monograph}/document/chapters/02-workflow/content/01-workflow.mdx +0 -0
  121. /package/template/{skills/editorial-monograph/starter → packs/editorial-monograph}/document/chapters/04-validation-deploy/content/01-validation-deploy.mdx +0 -0
  122. /package/template/{skills/editorial-monograph/starter → packs/editorial-monograph}/document/index.tsx +0 -0
  123. /package/template/{skills/editorial-monograph/starter → packs/editorial-monograph}/document/openpress.config.mjs +0 -0
  124. /package/template/{skills/editorial-monograph/starter → packs/editorial-monograph}/document/theme/page-surfaces/cover.css +0 -0
@@ -0,0 +1,229 @@
1
+ import { existsSync } from "node:fs";
2
+ import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+
5
+ const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24h
6
+ const CORE_PACKAGE = "@open-press/core";
7
+
8
+ export async function run({ root, options }) {
9
+ const json = Boolean(options?.json);
10
+ const noCache = Boolean(options?.noCache);
11
+
12
+ const report = await diagnose(root, { noCache });
13
+
14
+ if (json) {
15
+ process.stdout.write(JSON.stringify(report, null, 2) + "\n");
16
+ } else {
17
+ printHumanReport(report);
18
+ }
19
+
20
+ // Exit 0 even when stale — doctor is informational, not a gate.
21
+ // Agents / CI can check report.stale or report.coreUpdateAvailable.
22
+ return 0;
23
+ }
24
+
25
+ /**
26
+ * Diagnose workspace against latest framework state.
27
+ * Result shape:
28
+ * {
29
+ * coreVersion: "0.4.0", // installed
30
+ * coreLatest: "0.5.0" | null, // null on network failure
31
+ * coreUpdateAvailable: boolean,
32
+ * skillsInstalled: ["openpress", ...],
33
+ * skillsLockSource: "quan0715/open-press" | null,
34
+ * pendingMigrations: ["0.5.0"], // versions with docs/migrations notes
35
+ * stale: boolean, // either core or skills behind
36
+ * cachedAt: ISO timestamp
37
+ * }
38
+ */
39
+ export async function diagnose(root, { noCache = false } = {}) {
40
+ const cachePath = path.join(root, ".openpress", "cache", "doctor.json");
41
+
42
+ if (!noCache) {
43
+ const cached = await readCached(cachePath);
44
+ if (cached) return cached;
45
+ }
46
+
47
+ const coreVersion = await readCoreVersion(root);
48
+ const coreLatest = await fetchCoreLatest();
49
+ const skillsInstalled = await listInstalledSkills(root);
50
+ const skillsLockSource = await readSkillsLockSource(root);
51
+ const pendingMigrations = await listPendingMigrations(root, coreVersion, coreLatest);
52
+
53
+ const coreUpdateAvailable = Boolean(
54
+ coreVersion && coreLatest && coreVersion !== coreLatest && semverLt(coreVersion, coreLatest),
55
+ );
56
+
57
+ const report = {
58
+ coreVersion,
59
+ coreLatest,
60
+ coreUpdateAvailable,
61
+ skillsInstalled,
62
+ skillsLockSource,
63
+ pendingMigrations,
64
+ stale: coreUpdateAvailable || pendingMigrations.length > 0,
65
+ cachedAt: new Date().toISOString(),
66
+ };
67
+
68
+ await writeCached(cachePath, report).catch(() => {});
69
+ return report;
70
+ }
71
+
72
+ async function readCached(cachePath) {
73
+ try {
74
+ const stats = await stat(cachePath);
75
+ if (Date.now() - stats.mtimeMs > CACHE_TTL_MS) return null;
76
+ return JSON.parse(await readFile(cachePath, "utf8"));
77
+ } catch {
78
+ return null;
79
+ }
80
+ }
81
+
82
+ async function writeCached(cachePath, report) {
83
+ await mkdir(path.dirname(cachePath), { recursive: true });
84
+ await writeFile(cachePath, JSON.stringify(report, null, 2) + "\n", "utf8");
85
+ }
86
+
87
+ async function readCoreVersion(root) {
88
+ // Try workspace package.json deps first; fall back to installed package.
89
+ try {
90
+ const pkg = JSON.parse(await readFile(path.join(root, "package.json"), "utf8"));
91
+ const range = pkg.dependencies?.[CORE_PACKAGE] ?? pkg.devDependencies?.[CORE_PACKAGE];
92
+ if (range) {
93
+ // Try the installed version (more accurate than the range).
94
+ try {
95
+ const installed = JSON.parse(
96
+ await readFile(path.join(root, "node_modules", CORE_PACKAGE, "package.json"), "utf8"),
97
+ );
98
+ return installed.version;
99
+ } catch {
100
+ return range.replace(/^[\^~>=<\s]+/, "");
101
+ }
102
+ }
103
+ } catch {}
104
+
105
+ // Self-bundled framework (cli scaffolded workspace): pkg.version is the framework version.
106
+ try {
107
+ const pkg = JSON.parse(await readFile(path.join(root, "package.json"), "utf8"));
108
+ if (pkg.name === CORE_PACKAGE) return pkg.version;
109
+ } catch {}
110
+
111
+ return null;
112
+ }
113
+
114
+ async function fetchCoreLatest() {
115
+ try {
116
+ const res = await fetch(`https://registry.npmjs.org/${CORE_PACKAGE}/latest`, {
117
+ headers: { Accept: "application/json" },
118
+ signal: AbortSignal.timeout(5000),
119
+ });
120
+ if (!res.ok) return null;
121
+ const data = await res.json();
122
+ return typeof data.version === "string" ? data.version : null;
123
+ } catch {
124
+ return null;
125
+ }
126
+ }
127
+
128
+ async function listInstalledSkills(root) {
129
+ const skillsDir = path.join(root, ".agents", "skills");
130
+ try {
131
+ const { readdir } = await import("node:fs/promises");
132
+ const entries = await readdir(skillsDir, { withFileTypes: true });
133
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
134
+ } catch {
135
+ return [];
136
+ }
137
+ }
138
+
139
+ async function readSkillsLockSource(root) {
140
+ try {
141
+ const lock = JSON.parse(await readFile(path.join(root, "skills-lock.json"), "utf8"));
142
+ const sources = lock?.sources;
143
+ if (Array.isArray(sources) && sources.length > 0) return sources[0]?.source ?? null;
144
+ return null;
145
+ } catch {
146
+ return null;
147
+ }
148
+ }
149
+
150
+ async function listPendingMigrations(root, currentVersion, latestVersion) {
151
+ if (!currentVersion || !latestVersion || !semverLt(currentVersion, latestVersion)) return [];
152
+ // Look for docs/migrations/<version>.md files for versions in (current, latest].
153
+ const migrationsDir = path.join(root, "docs", "migrations");
154
+ try {
155
+ const { readdir } = await import("node:fs/promises");
156
+ const files = await readdir(migrationsDir);
157
+ return files
158
+ .filter((f) => /^\d+\.\d+\.\d+\.md$/.test(f))
159
+ .map((f) => f.replace(/\.md$/, ""))
160
+ .filter((v) => semverGt(v, currentVersion) && !semverGt(v, latestVersion))
161
+ .sort(semverCompare);
162
+ } catch {
163
+ return [];
164
+ }
165
+ }
166
+
167
+ function semverParse(v) {
168
+ const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v);
169
+ if (!m) return [0, 0, 0];
170
+ return [Number(m[1]), Number(m[2]), Number(m[3])];
171
+ }
172
+ function semverCompare(a, b) {
173
+ const A = semverParse(a);
174
+ const B = semverParse(b);
175
+ for (let i = 0; i < 3; i++) if (A[i] !== B[i]) return A[i] - B[i];
176
+ return 0;
177
+ }
178
+ function semverLt(a, b) { return semverCompare(a, b) < 0; }
179
+ function semverGt(a, b) { return semverCompare(a, b) > 0; }
180
+
181
+ function printHumanReport(report) {
182
+ const lines = [];
183
+ lines.push("○ open-press doctor");
184
+ lines.push("");
185
+ lines.push("framework");
186
+ if (report.coreVersion) {
187
+ if (report.coreLatest === null) {
188
+ lines.push(` ? @open-press/core: ${report.coreVersion} installed (couldn't check latest — offline?)`);
189
+ } else if (report.coreUpdateAvailable) {
190
+ lines.push(` ⚠ @open-press/core: ${report.coreVersion} installed → ${report.coreLatest} available`);
191
+ } else {
192
+ lines.push(` ✓ @open-press/core: ${report.coreVersion} (latest)`);
193
+ }
194
+ } else {
195
+ lines.push(" ? @open-press/core: not detected in this workspace");
196
+ }
197
+ lines.push("");
198
+ lines.push("skills");
199
+ if (report.skillsInstalled.length === 0) {
200
+ lines.push(" ? no skills installed under .agents/skills/");
201
+ lines.push(" run: npx skills add quan0715/open-press");
202
+ } else {
203
+ lines.push(` ✓ ${report.skillsInstalled.length} skills installed`);
204
+ if (report.skillsLockSource) {
205
+ lines.push(` source: ${report.skillsLockSource}`);
206
+ lines.push(" refresh: npx skills upgrade");
207
+ }
208
+ }
209
+ lines.push("");
210
+ lines.push("migrations");
211
+ if (report.pendingMigrations.length === 0) {
212
+ if (report.coreUpdateAvailable) {
213
+ lines.push(` ✓ no breaking migrations documented for the ${report.coreLatest} window`);
214
+ } else {
215
+ lines.push(" ✓ up to date");
216
+ }
217
+ } else {
218
+ lines.push(` ⚠ ${report.pendingMigrations.length} migration note(s) since your version:`);
219
+ for (const v of report.pendingMigrations) lines.push(` - docs/migrations/${v}.md`);
220
+ }
221
+ lines.push("");
222
+ if (report.stale) {
223
+ lines.push("next");
224
+ lines.push(" npx open-press upgrade # apply all updates (agent-driven)");
225
+ lines.push(" npx open-press doctor --json # machine-readable output");
226
+ lines.push("");
227
+ }
228
+ process.stdout.write(lines.join("\n"));
229
+ }
@@ -1,5 +1,5 @@
1
1
  import path from "node:path";
2
- import { buildReactPdf } from "./_shared.mjs";
2
+ import { CLI_ENTRY, STATIC_SERVER, buildReactPdf, formatNodeScriptCommand } from "./_shared.mjs";
3
3
 
4
4
  export async function run({ root, config, options, recurse }) {
5
5
  const outputPath = options.output ? path.resolve(root, options.output) : undefined;
@@ -7,8 +7,8 @@ export async function run({ root, config, options, recurse }) {
7
7
  const relOutput = path.relative(root, outputPath ?? config.paths.pdf);
8
8
  const host = options.host ?? "127.0.0.1";
9
9
  const port = options.port ?? "5185";
10
- console.log("Command: node engine/cli.mjs render . --renderer react");
11
- console.log(`Command: node engine/static-server.mjs ${config.outputDir} --host ${host} --port ${port} --workspace .`);
10
+ console.log(`Command: ${formatNodeScriptCommand(root, CLI_ENTRY)} render . --renderer react`);
11
+ console.log(`Command: ${formatNodeScriptCommand(root, STATIC_SERVER)} ${config.outputDir} --host ${host} --port ${port} --workspace .`);
12
12
  console.log(`Command: Chrome --print-to-pdf=${relOutput} http://${host}:${port}/?print=1`);
13
13
  return 0;
14
14
  }
@@ -1,4 +1,4 @@
1
- import { runCommand } from "./_shared.mjs";
1
+ import { CLI_ENTRY, STATIC_SERVER, formatNodeScriptCommand, runCommand } from "./_shared.mjs";
2
2
 
3
3
  export async function run({ root, config, options, recurse }) {
4
4
  const renderer = options.renderer ?? "react";
@@ -12,9 +12,9 @@ export async function run({ root, config, options, recurse }) {
12
12
  if (options.dryRun) {
13
13
  console.log(`OpenPress preview URL: ${url}`);
14
14
  if (!options.noBuild) {
15
- console.log("Command: node engine/cli.mjs render . --renderer react");
15
+ console.log(`Command: ${formatNodeScriptCommand(root, CLI_ENTRY)} render . --renderer react`);
16
16
  }
17
- console.log(`Command: node engine/static-server.mjs ${config.outputDir} --host ${host} --port ${port} --workspace .`);
17
+ console.log(`Command: ${formatNodeScriptCommand(root, STATIC_SERVER)} ${config.outputDir} --host ${host} --port ${port} --workspace .`);
18
18
  return 0;
19
19
  }
20
20
  if (!options.noBuild) {
@@ -22,5 +22,5 @@ export async function run({ root, config, options, recurse }) {
22
22
  if (renderCode !== 0) return renderCode;
23
23
  }
24
24
  console.log(`OpenPress preview: ${url}`);
25
- return runCommand("node", ["engine/static-server.mjs", config.outputDir, "--host", host, "--port", port, "--workspace", "."], root);
25
+ return runCommand("node", [STATIC_SERVER, config.outputDir, "--host", host, "--port", port, "--workspace", "."], root);
26
26
  }
@@ -0,0 +1,117 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { diagnose } from "./doctor.mjs";
5
+ import { runCommand } from "./_shared.mjs";
6
+
7
+ export async function run({ root, options }) {
8
+ const dryRun = Boolean(options?.dryRun);
9
+ const skipSkills = Boolean(options?.noSkills);
10
+ const skipDeps = Boolean(options?.noDeps);
11
+ const json = Boolean(options?.json);
12
+
13
+ // 1. Fresh diagnose (force re-check, ignore cache).
14
+ const before = await diagnose(root, { noCache: true });
15
+
16
+ if (!before.stale) {
17
+ const message = "open-press is already up to date.";
18
+ if (json) {
19
+ process.stdout.write(JSON.stringify({ status: "noop", before }, null, 2) + "\n");
20
+ } else {
21
+ process.stdout.write(`✓ ${message}\n`);
22
+ }
23
+ return 0;
24
+ }
25
+
26
+ if (!json) {
27
+ process.stdout.write("○ open-press upgrade\n\n");
28
+ if (before.coreUpdateAvailable) {
29
+ process.stdout.write(
30
+ ` @open-press/core: ${before.coreVersion} → ${before.coreLatest}\n`,
31
+ );
32
+ }
33
+ if (before.pendingMigrations.length > 0) {
34
+ process.stdout.write(` migration notes: ${before.pendingMigrations.join(", ")}\n`);
35
+ }
36
+ process.stdout.write("\n");
37
+ }
38
+
39
+ if (dryRun) {
40
+ if (!json) {
41
+ process.stdout.write("dry run — nothing changed. The agent should:\n");
42
+ process.stdout.write(" 1. read each docs/migrations/<version>.md for document-level changes\n");
43
+ process.stdout.write(" 2. apply edits to document/ where needed\n");
44
+ process.stdout.write(" 3. re-run: npx open-press upgrade (without --dry-run)\n");
45
+ } else {
46
+ process.stdout.write(JSON.stringify({ status: "dry-run", before }, null, 2) + "\n");
47
+ }
48
+ return 0;
49
+ }
50
+
51
+ // 2. Refresh framework dep (only when workspace declares @open-press/core).
52
+ if (!skipDeps && (await hasCoreDep(root))) {
53
+ if (!json) process.stdout.write("▸ updating @open-press/core via npm…\n");
54
+ const code = runCommand("npm", ["update", "@open-press/core"], root);
55
+ if (code !== 0) {
56
+ if (!json) process.stdout.write(" ⚠ npm update returned non-zero; continuing\n");
57
+ }
58
+ }
59
+
60
+ // 3. Refresh skills (npx skills upgrade respects skills-lock.json).
61
+ if (!skipSkills) {
62
+ if (!json) process.stdout.write("▸ refreshing skills via npx skills upgrade…\n");
63
+ runCommand("npx", ["-y", "skills@latest", "upgrade"], root);
64
+ }
65
+
66
+ // 4. Surface migration notes for the agent to read.
67
+ const migrationContents = await loadMigrations(root, before.pendingMigrations);
68
+
69
+ // 5. Re-diagnose to confirm the move.
70
+ const after = await diagnose(root, { noCache: true });
71
+
72
+ if (json) {
73
+ process.stdout.write(
74
+ JSON.stringify(
75
+ { status: "applied", before, after, migrationContents: migrationContents.map((m) => m.path) },
76
+ null,
77
+ 2,
78
+ ) + "\n",
79
+ );
80
+ return 0;
81
+ }
82
+
83
+ process.stdout.write("\n✓ upgrade applied. Now read these migration notes:\n\n");
84
+ if (migrationContents.length === 0) {
85
+ process.stdout.write(" (no migration docs in this version range)\n\n");
86
+ } else {
87
+ for (const m of migrationContents) {
88
+ process.stdout.write(` ─ ${m.path}\n`);
89
+ }
90
+ process.stdout.write(
91
+ "\nAgent: open each file, identify document-level changes, grep document/ for affected patterns, propose edits before applying.\n",
92
+ );
93
+ }
94
+
95
+ process.stdout.write("\nVerify with:\n npm run openpress:validate\n npm run openpress:render\n\n");
96
+ return 0;
97
+ }
98
+
99
+ async function hasCoreDep(root) {
100
+ try {
101
+ const pkg = JSON.parse(await readFile(path.join(root, "package.json"), "utf8"));
102
+ return Boolean(pkg.dependencies?.["@open-press/core"] || pkg.devDependencies?.["@open-press/core"]);
103
+ } catch {
104
+ return false;
105
+ }
106
+ }
107
+
108
+ async function loadMigrations(root, versions) {
109
+ const results = [];
110
+ for (const v of versions) {
111
+ const p = path.join(root, "docs", "migrations", `${v}.md`);
112
+ if (existsSync(p)) {
113
+ results.push({ version: v, path: path.relative(root, p) });
114
+ }
115
+ }
116
+ return results;
117
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-press/core",
3
- "version": "0.3.0",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "description": "open-press core — runtime primitives, CLI, and render pipeline for AI-first fixed-layout documents.",
6
6
  "license": "MIT",
@@ -65,6 +65,8 @@
65
65
  "js-yaml": "^4.1.1",
66
66
  "katex": "^0.16.47",
67
67
  "lucide-react": "^1.16.0",
68
+ "playwright": "^1.60.0",
69
+ "postcss": "^8.5.6",
68
70
  "react": "^19.2.6",
69
71
  "react-dom": "^19.2.6",
70
72
  "rehype-katex": "^7.0.1",
@@ -9,13 +9,18 @@ import { loadConfig, publicPdfHref } from "./engine/config.mjs";
9
9
  import { handleCommentRequest } from "./engine/react/comment-endpoint.mjs";
10
10
  import { handleProjectAssetRequest } from "./engine/react/project-asset-endpoint.mjs";
11
11
 
12
- const sourceRoot = fileURLToPath(new URL("./src", import.meta.url));
13
- const workspaceRoot = fileURLToPath(new URL("./", import.meta.url));
14
- const openpressCoreEntry = fileURLToPath(new URL("./src/openpress/core/index.tsx", import.meta.url));
15
- const reactDocumentComponentsRoot = path.join(workspaceRoot, "document", "components");
12
+ const frameworkRoot = fileURLToPath(new URL("./", import.meta.url));
13
+ const workspaceRoot = process.env.OPENPRESS_WORKSPACE_ROOT
14
+ ? path.resolve(process.env.OPENPRESS_WORKSPACE_ROOT)
15
+ : frameworkRoot;
16
+ const sourceRoot = path.join(frameworkRoot, "src");
17
+ const openpressCliPath = path.join(frameworkRoot, "engine", "cli.mjs");
18
+ const staticServerPath = path.join(frameworkRoot, "engine", "static-server.mjs");
19
+ const openpressCoreEntry = path.join(frameworkRoot, "src", "openpress", "core", "index.tsx");
16
20
  const openpressConfig = await loadConfig(workspaceRoot);
17
21
  const outputDir = openpressConfig.paths.outputDir;
18
- const reactDocumentRoot = path.join(workspaceRoot, "document");
22
+ const reactDocumentRoot = openpressConfig.paths.documentRoot;
23
+ const reactDocumentComponentsRoot = openpressConfig.paths.componentsDir;
19
24
  const reactDocumentEntry = path.join(reactDocumentRoot, "index.tsx");
20
25
  const activeContentDir = await fileExists(reactDocumentEntry)
21
26
  ? path.join(reactDocumentRoot, "chapters")
@@ -48,6 +53,7 @@ export default defineConfig({
48
53
  plugins: [openpressLocalDeployPlugin(), react()],
49
54
  define: workspaceDefines,
50
55
  resolve: {
56
+ dedupe: ["react", "react-dom", "@mdx-js/react"],
51
57
  alias: {
52
58
  "@openpress/core": openpressCoreEntry,
53
59
  "@/components": reactDocumentComponentsRoot,
@@ -69,6 +75,9 @@ export default defineConfig({
69
75
  server: {
70
76
  host: "127.0.0.1",
71
77
  port: 5173,
78
+ fs: {
79
+ allow: Array.from(new Set([frameworkRoot, workspaceRoot])),
80
+ },
72
81
  watch: {
73
82
  ignored: ["**/.openpress/tmp/**", `**/${openpressConfig.outputDir}/**`],
74
83
  },
@@ -201,7 +210,7 @@ async function handleLocalPdfExportRequest(req: IncomingMessage, res: ServerResp
201
210
  ok: result.code === 0 && exists,
202
211
  code: result.code,
203
212
  pdf: `/__openpress/local-pdf-file?ts=${Date.now()}`,
204
- command: "node engine/cli.mjs pdf .",
213
+ command: openpressCliCommand(["pdf", "."]),
205
214
  stdout: result.stdout,
206
215
  stderr: result.stderr,
207
216
  });
@@ -266,7 +275,7 @@ async function handleLocalDeployRequest(req: IncomingMessage, res: ServerRespons
266
275
  deploy_adapter: openpressConfig.deploy.adapter,
267
276
  deploy_source: openpressConfig.deploy.source,
268
277
  deploy_project_name: openpressConfig.deploy.projectName,
269
- command: "node engine/cli.mjs deploy . --confirm",
278
+ command: openpressCliCommand(["deploy", ".", "--confirm"]),
270
279
  });
271
280
  return;
272
281
  }
@@ -285,7 +294,7 @@ async function handleLocalDeployRequest(req: IncomingMessage, res: ServerRespons
285
294
  pdf: deployedUrl ? `${deployedUrl}/${openpressConfig.pdf.filename}` : deploymentInfo.pdf,
286
295
  public_url: publicUrl,
287
296
  dirty: false,
288
- command: "node engine/cli.mjs deploy . --confirm",
297
+ command: openpressCliCommand(["deploy", ".", "--confirm"]),
289
298
  stdout: result.stdout,
290
299
  stderr: result.stderr,
291
300
  });
@@ -293,7 +302,7 @@ async function handleLocalDeployRequest(req: IncomingMessage, res: ServerRespons
293
302
 
294
303
  function runLocalPdfExport() {
295
304
  return new Promise<{ code: number; stdout: string; stderr: string }>((resolve) => {
296
- const child = spawn("node", ["engine/cli.mjs", "pdf", "."], {
305
+ const child = spawn("node", [openpressCliPath, "pdf", "."], {
297
306
  cwd: workspaceRoot,
298
307
  shell: false,
299
308
  });
@@ -316,7 +325,7 @@ function runLocalPdfExport() {
316
325
 
317
326
  function runLocalDeploy() {
318
327
  return new Promise<{ code: number; stdout: string; stderr: string }>((resolve) => {
319
- const child = spawn("node", ["engine/cli.mjs", "deploy", ".", "--confirm"], {
328
+ const child = spawn("node", [openpressCliPath, "deploy", ".", "--confirm"], {
320
329
  cwd: workspaceRoot,
321
330
  shell: false,
322
331
  });
@@ -405,7 +414,7 @@ function getLocalDeploymentSourcePaths() {
405
414
  openpressConfig.paths.themeDir,
406
415
  openpressConfig.paths.designDoc,
407
416
  openpressConfig.paths.componentsDir,
408
- path.join(workspaceRoot, "src"),
417
+ path.join(frameworkRoot, "src"),
409
418
  path.join(workspaceRoot, "index.html"),
410
419
  path.join(workspaceRoot, "package.json"),
411
420
  path.join(workspaceRoot, "openpress.config.mjs"),
@@ -414,6 +423,12 @@ function getLocalDeploymentSourcePaths() {
414
423
  ];
415
424
  }
416
425
 
426
+ function openpressCliCommand(args: string[]) {
427
+ const relativeCliPath = path.relative(workspaceRoot, openpressCliPath).replaceAll("\\", "/");
428
+ const displayCliPath = relativeCliPath && !relativeCliPath.startsWith("../") ? relativeCliPath : openpressCliPath;
429
+ return `node ${displayCliPath} ${args.join(" ")}`;
430
+ }
431
+
417
432
  async function findNewestLocalSourceMtime(paths: string[]) {
418
433
  const times = await Promise.all(paths.map((sourcePath) => findNewestLocalMtime(sourcePath)));
419
434
  return Math.max(0, ...times);
@@ -0,0 +1,21 @@
1
+ ## Introduction
2
+
3
+ This document is a model and starting point for an academic paper drafted in open-press. The structure follows the IEEE conference template conventions where they make sense for screen-first reading: numbered sections, italic sub-sections, an abstract block, and a numbered references list. The two-column body of IEEEtran is not yet supported — single-column will land as the v0.8 paged.js migration arrives.
4
+
5
+ Use this pack for **drafting, iteration, and preprint distribution**. When the paper is ready for venue submission, export the prose into the publisher's required LaTeX class (`IEEEtran`, `acmart`, etc.). The pack does not replace LaTeX for camera-ready submission; it replaces the awkward iteration loop *before* submission.
6
+
7
+ ### Where to start
8
+
9
+ Each chapter directory under `document/chapters/` is one paper section:
10
+
11
+ - `01-introduction/` — motivate the work, state the contribution, summarise the structure.
12
+ - `02-methods/` — describe what you did, with enough detail to reproduce.
13
+ - `03-results-and-discussion/` — present results with figures and tables; interpret what they mean.
14
+ - `04-acknowledgment/` — acknowledge funding, collaborators, reviewers.
15
+ - `05-references/` — numbered `[1]`, `[2]` references.
16
+
17
+ Replace each chapter's MDX content with your own. The skeleton text below each `##` heading is illustrative — overwrite it.
18
+
19
+ ### Abstract and Index Terms
20
+
21
+ Edit the abstract and index terms inside `document/index.tsx` (the `cover` JSX export). They render on the title page above the body. Keep the abstract under 250 words and avoid abbreviations, symbols, or math in it.
@@ -0,0 +1,30 @@
1
+ ## Methods
2
+
3
+ Describe the materials, instruments, datasets, models, or procedures you used. Anyone familiar with the field should be able to reproduce your work from this section.
4
+
5
+ ### Apparatus and Materials
6
+
7
+ Specify hardware, software versions, datasets, and any pre-processing. Cite reused work using `[N]` numeric references (e.g. "we trained the encoder following the procedure of [3]").
8
+
9
+ ### Procedure
10
+
11
+ Describe the experimental procedure as steps a reader could follow:
12
+
13
+ 1. State the input data and pre-processing.
14
+ 2. State the model / algorithm / instrument settings.
15
+ 3. State the evaluation metric(s) and any held-out splits.
16
+ 4. State randomness control (seeds, repeats, confidence reporting).
17
+
18
+ ### Notation
19
+
20
+ Define notation early. Italicise Roman symbols for quantities and variables, but not Greek symbols. Use a long dash rather than a hyphen for a minus sign. Number equations consecutively:
21
+
22
+ $$a + b = \gamma \tag{1}$$
23
+
24
+ Refer to equations by `(1)`, not `Eq. (1)` or `equation (1)`, except at the start of a sentence: "Equation (1) shows ...".
25
+
26
+ ### Reporting Units
27
+
28
+ - Use SI (MKS) as primary units; English units only as secondary (in parentheses).
29
+ - Don't mix complete spellings and abbreviations: "Wb/m²" or "webers per square meter", not "webers/m²".
30
+ - Use a zero before decimal points: `0.25`, not `.25`.
@@ -0,0 +1,29 @@
1
+ ## Results and Discussion
2
+
3
+ Present the findings of your study, then interpret what they mean for the research question stated in the introduction.
4
+
5
+ ### Quantitative Results
6
+
7
+ Use a table to summarise results across conditions. Caption above the table.
8
+
9
+ <TableCaption>Sample comparison of conditions.</TableCaption>
10
+
11
+ | Condition | Metric A | Metric B | Notes |
12
+ | --- | --- | --- | --- |
13
+ | Baseline | 0.71 | 0.65 | Prior work [3] |
14
+ | Ours (v1) | 0.78 | 0.69 | This work |
15
+ | Ours (v2) | **0.83** | **0.72** | Best |
16
+
17
+ Cite figures and tables in the text the first time they appear: "Fig. 1 shows the architecture", "Table I summarises the conditions". Use `Fig.` even at the start of a sentence; spell out `Figure` only when it is the subject.
18
+
19
+ ### Qualitative Discussion
20
+
21
+ Discuss what the results mean. State the limitations honestly — if the experiment doesn't cover a case you'd like to claim, say so. Avoid the word "essentially" when you mean "approximately" or "effectively".
22
+
23
+ ### Threats to Validity
24
+
25
+ Briefly list what could invalidate the results: small sample size, single dataset, environmental drift, evaluator bias, etc. Reviewers will look for this section; pre-empt it.
26
+
27
+ ### Future Work
28
+
29
+ Indicate where the work goes next. Keep this to one short paragraph — over-claiming future scope hurts more than it helps in review.
@@ -0,0 +1,12 @@
1
+ ## Acknowledgment
2
+
3
+ The preferred American spelling is *acknowledgment* without an "e" after the "g". Avoid stilted constructions like "one of us (R. B. G.) thanks ...". Prefer "R. B. G. thanks ...".
4
+
5
+ Acknowledge:
6
+
7
+ - Funding sources (with grant numbers if applicable).
8
+ - Collaborators who contributed materially but are not listed as authors.
9
+ - Anonymous reviewers whose comments improved the paper.
10
+ - Institutional or computational resources.
11
+
12
+ Place sponsor acknowledgments in the unnumbered footnote on the first page of the published version. In this draft pack, keep them here until you migrate to a publisher's class file at submission time.
@@ -0,0 +1,27 @@
1
+ ## References
2
+
3
+ Number citations consecutively within square brackets `[1]`. Sentence punctuation follows the bracket. Refer simply to the reference number, as in `[3]` — do not use `Ref. [3]` or `reference [3]` except at the start of a sentence: "Reference [3] was the first ...".
4
+
5
+ Unless there are six authors or more, give all authors' names; do not use `et al.`. Papers not yet published should be cited as *unpublished*; accepted but not yet published as *in press*. Capitalize only the first word in a paper title, except for proper nouns and element symbols.
6
+
7
+ For papers published in translation journals, give the English citation first, followed by the original foreign-language citation.
8
+
9
+ ---
10
+
11
+ [1] G. Eason, B. Noble, and I. N. Sneddon, "On certain integrals of Lipschitz–Hankel type involving products of Bessel functions," *Phil. Trans. Roy. Soc. London*, vol. A247, pp. 529–551, April 1955.
12
+
13
+ [2] J. Clerk Maxwell, *A Treatise on Electricity and Magnetism*, 3rd ed., vol. 2. Oxford: Clarendon, 1892, pp. 68–73.
14
+
15
+ [3] I. S. Jacobs and C. P. Bean, "Fine particles, thin films and exchange anisotropy," in *Magnetism*, vol. III, G. T. Rado and H. Suhl, Eds. New York: Academic, 1963, pp. 271–350.
16
+
17
+ [4] K. Elissa, "Title of paper if known," unpublished.
18
+
19
+ [5] R. Nicole, "Title of paper with only first word capitalized," *J. Name Stand. Abbrev.*, in press.
20
+
21
+ [6] Y. Yorozu, M. Hirano, K. Oka, and Y. Tagawa, "Electron spectroscopy studies on magneto-optical media and plastic substrate interface," *IEEE Transl. J. Magn. Japan*, vol. 2, pp. 740–741, August 1987.
22
+
23
+ [7] M. Young, *The Technical Writer's Handbook*. Mill Valley, CA: University Science, 1989.
24
+
25
+ ---
26
+
27
+ > **Reminder**: replace the seven placeholder references above with your own bibliography before circulating the draft. They are the IEEE conference template's example citations and have nothing to do with your paper.