@markii/host 0.0.1 → 0.17.0-next.3

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 (193) hide show
  1. package/README.md +20 -2
  2. package/dist/browser-worker.js +639 -0
  3. package/dist/browser.d.ts +61 -0
  4. package/dist/browser.js +172 -0
  5. package/dist/conformance/host/01-plain-open/expected.json +5 -0
  6. package/dist/conformance/host/01-plain-open/note.mk.md +8 -0
  7. package/dist/conformance/host/01-plain-open/scenario.json +6 -0
  8. package/dist/conformance/host/02-run-manual-grant-accepted/expected.json +16 -0
  9. package/dist/conformance/host/02-run-manual-grant-accepted/note.mk.md +5 -0
  10. package/dist/conformance/host/02-run-manual-grant-accepted/scenario.json +14 -0
  11. package/dist/conformance/host/03-run-manual-grant-refused/expected.json +18 -0
  12. package/dist/conformance/host/03-run-manual-grant-refused/note.mk.md +5 -0
  13. package/dist/conformance/host/03-run-manual-grant-refused/scenario.json +14 -0
  14. package/dist/conformance/host/04-run-auto-write-denied/expected.json +16 -0
  15. package/dist/conformance/host/04-run-auto-write-denied/note.mk.md +10 -0
  16. package/dist/conformance/host/04-run-auto-write-denied/scenario.json +13 -0
  17. package/dist/conformance/host/05-export-html-pack-directive/expected.json +14 -0
  18. package/dist/conformance/host/05-export-html-pack-directive/note.mk.md +8 -0
  19. package/dist/conformance/host/05-export-html-pack-directive/scenario.json +6 -0
  20. package/dist/conformance/host/06-install-pack-then-render/expected.json +18 -0
  21. package/dist/conformance/host/06-install-pack-then-render/files/ana-1.0.0/pack.json +13 -0
  22. package/dist/conformance/host/06-install-pack-then-render/files/ana-1.0.0/webview.js +1 -0
  23. package/dist/conformance/host/06-install-pack-then-render/note.mk.md +5 -0
  24. package/dist/conformance/host/06-install-pack-then-render/scenario.json +15 -0
  25. package/dist/conformance/host/07-complete-in-directive/expected.json +14 -0
  26. package/dist/conformance/host/07-complete-in-directive/note.mk.md +3 -0
  27. package/dist/conformance/host/07-complete-in-directive/scenario.json +6 -0
  28. package/dist/conformance/host/08-hover-standard-component/expected.json +5 -0
  29. package/dist/conformance/host/08-hover-standard-component/note.mk.md +5 -0
  30. package/dist/conformance/host/08-hover-standard-component/scenario.json +6 -0
  31. package/dist/conformance/host/09-insert-skeleton/expected.json +5 -0
  32. package/dist/conformance/host/09-insert-skeleton/note.mk.md +4 -0
  33. package/dist/conformance/host/09-insert-skeleton/scenario.json +6 -0
  34. package/dist/conformance/host/10-scripts-disabled/expected.json +11 -0
  35. package/dist/conformance/host/10-scripts-disabled/note.mk.md +5 -0
  36. package/dist/conformance/host/10-scripts-disabled/scenario.json +9 -0
  37. package/dist/conformance/host/11-complete-closing-fence/expected.json +5 -0
  38. package/dist/conformance/host/11-complete-closing-fence/note.mk.md +5 -0
  39. package/dist/conformance/host/11-complete-closing-fence/scenario.json +12 -0
  40. package/dist/conformance/host/12-complete-nested-container/expected.json +5 -0
  41. package/dist/conformance/host/12-complete-nested-container/note.mk.md +5 -0
  42. package/dist/conformance/host/12-complete-nested-container/scenario.json +12 -0
  43. package/dist/conformance/host/13-install-pack-declined/expected.json +12 -0
  44. package/dist/conformance/host/13-install-pack-declined/files/ana-1.0.0/pack.json +13 -0
  45. package/dist/conformance/host/13-install-pack-declined/files/ana-1.0.0/webview.js +1 -0
  46. package/dist/conformance/host/13-install-pack-declined/note.mk.md +5 -0
  47. package/dist/conformance/host/13-install-pack-declined/scenario.json +10 -0
  48. package/dist/conformance/host/14-remove-pack/expected.json +14 -0
  49. package/dist/conformance/host/14-remove-pack/files/ana-1.0.0/pack.json +13 -0
  50. package/dist/conformance/host/14-remove-pack/files/ana-1.0.0/webview.js +1 -0
  51. package/dist/conformance/host/14-remove-pack/note.mk.md +5 -0
  52. package/dist/conformance/host/14-remove-pack/scenario.json +11 -0
  53. package/dist/conformance/host/15-load-packs-engine-views/expected.json +19 -0
  54. package/dist/conformance/host/15-load-packs-engine-views/files/duo-1.0.0/pack.json +9 -0
  55. package/dist/conformance/host/15-load-packs-engine-views/files/duo-1.0.0/webview.js +1 -0
  56. package/dist/conformance/host/15-load-packs-engine-views/note.mk.md +9 -0
  57. package/dist/conformance/host/15-load-packs-engine-views/scenario.json +10 -0
  58. package/dist/conformance/host/16-pack-trust/expected.json +37 -0
  59. package/dist/conformance/host/16-pack-trust/files/ana-1.0.0/pack.json +12 -0
  60. package/dist/conformance/host/16-pack-trust/files/ana-1.0.0/webview.js +1 -0
  61. package/dist/conformance/host/16-pack-trust/files/sneaky-1.0.0/pack.json +12 -0
  62. package/dist/conformance/host/16-pack-trust/files/sneaky-1.0.0/webview.js +1 -0
  63. package/dist/conformance/host/16-pack-trust/note.mk.md +5 -0
  64. package/dist/conformance/host/16-pack-trust/scenario.json +26 -0
  65. package/dist/conformance/host/README.md +221 -0
  66. package/dist/conformance.d.ts +2 -0
  67. package/dist/conformance.js +5 -0
  68. package/dist/diagnostics/render-diagnostics.d.ts +41 -0
  69. package/dist/diagnostics/render-diagnostics.js +46 -0
  70. package/dist/export/cascade.d.ts +98 -0
  71. package/dist/export/cascade.js +159 -0
  72. package/dist/export/export-zip.d.ts +19 -0
  73. package/dist/export/export-zip.js +38 -0
  74. package/dist/export/image-embed.d.ts +126 -0
  75. package/dist/export/image-embed.js +274 -0
  76. package/dist/export/note-export.d.ts +332 -0
  77. package/dist/export/note-export.js +366 -0
  78. package/dist/export/note-links.d.ts +83 -0
  79. package/dist/export/note-links.js +241 -0
  80. package/dist/glue.wasm +0 -0
  81. package/dist/host/adapter.d.ts +218 -0
  82. package/dist/host/adapter.js +9 -0
  83. package/dist/host/cascade-export-behavior.d.ts +120 -0
  84. package/dist/host/cascade-export-behavior.js +151 -0
  85. package/dist/host/conformance-runner.d.ts +159 -0
  86. package/dist/host/conformance-runner.js +632 -0
  87. package/dist/host/create-host.d.ts +251 -0
  88. package/dist/host/create-host.js +329 -0
  89. package/dist/host/editor-behavior.d.ts +153 -0
  90. package/dist/host/editor-behavior.js +223 -0
  91. package/dist/host/export/md-plain.d.ts +15 -0
  92. package/dist/host/export/md-plain.js +125 -0
  93. package/dist/host/export-behavior.d.ts +111 -0
  94. package/dist/host/export-behavior.js +257 -0
  95. package/dist/host/labels.d.ts +23 -0
  96. package/dist/host/labels.js +32 -0
  97. package/dist/host/pack-archive.d.ts +30 -0
  98. package/dist/host/pack-archive.js +62 -0
  99. package/dist/host/pack-install.d.ts +87 -0
  100. package/dist/host/pack-install.js +186 -0
  101. package/dist/host/pack-load.d.ts +71 -0
  102. package/dist/host/pack-load.js +57 -0
  103. package/dist/host/pack-remove.d.ts +21 -0
  104. package/dist/host/pack-remove.js +65 -0
  105. package/dist/host/pack-trust-behavior.d.ts +79 -0
  106. package/dist/host/pack-trust-behavior.js +262 -0
  107. package/dist/host/refresh-interval.d.ts +37 -0
  108. package/dist/host/refresh-interval.js +59 -0
  109. package/dist/host/run-behavior.d.ts +88 -0
  110. package/dist/host/run-behavior.js +189 -0
  111. package/dist/host/run-diagnostics.d.ts +26 -0
  112. package/dist/host/run-diagnostics.js +43 -0
  113. package/dist/host/script-execution.d.ts +72 -0
  114. package/dist/host/script-execution.js +68 -0
  115. package/dist/host/terminal-packs.d.ts +39 -0
  116. package/dist/host/terminal-packs.js +51 -0
  117. package/dist/html-engine.d.ts +2 -0
  118. package/dist/html-engine.js +29 -0
  119. package/dist/index.d.ts +95 -0
  120. package/dist/index.js +69 -0
  121. package/dist/insert/component-catalog.d.ts +23 -0
  122. package/dist/insert/component-catalog.js +96 -0
  123. package/dist/node-fs.d.ts +14 -0
  124. package/dist/node-fs.js +70 -0
  125. package/dist/node-worker.js +20657 -0
  126. package/dist/node.d.ts +6 -0
  127. package/dist/node.js +13 -0
  128. package/dist/package.json +3 -0
  129. package/dist/packs/discover.d.ts +119 -0
  130. package/dist/packs/discover.js +266 -0
  131. package/dist/packs/pack-diagnostics.d.ts +79 -0
  132. package/dist/packs/pack-diagnostics.js +103 -0
  133. package/dist/packs/pack-paths.d.ts +53 -0
  134. package/dist/packs/pack-paths.js +105 -0
  135. package/dist/packs/pack-render-registry.d.ts +82 -0
  136. package/dist/packs/pack-render-registry.js +182 -0
  137. package/dist/packs/pack-scripts.d.ts +20 -0
  138. package/dist/packs/pack-scripts.js +96 -0
  139. package/dist/packs/pack-trust.d.ts +57 -0
  140. package/dist/packs/pack-trust.js +111 -0
  141. package/dist/packs/prebuilt.d.ts +49 -0
  142. package/dist/packs/prebuilt.js +97 -0
  143. package/dist/path-util.d.ts +24 -0
  144. package/dist/path-util.js +82 -0
  145. package/dist/run/browser-isolate.d.ts +45 -0
  146. package/dist/run/browser-isolate.js +85 -0
  147. package/dist/run/bundle-document.d.ts +29 -0
  148. package/dist/run/bundle-document.js +40 -0
  149. package/dist/run/bundle-run.d.ts +188 -0
  150. package/dist/run/bundle-run.js +232 -0
  151. package/dist/run/grant-flow.d.ts +232 -0
  152. package/dist/run/grant-flow.js +390 -0
  153. package/dist/run/ip-address.d.ts +41 -0
  154. package/dist/run/ip-address.js +255 -0
  155. package/dist/run/isolate.d.ts +59 -0
  156. package/dist/run/isolate.js +22 -0
  157. package/dist/run/lua-resolver.d.ts +21 -0
  158. package/dist/run/lua-resolver.js +63 -0
  159. package/dist/run/net-bridge-worker.d.ts +26 -0
  160. package/dist/run/net-bridge-worker.js +66 -0
  161. package/dist/run/net-bridge.d.ts +56 -0
  162. package/dist/run/net-bridge.js +59 -0
  163. package/dist/run/net-pinning.d.ts +82 -0
  164. package/dist/run/net-pinning.js +137 -0
  165. package/dist/run/net-provider.d.ts +72 -0
  166. package/dist/run/net-provider.js +303 -0
  167. package/dist/run/node-isolate.d.ts +22 -0
  168. package/dist/run/node-isolate.js +68 -0
  169. package/dist/run/run-flow.d.ts +186 -0
  170. package/dist/run/run-flow.js +323 -0
  171. package/dist/run/run-host.d.ts +107 -0
  172. package/dist/run/run-host.js +241 -0
  173. package/dist/run/run-job.d.ts +130 -0
  174. package/dist/run/run-job.js +279 -0
  175. package/dist/run/run-progress.d.ts +55 -0
  176. package/dist/run/run-progress.js +42 -0
  177. package/dist/run/run-trace.d.ts +44 -0
  178. package/dist/run/run-trace.js +36 -0
  179. package/dist/run/script-requirements.d.ts +98 -0
  180. package/dist/run/script-requirements.js +212 -0
  181. package/dist/run/snapshot-storage.d.ts +25 -0
  182. package/dist/run/snapshot-storage.js +31 -0
  183. package/dist/run/stale-values.d.ts +25 -0
  184. package/dist/run/stale-values.js +32 -0
  185. package/dist/run/worker-entry-browser.d.ts +62 -0
  186. package/dist/run/worker-entry-browser.js +172 -0
  187. package/dist/run/worker-entry.d.ts +1 -0
  188. package/dist/run/worker-entry.js +98 -0
  189. package/dist/values-failure.d.ts +17 -0
  190. package/dist/values-failure.js +1 -0
  191. package/dist/values-merge.d.ts +39 -0
  192. package/dist/values-merge.js +33 -0
  193. package/package.json +80 -4
