@restless-dreams/orbit 0.0.0-stage → 0.1.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 (100) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +165 -2
  3. package/dist/bin/orbit.js +8907 -0
  4. package/dist/bin/orbit.js.map +48 -0
  5. package/dist/chunk-0gwh723s.js +408 -0
  6. package/dist/chunk-0gwh723s.js.map +10 -0
  7. package/dist/chunk-7n00wx0t.js +82 -0
  8. package/dist/chunk-7n00wx0t.js.map +10 -0
  9. package/dist/chunk-mtrnm0yc.js +16993 -0
  10. package/dist/chunk-mtrnm0yc.js.map +63 -0
  11. package/dist/core/auto-render.d.ts +85 -0
  12. package/dist/core/browser.d.ts +23 -0
  13. package/dist/core/create.d.ts +23 -0
  14. package/dist/core/diagnostics.d.ts +40 -0
  15. package/dist/core/dispatch.d.ts +23 -0
  16. package/dist/core/document-options.d.ts +18 -0
  17. package/dist/core/document-source.d.ts +128 -0
  18. package/dist/core/editing.d.ts +63 -0
  19. package/dist/core/engine/animation-clock.d.ts +40 -0
  20. package/dist/core/engine/camera.d.ts +70 -0
  21. package/dist/core/engine/colors.d.ts +51 -0
  22. package/dist/core/engine/display.d.ts +125 -0
  23. package/dist/core/engine/export.d.ts +32 -0
  24. package/dist/core/engine/glide.d.ts +34 -0
  25. package/dist/core/engine/labels.d.ts +106 -0
  26. package/dist/core/engine/node-drag.d.ts +49 -0
  27. package/dist/core/engine/scene.d.ts +159 -0
  28. package/dist/core/engine/spin.d.ts +62 -0
  29. package/dist/core/engine/surface-pattern.d.ts +20 -0
  30. package/dist/core/engine/text-measure.d.ts +116 -0
  31. package/dist/core/engine/views.d.ts +353 -0
  32. package/dist/core/extensions.d.ts +155 -0
  33. package/dist/core/format-upgrades.d.ts +33 -0
  34. package/dist/core/index.d.ts +36 -0
  35. package/dist/core/input-controller.d.ts +109 -0
  36. package/dist/core/languages.d.ts +199 -0
  37. package/dist/core/layout.d.ts +61 -0
  38. package/dist/core/map-tools/definitions.d.ts +71 -0
  39. package/dist/core/map-tools/input.d.ts +62 -0
  40. package/dist/core/map-tools/reading.d.ts +40 -0
  41. package/dist/core/map-tools/tools.d.ts +51 -0
  42. package/dist/core/markdown-writing.d.ts +87 -0
  43. package/dist/core/material.d.ts +25 -0
  44. package/dist/core/media.d.ts +42 -0
  45. package/dist/core/node-attributes.d.ts +31 -0
  46. package/dist/core/node-links.d.ts +25 -0
  47. package/dist/core/parser-markdown.d.ts +42 -0
  48. package/dist/core/parser-yaml.d.ts +8 -0
  49. package/dist/core/render-to-svg.d.ts +57 -0
  50. package/dist/core/serializer-markdown.d.ts +18 -0
  51. package/dist/core/serializer-yaml.d.ts +2 -0
  52. package/dist/core/source-patch.d.ts +24 -0
  53. package/dist/core/source.d.ts +27 -0
  54. package/dist/core/styles.d.ts +6 -0
  55. package/dist/core/suggest.d.ts +8 -0
  56. package/dist/core/svg-renderer.d.ts +729 -0
  57. package/dist/core/text-layout.d.ts +54 -0
  58. package/dist/core/themes.d.ts +62 -0
  59. package/dist/core/types.d.ts +463 -0
  60. package/dist/core/ui/actions.d.ts +75 -0
  61. package/dist/core/ui/controls.d.ts +61 -0
  62. package/dist/core/ui/detail-sheet.d.ts +88 -0
  63. package/dist/core/ui/frames.d.ts +11 -0
  64. package/dist/core/ui/icons.d.ts +14 -0
  65. package/dist/core/ui/inline-editor.d.ts +79 -0
  66. package/dist/core/ui/media-players.d.ts +65 -0
  67. package/dist/core/ui/media-viewer.d.ts +169 -0
  68. package/dist/core/ui/media-zoom.d.ts +51 -0
  69. package/dist/core/ui/note-markdown.d.ts +19 -0
  70. package/dist/core/ui/outline.d.ts +44 -0
  71. package/dist/core/url-guard.d.ts +40 -0
  72. package/dist/core/utils.d.ts +23 -0
  73. package/dist/core/web-page/page.d.ts +58 -0
  74. package/dist/core/web-page/runtime.d.ts +13 -0
  75. package/dist/core/web-page/start.d.ts +1 -0
  76. package/dist/core/yaml-mini.d.ts +54 -0
  77. package/dist/index.d.ts +1 -0
  78. package/dist/index.js +1448 -0
  79. package/dist/index.js.map +17 -0
  80. package/dist/marked/index.d.ts +31 -0
  81. package/dist/marked/index.js +110 -0
  82. package/dist/marked/index.js.map +10 -0
  83. package/dist/react/index.d.ts +55 -0
  84. package/dist/react/index.js +352 -0
  85. package/dist/react/index.js.map +1 -0
  86. package/dist/web-page.d.ts +2 -0
  87. package/dist/web-page.js +130 -0
  88. package/llms-full.txt +2585 -0
  89. package/llms.txt +35 -0
  90. package/package.json +118 -4
  91. package/skills/orbit/SKILL.md +140 -0
  92. package/skills/orbit/references/editing.md +194 -0
  93. package/skills/orbit/references/extending.md +247 -0
  94. package/skills/orbit/references/format.md +303 -0
  95. package/skills/orbit/references/guide.md +522 -0
  96. package/skills/orbit/references/languages.md +159 -0
  97. package/skills/orbit/references/map-tools.md +188 -0
  98. package/skills/orbit/references/media.md +160 -0
  99. package/skills/orbit/references/recipes.md +227 -0
  100. package/skills/orbit/references/theming.md +184 -0
