@markii/host 0.0.0-stage → 0.17.0-next.2

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/LICENSE +21 -0
  2. package/README.md +19 -2
  3. package/dist/browser-worker.js +639 -0
  4. package/dist/browser.d.ts +57 -0
  5. package/dist/browser.js +167 -0
  6. package/dist/conformance/host/01-plain-open/expected.json +5 -0
  7. package/dist/conformance/host/01-plain-open/note.mk.md +8 -0
  8. package/dist/conformance/host/01-plain-open/scenario.json +6 -0
  9. package/dist/conformance/host/02-run-manual-grant-accepted/expected.json +16 -0
  10. package/dist/conformance/host/02-run-manual-grant-accepted/note.mk.md +5 -0
  11. package/dist/conformance/host/02-run-manual-grant-accepted/scenario.json +14 -0
  12. package/dist/conformance/host/03-run-manual-grant-refused/expected.json +18 -0
  13. package/dist/conformance/host/03-run-manual-grant-refused/note.mk.md +5 -0
  14. package/dist/conformance/host/03-run-manual-grant-refused/scenario.json +14 -0
  15. package/dist/conformance/host/04-run-auto-write-denied/expected.json +16 -0
  16. package/dist/conformance/host/04-run-auto-write-denied/note.mk.md +10 -0
  17. package/dist/conformance/host/04-run-auto-write-denied/scenario.json +13 -0
  18. package/dist/conformance/host/05-export-html-pack-directive/expected.json +14 -0
  19. package/dist/conformance/host/05-export-html-pack-directive/note.mk.md +8 -0
  20. package/dist/conformance/host/05-export-html-pack-directive/scenario.json +6 -0
  21. package/dist/conformance/host/06-install-pack-then-render/expected.json +18 -0
  22. package/dist/conformance/host/06-install-pack-then-render/files/ana-1.0.0/pack.json +13 -0
  23. package/dist/conformance/host/06-install-pack-then-render/files/ana-1.0.0/webview.js +1 -0
  24. package/dist/conformance/host/06-install-pack-then-render/note.mk.md +5 -0
  25. package/dist/conformance/host/06-install-pack-then-render/scenario.json +15 -0
  26. package/dist/conformance/host/07-complete-in-directive/expected.json +14 -0
  27. package/dist/conformance/host/07-complete-in-directive/note.mk.md +3 -0
  28. package/dist/conformance/host/07-complete-in-directive/scenario.json +6 -0
  29. package/dist/conformance/host/08-hover-standard-component/expected.json +5 -0
  30. package/dist/conformance/host/08-hover-standard-component/note.mk.md +5 -0
  31. package/dist/conformance/host/08-hover-standard-component/scenario.json +6 -0
  32. package/dist/conformance/host/09-insert-skeleton/expected.json +5 -0
  33. package/dist/conformance/host/09-insert-skeleton/note.mk.md +4 -0
  34. package/dist/conformance/host/09-insert-skeleton/scenario.json +6 -0
  35. package/dist/conformance/host/10-scripts-disabled/expected.json +11 -0
  36. package/dist/conformance/host/10-scripts-disabled/note.mk.md +5 -0
  37. package/dist/conformance/host/10-scripts-disabled/scenario.json +9 -0
  38. package/dist/conformance/host/11-complete-closing-fence/expected.json +5 -0
  39. package/dist/conformance/host/11-complete-closing-fence/note.mk.md +5 -0
  40. package/dist/conformance/host/11-complete-closing-fence/scenario.json +12 -0
  41. package/dist/conformance/host/12-complete-nested-container/expected.json +5 -0
  42. package/dist/conformance/host/12-complete-nested-container/note.mk.md +5 -0
  43. package/dist/conformance/host/12-complete-nested-container/scenario.json +12 -0
  44. package/dist/conformance/host/13-install-pack-declined/expected.json +12 -0
  45. package/dist/conformance/host/13-install-pack-declined/files/ana-1.0.0/pack.json +13 -0
  46. package/dist/conformance/host/13-install-pack-declined/files/ana-1.0.0/webview.js +1 -0
  47. package/dist/conformance/host/13-install-pack-declined/note.mk.md +5 -0
  48. package/dist/conformance/host/13-install-pack-declined/scenario.json +10 -0
  49. package/dist/conformance/host/14-remove-pack/expected.json +14 -0
  50. package/dist/conformance/host/14-remove-pack/files/ana-1.0.0/pack.json +13 -0
  51. package/dist/conformance/host/14-remove-pack/files/ana-1.0.0/webview.js +1 -0
  52. package/dist/conformance/host/14-remove-pack/note.mk.md +5 -0
  53. package/dist/conformance/host/14-remove-pack/scenario.json +11 -0
  54. package/dist/conformance/host/15-load-packs-engine-views/expected.json +19 -0
  55. package/dist/conformance/host/15-load-packs-engine-views/files/duo-1.0.0/pack.json +9 -0
  56. package/dist/conformance/host/15-load-packs-engine-views/files/duo-1.0.0/webview.js +1 -0
  57. package/dist/conformance/host/15-load-packs-engine-views/note.mk.md +9 -0
  58. package/dist/conformance/host/15-load-packs-engine-views/scenario.json +10 -0
  59. package/dist/conformance/host/16-pack-trust/expected.json +37 -0
  60. package/dist/conformance/host/16-pack-trust/files/ana-1.0.0/pack.json +12 -0
  61. package/dist/conformance/host/16-pack-trust/files/ana-1.0.0/webview.js +1 -0
  62. package/dist/conformance/host/16-pack-trust/files/sneaky-1.0.0/pack.json +12 -0
  63. package/dist/conformance/host/16-pack-trust/files/sneaky-1.0.0/webview.js +1 -0
  64. package/dist/conformance/host/16-pack-trust/note.mk.md +5 -0
  65. package/dist/conformance/host/16-pack-trust/scenario.json +26 -0
  66. package/dist/conformance/host/README.md +221 -0
  67. package/dist/conformance.d.ts +2 -0
  68. package/dist/conformance.js +5 -0
  69. package/dist/diagnostics/render-diagnostics.d.ts +41 -0
  70. package/dist/diagnostics/render-diagnostics.js +46 -0
  71. package/dist/export/cascade.d.ts +98 -0
  72. package/dist/export/cascade.js +159 -0
  73. package/dist/export/export-zip.d.ts +19 -0
  74. package/dist/export/export-zip.js +38 -0
  75. package/dist/export/image-embed.d.ts +126 -0
  76. package/dist/export/image-embed.js +274 -0
  77. package/dist/export/note-export.d.ts +332 -0
  78. package/dist/export/note-export.js +366 -0
  79. package/dist/export/note-links.d.ts +83 -0
  80. package/dist/export/note-links.js +241 -0
  81. package/dist/glue.wasm +0 -0
  82. package/dist/host/adapter.d.ts +223 -0
  83. package/dist/host/adapter.js +9 -0
  84. package/dist/host/cascade-export-behavior.d.ts +120 -0
  85. package/dist/host/cascade-export-behavior.js +151 -0
  86. package/dist/host/conformance-runner.d.ts +156 -0
  87. package/dist/host/conformance-runner.js +634 -0
  88. package/dist/host/create-host.d.ts +253 -0
  89. package/dist/host/create-host.js +329 -0
  90. package/dist/host/editor-behavior.d.ts +157 -0
  91. package/dist/host/editor-behavior.js +227 -0
  92. package/dist/host/export/md-plain.d.ts +15 -0
  93. package/dist/host/export/md-plain.js +125 -0
  94. package/dist/host/export-behavior.d.ts +113 -0
  95. package/dist/host/export-behavior.js +257 -0
  96. package/dist/host/labels.d.ts +24 -0
  97. package/dist/host/labels.js +32 -0
  98. package/dist/host/pack-archive.d.ts +30 -0
  99. package/dist/host/pack-archive.js +68 -0
  100. package/dist/host/pack-install.d.ts +88 -0
  101. package/dist/host/pack-install.js +189 -0
  102. package/dist/host/pack-load.d.ts +72 -0
  103. package/dist/host/pack-load.js +57 -0
  104. package/dist/host/pack-remove.d.ts +21 -0
  105. package/dist/host/pack-remove.js +65 -0
  106. package/dist/host/pack-trust-behavior.d.ts +79 -0
  107. package/dist/host/pack-trust-behavior.js +262 -0
  108. package/dist/host/refresh-interval.d.ts +39 -0
  109. package/dist/host/refresh-interval.js +61 -0
  110. package/dist/host/run-behavior.d.ts +89 -0
  111. package/dist/host/run-behavior.js +168 -0
  112. package/dist/host/run-diagnostics.d.ts +26 -0
  113. package/dist/host/run-diagnostics.js +43 -0
  114. package/dist/host/script-execution.d.ts +72 -0
  115. package/dist/host/script-execution.js +68 -0
  116. package/dist/host/terminal-packs.d.ts +39 -0
  117. package/dist/host/terminal-packs.js +51 -0
  118. package/dist/html-engine.d.ts +2 -0
  119. package/dist/html-engine.js +29 -0
  120. package/dist/index.d.ts +95 -0
  121. package/dist/index.js +70 -0
  122. package/dist/insert/component-catalog.d.ts +23 -0
  123. package/dist/insert/component-catalog.js +96 -0
  124. package/dist/node-fs.d.ts +14 -0
  125. package/dist/node-fs.js +70 -0
  126. package/dist/node.d.ts +5 -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 +81 -0
  132. package/dist/packs/pack-diagnostics.js +105 -0
  133. package/dist/packs/pack-paths.d.ts +53 -0
  134. package/dist/packs/pack-paths.js +106 -0
  135. package/dist/packs/pack-render-registry.d.ts +83 -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 +81 -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 +57 -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 +70 -0
  166. package/dist/run/net-provider.js +289 -0
  167. package/dist/run/node-isolate.d.ts +30 -0
  168. package/dist/run/node-isolate.js +117 -0
  169. package/dist/run/run-flow.d.ts +187 -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 +42 -0
  186. package/dist/run/worker-entry-browser.js +77 -0
  187. package/dist/run/worker-entry.d.ts +1 -0
  188. package/dist/run/worker-entry.js +96 -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 +84 -4