@@ -0,0 +1,257 @@
1
+ import { buildNoteExport, exportedFileName, noteHasScripts, } from '../export/note-export.js';
2
+ import { EMPTY_IMAGE_REPORT, MAX_EMBEDDED_IMAGE_BYTES, } from '../export/image-embed.js';
3
+ import { mdPlainFromSource } from './export/md-plain.js';
4
+ /** The file name an export lands under, by format: the note's own base name with the format's own extension, so the export lands beside the note unless the host's picker says otherwise. */
5
+ export function exportDefaultFileName(notePath, format) {
6
+ const extension = {
7
+ html: '.html',
8
+ pdf: '.pdf',
9
+ ansi: '.txt',
10
+ 'md-plain': '.md',
11
+ };
12
+ return exportedFileName(notePath, extension[format]);
13
+ }
14
+ /** The last path segment of a `/`-separated or `\`-separated path, for naming a file in a message. */
15
+ export function fileNameOf(path) {
16
+ const segments = path.split(/[/\\]/);
17
+ return segments[segments.length - 1] ?? path;
18
+ }
19
+ /**
20
+ * The short result message for a written or failed html/pdf export. A
21
+ * success names the file. A failure says what failed and points at this
22
+ * host's own diagnostics surface (`labels.diagnosticsSurface`), never at
23
+ * a stack trace — the verbatim reason goes there instead.
24
+ */
25
+ export function exportResultMessage(outcome, labels) {
26
+ if (outcome.kind === 'failed') {
27
+ return `Markii: could not export this note. Open ${labels.diagnosticsSurface} for details.`;
28
+ }
29
+ const name = fileNameOf(outcome.path);
30
+ return outcome.valueCount === 0 && outcome.hasScripts === true
31
+ ? `Markii: exported ${name}. The note has no stored script values, so data-bound components show their empty states.`
32
+ : `Markii: exported ${name}.`;
33
+ }
34
+ /** One diagnostic line describing HOW an export's body was rendered: the pack-vs-static distinction, kept off the result message and put here instead. */
35
+ export function renderEngineDiagnosticLine(render) {
36
+ if (render.engine === 'react') {
37
+ const packs = render.packCount === 1 ? '1 pack' : `${String(render.packCount)} packs`;
38
+ const stylesheets = render.stylesheetCount === 1
39
+ ? '1 stylesheet'
40
+ : `${String(render.stylesheetCount)} stylesheets`;
41
+ return `Rendered through the preview's React engine, with ${packs} and ${stylesheets} embedded.`;
42
+ }
43
+ if (render.reason === 'no-packs') {
44
+ return 'Rendered with the static engine because no pack components are loaded, which matches what the preview shows.';
45
+ }
46
+ if (render.reason === 'no-renderer') {
47
+ return 'Rendered with the static engine because a preview panel could not be opened for this note. Pack components exported as labeled boxes; open the preview and export again to include them.';
48
+ }
49
+ if (render.reason === 'timeout') {
50
+ const detail = render.detail ? ` Detail: ${render.detail}` : '';
51
+ return `Rendered with the static engine because the preview did not answer in time. Pack components exported as labeled boxes.${detail}`;
52
+ }
53
+ const detail = render.detail ? ` Detail: ${render.detail}` : '';
54
+ return `Rendered with the static engine because the preview could not render the note. Pack components exported as labeled boxes.${detail}`;
55
+ }
56
+ /** One skipped image's diagnostics line: which file, and why it kept its original source instead of being embedded. */
57
+ function skippedImageLine(skipped) {
58
+ if (skipped.reason === 'too-large') {
59
+ const size = skipped.byteLength !== undefined
60
+ ? formatByteSize(skipped.byteLength)
61
+ : 'an unknown size';
62
+ return `Skipped ${skipped.src}: ${size}, over the ${formatByteSize(MAX_EMBEDDED_IMAGE_BYTES)} embed limit.`;
63
+ }
64
+ if (skipped.reason === 'unsupported-type') {
65
+ return `Skipped ${skipped.src}: this file type is not supported for embedding.`;
66
+ }
67
+ return `Skipped ${skipped.src}: ${skipped.detail ?? 'the file could not be read'}.`;
68
+ }
69
+ /** `bytes`, formatted as a human-readable size (`"1.2 KB"`, `"512 bytes"`). */
70
+ export function formatByteSize(bytes) {
71
+ if (bytes < 1024)
72
+ return bytes === 1 ? '1 byte' : `${String(bytes)} bytes`;
73
+ const kb = bytes / 1024;
74
+ if (kb < 1024)
75
+ return `${kb.toFixed(1)} KB`;
76
+ return `${(kb / 1024).toFixed(1)} MB`;
77
+ }
78
+ /** The image lines for `exportDiagnosticLines`: how many images were embedded and how many bytes they added, one line per skipped image, and a remote-source count when any sources still reach the network. Empty when a note has no images at all. */
79
+ export function imageEmbedDiagnosticLines(images) {
80
+ const lines = [];
81
+ if (images.embedded.length > 0) {
82
+ const count = images.embedded.length === 1
83
+ ? '1 image'
84
+ : `${String(images.embedded.length)} images`;
85
+ lines.push(`Embedded ${count}, adding ${formatByteSize(images.embeddedBytes)} to the exported file.`);
86
+ }
87
+ for (const skipped of images.skipped) {
88
+ lines.push(skippedImageLine(skipped));
89
+ }
90
+ if (images.remote > 0) {
91
+ const line = images.remote === 1
92
+ ? '1 image source still points at a remote URL and needs network access to load.'
93
+ : `${String(images.remote)} image sources still point at remote URLs and need network access to load.`;
94
+ lines.push(line);
95
+ }
96
+ return lines;
97
+ }
98
+ /** The full diagnostics-surface detail for one html/pdf export attempt. */
99
+ export function exportDiagnosticLines(outcome) {
100
+ if (outcome.kind === 'failed') {
101
+ const where = outcome.path ? ` to ${outcome.path}` : '';
102
+ return [`Export failed${where}: ${outcome.reason}`];
103
+ }
104
+ return [
105
+ `Export wrote ${outcome.path}: ${String(outcome.bytes)} bytes, ${String(outcome.valueCount)} stored values baked in.`,
106
+ renderEngineDiagnosticLine(outcome.render),
107
+ ...imageEmbedDiagnosticLines(outcome.images),
108
+ ];
109
+ }
110
+ /** Builds the standalone HTML document `html` and `pdf` both export, via the shared, already-host-neutral `buildNoteExport`. Never throws: a failing `renderBody` degrades to the static engine rather than propagating. */
111
+ export async function buildExportDocument(request) {
112
+ const values = request.values ?? {};
113
+ const document = await buildNoteExport({
114
+ engine: request.engine,
115
+ text: request.text,
116
+ fileName: request.notePath,
117
+ values,
118
+ ...(request.renderBody !== undefined
119
+ ? { renderBody: request.renderBody }
120
+ : {}),
121
+ ...(request.staticReason !== undefined
122
+ ? { staticReason: request.staticReason }
123
+ : {}),
124
+ ...(request.packStylesheets !== undefined
125
+ ? { packStylesheets: request.packStylesheets }
126
+ : {}),
127
+ ...(request.packCount !== undefined
128
+ ? { packCount: request.packCount }
129
+ : {}),
130
+ ...(request.embedImages !== undefined
131
+ ? { embedImages: request.embedImages }
132
+ : {}),
133
+ ...(request.hideScriptBlocks !== undefined
134
+ ? { hideScriptBlocks: request.hideScriptBlocks }
135
+ : {}),
136
+ ...(request.unavailableComponents !== undefined
137
+ ? { unavailableComponents: request.unavailableComponents }
138
+ : {}),
139
+ ...(request.onDiagnostic !== undefined
140
+ ? { onDiagnostic: request.onDiagnostic }
141
+ : {}),
142
+ });
143
+ return {
144
+ html: document.html,
145
+ valueCount: document.valueCount,
146
+ hasScripts: noteHasScripts(request.text),
147
+ render: document.render,
148
+ images: document.images,
149
+ };
150
+ }
151
+ function reasonOf(error) {
152
+ return error instanceof Error ? error.message : String(error);
153
+ }
154
+ /**
155
+ * Exports one note as `format`, gated on `adapter.exports?.formats`. A
156
+ * format the host has not declared returns `undefined` immediately
157
+ * (`createMarkiiHost`'s `exportNote` turns that into the shared
158
+ * `Unsupported` outcome and writes its own diagnostics line, matching
159
+ * every other capability-gated behavior).
160
+ *
161
+ * `renderAnsi` is required to actually produce an `'ansi'` outcome — a
162
+ * host that offers the `'ansi'` FORMAT capability but passes no renderer
163
+ * is a configuration bug on that host's part, not a Markii user's problem
164
+ * to see, so it also resolves to `undefined` here (never a thrown error)
165
+ * and the caller's `Unsupported` fallback covers it.
166
+ */
167
+ export async function exportNoteFileViaAdapter(adapter, format, request, renderAnsi) {
168
+ const formats = adapter.exports?.formats;
169
+ if (formats === undefined || !formats.has(format))
170
+ return undefined;
171
+ if (format === 'html' || format === 'pdf') {
172
+ const engine = request.engine;
173
+ if (engine === undefined)
174
+ return undefined;
175
+ try {
176
+ const built = await buildExportDocument({ ...request, engine });
177
+ if (format === 'html') {
178
+ const bytes = new TextEncoder().encode(built.html).byteLength;
179
+ const path = await adapter.exports?.resolveTarget(exportDefaultFileName(request.notePath, 'html'), 'html');
180
+ if (path === undefined)
181
+ return undefined;
182
+ await adapter.writeFile(path, new TextEncoder().encode(built.html));
183
+ return {
184
+ kind: 'written',
185
+ path,
186
+ bytes,
187
+ valueCount: built.valueCount,
188
+ hasScripts: built.hasScripts,
189
+ render: built.render,
190
+ images: built.images,
191
+ };
192
+ }
193
+ // 'pdf': the seam only. Degradation policy stays app-side.
194
+ const htmlToPdf = adapter.exports?.htmlToPdf;
195
+ if (htmlToPdf === undefined)
196
+ return undefined;
197
+ const path = await adapter.exports?.resolveTarget(exportDefaultFileName(request.notePath, 'pdf'), 'pdf');
198
+ if (path === undefined)
199
+ return undefined;
200
+ const pdfBytes = await htmlToPdf(built.html);
201
+ await adapter.writeFile(path, pdfBytes);
202
+ return {
203
+ kind: 'written',
204
+ path,
205
+ bytes: pdfBytes.byteLength,
206
+ valueCount: built.valueCount,
207
+ hasScripts: built.hasScripts,
208
+ render: built.render,
209
+ images: built.images,
210
+ };
211
+ }
212
+ catch (error) {
213
+ return { kind: 'failed', reason: reasonOf(error) };
214
+ }
215
+ }
216
+ if (format === 'md-plain') {
217
+ try {
218
+ const text = mdPlainFromSource(request.text);
219
+ const path = await adapter.exports?.resolveTarget(exportDefaultFileName(request.notePath, 'md-plain'), 'md-plain');
220
+ if (path === undefined)
221
+ return undefined;
222
+ await adapter.writeFile(path, new TextEncoder().encode(text));
223
+ return {
224
+ kind: 'written',
225
+ path,
226
+ bytes: new TextEncoder().encode(text).byteLength,
227
+ valueCount: 0,
228
+ render: { engine: 'static', reason: 'no-packs' },
229
+ images: EMPTY_IMAGE_REPORT,
230
+ };
231
+ }
232
+ catch (error) {
233
+ return { kind: 'failed', reason: reasonOf(error) };
234
+ }
235
+ }
236
+ // format === 'ansi'
237
+ if (renderAnsi === undefined)
238
+ return undefined;
239
+ try {
240
+ const text = await renderAnsi(request.text);
241
+ const path = await adapter.exports?.resolveTarget(exportDefaultFileName(request.notePath, 'ansi'), 'ansi');
242
+ if (path === undefined)
243
+ return undefined;
244
+ await adapter.writeFile(path, new TextEncoder().encode(text));
245
+ return {
246
+ kind: 'written',
247
+ path,
248
+ bytes: new TextEncoder().encode(text).byteLength,
249
+ valueCount: 0,
250
+ render: { engine: 'static', reason: 'no-packs' },
251
+ images: EMPTY_IMAGE_REPORT,
252
+ };
253
+ }
254
+ catch (error) {
255
+ return { kind: 'failed', reason: reasonOf(error) };
256
+ }
257
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * `HostLabels`: the wording nouns every shared behavior module
3
+ * (`./script-execution.ts`, `./refresh-interval.ts`) interpolates, and the
4
+ * one concrete record per app this repo ships. Each app's `HostAdapter`
5
+ * imports its record rather than inventing its own strings, which is what
6
+ * lets one template serve every host.
7
+ *
8
+ * Pure and dependency-free: re-exported from `../browser.ts` as well as
9
+ * `../index.ts` so VS Code's Node-free webview bundle can read it too.
10
+ */
11
+ import type { HostLabels } from './adapter.js';
12
+ /** The VS Code extension's labels, taken from its current wording (`apps/vscode/src/script-execution.ts`, `apps/vscode/src/extension.ts`'s output channel). */
13
+ export declare const VSCODE_LABELS: HostLabels;
14
+ /** The Obsidian plugin's labels, taken from its current wording (`apps/obsidian/src/script-execution.ts`, docs/integration.md's host checklist). */
15
+ export declare const OBSIDIAN_LABELS: HostLabels;
16
+ /** The `markii` command line tool's labels: it has no persistent preview panel, and its diagnostics surface is stderr, gated behind `--verbose` for anything short of a failure (`apps/cli/src/main.ts`). */
17
+ export declare const CLI_LABELS: HostLabels;
18
+ /**
19
+ * The quiet marker a rendered note shows when its `uses:` list names packs
20
+ * this host has not installed (docs/packs.md). One sentence for every host;
21
+ * the tooltip beside it, which says how to install, stays host-specific.
22
+ */
23
+ export declare function packsNotInstalledLabel(missing: readonly string[]): string;
@@ -0,0 +1,32 @@
1
+ /** The VS Code extension's labels, taken from its current wording (`apps/vscode/src/script-execution.ts`, `apps/vscode/src/extension.ts`'s output channel). */
2
+ export const VSCODE_LABELS = {
3
+ appNoun: 'extension',
4
+ previewNoun: 'the Markii preview',
5
+ diagnosticsSurface: 'the Markii output channel',
6
+ deviceNoun: 'this device',
7
+ settingName: 'markii.scriptsDisabled',
8
+ };
9
+ /** The Obsidian plugin's labels, taken from its current wording (`apps/obsidian/src/script-execution.ts`, docs/integration.md's host checklist). */
10
+ export const OBSIDIAN_LABELS = {
11
+ appNoun: 'plugin',
12
+ previewNoun: 'the Markii preview',
13
+ diagnosticsSurface: 'the Markii diagnostics',
14
+ deviceNoun: 'this device',
15
+ };
16
+ /** The `markii` command line tool's labels: it has no persistent preview panel, and its diagnostics surface is stderr, gated behind `--verbose` for anything short of a failure (`apps/cli/src/main.ts`). */
17
+ export const CLI_LABELS = {
18
+ appNoun: 'command line tool',
19
+ previewNoun: 'the rendered note',
20
+ diagnosticsSurface: 'stderr',
21
+ deviceNoun: 'this device',
22
+ };
23
+ /**
24
+ * The quiet marker a rendered note shows when its `uses:` list names packs
25
+ * this host has not installed (docs/packs.md). One sentence for every host;
26
+ * the tooltip beside it, which says how to install, stays host-specific.
27
+ */
28
+ export function packsNotInstalledLabel(missing) {
29
+ return missing.length === 1
30
+ ? `Pack not installed: ${missing[0]}`
31
+ : `Packs not installed: ${missing.join(', ')}`;
32
+ }
@@ -0,0 +1,30 @@
1
+ import type { PackArchiveContents, PackArchiveError } from '@markii/pack';
2
+ /** One line describing why `openPackArchive` rejected an archive, for a host's install-diagnostics wording. Passes `kind: 'zip'`'s and `kind: 'missing-entry'`'s message through verbatim (already specific); summarizes a manifest failure plainly. */
3
+ export declare function describeArchiveError(error: PackArchiveError): string;
4
+ /** The filesystem write seam `writeArchiveContents` needs (`./pack-install.ts`). Deliberately narrow: extracting an archive only ever creates files under a directory it first clears, it never reads back or deletes anything else. */
5
+ export interface ArchiveExtractFs {
6
+ /** Removes `absolutePath` and everything under it, or resolves quietly if it does not exist. Never rejects: a directory that fails to clear is treated the same as a fresh one, and the write that follows fails loudly on its own if the path is genuinely unusable. */
7
+ readonly removeDirectory: (absolutePath: string) => Promise<void>;
8
+ /** Creates a directory and any missing parents. May reject on a genuine I/O failure. */
9
+ readonly makeDirectory: (absolutePath: string) => Promise<void>;
10
+ readonly writeFile: (absolutePath: string, bytes: Uint8Array) => Promise<void>;
11
+ }
12
+ /**
13
+ * Writes a validated archive's contents into `destinationDir`: `pack.json`
14
+ * (the parsed manifest, re-serialized: `openPackArchive` keeps only the
15
+ * validated manifest fields, not the original bytes, so the written file
16
+ * is a normalized round-trip of the same fields, not a byte-for-byte
17
+ * copy), `webview.js`, `webview.css` and `terminal.js` when the archive has them, and
18
+ * `scripts/*` when it ships any. Does NOT clear `destinationDir` first;
19
+ * a caller that needs a clean re-extract (a reinstall replacing an
20
+ * existing pack) calls `fs.removeDirectory` itself before this.
21
+ *
22
+ * PATH JAIL: `@markii/pack`'s `openPackArchive` has already rejected any
23
+ * entry whose name escapes the archive (`../`, an absolute path) or
24
+ * exceeds its size caps before this function ever sees `archive` — every
25
+ * path it writes here is built from `destinationDir` joined with a plain,
26
+ * already-validated relative name (`pack.json`, `webview.js`,
27
+ * `webview.css`, or a `scripts/`-relative Lua module name), never from
28
+ * anything read back out of the zip's raw entry names.
29
+ */
30
+ export declare function writeArchiveContents(archive: PackArchiveContents, destinationDir: string, fs: ArchiveExtractFs): Promise<void>;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * `.mkp` pack archive handling, merged from `apps/vscode/src/packs/archive-packs.ts`
3
+ * and `apps/obsidian/src/packs/archive-packs.ts`. It exports
4
+ * `describeArchiveError`, `writeArchiveContents` and `ArchiveExtractFs`.
5
+ *
6
+ * This module bridges `@markii/pack`'s in-memory archive reader
7
+ * (`openPackArchive`, never modified here) into the write a pack install
8
+ * needs: extracting a validated archive's contents onto disk, under
9
+ * whichever install root the calling host names (`HostPackSource.installRoot`).
10
+ *
11
+ * Environment-free (`ArchiveExtractFs` is an injected seam). The real
12
+ * Node-backed one, `createNodeArchiveExtractFs`, lives in `../node-fs.ts`
13
+ * and is exported from `@markii/host/node`.
14
+ */
15
+ import { dirnamePath, joinPath } from '../path-util.js';
16
+ /** One line describing why `openPackArchive` rejected an archive, for a host's install-diagnostics wording. Passes `kind: 'zip'`'s and `kind: 'missing-entry'`'s message through verbatim (already specific); summarizes a manifest failure plainly. */
17
+ export function describeArchiveError(error) {
18
+ if (error.kind === 'zip')
19
+ return error.message;
20
+ if (error.kind === 'missing-entry')
21
+ return error.message;
22
+ return `invalid pack.json in archive (${error.errors.join('; ')})`;
23
+ }
24
+ /**
25
+ * Writes a validated archive's contents into `destinationDir`: `pack.json`
26
+ * (the parsed manifest, re-serialized: `openPackArchive` keeps only the
27
+ * validated manifest fields, not the original bytes, so the written file
28
+ * is a normalized round-trip of the same fields, not a byte-for-byte
29
+ * copy), `webview.js`, `webview.css` and `terminal.js` when the archive has them, and
30
+ * `scripts/*` when it ships any. Does NOT clear `destinationDir` first;
31
+ * a caller that needs a clean re-extract (a reinstall replacing an
32
+ * existing pack) calls `fs.removeDirectory` itself before this.
33
+ *
34
+ * PATH JAIL: `@markii/pack`'s `openPackArchive` has already rejected any
35
+ * entry whose name escapes the archive (`../`, an absolute path) or
36
+ * exceeds its size caps before this function ever sees `archive` — every
37
+ * path it writes here is built from `destinationDir` joined with a plain,
38
+ * already-validated relative name (`pack.json`, `webview.js`,
39
+ * `webview.css`, or a `scripts/`-relative Lua module name), never from
40
+ * anything read back out of the zip's raw entry names.
41
+ */
42
+ export async function writeArchiveContents(archive, destinationDir, fs) {
43
+ await fs.makeDirectory(destinationDir);
44
+ const encoder = new TextEncoder();
45
+ await fs.writeFile(joinPath(destinationDir, 'pack.json'), encoder.encode(JSON.stringify(archive.manifest, null, 2) + '\n'));
46
+ await fs.writeFile(joinPath(destinationDir, 'webview.js'), archive.scriptBytes);
47
+ if (archive.stylesheetBytes !== undefined) {
48
+ await fs.writeFile(joinPath(destinationDir, 'webview.css'), archive.stylesheetBytes);
49
+ }
50
+ if (archive.terminalScriptBytes !== undefined) {
51
+ await fs.writeFile(joinPath(destinationDir, 'terminal.js'), archive.terminalScriptBytes);
52
+ }
53
+ const moduleNames = Object.keys(archive.scriptModules);
54
+ if (moduleNames.length > 0) {
55
+ const scriptsDir = joinPath(destinationDir, 'scripts');
56
+ for (const name of moduleNames) {
57
+ const target = joinPath(scriptsDir, name);
58
+ await fs.makeDirectory(dirnamePath(target));
59
+ await fs.writeFile(target, archive.scriptModules[name]);
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,87 @@
1
+ import type { ArchiveExtractFs } from './pack-archive.js';
2
+ import type { HostAdapter, HostLabels } from './adapter.js';
3
+ /** Whether a directory already exists under the install root, injected so this module needs no real disk to test. */
4
+ export type PackDirectoryExists = (absolutePath: string) => Promise<boolean>;
5
+ export interface InstallPackFromArchiveOptions {
6
+ readonly archiveBytes: Uint8Array;
7
+ /** The `.mkp` file's own path, used only in a rejection's diagnostic wording. Never read again once `archiveBytes` is in hand. */
8
+ readonly archivePath: string;
9
+ /** This host's own pack-install directory (`HostPackSource.installRoot()`). Each installed pack gets a subdirectory named by its namespace, since two packs can never share one (docs/packs.md's collision rule), which makes "is this namespace already installed" a plain directory check. */
10
+ readonly installRoot: string;
11
+ readonly exists: PackDirectoryExists;
12
+ readonly extractFs: ArchiveExtractFs;
13
+ /**
14
+ * Namespaces this install may never claim (`HostPackSource.reservedNamespaces()`).
15
+ * Empty for a host with no reserved set (VS Code). Checked before the
16
+ * consent prompt — a reserved namespace cannot be shadowed by an install.
17
+ */
18
+ readonly reservedNamespaces: ReadonlySet<string>;
19
+ }
20
+ export type InstallPackOutcome = {
21
+ readonly kind: 'installed';
22
+ readonly packName: string;
23
+ readonly installedDir: string;
24
+ readonly replaced: boolean;
25
+ } | {
26
+ readonly kind: 'declined';
27
+ readonly step: 'consent' | 'replace';
28
+ readonly packName: string;
29
+ } | {
30
+ readonly kind: 'reserved';
31
+ readonly packName: string;
32
+ } | {
33
+ readonly kind: 'rejected';
34
+ readonly reason: string;
35
+ };
36
+ /**
37
+ * Never throws: a validation failure, a declined prompt, or a write
38
+ * failure all come back as a structured outcome. Order: validate the
39
+ * archive first (a rejected archive is never even offered the consent
40
+ * prompt), then refuse a reserved namespace, then ask consent to run its
41
+ * code, then, only if a pack of the same namespace is already installed,
42
+ * ask to replace it. Nothing is written until every applicable step has
43
+ * said yes.
44
+ *
45
+ * Every prompt routes through `adapter.prompt` with `kind:
46
+ * 'pack-install-consent'`/`'pack-replace'`, per `HostAdapter`'s contract —
47
+ * a host that can only show a non-blocking surface must deny rather than
48
+ * assume yes.
49
+ */
50
+ export declare function installPackFromArchive(adapter: HostAdapter, options: InstallPackFromArchiveOptions): Promise<InstallPackOutcome>;
51
+ /**
52
+ * The consent prompt's wording (AGENTS.md's product principles: "clean is
53
+ * not silent," and a consent step must say plainly what it authorizes).
54
+ * States outright that the pack's code will run inside the preview, since
55
+ * this is the one place that consent is asked, so it has to say it, not
56
+ * hint at it.
57
+ *
58
+ * CANONICAL WORDING CHOSEN over the two apps' prior copy (report this as a
59
+ * before/after pair): "from someone you trust" (Obsidian's phrasing) over
60
+ * "from a source you trust" (VS Code's), and `labels.previewNoun` ("the
61
+ * Markii preview" for both hosts) in place of VS Code's prior "every
62
+ * Markii preview" — `HostLabels.previewNoun` already gives both hosts the
63
+ * same noun, so the interpolation settles the wording rather than
64
+ * widening `HostLabels` for a distinction the record does not carry.
65
+ */
66
+ export declare function installConsentMessage(packName: string, labels: HostLabels): string;
67
+ /**
68
+ * The collision confirmation's wording: a namespace already installed,
69
+ * asked before replacing it. CANONICAL WORDING: "removes" (VS Code's prior
70
+ * wording) over Obsidian's prior "deletes".
71
+ */
72
+ export declare function installReplaceConfirmMessage(packName: string): string;
73
+ /**
74
+ * The one result message for the install command, matching this host's
75
+ * wording rules (two short sentences, no em dashes or parentheses in the
76
+ * popup; the full detail goes to the diagnostics surface).
77
+ *
78
+ * `reloadsAutomatically` is a per-host BEHAVIOR fact, not a wording noun:
79
+ * Obsidian's install command reloads every open view's packs immediately
80
+ * after a successful install, so its message says so; VS Code's install
81
+ * command does not (an installed pack still needs to be added to
82
+ * `markii.packs` before anything loads it), so it doesn't. Defaults to
83
+ * `false`.
84
+ */
85
+ export declare function installPackMessage(outcome: InstallPackOutcome, archivePath: string, labels: HostLabels, reloadsAutomatically?: boolean): string;
86
+ /** The full diagnostics-surface detail for one install attempt (AGENTS.md's "clean is not silent": the other of a failure's two homes). */
87
+ export declare function installPackDiagnosticLines(outcome: InstallPackOutcome, archivePath: string): string[];