package/dist/index.js ADDED
@@ -0,0 +1,1448 @@
1
+ import {
2
+ renderToSVGString2
3
+ } from "./chunk-7n00wx0t.js";
4
+ import {
5
+ renderOrbitElement2,
6
+ renderOrbitMarkdownBlocks2,
7
+ defineOrbitMapElement2
8
+ } from "./chunk-0gwh723s.js";
9
+ import {
10
+ assignKey,
11
+ emitYaml,
12
+ nodeSlug,
13
+ nodesWithExplicitIds,
14
+ findNodeById2,
15
+ getNodePath2,
16
+ guardUrl,
17
+ DIAGNOSTIC_CODES2,
18
+ OrbitParseError2,
19
+ formatDiagnostic2,
20
+ reportParseDiagnostics,
21
+ suggest,
22
+ mediaExtras,
23
+ THEME_TOKENS2,
24
+ ORBIT_THEME_PRESETS2,
25
+ isOrbitThemePreset2,
26
+ registerTheme2,
27
+ isThemeName2,
28
+ themeNames2,
29
+ expandTheme,
30
+ themeTokens2,
31
+ documentToRendererOptions2,
32
+ isNodeLink,
33
+ findLinkedNode,
34
+ findIndexedLinkedNode,
35
+ indexNodes,
36
+ nodeLinksOf,
37
+ SUPPORTED_SCHEMA_VERSION2,
38
+ leadingEmoji,
39
+ parseOrbitMarkdown2,
40
+ parseOrbitYAML2,
41
+ parseOrbit2,
42
+ serializeOrbitMarkdown2,
43
+ newNodeFrom,
44
+ addToTree,
45
+ updateInTree,
46
+ removeFromTree,
47
+ moveInTree,
48
+ registerLanguage2,
49
+ languageTags2,
50
+ OrbitWords,
51
+ wordsFor,
52
+ parseColor,
53
+ toHex,
54
+ toOklch,
55
+ toneColor,
56
+ renderNoteMarkdown2,
57
+ statusTone,
58
+ registerNodeType2,
59
+ registerAction2,
60
+ registerStatus2,
61
+ upgradeNodeTypes2,
62
+ OrbitSVGRenderer2
63
+ } from "./chunk-mtrnm0yc.js";
64
+ // src/core/serializer-yaml.ts
65
+ var serializeOrbitYAML = (doc) => {
66
+ const out = {};
67
+ const opts = doc.options;
68
+ if (opts.title) {
69
+ out.title = opts.title;
70
+ }
71
+ if (opts.theme) {
72
+ out.theme = opts.theme;
73
+ }
74
+ if (opts.viewMode) {
75
+ out.view = opts.viewMode;
76
+ }
77
+ if (opts.showRings !== undefined) {
78
+ out.showRings = opts.showRings;
79
+ }
80
+ if (opts.interactive !== undefined) {
81
+ out.interactive = opts.interactive;
82
+ }
83
+ if (opts.orbitAnimation !== undefined) {
84
+ out.animation = opts.orbitAnimation;
85
+ }
86
+ if (opts.orbitSpeed !== undefined) {
87
+ out.speed = opts.orbitSpeed;
88
+ }
89
+ if (opts.palette) {
90
+ out.palette = opts.palette;
91
+ }
92
+ if (opts.height !== undefined) {
93
+ out.height = opts.height;
94
+ }
95
+ if (opts.maxDepth !== undefined) {
96
+ out.maxDepth = opts.maxDepth;
97
+ }
98
+ if (opts.className) {
99
+ out.className = opts.className;
100
+ }
101
+ if (opts.baseUrl) {
102
+ out.baseUrl = opts.baseUrl;
103
+ }
104
+ if (opts.schemaVersion !== undefined) {
105
+ out.schemaVersion = opts.schemaVersion;
106
+ }
107
+ for (const [key, value] of Object.entries(doc.extra ?? {})) {
108
+ if (!Object.prototype.hasOwnProperty.call(out, key) && key !== "nodes") {
109
+ assignKey(out, key, value);
110
+ }
111
+ }
112
+ out.nodes = [serializeNode(doc.root, nodesWithExplicitIds(doc.root))];
113
+ return emitYaml(out);
114
+ };
115
+ var serializeNode = (n, explicitIds) => {
116
+ const obj = { text: n.text };
117
+ if (explicitIds.has(n)) {
118
+ obj.id = n.id;
119
+ }
120
+ if (n.icon) {
121
+ obj.icon = n.icon;
122
+ }
123
+ if (n.color) {
124
+ obj.color = n.color;
125
+ }
126
+ if (n.type) {
127
+ obj.type = n.typeVersion === undefined ? n.type : `${n.type}@${n.typeVersion}`;
128
+ }
129
+ if (n.link) {
130
+ obj.link = n.link;
131
+ }
132
+ if (n.tags) {
133
+ obj.tags = n.tags;
134
+ }
135
+ if (n.status) {
136
+ obj.status = n.status;
137
+ }
138
+ if (n.priority !== undefined) {
139
+ obj.priority = n.priority;
140
+ }
141
+ if (n.collapsed !== undefined) {
142
+ obj.collapsed = n.collapsed;
143
+ }
144
+ if (n.image) {
145
+ obj.image = { ...mediaExtras(n.image), ...n.image };
146
+ }
147
+ if (n.video) {
148
+ obj.video = { ...mediaExtras(n.video), ...n.video };
149
+ }
150
+ if (n.embed) {
151
+ obj.embed = { ...mediaExtras(n.embed), ...n.embed };
152
+ }
153
+ if (n.note) {
154
+ obj.note = n.note;
155
+ if (n.noteFormat && n.noteFormat !== "markdown") {
156
+ obj.noteFormat = n.noteFormat;
157
+ }
158
+ }
159
+ if (n.attrs) {
160
+ for (const [k, v] of Object.entries(n.attrs)) {
161
+ if (!RESERVED_NODE_KEYS.has(k) && !Object.prototype.hasOwnProperty.call(obj, k)) {
162
+ assignKey(obj, k, v);
163
+ }
164
+ }
165
+ }
166
+ if (n.children.length) {
167
+ obj.children = n.children.map((child) => serializeNode(child, explicitIds));
168
+ }
169
+ return obj;
170
+ };
171
+ var RESERVED_NODE_KEYS = new Set(["text", "children"]);
172
+ // src/core/create.ts
173
+ var createOrbitMap = (target, source, options = {}) => {
174
+ const { strict, ...rendererOptions } = options;
175
+ const renderer = new OrbitSVGRenderer2(target, rendererOptions);
176
+ if (typeof source === "string") {
177
+ const doc = parseOrbit2(source, { strict, untitled: wordsFor(rendererOptions.language, target, rendererOptions.strings).text("untitledMap") });
178
+ reportParseDiagnostics(doc);
179
+ renderer.render(doc);
180
+ } else {
181
+ renderer.render(source);
182
+ }
183
+ return renderer;
184
+ };
185
+ // src/core/web-page/page.ts
186
+ var ORBIT_ICON = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='18 18 72 72'%3E%3Cdefs%3E%3CradialGradient id='d' gradientUnits='userSpaceOnUse' cx='32.4' cy='23.76' r='102.6'%3E%3Cstop offset='0' stop-color='%234A35A8'/%3E%3Cstop offset='1' stop-color='%2318134A'/%3E%3C/radialGradient%3E%3C/defs%3E%3Crect x='18' y='18' width='72' height='72' rx='16' fill='url(%23d)'/%3E%3Cpath d='M67.53,35.36 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M69.84,37.38 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M71.86,39.69 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M76.52,57.07 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M75.92,60.08 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M74.93,62.99 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M62.21,75.71 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M59.3,76.7 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M56.29,77.3 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M38.91,72.64 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M36.6,70.62 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M34.58,68.31 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M29.92,50.93 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M30.52,47.92 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M31.51,45.01 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M44.23,32.29 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M47.14,31.3 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z M50.15,30.7 a0.78,0.78 0 1,0 1.56,0 a0.78,0.78 0 1,0 -1.56,0 Z' fill='%23E2E6FF' fill-opacity='.5'/%3E%3Ccircle cx='54' cy='54' r='11' fill='%23FAD86F'/%3E%3Ccircle cx='60.08' cy='31.3' r='6.4' fill='%23FF9E96'/%3E%3Ccircle cx='76.7' cy='47.92' r='6.4' fill='%23F8B478'/%3E%3Ccircle cx='70.62' cy='70.62' r='6.4' fill='%237FDAAB'/%3E%3Ccircle cx='47.92' cy='76.7' r='6.4' fill='%2386CDFF'/%3E%3Ccircle cx='31.3' cy='60.08' r='6.4' fill='%23C9B1FE'/%3E%3Ccircle cx='37.38' cy='37.38' r='6.4' fill='%23F7A3D4'/%3E%3C/svg%3E";
187
+ var exportWebPage = (input, options) => {
188
+ const document = typeof input === "string" ? parseOrbit2(input) : input;
189
+ const source = typeof input === "string" ? input : serializeOrbitMarkdown2(input);
190
+ const words = new OrbitWords(options.language, options.strings);
191
+ const title = options.title ?? (document.options.title || document.root.text || "Orbit");
192
+ const data = {
193
+ source,
194
+ theme: options.theme,
195
+ viewMode: options.viewMode,
196
+ focus: options.focus,
197
+ language: options.language,
198
+ strings: options.strings,
199
+ media: options.media ?? {}
200
+ };
201
+ const theme = options.theme ?? document.options.theme;
202
+ return `<!doctype html>
203
+ <html lang="${escapeAttribute(words.locale)}">
204
+ <head>
205
+ <meta charset="utf-8">
206
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
207
+ <meta name="generator" content="Orbit">
208
+ <link rel="icon" href="${ORBIT_ICON}">
209
+ <title>${escapeText(title)}</title>
210
+ <style>
211
+ ${pageStyles(theme)}
212
+ </style>
213
+ </head>
214
+ <body>
215
+ <div class="page" id="page">
216
+ <div class="page__map" id="page-map" aria-label="${escapeAttribute(title)}"></div>
217
+ <button class="page__present" type="button" data-present><span aria-hidden="true">▶</span> ${escapeText(words.text("present"))}</button>
218
+ <section class="page__caption" id="page-caption" aria-live="polite" hidden>
219
+ <h1 class="page__title" id="page-title"></h1>
220
+ <div class="page__note" id="page-note"></div>
221
+ </section>
222
+ <nav class="page__controls" id="page-controls" hidden>
223
+ <button class="page__button" type="button" data-step="-1" aria-label="${escapeAttribute(words.text("previous"))}">‹</button>
224
+ <span class="page__count" id="page-count"></span>
225
+ <button class="page__button" type="button" data-step="1" aria-label="${escapeAttribute(words.text("next"))}">›</button>
226
+ <button class="page__button" type="button" data-close aria-label="${escapeAttribute(words.text("close"))}">✕</button>
227
+ </nav>
228
+ </div>
229
+ <script type="application/json" id="orbit-page">${scriptJson(data)}</script>
230
+ <script type="application/json" id="orbit-script">${scriptJson(options.script)}</script>
231
+ <script>
232
+ // Orbit runs from a script element made here: text inside one is never read as HTML.
233
+ (function () {
234
+ var script = document.createElement('script');
235
+ script.textContent = JSON.parse(document.getElementById('orbit-script').textContent);
236
+ document.body.appendChild(script);
237
+ })();
238
+ </script>
239
+ </body>
240
+ </html>
241
+ `;
242
+ };
243
+ var pageColors = (tokens) => {
244
+ const surface = tokens.surface === "transparent" ? "#ffffff" : tokens.surface;
245
+ return [
246
+ `--page-background: ${surface};`,
247
+ `--page-card: ${tokens["sheet-background"]};`,
248
+ `--page-text: ${tokens["sheet-text"]};`,
249
+ `--page-muted: ${tokens["sheet-muted-text"]};`,
250
+ `--page-border: ${tokens["sheet-border"]};`,
251
+ `--page-shadow: ${tokens["sheet-shadow"]};`
252
+ ].join(" ");
253
+ };
254
+ var tokensOf = (theme) => {
255
+ const expanded = expandTheme(theme);
256
+ const preset = typeof expanded === "string" ? expanded : expanded?.preset;
257
+ const base = themeTokens2(isOrbitThemePreset2(preset) ? preset : "paper");
258
+ return typeof expanded === "object" && expanded?.tokens ? { ...base, ...expanded.tokens } : base;
259
+ };
260
+ var pageStyles = (theme) => {
261
+ const automatic = (typeof theme === "string" ? theme : theme?.preset) === "auto";
262
+ const colors = automatic ? `:root { ${pageColors(themeTokens2("paper"))} }
263
+ @media (prefers-color-scheme: dark) { :root { ${pageColors(themeTokens2("night"))} } }` : `:root { ${pageColors(tokensOf(theme))} }`;
264
+ return `${colors}
265
+ html, body { margin: 0; height: 100%; background: var(--page-background); }
266
+ body { font: 16px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; color: var(--page-text); }
267
+ .page__map { position: fixed; inset: 0; }
268
+ .page__present, .page__controls {
269
+ position: fixed; left: 50%; bottom: max(16px, env(safe-area-inset-bottom)); transform: translateX(-50%);
270
+ display: inline-flex; align-items: center; gap: 8px; min-height: 44px; padding: 0 18px; border: 1px solid var(--page-border);
271
+ border-radius: 22px; background: var(--page-card); color: var(--page-text); box-shadow: var(--page-shadow); font: inherit; font-weight: 600;
272
+ }
273
+ .page__present { cursor: pointer; }
274
+ .page__controls { padding: 0 6px; transition: opacity 200ms ease; }
275
+ .page__button { min-width: 44px; min-height: 44px; border: 0; border-radius: 22px; background: none; color: inherit; font: inherit; font-size: 20px; cursor: pointer; }
276
+ .page__button:focus-visible, .page__present:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
277
+ .page__count { min-width: 4em; text-align: center; color: var(--page-muted); font-variant-numeric: tabular-nums; }
278
+ .page__caption {
279
+ position: fixed; left: 50%; bottom: calc(max(16px, env(safe-area-inset-bottom)) + 60px); transform: translateX(-50%);
280
+ box-sizing: border-box; width: min(720px, calc(100% - 32px)); max-height: 40vh; overflow: auto; padding: 16px 20px;
281
+ border: 1px solid var(--page-border); border-radius: 16px; background: var(--page-card); color: var(--page-text); box-shadow: var(--page-shadow);
282
+ }
283
+ .page__title { margin: 0; font-size: 20px; line-height: 1.3; }
284
+ .page__note:not(:empty) { margin-top: 8px; }
285
+ .page__note p { margin: 0 0 8px; }
286
+ .page__note img { max-width: 100%; border-radius: 8px; }
287
+ .page__note a { color: inherit; }
288
+ .page--presenting .page__present { display: none; }
289
+ .page--presenting:not(.page--controls) .page__controls { opacity: 0; }
290
+ .page--presenting:not(.page--controls) { cursor: none; }
291
+ [hidden] { display: none !important; }`;
292
+ };
293
+ var scriptJson = (value) => {
294
+ return JSON.stringify(value).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
295
+ };
296
+ var escapeText = (text) => {
297
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
298
+ };
299
+ var escapeAttribute = (text) => {
300
+ return escapeText(text).replace(/"/g, "&quot;");
301
+ };
302
+ // src/core/map-tools/input.ts
303
+ var schemaOf = (fields) => {
304
+ const required = Object.keys(fields).filter((name) => fields[name].required);
305
+ return {
306
+ type: "object",
307
+ properties: Object.fromEntries(Object.entries(fields).map(([name, field]) => [name, propertyOf(field)])),
308
+ ...required.length > 0 ? { required } : {}
309
+ };
310
+ };
311
+ var propertyOf = (field) => {
312
+ if (field.type === "list") {
313
+ return { type: "array", description: field.description, items: field.values ? { type: "string", enum: [...field.values] } : { type: "string" } };
314
+ }
315
+ if (field.type === "record") {
316
+ return { ...schemaOf(field.fields ?? {}), description: field.description };
317
+ }
318
+ return { type: field.type, description: field.description, ...field.minimum === undefined ? {} : { minimum: field.minimum } };
319
+ };
320
+
321
+ class InputError extends Error {
322
+ }
323
+ var readInput = (tool, fields, input, synonyms = {}) => {
324
+ const record = inputRecord(tool, fields, input);
325
+ const values = {};
326
+ const ignored = [];
327
+ const names = Object.keys(fields);
328
+ for (const [given, value] of Object.entries(record)) {
329
+ if (value === null || value === undefined) {
330
+ continue;
331
+ }
332
+ const name = owns(fields, given) ? given : synonymOf(given, names, synonyms);
333
+ if (name === undefined) {
334
+ const near = suggest(given, names);
335
+ if (near !== undefined) {
336
+ throw new InputError(`${tool} has no field "${given}". Did you mean "${near}"? It takes ${list(names)}.`);
337
+ }
338
+ ignored.push(given);
339
+ continue;
340
+ }
341
+ if (owns(values, name)) {
342
+ throw new InputError(`${tool} got ${name} twice, as "${name}" and as "${given}": give it once.`);
343
+ }
344
+ values[name] = readValue(name, fields[name], value);
345
+ }
346
+ for (const name of names) {
347
+ if (fields[name].required && !owns(values, name)) {
348
+ throw new InputError(`${tool} needs ${name}: ${lowerFirst(fields[name].description)}`);
349
+ }
350
+ }
351
+ return { values, ignored };
352
+ };
353
+ var inputRecord = (tool, fields, input) => {
354
+ let value = input;
355
+ if (typeof value === "string") {
356
+ if (value.trim() === "") {
357
+ return {};
358
+ }
359
+ try {
360
+ value = JSON.parse(value);
361
+ } catch (error) {
362
+ throw new InputError(`${tool}'s input isn't valid JSON (${error.message}). Send an object, like ${example(fields)}.`);
363
+ }
364
+ }
365
+ if (value === null || value === undefined) {
366
+ return {};
367
+ }
368
+ if (typeof value !== "object" || Array.isArray(value)) {
369
+ throw new InputError(`${tool} takes an object, like ${example(fields)}, not ${describe(value)}.`);
370
+ }
371
+ return value;
372
+ };
373
+ var synonymOf = (given, names, synonyms) => {
374
+ const lowered = given.toLowerCase();
375
+ return names.find((name) => name.toLowerCase() === lowered) ?? (owns(synonyms, lowered) ? synonyms[lowered] : undefined);
376
+ };
377
+ var owns = (record, key) => {
378
+ return Object.prototype.hasOwnProperty.call(record, key);
379
+ };
380
+ var readValue = (name, field, value) => {
381
+ switch (field.type) {
382
+ case "string":
383
+ if (typeof value === "string") {
384
+ return value;
385
+ }
386
+ if (typeof value === "number" || typeof value === "boolean") {
387
+ return String(value);
388
+ }
389
+ throw new InputError(`${name} takes text, not ${describe(value)}.`);
390
+ case "integer":
391
+ case "number": {
392
+ const number = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : Number.NaN;
393
+ if (!Number.isFinite(number) || field.type === "integer" && !Number.isInteger(number)) {
394
+ throw new InputError(`${name} takes ${field.type === "integer" ? "a whole number" : "a number"}, like ${field.minimum ?? 1}, not ${describe(value)}.`);
395
+ }
396
+ if (field.minimum !== undefined && number < field.minimum) {
397
+ throw new InputError(`${name} takes ${field.minimum} or more, not ${number}. ${field.description}`);
398
+ }
399
+ return number;
400
+ }
401
+ case "boolean": {
402
+ const word = typeof value === "string" ? value.trim().toLowerCase() : value;
403
+ if (word === true || word === "true" || word === "yes") {
404
+ return true;
405
+ }
406
+ if (word === false || word === "false" || word === "no") {
407
+ return false;
408
+ }
409
+ throw new InputError(`${name} takes true or false, not ${describe(value)}.`);
410
+ }
411
+ case "list": {
412
+ const items = Array.isArray(value) ? value : typeof value === "string" ? value.trim() === "" ? [] : value.split(",") : null;
413
+ if (!items || items.some((item) => typeof item !== "string" && typeof item !== "number")) {
414
+ throw new InputError(`${name} takes a list of words, like ["${field.values?.[0] ?? "design"}"], not ${describe(value)}.`);
415
+ }
416
+ const words = items.map((item) => String(item).trim()).filter((item) => item !== "");
417
+ for (const word of words) {
418
+ if (field.values && !field.values.includes(word)) {
419
+ const near = suggest(word, field.values);
420
+ throw new InputError(`${name} can't take "${word}".${near === undefined ? "" : ` Did you mean "${near}"?`} It takes ${list(field.values)}.`);
421
+ }
422
+ }
423
+ return words;
424
+ }
425
+ case "record": {
426
+ const fields = field.fields ?? {};
427
+ const first = Object.keys(fields)[0];
428
+ if (typeof value === "string") {
429
+ return { [first]: value };
430
+ }
431
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
432
+ throw new InputError(`${name} takes an object, like {"${first}": "…"}, not ${describe(value)}.`);
433
+ }
434
+ return readInput(name, fields, value).values;
435
+ }
436
+ }
437
+ };
438
+ var describe = (value) => {
439
+ if (value === null) {
440
+ return "null";
441
+ }
442
+ if (Array.isArray(value)) {
443
+ return "a list";
444
+ }
445
+ if (typeof value === "string") {
446
+ return JSON.stringify(value.length > 40 ? `${value.slice(0, 40)}…` : value);
447
+ }
448
+ if (typeof value === "object") {
449
+ return "an object";
450
+ }
451
+ return String(value);
452
+ };
453
+ var list = (words) => {
454
+ return words.length <= 1 ? words.join("") : `${words.slice(0, -1).join(", ")} and ${words[words.length - 1]}`;
455
+ };
456
+ var example = (fields) => {
457
+ const name = Object.keys(fields).find((key) => fields[key].required) ?? Object.keys(fields)[0];
458
+ return name === undefined ? "{}" : `{"${name}": "…"}`;
459
+ };
460
+ var lowerFirst = (text) => {
461
+ return text.charAt(0).toLowerCase() + text.slice(1);
462
+ };
463
+
464
+ // src/core/map-tools/definitions.ts
465
+ var PROVIDERS = ["anthropic", "openai", "openai-chat", "gemini"];
466
+ var ATTRIBUTES = ["note", "status", "priority", "tags", "icon", "color", "link", "image", "video", "embed", "collapsed"];
467
+ var NODE_ID = "node_id";
468
+ var NODE_SYNONYMS = { id: NODE_ID, node: NODE_ID, nodeid: NODE_ID };
469
+ var TOOL_SPECS = [
470
+ {
471
+ name: "read_map",
472
+ description: "Read the mind map as an outline: a line for each node (an idea), indented under its parent, with its id after a #, its attributes in parentheses, and the start of its note on the next line after >. Read it before changing the map: the other tools take these ids. A big map is cut short: read a branch with node_id, or search with find_nodes.",
473
+ fields: {
474
+ node_id: { type: "string", description: "Read only this node and the nodes under it. Leave out to read the whole map." },
475
+ depth: { type: "integer", minimum: 1, description: "How many levels under it to read: 1 for its children only. Leave out to read them all." }
476
+ },
477
+ synonyms: NODE_SYNONYMS,
478
+ readOnly: true,
479
+ destructive: false
480
+ },
481
+ {
482
+ name: "find_nodes",
483
+ description: "Search the map for nodes by the words in their titles, notes and tags, by status or by tag. Returns each match with its id and where it is in the map.",
484
+ fields: {
485
+ query: { type: "string", description: "Words to look for, in any case, with or without accents. A node matches when it has every word." },
486
+ status: { type: "string", description: "Only nodes with this status. todo, active, done and blocked also find the words that mean the same, such as planned, in progress, shipped or stuck." },
487
+ tag: { type: "string", description: "Only nodes with this tag." }
488
+ },
489
+ synonyms: { text: "query", search: "query", words: "query", tags: "tag" },
490
+ readOnly: true,
491
+ destructive: false
492
+ },
493
+ {
494
+ name: "read_node",
495
+ description: "Read everything about one node: its title, its whole note, its attributes, its picture, video or embedded page, where it is in the map, its children, and the nodes that link to it.",
496
+ fields: {
497
+ node_id: { type: "string", required: true, description: "The node to read, by the id read_map or find_nodes gave." }
498
+ },
499
+ synonyms: NODE_SYNONYMS,
500
+ readOnly: true,
501
+ destructive: false
502
+ },
503
+ {
504
+ name: "add_nodes",
505
+ description: "Add nodes under a node: one, several, or whole branches at once, written as a markdown list. Each item is a node, items indented under it are its children, at any depth, and indented lines under an item that aren't items are its note. On an item's line, [ ], [/] or [x] first sets its status to todo, active or done, an emoji first sets its icon, words after a # at the end are its tags, and <!-- orbit color: tomato, priority: 1 --> at the end sets other attributes. Returns the new nodes with their ids.",
506
+ fields: {
507
+ parent_id: { type: "string", required: true, description: "The node to add them under." },
508
+ outline: { type: "string", required: true, description: 'The nodes, as a markdown list: "- Social media\\n - Instagram\\n- Newsletter".' },
509
+ index: { type: "integer", minimum: 0, description: "Where they go among the children of parent_id: 0 puts them first. Leave out to put them last." }
510
+ },
511
+ synonyms: { parent: "parent_id", parentid: "parent_id", under: "parent_id", markdown: "outline", nodes: "outline", items: "outline", children: "outline", text: "outline", position: "index" },
512
+ readOnly: false,
513
+ destructive: false
514
+ },
515
+ {
516
+ name: "update_node",
517
+ description: "Change a node's title or attributes. Only the fields you give change; to remove an attribute, name it in clear. A new title can give the node a new id, which the result says.",
518
+ fields: {
519
+ node_id: { type: "string", required: true, description: "The node to change." },
520
+ text: { type: "string", description: "Its title: plain text, a few words." },
521
+ note: { type: "string", description: "Its note, in markdown, for details and longer text. Replaces the note it has." },
522
+ status: { type: "string", description: "todo, active, done or blocked, which show a colored dot, or any other word." },
523
+ priority: { type: "number", description: "A number, such as 1 for the most important." },
524
+ tags: { type: "list", description: "All its tags, as words: they replace the ones it has." },
525
+ icon: { type: "string", description: "An emoji shown before its title." },
526
+ color: { type: "string", description: "A CSS color, such as #E8A33D or tomato: its children take shades of it. Nodes without one take the theme's colors, so set it only when asked." },
527
+ link: { type: "string", description: "A web address it links to, or #id to link to another node of the map." },
528
+ image: {
529
+ type: "record",
530
+ description: "A picture shown in the node.",
531
+ fields: {
532
+ src: { type: "string", required: true, description: "The picture's address." },
533
+ alt: { type: "string", description: "What the picture shows, for people who can't see it." },
534
+ caption: { type: "string", description: "A caption." }
535
+ }
536
+ },
537
+ video: {
538
+ type: "record",
539
+ description: "A video the node plays.",
540
+ fields: {
541
+ src: { type: "string", required: true, description: "The video's address: YouTube, Vimeo or a video file." },
542
+ caption: { type: "string", description: "A caption." }
543
+ }
544
+ },
545
+ embed: {
546
+ type: "record",
547
+ description: "A web page shown in a frame in the node's details.",
548
+ fields: {
549
+ src: { type: "string", required: true, description: "The page's address." },
550
+ caption: { type: "string", description: "A caption." }
551
+ }
552
+ },
553
+ collapsed: { type: "boolean", description: "true hides its children until the reader opens it." },
554
+ clear: { type: "list", values: ATTRIBUTES, description: 'Attributes to remove, such as ["status", "color"].' }
555
+ },
556
+ synonyms: {
557
+ ...NODE_SYNONYMS,
558
+ title: "text",
559
+ name: "text",
560
+ label: "text",
561
+ notes: "note",
562
+ description: "note",
563
+ details: "note",
564
+ state: "status",
565
+ tag: "tags",
566
+ labels: "tags",
567
+ emoji: "icon",
568
+ colour: "color",
569
+ url: "link",
570
+ href: "link",
571
+ picture: "image",
572
+ photo: "image",
573
+ remove: "clear",
574
+ unset: "clear",
575
+ delete: "clear"
576
+ },
577
+ readOnly: false,
578
+ destructive: true
579
+ },
580
+ {
581
+ name: "move_node",
582
+ description: "Move a node, with everything under it, under another node, or to another place among its siblings.",
583
+ fields: {
584
+ node_id: { type: "string", required: true, description: "The node to move." },
585
+ parent_id: { type: "string", description: "The node to move it under. Leave out to keep its parent and change only its place." },
586
+ index: { type: "integer", minimum: 0, description: "Its place among the children of its new parent: 0 puts it first. Leave out to put it last." }
587
+ },
588
+ synonyms: { ...NODE_SYNONYMS, parent: "parent_id", parentid: "parent_id", under: "parent_id", to: "parent_id", position: "index" },
589
+ readOnly: false,
590
+ destructive: false
591
+ },
592
+ {
593
+ name: "reorder_children",
594
+ description: "Put the children of a node in a new order, all at once: to sort them, or to group the ones that go together.",
595
+ fields: {
596
+ parent_id: { type: "string", required: true, description: "The node whose children to reorder." },
597
+ order: { type: "list", required: true, description: "The ids of all its children, in their new order." }
598
+ },
599
+ synonyms: { parent: "parent_id", parentid: "parent_id", node_id: "parent_id", id: "parent_id", ids: "order", children: "order", child_ids: "order" },
600
+ readOnly: false,
601
+ destructive: false
602
+ },
603
+ {
604
+ name: "remove_node",
605
+ description: "Remove a node and everything under it. Remove nodes only when asked to.",
606
+ fields: {
607
+ node_id: { type: "string", required: true, description: "The node to remove. The root can't be removed." }
608
+ },
609
+ synonyms: NODE_SYNONYMS,
610
+ readOnly: false,
611
+ destructive: true
612
+ },
613
+ {
614
+ name: "show_node",
615
+ description: "Show the reader a node: the map flies to it, with its children around it. Use it when they ask to see or go to something, or to show them what you changed.",
616
+ fields: {
617
+ node_id: { type: "string", required: true, description: "The node to show." }
618
+ },
619
+ synonyms: NODE_SYNONYMS,
620
+ readOnly: true,
621
+ destructive: false
622
+ }
623
+ ];
624
+ var definitionOf = (spec) => {
625
+ return {
626
+ name: spec.name,
627
+ description: spec.description,
628
+ inputSchema: schemaOf(spec.fields),
629
+ annotations: { readOnlyHint: spec.readOnly, destructiveHint: spec.destructive }
630
+ };
631
+ };
632
+ var formatDefinitions = (definitions, provider) => {
633
+ const tools = definitions.map(({ name, description, inputSchema }) => ({ name, description, schema: structuredCopy(inputSchema) }));
634
+ switch (provider) {
635
+ case "anthropic":
636
+ return tools.map(({ name, description, schema }) => ({ name, description, input_schema: schema }));
637
+ case "openai":
638
+ return tools.map(({ name, description, schema }) => ({ type: "function", name, description, parameters: schema, strict: false }));
639
+ case "openai-chat":
640
+ return tools.map(({ name, description, schema }) => ({ type: "function", function: { name, description, parameters: schema } }));
641
+ case "gemini":
642
+ return [{ functionDeclarations: tools.map(({ name, description, schema }) => ({ name, description, parametersJsonSchema: schema })) }];
643
+ default:
644
+ throw new TypeError(`Map tools come in the shapes of ${PROVIDERS.map((name) => `"${name}"`).join(", ")}, not ${JSON.stringify(provider)}. For any other, build them from tools.definitions: each has a name, a description and an inputSchema (JSON Schema).`);
645
+ }
646
+ };
647
+ var structuredCopy = (value) => {
648
+ return JSON.parse(JSON.stringify(value));
649
+ };
650
+
651
+ // src/core/map-tools/reading.ts
652
+ var NOTE_PREVIEW = 100;
653
+ var MATCH_LIMIT = 50;
654
+ var nameOf = (node) => {
655
+ return `"${node.text}" #${node.id}`;
656
+ };
657
+ var outlineLine = (node, depth) => {
658
+ const details = attributesOf(node);
659
+ return `${" ".repeat(depth)}- ${node.text} #${node.id}${details.length > 0 ? ` (${details.join("; ")})` : ""}`;
660
+ };
661
+ var attributesOf = (node) => {
662
+ const details = [];
663
+ if (node.icon) {
664
+ details.push(`icon: ${node.icon}`);
665
+ }
666
+ if (node.status) {
667
+ details.push(`status: ${node.status}`);
668
+ }
669
+ if (node.priority !== undefined) {
670
+ details.push(`priority: ${node.priority}`);
671
+ }
672
+ if (node.tags && node.tags.length > 0) {
673
+ details.push(`tags: ${node.tags.join(", ")}`);
674
+ }
675
+ if (node.color) {
676
+ details.push(`color: ${node.color}`);
677
+ }
678
+ if (node.link) {
679
+ details.push(`link: ${node.link}`);
680
+ }
681
+ for (const media of ["image", "video", "embed"]) {
682
+ if (node[media]) {
683
+ details.push(media);
684
+ }
685
+ }
686
+ if (node.type) {
687
+ details.push(`type: ${node.type}`);
688
+ }
689
+ if (node.collapsed) {
690
+ details.push("collapsed");
691
+ }
692
+ return details;
693
+ };
694
+ var notePreview = (note) => {
695
+ const text = note.trim().replace(/\s+/g, " ");
696
+ return text.length > NOTE_PREVIEW ? `${text.slice(0, NOTE_PREVIEW - 1).trimEnd()}…` : text;
697
+ };
698
+ var headOf = (node, depth) => {
699
+ const lines = [outlineLine(node, depth)];
700
+ if (node.note?.trim()) {
701
+ lines.push(`${" ".repeat(depth + 1)}> ${notePreview(node.note)}`);
702
+ }
703
+ return lines;
704
+ };
705
+ var outlineOf = (node, depth = 0, levels = Infinity) => {
706
+ const lines = headOf(node, depth);
707
+ if (levels > 0) {
708
+ for (const child of node.children) {
709
+ lines.push(...outlineOf(child, depth + 1, levels - 1));
710
+ }
711
+ } else if (node.children.length > 0) {
712
+ lines.push(`${" ".repeat(depth + 1)}- … ${count(node.children.length, "child", "children")}, ${count(descendants(node), "node")} in all under it`);
713
+ }
714
+ return lines;
715
+ };
716
+ var readOutline = (start, levels, limit) => {
717
+ let shown = 0;
718
+ let total = 1;
719
+ let level = [start];
720
+ while (shown < levels) {
721
+ const next = level.flatMap((node) => node.children);
722
+ if (next.length === 0 || total + next.length > limit) {
723
+ break;
724
+ }
725
+ total += next.length;
726
+ level = next;
727
+ shown += 1;
728
+ }
729
+ const complete = shown === levels || level.every((node) => node.children.length === 0);
730
+ if (shown === 0 && !complete) {
731
+ const children = start.children.slice(0, Math.max(1, limit - 1));
732
+ return {
733
+ lines: [
734
+ ...headOf(start, 0),
735
+ ...children.flatMap((child) => outlineOf(child, 1, 0)),
736
+ ` - … ${count(start.children.length - children.length, "more child", "more children")} of ${nameOf(start)}`
737
+ ],
738
+ complete
739
+ };
740
+ }
741
+ return { lines: outlineOf(start, 0, shown), complete };
742
+ };
743
+ var descendants = (node) => {
744
+ return node.children.reduce((sum, child) => sum + 1 + descendants(child), 0);
745
+ };
746
+ var count = (amount, one, many = `${one}s`) => {
747
+ return `${amount} ${amount === 1 ? one : many}`;
748
+ };
749
+ var nodeDetails = (root, node, path) => {
750
+ const lines = [nameOf(node)];
751
+ lines.push(path.length > 1 ? `Where: ${path.map((step) => step.text).join(" › ")}` : "Where: the root of the map");
752
+ const fields = [
753
+ ["Icon", node.icon],
754
+ ["Status", node.status],
755
+ ["Priority", node.priority],
756
+ ["Tags", node.tags && node.tags.length > 0 ? node.tags.join(", ") : undefined],
757
+ ["Color", node.color],
758
+ ["Link", node.link],
759
+ ["Picture", node.image && mediaLine(node.image.src, { alt: node.image.alt, caption: node.image.caption })],
760
+ ["Video", node.video && mediaLine(node.video.src, { caption: node.video.caption })],
761
+ ["Embedded page", node.embed && mediaLine(node.embed.src, { caption: node.embed.caption })],
762
+ ["Type", node.type],
763
+ ["Collapsed", node.collapsed ? "yes: its children are hidden until the reader opens it" : undefined]
764
+ ];
765
+ for (const [label, value] of fields) {
766
+ if (value !== undefined && value !== "") {
767
+ lines.push(`${label}: ${String(value)}`);
768
+ }
769
+ }
770
+ const custom = Object.entries(node.attrs ?? {});
771
+ if (custom.length > 0) {
772
+ lines.push(`Other attributes: ${custom.map(([key, value]) => `${key}: ${typeof value === "string" ? value : JSON.stringify(value)}`).join("; ")}`);
773
+ }
774
+ lines.push(node.note?.trim() ? `Note${node.noteFormat === "plain" ? " (plain text)" : ""}:
775
+ ${node.note.trim()}` : "Note: none");
776
+ lines.push(node.children.length > 0 ? `Children (${node.children.length}): ${node.children.map(nameOf).join(", ")}` : "Children: none");
777
+ const linking = nodesLinkingTo(root, node);
778
+ if (linking.length > 0) {
779
+ lines.push(`Linked from: ${linking.map(nameOf).join(", ")}`);
780
+ }
781
+ return lines.join(`
782
+ `);
783
+ };
784
+ var mediaLine = (src, extras) => {
785
+ const described = Object.entries(extras).filter(([, value]) => value).map(([key, value]) => `${key}: ${value}`);
786
+ return described.length > 0 ? `${src} (${described.join("; ")})` : src;
787
+ };
788
+ var nodesLinkingTo = (root, target) => {
789
+ const nodes = indexNodes(root);
790
+ const linking = [];
791
+ const visit = (node) => {
792
+ if (nodeLinksOf(node).some((href) => findIndexedLinkedNode(nodes, href) === target)) {
793
+ linking.push(node);
794
+ }
795
+ node.children.forEach(visit);
796
+ };
797
+ visit(root);
798
+ return linking;
799
+ };
800
+ var searchable = (text) => {
801
+ return text.normalize("NFKD").replace(/\p{M}+/gu, "").toLowerCase();
802
+ };
803
+ var findNodes = (root, search) => {
804
+ const words = searchable(search.query ?? "").split(/\s+/).filter((word) => word !== "");
805
+ const status = search.status === undefined ? undefined : searchable(search.status.trim());
806
+ const tag = search.tag === undefined ? undefined : searchable(search.tag.trim().replace(/^#/, ""));
807
+ const statusMatches = (node) => {
808
+ if (status === undefined) {
809
+ return true;
810
+ }
811
+ if (!node.status) {
812
+ return false;
813
+ }
814
+ const own = searchable(node.status.trim());
815
+ const tone = statusTone(status);
816
+ return own === status || tone !== "neutral" && statusTone(own) === tone;
817
+ };
818
+ const matches = [];
819
+ const visit = (node, path) => {
820
+ const title = searchable([node.text, ...node.tags ?? []].join(" "));
821
+ const everything = `${title} ${searchable(node.note ?? "")}`;
822
+ if (words.every((word) => everything.includes(word)) && statusMatches(node) && (tag === undefined || (node.tags ?? []).some((own) => searchable(own) === tag))) {
823
+ matches.push({ node, path, inTitle: words.every((word) => title.includes(word)) });
824
+ }
825
+ node.children.forEach((child) => visit(child, [...path, node]));
826
+ };
827
+ visit(root, []);
828
+ if (matches.length === 0) {
829
+ return "No node matches. Try fewer or other words, or read the map with read_map.";
830
+ }
831
+ matches.sort((first, second) => Number(second.inTitle) - Number(first.inTitle));
832
+ const shown = matches.slice(0, MATCH_LIMIT);
833
+ const lines = [`${count(matches.length, "node matches", "nodes match")}${matches.length > shown.length ? `; the first ${shown.length}` : ""}:`];
834
+ for (const { node, path } of shown) {
835
+ lines.push(`${outlineLine(node, 0)}${path.length > 0 ? ` in ${path.map((step) => step.text).join(" › ")}` : " (the root)"}`);
836
+ }
837
+ if (matches.length > shown.length) {
838
+ lines.push("Narrow the search to see the others.");
839
+ }
840
+ return lines.join(`
841
+ `);
842
+ };
843
+
844
+ // src/core/map-tools/tools.ts
845
+ var READ_LIMIT = 250;
846
+ var LONG_TITLE = 60;
847
+ var COLOR_SYNTAX = /^(?:#(?:[\da-f]{3,4}|[\da-f]{6}|[\da-f]{8})|[a-z]+|(?:rgba?|hsla?|hwb|lab|lch|oklab|oklch|color|color-mix|light-dark|var)\(.+\))$/i;
848
+ var OUTLINE_CODES_LEFT_OUT = new Set(["synthetic-root", "no-structure", "unknown-link-target"]);
849
+
850
+ class CallError extends Error {
851
+ }
852
+ var createMapTools = (map, options = {}) => {
853
+ const target = targetOf(map);
854
+ const readLimit = typeof options.readLimit === "number" && Number.isFinite(options.readLimit) ? Math.max(10, Math.trunc(options.readLimit)) : READ_LIMIT;
855
+ const specs = TOOL_SPECS.filter((spec) => (spec.name !== "show_node" || target.live !== null) && (spec.readOnly || !options.readOnly));
856
+ const definitions = specs.map(definitionOf);
857
+ const renamed = new Map;
858
+ const removed = new Map;
859
+ const notes = [];
860
+ const resolve = (root, given, field) => {
861
+ const id = given.trim().replace(/^#/, "");
862
+ const direct = findNodeById2(root, id);
863
+ if (direct) {
864
+ return direct;
865
+ }
866
+ let current = id;
867
+ const seen = new Set;
868
+ while (renamed.has(current) && !seen.has(current)) {
869
+ seen.add(current);
870
+ current = renamed.get(current);
871
+ }
872
+ const followed = current === id ? null : findNodeById2(root, current);
873
+ if (followed) {
874
+ notes.push(`#${id} is ${nameOf(followed)} now.`);
875
+ return followed;
876
+ }
877
+ if (removed.has(id)) {
878
+ throw new CallError(`${field} names #${id}, but "${removed.get(id)}" was removed.`);
879
+ }
880
+ const wanted = searchable(id);
881
+ const titled = allNodes(root).filter((node) => searchable(node.text.trim()) === wanted || node.id === nodeSlug(id));
882
+ if (titled.length === 1) {
883
+ notes.push(`${field} takes ids: the id of "${titled[0].text}" is #${titled[0].id}.`);
884
+ return titled[0];
885
+ }
886
+ if (titled.length > 1) {
887
+ throw new CallError(`${titled.length} nodes are called "${given}": ${titled.map((node) => `${nameOf(node)}${node.parent ? ` under "${node.parent.text}"` : ""}`).join(", ")}. Give ${field} one of their ids.`);
888
+ }
889
+ const near = suggest(id, allNodes(root).map((node) => node.id));
890
+ const nearNode = near === undefined ? null : findNodeById2(root, near);
891
+ throw new CallError(`No node has the id "${id}".${nearNode ? ` Did you mean ${nameOf(nearNode)}?` : ""} read_map and find_nodes give every node's id.`);
892
+ };
893
+ const calls = {
894
+ read_map: (values, root, document) => {
895
+ const start = typeof values.node_id === "string" ? resolve(root, values.node_id, "node_id") : root;
896
+ const levels = typeof values.depth === "number" ? values.depth : Infinity;
897
+ const { lines, complete } = readOutline(start, levels, readLimit);
898
+ const header = [];
899
+ if (start === root) {
900
+ const title = document.options.title ?? root.text;
901
+ header.push(`The map "${title}" has ${count(1 + descendants(root), "node")}.`);
902
+ } else {
903
+ const path = getNodePath2(root, start.id);
904
+ header.push(`${nameOf(start)}, in ${path.slice(0, -1).map((node) => node.text).join(" › ")}, has ${count(descendants(start), "node")} under it.`);
905
+ }
906
+ const live = target.live;
907
+ if (live) {
908
+ const focused = live.getFocusedNode();
909
+ header.push(live.getViewMode() === "all" || !focused ? "The reader is looking at the whole map." : `The reader is looking at ${nameOf(focused)}.`);
910
+ }
911
+ const footer = complete ? [] : ["Nodes further down aren't shown: read a branch with read_map and its node_id, or search with find_nodes."];
912
+ return { text: [header.join(" "), ...lines, ...footer].join(`
913
+ `), changed: false };
914
+ },
915
+ find_nodes: (values, root) => {
916
+ if (values.query === undefined && values.status === undefined && values.tag === undefined) {
917
+ throw new CallError("find_nodes needs something to look for: query, status, tag, or more than one.");
918
+ }
919
+ return { text: findNodes(root, values), changed: false };
920
+ },
921
+ read_node: (values, root) => {
922
+ const node = resolve(root, values.node_id, "node_id");
923
+ return { text: nodeDetails(root, node, getNodePath2(root, node.id)), changed: false };
924
+ },
925
+ add_nodes: (values, root) => {
926
+ const parent = resolve(root, values.parent_id, "parent_id");
927
+ const nodes = readNewNodes(values.outline);
928
+ const index = values.index;
929
+ const added = [];
930
+ nodes.forEach((input, offset) => {
931
+ const node = target.add(parent.id, input, index === undefined ? undefined : index + offset);
932
+ if (node) {
933
+ added.push(node);
934
+ }
935
+ });
936
+ if (added.length === 0) {
937
+ throw new CallError(`Nothing could be added under ${nameOf(parent)}.`);
938
+ }
939
+ for (const node of added) {
940
+ const twin = parent.children.find((sibling) => sibling !== node && !added.includes(sibling) && searchable(sibling.text) === searchable(node.text));
941
+ if (twin) {
942
+ notes.push(`${nameOf(node)} has the same title as ${nameOf(twin)} beside it. If it's there twice by mistake, remove one with remove_node.`);
943
+ }
944
+ }
945
+ const all = added.flatMap(allNodes);
946
+ const long = all.filter((node) => node.text.length > LONG_TITLE);
947
+ if (long.length > 0) {
948
+ notes.push(`${long.map(nameOf).join(", ")} ${long.length === 1 ? "has a long title" : "have long titles"}: a few words read best on the map, with the rest in the note.`);
949
+ }
950
+ notes.push(...brokenLinks(target.document()?.root ?? root, all));
951
+ return {
952
+ text: [`Added ${count(all.length, "node")} under ${nameOf(parent)}${placeOf(added, parent)}:`, ...added.flatMap((node) => outlineOf(node))].join(`
953
+ `),
954
+ changed: added.length > 0
955
+ };
956
+ },
957
+ update_node: (values, root) => {
958
+ const node = resolve(root, values.node_id, "node_id");
959
+ const previousId = node.id;
960
+ const { changes, set, cleared, absent } = readChanges(values, root, node);
961
+ if (set.length === 0 && cleared.length === 0) {
962
+ if (absent.length === 0) {
963
+ throw new CallError(`update_node needs something to change: ${list(["text", ...ATTRIBUTES])}, or clear to remove attributes.`);
964
+ }
965
+ return { text: `${nameOf(node)} has no ${list(absent)} to remove: nothing changed.`, changed: false };
966
+ }
967
+ const updated = target.update(node.id, changes);
968
+ if (!updated) {
969
+ throw new CallError(`${nameOf(node)} couldn't be changed.`);
970
+ }
971
+ if (changes.note !== undefined) {
972
+ notes.push(...brokenLinks(root, [updated]));
973
+ }
974
+ const described = [set.length > 0 ? `set its ${list(set)}` : "", cleared.length > 0 ? `removed its ${list(cleared)}` : ""].filter((part) => part !== "").join("; ");
975
+ let text = `Changed ${nameOf(updated)}: ${described}.`;
976
+ if (updated.id !== previousId) {
977
+ renamed.set(previousId, updated.id);
978
+ text += ` Its id is #${updated.id} now: it was #${previousId}.`;
979
+ }
980
+ return { text, changed: true };
981
+ },
982
+ move_node: (values, root) => {
983
+ const node = resolve(root, values.node_id, "node_id");
984
+ if (values.parent_id === undefined && values.index === undefined) {
985
+ throw new CallError("move_node needs where to move the node: parent_id, index, or both.");
986
+ }
987
+ const path = getNodePath2(root, node.id);
988
+ if (path.length < 2) {
989
+ throw new CallError(`${nameOf(node)} is the root: it can't move, as every other node is under it.`);
990
+ }
991
+ const parent = values.parent_id === undefined ? path[path.length - 2] : resolve(root, values.parent_id, "parent_id");
992
+ if (parent === node) {
993
+ throw new CallError(`${nameOf(node)} can't go under itself. Give parent_id another node.`);
994
+ }
995
+ if (getNodePath2(root, parent.id).includes(node)) {
996
+ throw new CallError(`${nameOf(node)} can't go under ${nameOf(parent)}, which is inside it. Move ${nameOf(parent)} out of it first, or choose another parent.`);
997
+ }
998
+ const under = descendants(node);
999
+ const moved = target.move(node.id, parent.id, values.index);
1000
+ if (!moved) {
1001
+ throw new CallError(`${nameOf(node)} couldn't be moved.`);
1002
+ }
1003
+ return { text: `Moved ${nameOf(moved)}${under > 0 ? `, with the ${count(under, "node")} under it,` : ""} under ${nameOf(parent)}${placeOf([moved], parent)}.`, changed: true };
1004
+ },
1005
+ reorder_children: (values, root) => {
1006
+ const parent = resolve(root, values.parent_id, "parent_id");
1007
+ const children = [...parent.children];
1008
+ if (children.length === 0) {
1009
+ throw new CallError(`${nameOf(parent)} has no children to reorder.`);
1010
+ }
1011
+ const order = values.order.map((given) => childNamed(parent, given));
1012
+ const twice = order.find((child, position) => order.indexOf(child) !== position);
1013
+ if (twice) {
1014
+ throw new CallError(`order lists ${nameOf(twice)} twice: list each child once.`);
1015
+ }
1016
+ const missing = children.filter((child) => !order.includes(child));
1017
+ if (missing.length > 0) {
1018
+ throw new CallError(`order needs all ${children.length} children of ${nameOf(parent)}, and leaves out ${missing.map(nameOf).join(", ")}.`);
1019
+ }
1020
+ if (order.every((child, position) => children[position] === child)) {
1021
+ return { text: `The children of ${nameOf(parent)} are in that order already.`, changed: false };
1022
+ }
1023
+ order.forEach((child, position) => {
1024
+ const current = findNodeById2(target.document()?.root ?? root, parent.id) ?? parent;
1025
+ if (current.children[position]?.id !== child.id) {
1026
+ target.move(child.id, parent.id, position);
1027
+ }
1028
+ });
1029
+ return { text: `The children of ${nameOf(parent)} are in this order now: ${order.map(nameOf).join(", ")}.`, changed: true };
1030
+ },
1031
+ remove_node: (values, root) => {
1032
+ const node = resolve(root, values.node_id, "node_id");
1033
+ if (node === root) {
1034
+ throw new CallError(`${nameOf(node)} is the root, and the map is all under it: it can't be removed. Remove the nodes under it one by one, or give it a new title with update_node.`);
1035
+ }
1036
+ const gone = allNodes(node);
1037
+ if (!target.remove(node.id)) {
1038
+ throw new CallError(`${nameOf(node)} couldn't be removed.`);
1039
+ }
1040
+ for (const each of gone) {
1041
+ removed.set(each.id, each.text);
1042
+ }
1043
+ return { text: `Removed ${nameOf(node)}${gone.length > 1 ? ` and the ${count(gone.length - 1, "node")} under it` : ""}.`, changed: true };
1044
+ },
1045
+ show_node: (values, root) => {
1046
+ const node = resolve(root, values.node_id, "node_id");
1047
+ target.live?.flyTo(node.id);
1048
+ return { text: `The map shows ${nameOf(node)} now, with its children around it.`, changed: false };
1049
+ }
1050
+ };
1051
+ const childNamed = (parent, given) => {
1052
+ const id = given.trim().replace(/^#/, "");
1053
+ const child = parent.children.find((each) => each.id === id || each.id === renamed.get(id)) ?? parent.children.find((each) => searchable(each.text.trim()) === searchable(id));
1054
+ if (!child) {
1055
+ throw new CallError(`${given} isn't a child of ${nameOf(parent)}. Its children: ${parent.children.map(nameOf).join(", ")}.`);
1056
+ }
1057
+ return child;
1058
+ };
1059
+ const run = (name, input) => {
1060
+ notes.length = 0;
1061
+ const spec = TOOL_SPECS.find((each) => each.name === name);
1062
+ const offered = specs.find((each) => each.name === name);
1063
+ if (!offered) {
1064
+ if (spec?.name === "show_node") {
1065
+ return failure("show_node only works on a map on screen.");
1066
+ }
1067
+ if (spec) {
1068
+ return failure(`${name} isn't available: this map can only be read.`);
1069
+ }
1070
+ const near = suggest(String(name), specs.map((each) => each.name));
1071
+ return failure(`There's no tool "${String(name)}".${near === undefined ? "" : ` Did you mean ${near}?`} The tools are ${list(specs.map((each) => each.name))}.`);
1072
+ }
1073
+ const document = target.document();
1074
+ if (!document) {
1075
+ return failure("There's no map to read yet: it hasn't been drawn.");
1076
+ }
1077
+ if (!offered.readOnly && target.newer) {
1078
+ return failure("This map was written for a newer version of Orbit, and changing it here could lose what this version doesn't read. It can be read, but not changed.");
1079
+ }
1080
+ try {
1081
+ const { values, ignored } = readInput(offered.name, offered.fields, input, offered.synonyms);
1082
+ const outcome = calls[offered.name](values, document.root, document);
1083
+ if (ignored.length > 0) {
1084
+ notes.push(`${offered.name} doesn't take ${list(ignored.map((key) => `"${key}"`))}, so ${ignored.length === 1 ? "it was" : "they were"} left out. It takes ${list(Object.keys(offered.fields))}.`);
1085
+ }
1086
+ return { ok: true, text: [outcome.text, ...notes.map((note) => `Note: ${note}`)].join(`
1087
+ `), changed: outcome.changed };
1088
+ } catch (error) {
1089
+ if (error instanceof CallError || error instanceof InputError) {
1090
+ return failure(error.message, offered);
1091
+ }
1092
+ throw error;
1093
+ }
1094
+ };
1095
+ return {
1096
+ definitions,
1097
+ instructions: instructionsFor(target.live !== null, options.readOnly === true),
1098
+ for: (provider) => formatDefinitions(definitions, provider),
1099
+ run,
1100
+ get document() {
1101
+ return target.document();
1102
+ }
1103
+ };
1104
+ };
1105
+ var failure = (message, spec) => {
1106
+ return { ok: false, text: spec && !spec.readOnly ? `${message} Nothing changed.` : message, changed: false };
1107
+ };
1108
+ var targetOf = (map) => {
1109
+ if (isLiveMap(map)) {
1110
+ return {
1111
+ document: () => map.getDocument(),
1112
+ add: (parentId, input, index) => map.addNode(parentId, input, index),
1113
+ update: (id, changes) => map.updateNode(id, changes),
1114
+ move: (id, parentId, index) => map.moveNode(id, parentId, index),
1115
+ remove: (id) => map.removeNode(id),
1116
+ live: map,
1117
+ newer: false
1118
+ };
1119
+ }
1120
+ const document = typeof map === "string" ? parseOrbit2(map) : map;
1121
+ if (!document || typeof document !== "object" || !document.root || !Array.isArray(document.root.children)) {
1122
+ throw new TypeError(`createMapTools() takes a map on screen (createOrbitMap's result, or <orbit-map>'s renderer), a parsed document, or a map's markdown or YAML, but got ${map === null ? "null" : typeof map}.`);
1123
+ }
1124
+ return {
1125
+ document: () => document,
1126
+ add: (parentId, input, index) => addToTree(document.root, parentId, input, index)?.node ?? null,
1127
+ update: (id, changes) => updateInTree(document.root, id, changes)?.node ?? null,
1128
+ move: (id, parentId, index) => moveInTree(document.root, id, parentId, index)?.node ?? null,
1129
+ remove: (id) => removeFromTree(document.root, id)?.node ?? null,
1130
+ live: null,
1131
+ newer: (document.diagnostics ?? []).some((diagnostic) => diagnostic.code === "unsupported-schema-version")
1132
+ };
1133
+ };
1134
+ var isLiveMap = (map) => {
1135
+ const candidate = map;
1136
+ return typeof candidate === "object" && candidate !== null && typeof candidate.getDocument === "function" && typeof candidate.addNode === "function";
1137
+ };
1138
+ var instructionsFor = (live, readOnly) => {
1139
+ const lines = readOnly ? [
1140
+ "You can read the mind map with the map tools. Each node is an idea, with an id.",
1141
+ "- read_map shows the map as an outline, with every node's id; find_nodes searches a big one, and read_node shows everything about one node, its whole note included.",
1142
+ "- Answer from what the map says, and say when it doesn't say."
1143
+ ] : [
1144
+ "You can read and change the mind map with the map tools. Each node is an idea, with an id.",
1145
+ "- Read the map before you change it: read_map shows it as an outline with every node's id, and find_nodes searches a big one. The other tools take those ids.",
1146
+ "- Keep titles to a few words, and put details and longer text in the note.",
1147
+ "- Write in the language the map is written in.",
1148
+ "- Add several nodes, or whole branches, with one add_nodes call.",
1149
+ "- A new title can give a node a new id: use the ids the latest results give.",
1150
+ "- Remove nodes only when asked to."
1151
+ ];
1152
+ if (live) {
1153
+ lines.push(readOnly ? "- show_node takes the reader to a node." : "- The reader sees each change on the map as you make it, and show_node takes them to a node.");
1154
+ }
1155
+ return lines.join(`
1156
+ `);
1157
+ };
1158
+ var allNodes = (root) => {
1159
+ return [root, ...root.children.flatMap(allNodes)];
1160
+ };
1161
+ var placeOf = (nodes, parent) => {
1162
+ const first = parent.children.indexOf(nodes[0]);
1163
+ const last = parent.children.indexOf(nodes[nodes.length - 1]);
1164
+ if (first > 0) {
1165
+ return `, after ${nameOf(parent.children[first - 1])}`;
1166
+ }
1167
+ if (first === 0 && last < parent.children.length - 1) {
1168
+ return `, first, before ${nameOf(parent.children[last + 1])}`;
1169
+ }
1170
+ return "";
1171
+ };
1172
+ var brokenLinks = (root, nodes) => {
1173
+ const index = indexNodes(root);
1174
+ const ids = [...index.keys()];
1175
+ return nodes.flatMap((node) => nodeLinksOf(node).filter((href) => !findIndexedLinkedNode(index, href)).map((href) => {
1176
+ const near = suggest(href.slice(1), ids);
1177
+ return `${nameOf(node)} links to ${href}, but no node has that id.${near === undefined ? "" : ` Did you mean #${near}?`}`;
1178
+ }));
1179
+ };
1180
+ var readNewNodes = (outline) => {
1181
+ let source = outline.replace(/\r\n?/g, `
1182
+ `);
1183
+ if (/^\s*---\s*\n/.test(source)) {
1184
+ throw new CallError("outline takes the nodes to add as a markdown list, without frontmatter.");
1185
+ }
1186
+ let document = parseOrbitMarkdown2(source);
1187
+ if (document.diagnostics.some((diagnostic) => diagnostic.code === "no-structure")) {
1188
+ source = source.split(`
1189
+ `).map((line) => line.trim()).filter((line) => line !== "").map((line) => `- ${line}`).join(`
1190
+ `);
1191
+ document = parseOrbitMarkdown2(source);
1192
+ }
1193
+ const roots = document.diagnostics.some((diagnostic) => diagnostic.code === "synthetic-root") ? document.root.children : [document.root];
1194
+ const untitled = roots.flatMap(allNodes).find((node) => node.text.trim() === "");
1195
+ if (roots.length === 0 || untitled) {
1196
+ throw new CallError(untitled?.source ? `Line ${untitled.source.line} of outline has an item without a title.` : 'outline has no nodes: write them as a markdown list, like "- Social media\\n- Newsletter".');
1197
+ }
1198
+ const problems = document.diagnostics.filter((diagnostic) => !OUTLINE_CODES_LEFT_OUT.has(diagnostic.code)).map((diagnostic) => formatDiagnostic2(diagnostic).replace(/[,;] kept as (?:a custom attribute|written)(?:, but not used)?/, ""));
1199
+ if (problems.length > 0) {
1200
+ throw new CallError(`outline doesn't read cleanly. Fix ${problems.length === 1 ? "this" : "these"}, and send it again:
1201
+ ${problems.map((problem) => `- ${problem}`).join(`
1202
+ `)}
1203
+ Lines count from the outline's first line.`);
1204
+ }
1205
+ return roots.map(newNodeFrom);
1206
+ };
1207
+ var readChanges = (values, root, node) => {
1208
+ const changes = {};
1209
+ const set = [];
1210
+ const clear = new Set(values.clear ?? []);
1211
+ const cleared = [];
1212
+ const absent = [];
1213
+ const remove = (name) => {
1214
+ if (node[name] === undefined) {
1215
+ absent.push(name);
1216
+ return;
1217
+ }
1218
+ changes[name] = undefined;
1219
+ cleared.push(name);
1220
+ };
1221
+ for (const name of clear) {
1222
+ if (values[name] !== undefined) {
1223
+ throw new CallError(`${name} is both given and in clear: give it a value, or clear it.`);
1224
+ }
1225
+ remove(name);
1226
+ }
1227
+ if (values.text !== undefined) {
1228
+ let text = String(values.text).replace(/\s+/g, " ").trim();
1229
+ if (text === "") {
1230
+ throw new CallError("text can't be empty: every node has a title. To take the node out of the map, use remove_node.");
1231
+ }
1232
+ const emoji = leadingEmoji(text);
1233
+ if (emoji && values.icon === undefined && !clear.has("icon")) {
1234
+ changes.icon = emoji.icon;
1235
+ text = emoji.text;
1236
+ set.push("icon");
1237
+ }
1238
+ changes.text = text;
1239
+ set.push("title");
1240
+ }
1241
+ const text = (name) => {
1242
+ const value = values[name];
1243
+ if (value === undefined) {
1244
+ return;
1245
+ }
1246
+ const written = name === "note" ? String(value).trim() : String(value).replace(/\s+/g, " ").trim();
1247
+ if (written === "") {
1248
+ remove(name);
1249
+ return;
1250
+ }
1251
+ set.push(name);
1252
+ return written;
1253
+ };
1254
+ const note = text("note");
1255
+ if (note !== undefined) {
1256
+ changes.note = note;
1257
+ }
1258
+ const status = text("status");
1259
+ if (status !== undefined) {
1260
+ changes.status = status;
1261
+ }
1262
+ if (values.priority !== undefined) {
1263
+ changes.priority = values.priority;
1264
+ set.push("priority");
1265
+ }
1266
+ if (values.tags !== undefined) {
1267
+ const tags = [...new Set(values.tags.map((tag) => tag.replace(/^#/, "").trim()).filter((tag) => tag !== ""))];
1268
+ if (tags.length === 0) {
1269
+ remove("tags");
1270
+ } else {
1271
+ changes.tags = tags;
1272
+ set.push("tags");
1273
+ }
1274
+ }
1275
+ const icon = text("icon");
1276
+ if (icon !== undefined) {
1277
+ if (/^[\p{L}\p{N}][\p{L}\p{N}\s'-]+$/u.test(icon) || Array.from(icon).length > 8) {
1278
+ throw new CallError(`icon takes an emoji, such as \uD83D\uDE80, not ${JSON.stringify(icon)}.`);
1279
+ }
1280
+ changes.icon = icon;
1281
+ }
1282
+ const color = text("color");
1283
+ if (color !== undefined) {
1284
+ const supports = typeof CSS !== "undefined" && typeof CSS.supports === "function" ? CSS.supports("color", color) : true;
1285
+ if (!COLOR_SYNTAX.test(color) || !supports) {
1286
+ throw new CallError(`"${color}" isn't a CSS color. Use a color name, such as tomato, or a code, such as #E8A33D.`);
1287
+ }
1288
+ changes.color = color;
1289
+ }
1290
+ const link = text("link");
1291
+ if (link !== undefined) {
1292
+ if (isNodeLink(link)) {
1293
+ if (!findLinkedNode(root, link)) {
1294
+ const near = suggest(link.slice(1), allNodes(root).map((each) => each.id));
1295
+ throw new CallError(`link ${link} points at no node.${near === undefined ? "" : ` Did you mean #${near}?`} Node links are # and a node's id.`);
1296
+ }
1297
+ } else if (guardUrl(link, "link", false) === null) {
1298
+ throw new CallError(`"${link}" isn't an address the map can open: use a web address, such as https://example.com.`);
1299
+ }
1300
+ changes.link = link;
1301
+ }
1302
+ for (const name of ["image", "video", "embed"]) {
1303
+ const media = values[name];
1304
+ if (media === undefined) {
1305
+ continue;
1306
+ }
1307
+ const src = media.src.trim();
1308
+ if (src === "") {
1309
+ remove(name);
1310
+ continue;
1311
+ }
1312
+ if (guardUrl(src, name, false) === null) {
1313
+ throw new CallError(`${name} can't show "${src}": use a web address, such as https://example.com/${name === "image" ? "photo.jpg" : name === "video" ? "video.mp4" : "page"}.`);
1314
+ }
1315
+ const extras = Object.fromEntries(Object.entries(media).filter(([key, value]) => key !== "src" && value.trim() !== "").map(([key, value]) => [key, value.trim()]));
1316
+ changes[name] = { src, ...extras };
1317
+ set.push(name === "image" ? "picture" : name === "embed" ? "embedded page" : "video");
1318
+ }
1319
+ if (values.collapsed !== undefined) {
1320
+ if (values.collapsed === true) {
1321
+ changes.collapsed = true;
1322
+ set.push("collapsed");
1323
+ } else {
1324
+ remove("collapsed");
1325
+ }
1326
+ }
1327
+ return { changes, set, cleared, absent };
1328
+ };
1329
+ // src/core/material.ts
1330
+ var MATERIAL_SEED = "#6750A4";
1331
+ var BRANCH_HUES = [0, 3, 6, 2, 5, 1, 4].map((step) => step * 360 / 7);
1332
+ var MOST_CHROMA = 0.16;
1333
+ var BRANCH_CHROMA = 0.11;
1334
+ var ERROR = { hue: 25, chroma: 0.19 };
1335
+ var SUCCESS = { hue: 145, chroma: 0.13 };
1336
+ var materialTheme = (seed, options = {}) => {
1337
+ const dark = options.dark === true;
1338
+ return { preset: dark ? "material-night" : "material", tokens: materialColors(seed, dark) };
1339
+ };
1340
+ var materialColors = (seed, dark) => {
1341
+ const parsed = parseColor(seed);
1342
+ if (!parsed) {
1343
+ throw new Error(`A Material theme needs a color Orbit can read, such as "#0B57D0" or "oklch(0.55 0.15 260)": got "${seed}".`);
1344
+ }
1345
+ const { hue, chroma } = toOklch(parsed);
1346
+ const primaryChroma = Math.min(chroma, MOST_CHROMA);
1347
+ const palette = (paletteHue, paletteChroma) => (tone) => toHex(toneColor(paletteHue, paletteChroma, tone));
1348
+ const primary = palette(hue, primaryChroma);
1349
+ const secondary = palette(hue, Math.min(primaryChroma / 3, 0.05));
1350
+ const neutral = palette(hue, Math.min(primaryChroma / 10, 0.012));
1351
+ const neutralVariant = palette(hue, Math.min(primaryChroma / 5, 0.024));
1352
+ const error = palette(ERROR.hue, ERROR.chroma);
1353
+ const success = palette(SUCCESS.hue, SUCCESS.chroma);
1354
+ const branches = BRANCH_HUES.map((offset) => palette((hue + offset) % 360, BRANCH_CHROMA));
1355
+ const [accent, container, onContainer] = dark ? [80, 30, 90] : [40, 90, 10];
1356
+ const branchTone = dark ? 32 : 88;
1357
+ const [colorRoot, ...colors] = [primary(accent), ...branches.map((branch) => branch(branchTone))];
1358
+ return {
1359
+ surface: neutral(dark ? 6 : 98),
1360
+ text: neutral(dark ? 90 : 10),
1361
+ "muted-text": neutralVariant(dark ? 80 : 30),
1362
+ "root-text": neutral(10),
1363
+ "node-text": neutral(10),
1364
+ "node-text-inverse": neutral(dark ? 90 : 100),
1365
+ "color-root": colorRoot,
1366
+ "color-1": colors[0],
1367
+ "color-2": colors[1],
1368
+ "color-3": colors[2],
1369
+ "color-4": colors[3],
1370
+ "color-5": colors[4],
1371
+ "color-6": colors[5],
1372
+ "color-7": colors[6],
1373
+ "shade-light": dark ? neutral(40) : neutralVariant(80),
1374
+ "shade-dark": dark ? neutral(12) : neutralVariant(50),
1375
+ "node-stroke": neutralVariant(dark ? 30 : 80),
1376
+ "badge-stroke": neutral(dark ? 6 : 98),
1377
+ "sun-halo": primary(accent),
1378
+ "ring-stroke": neutralVariant(dark ? 30 : 80),
1379
+ "link-stroke": neutralVariant(dark ? 60 : 50),
1380
+ "focus-ring": primary(accent),
1381
+ "note-indicator": primary(accent),
1382
+ "control-background": neutral(dark ? 12 : 94),
1383
+ "control-text": neutral(dark ? 90 : 10),
1384
+ "control-muted-text": neutralVariant(dark ? 80 : 30),
1385
+ "control-active-background": secondary(container),
1386
+ "control-active-text": secondary(onContainer),
1387
+ "tooltip-background": neutral(dark ? 90 : 20),
1388
+ "tooltip-text": neutral(dark ? 20 : 95),
1389
+ "action-background": neutral(dark ? 17 : 92),
1390
+ "action-text": neutral(dark ? 90 : 10),
1391
+ "sheet-background": neutral(dark ? 10 : 96),
1392
+ "sheet-text": neutral(dark ? 90 : 10),
1393
+ "sheet-muted-text": neutralVariant(dark ? 80 : 30),
1394
+ "sheet-border": neutralVariant(dark ? 30 : 80),
1395
+ "chip-background": secondary(container),
1396
+ "chip-text": secondary(onContainer),
1397
+ "link-text": primary(accent),
1398
+ "status-active": primary(accent),
1399
+ "status-done": success(accent),
1400
+ "status-blocked": error(accent),
1401
+ "status-todo": neutralVariant(60),
1402
+ "status-neutral": neutralVariant(dark ? 70 : 50),
1403
+ "error-background": error(container),
1404
+ "error-border": error(accent),
1405
+ "error-text": error(onContainer)
1406
+ };
1407
+ };
1408
+ export {
1409
+ DIAGNOSTIC_CODES2 as DIAGNOSTIC_CODES,
1410
+ MATERIAL_SEED,
1411
+ ORBIT_THEME_PRESETS2 as ORBIT_THEME_PRESETS,
1412
+ OrbitParseError2 as OrbitParseError,
1413
+ OrbitSVGRenderer2 as OrbitSVGRenderer,
1414
+ SUPPORTED_SCHEMA_VERSION2 as SUPPORTED_SCHEMA_VERSION,
1415
+ THEME_TOKENS2 as THEME_TOKENS,
1416
+ createMapTools,
1417
+ createOrbitMap,
1418
+ defineOrbitMapElement2 as defineOrbitMapElement,
1419
+ documentToRendererOptions2 as documentToRendererOptions,
1420
+ exportWebPage,
1421
+ findNodeById2 as findNodeById,
1422
+ formatDiagnostic2 as formatDiagnostic,
1423
+ getNodePath2 as getNodePath,
1424
+ isOrbitThemePreset2 as isOrbitThemePreset,
1425
+ isThemeName2 as isThemeName,
1426
+ languageTags2 as languageTags,
1427
+ materialTheme,
1428
+ parseOrbit2 as parseOrbit,
1429
+ parseOrbitMarkdown2 as parseOrbitMarkdown,
1430
+ parseOrbitYAML2 as parseOrbitYAML,
1431
+ registerAction2 as registerAction,
1432
+ registerLanguage2 as registerLanguage,
1433
+ registerNodeType2 as registerNodeType,
1434
+ registerStatus2 as registerStatus,
1435
+ registerTheme2 as registerTheme,
1436
+ renderNoteMarkdown2 as renderNoteMarkdown,
1437
+ renderOrbitElement2 as renderOrbitElement,
1438
+ renderOrbitMarkdownBlocks2 as renderOrbitMarkdownBlocks,
1439
+ renderToSVGString2 as renderToSVGString,
1440
+ serializeOrbitMarkdown2 as serializeOrbitMarkdown,
1441
+ serializeOrbitYAML,
1442
+ themeNames2 as themeNames,
1443
+ themeTokens2 as themeTokens,
1444
+ upgradeNodeTypes2 as upgradeNodeTypes
1445
+ };
1446
+
1447
+ //# debugId=DE40EE65BFF35E2264756E2164756E21
1448
+ //# sourceMappingURL=index.js.map