@@ -0,0 +1,253 @@
1
+ /**
2
+ * `createMarkiiHost`: the behaviors every app calls instead of assembling
3
+ * its own run/export/pack/editor wiring. Everything it touches (files, prompts,
4
+ * the isolate) arrives through the adapter, so the main entry stays
5
+ * environment-neutral. VS Code's webview imports only the pure pieces and
6
+ * never calls this function (`tmp/W11-adapter-design.md`, section 1).
7
+ *
8
+ * Phase 1a implemented `run`, `diagnostics`, and `open`. This phase
9
+ * (1b) fills in `exportNote`, `installPack`, `loadPacks`, `completeAt`,
10
+ * `hoverAt`, and `insertComponent`, over the behavior modules built in
11
+ * this same pass (`./export-behavior.js`, `./pack-install.js`,
12
+ * `./pack-load.js`, `./editor-behavior.js`). Every one of them still
13
+ * returns the shared `Unsupported` outcome, with one diagnostics line,
14
+ * when the adapter lacks the capability it needs — AGENTS.md's "clean is
15
+ * not silent": a decline is never a silent no-op.
16
+ */
17
+ import type { HostAdapter, HostExportFormat, HostTextEdit } from './adapter.js';
18
+ import type { RunResult, SpawnRunRequest } from '../run/run-host.js';
19
+ import type { StoredValue } from '@markii/runtime';
20
+ import type { OnDiagnostic, UnavailableComponent } from '@markii/stdlib';
21
+ import type { CompletionItem, ComponentDocumentation, LineColumn } from '@markii/stdlib/editor';
22
+ import { type RunViaAdapterRequest, type RunViaAdapterResult } from './run-behavior.js';
23
+ import { type ArchiveExtractFs } from './pack-archive.js';
24
+ import { type PackDiscoveryIo } from './pack-load.js';
25
+ import type { HtmlExportEngine } from '../export/note-export.js';
26
+ import type { DiscoveredPack, SkippedPackFolder } from '../packs/discover.js';
27
+ import { type TerminalPackScript } from './terminal-packs.js';
28
+ import { type RemovePackOutcome } from './pack-remove.js';
29
+ import type { DisablePackOutcome, EnablePackOutcome, InstalledPacksListing } from './pack-trust-behavior.js';
30
+ /** The shared decline shape every behavior returns when the adapter lacks the capability it needs. */
31
+ export interface Unsupported {
32
+ readonly kind: 'unsupported';
33
+ readonly capability: 'isolate' | 'editor' | 'packs' | 'pack-trust' | 'export-format';
34
+ readonly detail?: string;
35
+ }
36
+ export interface OpenRequest {
37
+ readonly path: string;
38
+ }
39
+ export type OpenOutcome = {
40
+ readonly kind: 'opened';
41
+ readonly text: string;
42
+ } | {
43
+ readonly kind: 'failed';
44
+ readonly reason: string;
45
+ };
46
+ export type RunRequest = RunViaAdapterRequest;
47
+ export type RunOutcome = ({
48
+ readonly kind: 'ran';
49
+ } & RunViaAdapterResult) | Unsupported;
50
+ export interface ExportRequest {
51
+ readonly format: HostExportFormat;
52
+ /** The note's own path, used to name the exported file. */
53
+ readonly notePath: string;
54
+ readonly text: string;
55
+ /** The note's last-run values, baked into an html/pdf export. Omitted or empty exports the note's standard empty states. */
56
+ readonly values?: Record<string, StoredValue>;
57
+ /**
58
+ * Installed pack components the static HTML engine cannot draw. A
59
+ * directive naming one is labeled "no HTML view" instead of "unknown
60
+ * component" in the exported file. Used only when the static engine
61
+ * renders the body.
62
+ */
63
+ readonly unavailableComponents?: readonly UnavailableComponent[];
64
+ /** Receives the static engine's render diagnostics, such as the `no-engine-view` event. */
65
+ readonly onDiagnostic?: OnDiagnostic;
66
+ }
67
+ export type ExportOutcome = {
68
+ readonly kind: 'exported';
69
+ readonly path: string;
70
+ readonly bytes: number;
71
+ } | {
72
+ readonly kind: 'failed';
73
+ readonly reason: string;
74
+ }
75
+ /** `adapter.exports.resolveTarget`/`htmlToPdf`/an injected `renderAnsi` was unavailable at the moment of export — a picker cancel, or a seam this host declared but did not wire up. */
76
+ | {
77
+ readonly kind: 'cancelled';
78
+ } | Unsupported;
79
+ export interface InstallPackRequest {
80
+ readonly archiveBytes: Uint8Array;
81
+ /** The archive's own path or display name, used only in wording. Defaults to a generic phrase when omitted. */
82
+ readonly archivePath?: string;
83
+ }
84
+ export type InstallPackOutcome = {
85
+ readonly kind: 'installed';
86
+ readonly namespace: string;
87
+ readonly replaced: boolean;
88
+ } | {
89
+ readonly kind: 'declined';
90
+ readonly step: 'consent' | 'replace';
91
+ readonly namespace: string;
92
+ } | {
93
+ readonly kind: 'reserved';
94
+ readonly namespace: string;
95
+ } | {
96
+ readonly kind: 'rejected';
97
+ readonly reason: string;
98
+ } | Unsupported;
99
+ export type LoadPacksOutcome = {
100
+ readonly kind: 'loaded';
101
+ readonly namespaces: readonly string[];
102
+ /** The discovered, engine-gated packs themselves, for a host that loads their code. */
103
+ readonly packs: readonly DiscoveredPack[];
104
+ /** Folders that produced no usable pack, each with its reason. */
105
+ readonly skipped: readonly SkippedPackFolder[];
106
+ /** Every installed pack component with the engines it has a view for, dropped packs included. For an engine that draws no pack views. */
107
+ readonly components: readonly UnavailableComponent[];
108
+ /** The installed components with no view for this host's engine, for a renderer's `unavailableComponents` option. */
109
+ readonly unavailable: readonly UnavailableComponent[];
110
+ } | Unsupported;
111
+ export type LoadTerminalPacksOutcome = {
112
+ readonly kind: 'loaded';
113
+ readonly namespaces: readonly string[];
114
+ readonly packs: readonly DiscoveredPack[];
115
+ readonly skipped: readonly SkippedPackFolder[];
116
+ /** Every installed pack component with the engines it has a view for, dropped packs included. For an engine that draws no pack views. */
117
+ readonly components: readonly UnavailableComponent[];
118
+ /** The installed components with no view for the terminal engine, for its renderer's `unavailableComponents` option. */
119
+ readonly unavailable: readonly UnavailableComponent[];
120
+ /** Each terminal-capable pack's `terminal.js` text, for the engine's own loader. */
121
+ readonly scripts: readonly TerminalPackScript[];
122
+ /**
123
+ * The diagnostics lines that report a real failure (an unusable pack
124
+ * folder, a missing or unreadable `terminal.js`), as opposed to the
125
+ * informational ones. Every line is also written to the diagnostics
126
+ * surface; a host that gates informational lines behind a flag still
127
+ * shows these.
128
+ */
129
+ readonly failureLines: readonly string[];
130
+ } | Unsupported;
131
+ export type RemovePackResult = RemovePackOutcome | Unsupported;
132
+ export type InstalledPacksOutcome = ({
133
+ readonly kind: 'listed';
134
+ } & InstalledPacksListing) | Unsupported;
135
+ export type EnablePackResult = EnablePackOutcome | Unsupported;
136
+ export type DisablePackResult = DisablePackOutcome | Unsupported;
137
+ export interface EditorPositionRequest {
138
+ readonly line: number;
139
+ readonly column: number;
140
+ /**
141
+ * The document the position belongs to. Omitted, the adapter's own
142
+ * `editor.documentText()` is read. A provider invoked for a document that
143
+ * is not the host's active editor (VS Code asks about any visible
144
+ * document) passes its own text here.
145
+ */
146
+ readonly documentText?: string;
147
+ }
148
+ export type CompleteOutcome = {
149
+ readonly kind: 'completions';
150
+ readonly labels: readonly string[];
151
+ /** The full items, so a host can map them onto its own popup rows. */
152
+ readonly items: readonly CompletionItem[];
153
+ /** Zero-based columns an accepted item replaces. */
154
+ readonly replaceStart: number;
155
+ readonly replaceEnd: number;
156
+ /** Where the name token starts, for filtering. Equals `replaceStart` unless a colon run precedes the name. */
157
+ readonly tokenStart: number;
158
+ /** Fence rewrites to apply with an accepted container item, in one undo step, in pre-edit coordinates. Empty otherwise. */
159
+ readonly fenceEdits: readonly HostTextEdit[];
160
+ }
161
+ /** Nothing to offer here: no directive context, or a bare colon run closing an open container. Not a failure. */
162
+ | {
163
+ readonly kind: 'none';
164
+ } | Unsupported;
165
+ export type HoverOutcome = {
166
+ readonly kind: 'hover';
167
+ readonly text: string;
168
+ /** The structured documentation, for a host that renders its own markup. */
169
+ readonly documentation: ComponentDocumentation;
170
+ /** Zero-based columns of the hovered directive name, for the hover range. */
171
+ readonly start: number;
172
+ readonly end: number;
173
+ } | {
174
+ readonly kind: 'none';
175
+ } | Unsupported;
176
+ export interface InsertComponentRequest {
177
+ readonly name: string;
178
+ }
179
+ export type InsertOutcome = {
180
+ readonly kind: 'inserted';
181
+ /**
182
+ * Where a caller with its own selection/cursor concept (VS Code's
183
+ * `editor.selection`, Obsidian's `Editor.setCursor`) should place it
184
+ * once the edit lands: the position `insertComponentPlan` computed
185
+ * INSIDE the inserted skeleton (e.g. the blank line between a
186
+ * container's fences), not merely the end of the inserted text.
187
+ * `HostEditor.applyEdits` only reports success or failure, so this
188
+ * is the one place that offset survives the call.
189
+ */
190
+ readonly cursor: LineColumn;
191
+ } | {
192
+ readonly kind: 'not-found';
193
+ readonly name: string;
194
+ } | {
195
+ readonly kind: 'failed';
196
+ } | Unsupported;
197
+ export interface MarkiiHost {
198
+ open(request: OpenRequest): Promise<OpenOutcome>;
199
+ run(request: RunRequest): Promise<RunOutcome>;
200
+ exportNote(request: ExportRequest): Promise<ExportOutcome>;
201
+ installPack(request: InstallPackRequest): Promise<InstallPackOutcome>;
202
+ loadPacks(): Promise<LoadPacksOutcome>;
203
+ /**
204
+ * `loadPacks` for a host running the terminal engine: also reads each
205
+ * pack's `terminal.js`. Unsupported unless the adapter's pack source
206
+ * declares the `ansi` engine.
207
+ */
208
+ loadTerminalPacks(): Promise<LoadTerminalPacksOutcome>;
209
+ /** Removes one installed pack by namespace, through the install root. Writes its outcome to the diagnostics surface. Under `trust: 'device-list'` it also withdraws the namespace's trust entry. */
210
+ removePack(namespace: string): Promise<RemovePackResult>;
211
+ /**
212
+ * Under `trust: 'device-list'`: the install root's folders split into the
213
+ * ones this device has enabled and the ones merely present. Unsupported
214
+ * for a host that does not declare the trust list.
215
+ */
216
+ installedPacks(): Promise<InstalledPacksOutcome>;
217
+ /** Asks the install consent prompt, then trusts an installed folder on this device. Unsupported without the trust list. */
218
+ enablePack(namespace: string): Promise<EnablePackResult>;
219
+ /** Withdraws a namespace's trust entry and leaves its folder in place. Unsupported without the trust list. */
220
+ disablePack(namespace: string): Promise<DisablePackResult>;
221
+ completeAt(request: EditorPositionRequest): Promise<CompleteOutcome>;
222
+ /**
223
+ * `completeAt` for a caller that must answer synchronously (Obsidian's
224
+ * `EditorSuggest.onTrigger`). Reads the cached catalog; while the cache
225
+ * is cold it answers from the standard set and starts warming the cache,
226
+ * so the next request sees installed packs.
227
+ */
228
+ completeAtSync(request: EditorPositionRequest): CompleteOutcome;
229
+ /** Drops the cached completion/hover catalog, for a host whose pack folders just changed. `installPack` and `loadPacks` do this themselves. */
230
+ invalidateCatalog(): void;
231
+ hoverAt(request: EditorPositionRequest): Promise<HoverOutcome>;
232
+ insertComponent(request: InsertComponentRequest): Promise<InsertOutcome>;
233
+ diagnostics(line: string): void;
234
+ }
235
+ /**
236
+ * Injectables a test needs to make a run/export deterministic. Defaults
237
+ * are the real implementations; no option changes behavior for a real
238
+ * host.
239
+ */
240
+ export interface MarkiiHostOptions {
241
+ readonly spawnRun?: (options: SpawnRunRequest) => Promise<RunResult>;
242
+ /** The `.mkp` extraction filesystem `installPack` writes through. Required for a host that installs packs (`createNodeArchiveExtractFs()` from `@markii/host/node`); without it `installPack` is `Unsupported`. */
243
+ readonly archiveExtractFs?: ArchiveExtractFs;
244
+ /** The static HTML engine behind the `html` and `pdf` exports (`createHtmlExportEngine()` from `@markii/host/node`). Without it those formats decline, like `renderAnsi` for `ansi`. */
245
+ readonly htmlEngine?: HtmlExportEngine;
246
+ /** Pack discovery's file reads. Defaults to reads through the adapter's own `readFile` and `listFolder`. */
247
+ readonly packDiscoveryIo?: PackDiscoveryIo;
248
+ /** `@markii/ansi`'s renderer, injected because this package never depends on `@markii/ansi` (a platform renderer, never a `@markii/host` dependency). A host declaring `'ansi'` in `adapter.exports.formats` without supplying this gets `Unsupported` at export time. */
249
+ readonly renderAnsi?: (text: string) => Promise<string>;
250
+ /** Composes a pack-aware render registry (webview registration, in-process evaluation, ...). Reserved for a future slice that renders through `exportNote`'s `html` path with packs; unused today, since `buildExportDocument`'s default (no `renderBody`) already produces a correct, pack-free static export. */
251
+ readonly buildPackRegistration?: unknown;
252
+ }
253
+ export declare function createMarkiiHost(adapter: HostAdapter, options?: MarkiiHostOptions): MarkiiHost;
@@ -0,0 +1,329 @@
1
+ import { runViaAdapter, } from './run-behavior.js';
2
+ import { installPackDiagnosticLines, installPackFromArchive, } from './pack-install.js';
3
+ import { loadPacksViaAdapter, packDiscoveryIoFromAdapter, } from './pack-load.js';
4
+ import { buildComponentCatalog } from '../insert/component-catalog.js';
5
+ import { formatPackDiagnosticLines, skippedPackFolderLine, } from '../packs/pack-diagnostics.js';
6
+ import { readTerminalPackScripts, } from './terminal-packs.js';
7
+ import { removePackDiagnosticLine, removePackViaAdapter, } from './pack-remove.js';
8
+ import { formatComponentDocumentation } from '@markii/stdlib/editor';
9
+ import { exportDiagnosticLines, exportNoteFileViaAdapter, } from './export-behavior.js';
10
+ import { disablePackDiagnosticLine, disablePackViaAdapter, enablePackDiagnosticLine, enablePackViaAdapter, forgetPackTrust, listInstalledPacks, notEnabledPackDiagnosticLine, trustedPackFolders, usesPackTrust, } from './pack-trust-behavior.js';
11
+ import { completionPlanAt, createCatalogCache, hoverAtViaEditor, insertComponentPlan, } from './editor-behavior.js';
12
+ function unsupported(adapter, capability, detail) {
13
+ adapter.diagnostics(`declined (${capability} unsupported on ${adapter.id}): ${detail}`);
14
+ return { kind: 'unsupported', capability, detail };
15
+ }
16
+ export function createMarkiiHost(adapter, options = {}) {
17
+ const packIo = options.packDiscoveryIo ?? packDiscoveryIoFromAdapter(adapter);
18
+ /**
19
+ * The pack source every load reads: under `trust: 'device-list'` the
20
+ * trusted installed folders are added to what the adapter authorizes, so
21
+ * an untrusted folder is never a candidate for discovery. Other hosts
22
+ * get their own source back unchanged.
23
+ */
24
+ function packSource(packs) {
25
+ if (!usesPackTrust(adapter))
26
+ return packs;
27
+ return {
28
+ ...packs,
29
+ authorizedFolders: async () => [
30
+ ...(await packs.authorizedFolders()),
31
+ ...(await trustedPackFolders(adapter)),
32
+ ],
33
+ };
34
+ }
35
+ /** The pack-aware insert/completion catalog, built fresh from this host's currently authorized folders. Not cached here: `./editor-behavior.js`'s `createCatalogCache` is the caching seam, and a caller wiring a live editor (repeated completion/hover calls per keystroke) is expected to wrap this call in one. */
36
+ async function currentCatalog() {
37
+ if (!adapter.packs)
38
+ return buildComponentCatalog([]);
39
+ const { packs } = await loadPacksViaAdapter(packSource(adapter.packs), packIo);
40
+ return buildComponentCatalog([...(adapter.packs.bundledPacks?.() ?? []), ...packs], adapter.packs.engine);
41
+ }
42
+ /** Completion and hover fire per keystroke, so they read a cached catalog. `insertComponent` stays uncached: it runs once per command. */
43
+ const catalogCache = createCatalogCache(currentCatalog);
44
+ function documentTextFor(editor, request) {
45
+ return request.documentText ?? editor.documentText();
46
+ }
47
+ function completeWith(catalog, request, editor) {
48
+ const plan = completionPlanAt(documentTextFor(editor, request), request, catalog);
49
+ if (plan === undefined)
50
+ return { kind: 'none' };
51
+ return {
52
+ kind: 'completions',
53
+ labels: plan.items.map((item) => item.label),
54
+ items: plan.items,
55
+ replaceStart: plan.replaceStart,
56
+ replaceEnd: plan.replaceEnd,
57
+ tokenStart: plan.tokenStart,
58
+ fenceEdits: plan.fenceEdits,
59
+ };
60
+ }
61
+ return {
62
+ async open(request) {
63
+ try {
64
+ const bytes = await adapter.readFile(request.path);
65
+ return { kind: 'opened', text: new TextDecoder().decode(bytes) };
66
+ }
67
+ catch (err) {
68
+ const reason = err instanceof Error ? err.message : String(err);
69
+ adapter.diagnostics(`could not open ${request.path}: ${reason}`);
70
+ return { kind: 'failed', reason };
71
+ }
72
+ },
73
+ async run(request) {
74
+ if (!adapter.isolate) {
75
+ return unsupported(adapter, 'isolate', 'no isolate is configured, so scripts cannot run.');
76
+ }
77
+ const result = await runViaAdapter(adapter, request, options.spawnRun);
78
+ return { kind: 'ran', ...result };
79
+ },
80
+ async exportNote(request) {
81
+ const formats = adapter.exports?.formats;
82
+ if (formats === undefined || !formats.has(request.format)) {
83
+ return unsupported(adapter, 'export-format', `export format "${request.format}" is not available on ${adapter.id}.`);
84
+ }
85
+ const outcome = await exportNoteFileViaAdapter(adapter, request.format, {
86
+ ...(options.htmlEngine !== undefined
87
+ ? { engine: options.htmlEngine }
88
+ : {}),
89
+ notePath: request.notePath,
90
+ text: request.text,
91
+ ...(request.values !== undefined ? { values: request.values } : {}),
92
+ ...(request.unavailableComponents !== undefined
93
+ ? { unavailableComponents: request.unavailableComponents }
94
+ : {}),
95
+ ...(request.onDiagnostic !== undefined
96
+ ? { onDiagnostic: request.onDiagnostic }
97
+ : {}),
98
+ }, options.renderAnsi);
99
+ if (outcome === undefined) {
100
+ adapter.diagnostics(`export declined: format "${request.format}" has no working export path on ${adapter.id} (a cancelled picker, or a missing renderer/PDF seam).`);
101
+ return { kind: 'cancelled' };
102
+ }
103
+ for (const line of exportDiagnosticLines(outcome)) {
104
+ adapter.diagnostics(line);
105
+ }
106
+ if (outcome.kind === 'failed') {
107
+ return { kind: 'failed', reason: outcome.reason };
108
+ }
109
+ return { kind: 'exported', path: outcome.path, bytes: outcome.bytes };
110
+ },
111
+ async installPack(request) {
112
+ if (!adapter.packs?.installRoot) {
113
+ return unsupported(adapter, 'packs', 'this host has no pack-install destination configured.');
114
+ }
115
+ const archivePath = request.archivePath ?? 'the selected archive';
116
+ const extractFs = options.archiveExtractFs;
117
+ if (extractFs === undefined) {
118
+ return unsupported(adapter, 'packs', 'this host was given no archive extraction filesystem.');
119
+ }
120
+ const outcome = await installPackFromArchive(adapter, {
121
+ archiveBytes: request.archiveBytes,
122
+ archivePath,
123
+ installRoot: adapter.packs.installRoot(),
124
+ exists: (path) => adapter.exists(path),
125
+ extractFs,
126
+ reservedNamespaces: adapter.packs.reservedNamespaces(),
127
+ });
128
+ catalogCache.invalidate();
129
+ for (const line of installPackDiagnosticLines(outcome, archivePath)) {
130
+ adapter.diagnostics(line);
131
+ }
132
+ if (outcome.kind === 'installed') {
133
+ return {
134
+ kind: 'installed',
135
+ namespace: outcome.packName,
136
+ replaced: outcome.replaced,
137
+ };
138
+ }
139
+ if (outcome.kind === 'declined') {
140
+ return {
141
+ kind: 'declined',
142
+ step: outcome.step,
143
+ namespace: outcome.packName,
144
+ };
145
+ }
146
+ if (outcome.kind === 'reserved') {
147
+ return { kind: 'reserved', namespace: outcome.packName };
148
+ }
149
+ return { kind: 'rejected', reason: outcome.reason };
150
+ },
151
+ async loadPacks() {
152
+ if (!adapter.packs) {
153
+ return unsupported(adapter, 'packs', 'this host has no pack folders configured.');
154
+ }
155
+ catalogCache.invalidate();
156
+ const result = await loadPacksViaAdapter(packSource(adapter.packs), packIo);
157
+ for (const line of formatPackDiagnosticLines({
158
+ packs: result.packs,
159
+ skipped: result.skipped,
160
+ engine: adapter.packs.engine,
161
+ cssWarnings: [],
162
+ })) {
163
+ adapter.diagnostics(line);
164
+ }
165
+ if (usesPackTrust(adapter)) {
166
+ for (const namespace of (await listInstalledPacks(adapter))
167
+ .notEnabled) {
168
+ adapter.diagnostics(notEnabledPackDiagnosticLine(namespace, adapter.labels.deviceNoun));
169
+ }
170
+ }
171
+ return {
172
+ kind: 'loaded',
173
+ namespaces: result.namespaces,
174
+ packs: result.packs,
175
+ skipped: result.skipped,
176
+ components: result.components,
177
+ unavailable: result.unavailable,
178
+ };
179
+ },
180
+ async loadTerminalPacks() {
181
+ if (!adapter.packs) {
182
+ return unsupported(adapter, 'packs', 'this host has no pack folders configured.');
183
+ }
184
+ if (adapter.packs.engine !== 'ansi') {
185
+ return unsupported(adapter, 'packs', 'this host does not run the terminal engine, so it has no terminal packs.');
186
+ }
187
+ catalogCache.invalidate();
188
+ const result = await loadPacksViaAdapter(packSource(adapter.packs), packIo);
189
+ const read = await readTerminalPackScripts(adapter, result.packs);
190
+ for (const line of formatPackDiagnosticLines({
191
+ packs: result.packs,
192
+ skipped: result.skipped,
193
+ engine: 'ansi',
194
+ cssWarnings: [],
195
+ })) {
196
+ adapter.diagnostics(line);
197
+ }
198
+ for (const line of read.problems)
199
+ adapter.diagnostics(line);
200
+ const failureLines = [
201
+ ...result.skipped
202
+ .filter((entry) => entry.engineUnsupported === undefined)
203
+ .map(skippedPackFolderLine),
204
+ ...read.problems,
205
+ ];
206
+ return {
207
+ kind: 'loaded',
208
+ namespaces: result.namespaces,
209
+ packs: result.packs,
210
+ skipped: result.skipped,
211
+ components: result.components,
212
+ unavailable: result.unavailable,
213
+ scripts: read.scripts,
214
+ failureLines,
215
+ };
216
+ },
217
+ async removePack(namespace) {
218
+ const packs = adapter.packs;
219
+ if (!packs?.installRoot || !packs.removeFolder) {
220
+ return unsupported(adapter, 'packs', 'this host has no pack-install destination configured.');
221
+ }
222
+ const outcome = await removePackViaAdapter(adapter, namespace, packs.installRoot(), packs.removeFolder.bind(packs), packs.reservedNamespaces());
223
+ catalogCache.invalidate();
224
+ // The trust entry goes whatever happened to the folder: this device
225
+ // has withdrawn its authorization, so the pack stops loading. Only a
226
+ // refused or malformed request leaves the list alone.
227
+ if (outcome.kind !== 'reserved' && outcome.kind !== 'invalid') {
228
+ await forgetPackTrust(adapter, namespace);
229
+ }
230
+ adapter.diagnostics(removePackDiagnosticLine(outcome));
231
+ return outcome;
232
+ },
233
+ async installedPacks() {
234
+ if (!usesPackTrust(adapter)) {
235
+ return unsupported(adapter, 'pack-trust', 'this host does not gate installed packs on a trust list.');
236
+ }
237
+ return { kind: 'listed', ...(await listInstalledPacks(adapter)) };
238
+ },
239
+ async enablePack(namespace) {
240
+ if (!usesPackTrust(adapter)) {
241
+ return unsupported(adapter, 'pack-trust', 'this host does not gate installed packs on a trust list.');
242
+ }
243
+ const outcome = await enablePackViaAdapter(adapter, namespace);
244
+ catalogCache.invalidate();
245
+ adapter.diagnostics(enablePackDiagnosticLine(outcome));
246
+ return outcome;
247
+ },
248
+ async disablePack(namespace) {
249
+ if (!usesPackTrust(adapter)) {
250
+ return unsupported(adapter, 'pack-trust', 'this host does not gate installed packs on a trust list.');
251
+ }
252
+ const outcome = await disablePackViaAdapter(adapter, namespace);
253
+ catalogCache.invalidate();
254
+ adapter.diagnostics(disablePackDiagnosticLine(outcome));
255
+ return outcome;
256
+ },
257
+ async completeAt(request) {
258
+ const editor = adapter.editor;
259
+ if (!editor) {
260
+ return unsupported(adapter, 'editor', 'completion is not available: this host has no live editor.');
261
+ }
262
+ return completeWith(await catalogCache.get(), request, editor);
263
+ },
264
+ completeAtSync(request) {
265
+ const editor = adapter.editor;
266
+ if (!editor) {
267
+ return unsupported(adapter, 'editor', 'completion is not available: this host has no live editor.');
268
+ }
269
+ const warm = catalogCache.peek();
270
+ if (warm === undefined)
271
+ void catalogCache.get();
272
+ return completeWith(warm ?? buildComponentCatalog([]), request, editor);
273
+ },
274
+ invalidateCatalog() {
275
+ catalogCache.invalidate();
276
+ },
277
+ async hoverAt(request) {
278
+ const editor = adapter.editor;
279
+ if (!editor) {
280
+ return unsupported(adapter, 'editor', 'hover is not available: this host has no live editor.');
281
+ }
282
+ const catalog = await catalogCache.get();
283
+ const hover = hoverAtViaEditor({ documentText: () => documentTextFor(editor, request) }, request, catalog);
284
+ if (hover === undefined)
285
+ return { kind: 'none' };
286
+ // The formatted text (summary, attributes, and the usage example),
287
+ // not the bare summary sentence: a component's description alone
288
+ // often never repeats its own directive name (see
289
+ // `./editor-behavior.js`'s `hoverDocumentationText` doc comment),
290
+ // while the usage example always does.
291
+ return {
292
+ kind: 'hover',
293
+ text: formatComponentDocumentation(hover.documentation),
294
+ documentation: hover.documentation,
295
+ start: hover.start,
296
+ end: hover.end,
297
+ };
298
+ },
299
+ async insertComponent(request) {
300
+ if (!adapter.editor) {
301
+ return unsupported(adapter, 'editor', 'insert component is not available: this host has no live editor.');
302
+ }
303
+ const catalog = await currentCatalog();
304
+ const component = catalog.find((entry) => entry.directiveName === request.name);
305
+ if (component === undefined) {
306
+ adapter.diagnostics(`insert component: no catalog entry named "${request.name}".`);
307
+ return { kind: 'not-found', name: request.name };
308
+ }
309
+ const cursor = adapter.editor.cursor();
310
+ const documentText = adapter.editor.documentText();
311
+ const plan = insertComponentPlan(component, documentText, cursor.line, cursor.column);
312
+ const edits = plan.edits.map((edit) => ({
313
+ line: edit.line,
314
+ startColumn: edit.startColumn,
315
+ endColumn: edit.endColumn,
316
+ text: edit.text,
317
+ }));
318
+ const applied = await adapter.editor.applyEdits(edits);
319
+ if (!applied) {
320
+ adapter.diagnostics(`insert component: the host declined to apply the edit for "${request.name}".`);
321
+ return { kind: 'failed' };
322
+ }
323
+ return { kind: 'inserted', cursor: plan.cursor };
324
+ },
325
+ diagnostics(line) {
326
+ adapter.diagnostics(line);
327
+ },
328
+ };
329
+ }