@markdstage/markdstage 0.1.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 (67) hide show
  1. package/README.md +90 -0
  2. package/bin/markdstage.mjs +12 -0
  3. package/package.json +45 -0
  4. package/shared/README.md +1014 -0
  5. package/shared/THIRD-PARTY-NOTICES.md +19 -0
  6. package/shared/deck-state.mjs +105 -0
  7. package/shared/docs/custom-theme-authoring.md +208 -0
  8. package/shared/markdown-deck.mjs +220 -0
  9. package/shared/markdstage-guide.mjs +276 -0
  10. package/shared/presenter-window.mjs +17 -0
  11. package/shared/renderer/architecture-document.mjs +596 -0
  12. package/shared/renderer/architecture-edit.mjs +298 -0
  13. package/shared/renderer/architecture-editor.mjs +449 -0
  14. package/shared/renderer/architecture.mjs +4033 -0
  15. package/shared/renderer/import-path.mjs +11 -0
  16. package/shared/renderer/index.html +106 -0
  17. package/shared/renderer/renderer.js +2082 -0
  18. package/shared/renderer/slides.css +614 -0
  19. package/shared/renderer/speaker-notes.mjs +106 -0
  20. package/shared/renderer/theme.mjs +205 -0
  21. package/shared/runtime/browser.mjs +539 -0
  22. package/shared/runtime/custom-theme.mjs +135 -0
  23. package/shared/runtime/deck-session.mjs +188 -0
  24. package/shared/runtime/errors.mjs +17 -0
  25. package/shared/runtime/output-paths.mjs +159 -0
  26. package/shared/runtime/output.mjs +385 -0
  27. package/shared/runtime/presentation-server.mjs +505 -0
  28. package/shared/runtime/static-files.mjs +70 -0
  29. package/shared/schema/README.md +228 -0
  30. package/shared/schema/architecture-v1.schema.json +664 -0
  31. package/shared/schema/examples/web-app.architecture.json +119 -0
  32. package/shared/schema/theme-metadata-v1.schema.json +75 -0
  33. package/shared/schema/theme-v1.json +84 -0
  34. package/shared/scripts/architecture-assets.mjs +226 -0
  35. package/shared/scripts/asset-paths.mjs +92 -0
  36. package/shared/scripts/atomic-markdown-replace.mjs +46 -0
  37. package/shared/scripts/markdown-blocks.mjs +182 -0
  38. package/shared/scripts/markdown-files.mjs +63 -0
  39. package/shared/scripts/markdown-save-coordinator.mjs +18 -0
  40. package/shared/scripts/markdown-watcher.mjs +80 -0
  41. package/shared/scripts/theme-paths.mjs +108 -0
  42. package/shared/scripts/vendor-assets.mjs +132 -0
  43. package/shared/scripts/workspace-root.mjs +32 -0
  44. package/shared/vendor/highlight.LICENSE +29 -0
  45. package/shared/vendor/highlight.min.js +1244 -0
  46. package/shared/vendor/marked.min.js +6 -0
  47. package/shared/vendor/mermaid.min.js.part-0001 +268 -0
  48. package/shared/vendor/mermaid.min.js.part-0002 +304 -0
  49. package/shared/vendor/mermaid.min.js.part-0003 +324 -0
  50. package/shared/vendor/mermaid.min.js.part-0004 +374 -0
  51. package/shared/vendor/mermaid.min.js.part-0005 +564 -0
  52. package/shared/vendor/mermaid.min.js.part-0006 +1308 -0
  53. package/shared/vendor/mermaid.min.js.part-0007 +269 -0
  54. package/shared/vendor/purify.min.js +3 -0
  55. package/shared/vendor/vendor-assets.lock.json +60 -0
  56. package/src/cli.mjs +347 -0
  57. package/src/commands/capture.mjs +23 -0
  58. package/src/commands/export.mjs +18 -0
  59. package/src/commands/guide.mjs +23 -0
  60. package/src/commands/inspect.mjs +35 -0
  61. package/src/commands/present.mjs +91 -0
  62. package/src/commands/skill.mjs +114 -0
  63. package/src/commands/validate.mjs +79 -0
  64. package/src/deck.mjs +63 -0
  65. package/src/exit.mjs +58 -0
  66. package/src/runtime.mjs +77 -0
  67. package/src/skills.mjs +155 -0
@@ -0,0 +1,276 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { parseArchitecture } from "./renderer/architecture.mjs";
5
+
6
+ const EXT_DIR = dirname(fileURLToPath(import.meta.url));
7
+ const README_PATH = join(EXT_DIR, "README.md");
8
+ const SCHEMA_PATH = join(EXT_DIR, "schema", "architecture-v1.schema.json");
9
+ const THEME_GUIDE_PATH = join(EXT_DIR, "docs", "custom-theme-authoring.md");
10
+ const THEME_SCHEMA_PATH = join(EXT_DIR, "schema", "theme-v1.json");
11
+ const GUIDE_POINTER =
12
+ "Use the markdstage_guide tool to review the format and schemas before using the MarkdStage canvas.";
13
+ const PRESENTATION_PROMPT =
14
+ /\bpresent(?:ation|er|ing)?\b|\bslides?\b|slides?\.md|\bdeck\b/i;
15
+
16
+ function section(markdown, heading) {
17
+ const lines = markdown.split(/\r?\n/);
18
+ const start = lines.findIndex((line) => line.trim() === heading);
19
+ if (start < 0) throw new Error(`guide section not found: ${heading}`);
20
+ const level = heading.match(/^#+/)?.[0].length ?? 1;
21
+ let end = lines.length;
22
+ let fence = null;
23
+ for (let index = start + 1; index < lines.length; index += 1) {
24
+ const fenceMatch = lines[index].match(/^\s*(`{3,}|~{3,})/);
25
+ if (fenceMatch) {
26
+ const marker = fenceMatch[1][0];
27
+ if (!fence) fence = marker;
28
+ else if (fence === marker) fence = null;
29
+ continue;
30
+ }
31
+ if (fence) continue;
32
+ const match = lines[index].match(/^(#+)\s/);
33
+ if (match && match[1].length <= level) {
34
+ end = index;
35
+ break;
36
+ }
37
+ }
38
+ return lines.slice(start, end).join("\n").trim();
39
+ }
40
+
41
+ function architectureSchemaSummary(schema) {
42
+ const defs = schema.$defs;
43
+ const summary = {
44
+ root: {
45
+ required: schema.required,
46
+ properties: Object.keys(schema.properties),
47
+ elementTypes: [
48
+ defs.nodeBase.properties.type.const,
49
+ defs.groupBase.properties.type.const,
50
+ defs.connector.properties.type.const,
51
+ ],
52
+ },
53
+ shape: defs.nodeBase.properties.shape.enum,
54
+ icon: {
55
+ builtIn: defs.iconName.enum,
56
+ assetPath: defs.iconAsset.description,
57
+ },
58
+ connector: {
59
+ labelLayer: defs.connector.properties.labelLayer,
60
+ },
61
+ style: {
62
+ keys: Object.keys(defs.style.properties),
63
+ colors: defs.color.description,
64
+ themeTokens: defs.themeToken.enum,
65
+ literalColors: defs.literalColor.description,
66
+ },
67
+ };
68
+ return [
69
+ "# Architecture DSL v1 schema summary",
70
+ "",
71
+ "Key details extracted at runtime from the bundled `schema/architecture-v1.schema.json`.",
72
+ "",
73
+ "```json",
74
+ JSON.stringify(summary, null, 2),
75
+ "```",
76
+ ].join("\n");
77
+ }
78
+
79
+ function themeSchemaSummary(schema) {
80
+ return [
81
+ "# Custom presentation theme v1 schema",
82
+ "",
83
+ "Custom properties available for theme authoring, extracted from the bundled `schema/theme-v1.json`.",
84
+ "",
85
+ "```json",
86
+ JSON.stringify(
87
+ {
88
+ format: schema["x-theme-file-format"],
89
+ allowedValueSyntax: schema["x-value-syntax"],
90
+ metadata: schema["x-theme-metadata"],
91
+ properties: Object.fromEntries(
92
+ Object.entries(schema.properties.variables.properties).map(([name, definition]) => [
93
+ name,
94
+ definition.description,
95
+ ]),
96
+ ),
97
+ },
98
+ null,
99
+ 2,
100
+ ),
101
+ "```",
102
+ ].join("\n");
103
+ }
104
+
105
+ export async function readGuide(topic = "overview") {
106
+ const readme = await readFile(README_PATH, "utf8");
107
+ switch (topic) {
108
+ case "overview":
109
+ return [
110
+ section(readme, "## How it works"),
111
+ "",
112
+ "Users can load workspace Markdown directly with the canvas 📂 button (deterministic splitting without AI; natural-language summarization remains the AI's responsibility). The workspace root is the Git repository root when available, otherwise the folder opened for the current session.",
113
+ "Use MarkdStage's ✎ control to adjust the placement of an existing Architecture diagram. For comprehensive editing, including adding or deleting elements, open the architecture-editor canvas with sourcePath and blockIndex. Comprehensive edits affect the source Markdown only when explicitly saved.",
114
+ "",
115
+ "For details, request `slide-format`, `themes`, `custom-themes`, `theme-schema`, `architecture-dsl`, or `architecture-schema`.",
116
+ ].join("\n");
117
+ case "slide-format":
118
+ return section(readme, "### Slide fragment format");
119
+ case "themes":
120
+ return section(readme, "### Choosing a theme");
121
+ case "custom-themes":
122
+ case "custom-ehemes":
123
+ return readFile(THEME_GUIDE_PATH, "utf8");
124
+ case "theme-schema": {
125
+ const schema = JSON.parse(await readFile(THEME_SCHEMA_PATH, "utf8"));
126
+ return themeSchemaSummary(schema);
127
+ }
128
+ case "architecture-dsl":
129
+ return section(readme, "## Architecture DSL v1");
130
+ case "architecture-schema": {
131
+ const schema = JSON.parse(await readFile(SCHEMA_PATH, "utf8"));
132
+ return architectureSchemaSummary(schema);
133
+ }
134
+ default:
135
+ throw new Error(`unknown MarkdStage guide topic: ${topic}`);
136
+ }
137
+ }
138
+
139
+ export function createMarkdStageHooks() {
140
+ const primed = new Set();
141
+ return {
142
+ onSessionStart: ({ sessionId }) => {
143
+ primed.delete(sessionId);
144
+ },
145
+ onSessionEnd: ({ sessionId }) => {
146
+ primed.delete(sessionId);
147
+ },
148
+ onUserPromptSubmitted: ({ sessionId, prompt }) => {
149
+ if (primed.has(sessionId) || !PRESENTATION_PROMPT.test(prompt ?? "")) return;
150
+ primed.add(sessionId);
151
+ return { additionalContext: GUIDE_POINTER };
152
+ },
153
+ onPostToolUse: ({ sessionId, toolName }) => {
154
+ if (toolName === "markdstage_guide") primed.add(sessionId);
155
+ },
156
+ };
157
+ }
158
+
159
+ export function hasFrontMatter(markdown) {
160
+ const normalized = markdown.replace(/\r\n?/g, "\n").replace(/^[\n \t\uFEFF]+/, "");
161
+ if (!normalized.startsWith("---\n")) return false;
162
+ return normalized.split("\n").slice(1).some((line) => line.trim() === "---");
163
+ }
164
+
165
+ function architectureSources(markdown) {
166
+ const sources = [];
167
+ let fence = "";
168
+ let lines = [];
169
+ for (const line of markdown.split(/\r?\n/)) {
170
+ if (!fence) {
171
+ const opening = line.match(/^\s*(`{3,}|~{3,})architecture\s*$/i);
172
+ if (opening) {
173
+ fence = opening[1];
174
+ lines = [];
175
+ }
176
+ continue;
177
+ }
178
+ const closing = new RegExp(`^${fence[0]}{${fence.length},}\\s*$`);
179
+ if (closing.test(line.trim())) {
180
+ sources.push(lines.join("\n"));
181
+ fence = "";
182
+ lines = [];
183
+ } else {
184
+ lines.push(line);
185
+ }
186
+ }
187
+ return {
188
+ sources,
189
+ unclosed: Boolean(fence),
190
+ unclosedBlockIndex: sources.length,
191
+ unclosedSource: fence ? lines.join("\n") : "",
192
+ };
193
+ }
194
+
195
+ function architectureError(slideIndex, blockIndex, code, message) {
196
+ return {
197
+ slideIndex,
198
+ page: slideIndex + 1,
199
+ blockIndex,
200
+ architecture: blockIndex + 1,
201
+ code,
202
+ message,
203
+ };
204
+ }
205
+
206
+ export function architectureValidationErrors(slides, { index } = {}) {
207
+ const targets = index === undefined
208
+ ? slides.map((slide, slideIndex) => ({ slide, slideIndex }))
209
+ : [{ slide: slides[index], slideIndex: index }];
210
+ const errors = [];
211
+
212
+ for (const { slide, slideIndex } of targets) {
213
+ const architecture = architectureSources(slide);
214
+ for (const [blockIndex, source] of architecture.sources.entries()) {
215
+ try {
216
+ parseArchitecture(source);
217
+ } catch (error) {
218
+ errors.push(
219
+ architectureError(
220
+ slideIndex,
221
+ blockIndex,
222
+ "invalid_architecture",
223
+ error?.message || String(error),
224
+ ),
225
+ );
226
+ }
227
+ }
228
+ if (architecture.unclosed) {
229
+ try {
230
+ parseArchitecture(architecture.unclosedSource);
231
+ } catch (error) {
232
+ errors.push(
233
+ architectureError(
234
+ slideIndex,
235
+ architecture.unclosedBlockIndex,
236
+ "invalid_architecture",
237
+ error?.message || String(error),
238
+ ),
239
+ );
240
+ }
241
+ errors.push(
242
+ architectureError(
243
+ slideIndex,
244
+ architecture.unclosedBlockIndex,
245
+ "unclosed_architecture_fence",
246
+ "The architecture code fence is not closed. Add ``` at the end.",
247
+ ),
248
+ );
249
+ }
250
+ }
251
+
252
+ return errors;
253
+ }
254
+
255
+ export function deckValidationFeedback(slides) {
256
+ const warnings = [];
257
+ slides.forEach((slide, slideIndex) => {
258
+ if (!hasFrontMatter(slide)) {
259
+ warnings.push(
260
+ `slide ${slideIndex + 1}: front matter is missing. Add the required deck/layout/page/total/size fields to the leading --- block.`,
261
+ );
262
+ }
263
+ });
264
+ for (const error of architectureValidationErrors(slides)) {
265
+ if (error.code === "unclosed_architecture_fence") {
266
+ warnings.push(
267
+ `slide ${error.page}: ${error.message}`,
268
+ );
269
+ continue;
270
+ }
271
+ warnings.push(
272
+ `slide ${error.page}, architecture ${error.architecture}: ${error.message}. Review architecture-dsl and architecture-schema in markdstage_guide.`,
273
+ );
274
+ }
275
+ return warnings.length ? `Slide validation feedback:\n- ${warnings.join("\n- ")}` : undefined;
276
+ }
@@ -0,0 +1,17 @@
1
+ export const PRESENTER_WINDOW_WIDTH = 1280;
2
+ export const PRESENTER_WINDOW_HEIGHT = 720;
3
+
4
+ export function buildPresenterBrowserArgs({ profileDir, presenterUrl }) {
5
+ return [
6
+ "--disable-background-mode",
7
+ "--disable-component-update",
8
+ "--disable-default-apps",
9
+ "--disable-extensions",
10
+ "--disable-session-crashed-bubble",
11
+ "--no-default-browser-check",
12
+ "--no-first-run",
13
+ `--window-size=${PRESENTER_WINDOW_WIDTH},${PRESENTER_WINDOW_HEIGHT}`,
14
+ `--user-data-dir=${profileDir}`,
15
+ `--app=${presenterUrl}`,
16
+ ];
17
+ }