caudate-cli 0.1.0__py3-none-any.whl

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 (153) hide show
  1. api/__init__.py +5 -0
  2. api/anthropic_compat.py +1518 -0
  3. api/artifact_viewer.py +366 -0
  4. api/caudate_middleware.py +618 -0
  5. api/forge_bootstrapper_routes.py +377 -0
  6. api/forge_routes.py +630 -0
  7. api/forge_system_routes.py +294 -0
  8. api/openai_compat.py +1993 -0
  9. api/server.py +667 -0
  10. api/storyboard_page.py +677 -0
  11. caudate_cli-0.1.0.dist-info/METADATA +354 -0
  12. caudate_cli-0.1.0.dist-info/RECORD +153 -0
  13. caudate_cli-0.1.0.dist-info/WHEEL +5 -0
  14. caudate_cli-0.1.0.dist-info/entry_points.txt +2 -0
  15. caudate_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
  16. caudate_cli-0.1.0.dist-info/top_level.txt +14 -0
  17. cognos_mcp/__init__.py +4 -0
  18. cognos_mcp/bridge.py +41 -0
  19. cognos_mcp/client.py +70 -0
  20. cognos_mcp/config.py +49 -0
  21. cognos_mcp/server.py +66 -0
  22. config.py +82 -0
  23. core/__init__.py +0 -0
  24. core/agent.py +468 -0
  25. core/agentic_loop.py +731 -0
  26. core/anthropic_auth.py +91 -0
  27. core/background.py +113 -0
  28. core/banner.py +134 -0
  29. core/bootstrap.py +292 -0
  30. core/citations.py +131 -0
  31. core/compaction.py +109 -0
  32. core/constitution.py +198 -0
  33. core/diff_viewer.py +87 -0
  34. core/export.py +85 -0
  35. core/file_refs.py +119 -0
  36. core/files.py +199 -0
  37. core/hooks.py +209 -0
  38. core/image.py +599 -0
  39. core/input.py +91 -0
  40. core/loop.py +238 -0
  41. core/memory_md.py +147 -0
  42. core/notifications.py +99 -0
  43. core/ownership.py +181 -0
  44. core/paste.py +81 -0
  45. core/permissions.py +210 -0
  46. core/plan_mode.py +215 -0
  47. core/sandbox_prompt.py +185 -0
  48. core/scheduler.py +195 -0
  49. core/schemas.py +202 -0
  50. core/session.py +90 -0
  51. core/settings.py +132 -0
  52. core/skills.py +398 -0
  53. core/slash_commands.py +977 -0
  54. core/statusline.py +61 -0
  55. core/subagent.py +300 -0
  56. core/thinking.py +50 -0
  57. core/updater.py +122 -0
  58. core/usage.py +109 -0
  59. core/worktree.py +93 -0
  60. execution/__init__.py +0 -0
  61. execution/executor.py +329 -0
  62. execution/plugins.py +108 -0
  63. execution/tools/__init__.py +0 -0
  64. execution/tools/agent_tool.py +107 -0
  65. execution/tools/agentic_tool.py +297 -0
  66. execution/tools/artifact_tool.py +191 -0
  67. execution/tools/ask_user_question_tool.py +137 -0
  68. execution/tools/base.py +81 -0
  69. execution/tools/calculator_tool.py +137 -0
  70. execution/tools/cognos_card_tool.py +124 -0
  71. execution/tools/cron_tool.py +215 -0
  72. execution/tools/datetime_tool.py +215 -0
  73. execution/tools/describe_image_tool.py +161 -0
  74. execution/tools/draw_tool.py +164 -0
  75. execution/tools/edit_image_tool.py +262 -0
  76. execution/tools/edit_tool.py +245 -0
  77. execution/tools/file_tool.py +90 -0
  78. execution/tools/find_anywhere_tool.py +255 -0
  79. execution/tools/forge_feature_tools.py +377 -0
  80. execution/tools/glob_tool.py +59 -0
  81. execution/tools/grep_tool.py +89 -0
  82. execution/tools/http_request_tool.py +224 -0
  83. execution/tools/load_skill_tool.py +104 -0
  84. execution/tools/longcat_avatar_tool.py +384 -0
  85. execution/tools/mcp_tool.py +100 -0
  86. execution/tools/notebook_tool.py +279 -0
  87. execution/tools/openapi_tool.py +440 -0
  88. execution/tools/plan_mode_tool.py +95 -0
  89. execution/tools/push_notification_tool.py +157 -0
  90. execution/tools/python_tool.py +61 -0
  91. execution/tools/respond_tool.py +40 -0
  92. execution/tools/sandbox_tool.py +378 -0
  93. execution/tools/search_tool.py +153 -0
  94. execution/tools/semantic_search_tool.py +106 -0
  95. execution/tools/shell_tool.py +283 -0
  96. execution/tools/speak_tool.py +134 -0
  97. execution/tools/storyboard_tool.py +727 -0
  98. execution/tools/system_info_tool.py +212 -0
  99. execution/tools/task_tool.py +323 -0
  100. execution/tools/think_tool.py +49 -0
  101. execution/tools/transcribe_audio_tool.py +86 -0
  102. execution/tools/update_memory_tool.py +92 -0
  103. execution/tools/web_fetch_tool.py +82 -0
  104. execution/tools/worktree_tool.py +174 -0
  105. llm/__init__.py +0 -0
  106. llm/fallback.py +116 -0
  107. llm/models.py +320 -0
  108. llm/provider.py +1356 -0
  109. llm/router.py +373 -0
  110. main.py +1889 -0
  111. memory/__init__.py +0 -0
  112. memory/episodic.py +99 -0
  113. memory/procedural.py +145 -0
  114. memory/semantic.py +71 -0
  115. memory/working.py +64 -0
  116. nn/__init__.py +43 -0
  117. nn/auto_evolve.py +245 -0
  118. nn/caudate.py +136 -0
  119. nn/config.py +141 -0
  120. nn/consolidator.py +81 -0
  121. nn/data.py +1635 -0
  122. nn/encoder.py +258 -0
  123. nn/forge_advisor.py +303 -0
  124. nn/format.py +235 -0
  125. nn/heads.py +432 -0
  126. nn/observer.py +994 -0
  127. nn/policy.py +214 -0
  128. nn/runtime.py +343 -0
  129. nn/scorer.py +175 -0
  130. nn/trainer.py +515 -0
  131. nn/vision.py +352 -0
  132. personality/__init__.py +23 -0
  133. personality/engine.py +129 -0
  134. personality/identity.py +144 -0
  135. personality/inner_voice.py +100 -0
  136. personality/mood.py +205 -0
  137. planning/__init__.py +0 -0
  138. planning/dev_server.py +221 -0
  139. planning/forge_models.py +718 -0
  140. planning/orchestrator.py +1363 -0
  141. planning/planner.py +451 -0
  142. planning/task_graph.py +61 -0
  143. reflection/__init__.py +0 -0
  144. reflection/meta_learner.py +156 -0
  145. reflection/reflector.py +127 -0
  146. ui/__init__.py +5 -0
  147. ui/display.py +88 -0
  148. voice/__init__.py +0 -0
  149. voice/conversation.py +125 -0
  150. voice/listener.py +111 -0
  151. voice/speaker.py +59 -0
  152. voice/stt.py +126 -0
  153. voice/tts.py +214 -0
api/artifact_viewer.py ADDED
@@ -0,0 +1,366 @@
1
+ """Artifact viewer — polished single-page renderer for FileStore items.
2
+
3
+ Phase 1 of `COGNOS_UI_ROADMAP.md`. The route `GET /artifact/{file_id}`
4
+ serves a clean HTML page that renders the file according to its type:
5
+
6
+ - HTML / SVG → iframe / inline preview + collapsible source view
7
+ - Code (py/js/ts/html/css/md/json/yaml/sh/...) → syntax-highlighted
8
+ - Markdown → rendered (marked.js) + raw toggle
9
+ - Image → full-size view + download
10
+ - Audio → HTML5 player + download
11
+ - Other → download-only fallback
12
+
13
+ Compared to the raw `/files/{id}/content` endpoint, this route gives
14
+ each artifact its own polished standalone page — the foundation for
15
+ later moving toward a side-pane experience inside the chat UI.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import html
21
+ import json
22
+ from pathlib import Path
23
+ from typing import Any
24
+
25
+
26
+ # Map file suffixes to (kind, prism-language) — drives the renderer.
27
+ # `kind` is one of: html, svg, image, audio, video, markdown, code, text, binary.
28
+ # `prism` is the Prism.js language tag used when the kind is `code` or `markdown` raw.
29
+ _TYPE_MAP: dict[str, tuple[str, str]] = {
30
+ ".html": ("html", "markup"),
31
+ ".htm": ("html", "markup"),
32
+ ".svg": ("svg", "markup"),
33
+ ".md": ("markdown", "markdown"),
34
+ ".markdown": ("markdown", "markdown"),
35
+ ".py": ("code", "python"),
36
+ ".js": ("code", "javascript"),
37
+ ".mjs": ("code", "javascript"),
38
+ ".ts": ("code", "typescript"),
39
+ ".tsx": ("code", "tsx"),
40
+ ".jsx": ("code", "jsx"),
41
+ ".css": ("code", "css"),
42
+ ".json": ("code", "json"),
43
+ ".yaml": ("code", "yaml"),
44
+ ".yml": ("code", "yaml"),
45
+ ".toml": ("code", "toml"),
46
+ ".sh": ("code", "bash"),
47
+ ".bash": ("code", "bash"),
48
+ ".rs": ("code", "rust"),
49
+ ".go": ("code", "go"),
50
+ ".java": ("code", "java"),
51
+ ".c": ("code", "c"),
52
+ ".cpp": ("code", "cpp"),
53
+ ".sql": ("code", "sql"),
54
+ ".xml": ("code", "xml"),
55
+ ".txt": ("text", "none"),
56
+ ".log": ("text", "none"),
57
+ ".png": ("image", "none"),
58
+ ".jpg": ("image", "none"),
59
+ ".jpeg": ("image", "none"),
60
+ ".webp": ("image", "none"),
61
+ ".gif": ("image", "none"),
62
+ ".bmp": ("image", "none"),
63
+ ".wav": ("audio", "none"),
64
+ ".mp3": ("audio", "none"),
65
+ ".ogg": ("audio", "none"),
66
+ ".flac": ("audio", "none"),
67
+ ".m4a": ("audio", "none"),
68
+ ".mp4": ("video", "none"),
69
+ ".webm": ("video", "none"),
70
+ }
71
+
72
+
73
+ def _classify(filename: str, mime_type: str) -> tuple[str, str]:
74
+ """Return (kind, prism-language) for the file."""
75
+ suffix = Path(filename).suffix.lower()
76
+ if suffix in _TYPE_MAP:
77
+ return _TYPE_MAP[suffix]
78
+ # Fallback by MIME family
79
+ if mime_type:
80
+ if mime_type.startswith("image/"):
81
+ return ("image", "none")
82
+ if mime_type.startswith("audio/"):
83
+ return ("audio", "none")
84
+ if mime_type.startswith("video/"):
85
+ return ("video", "none")
86
+ if mime_type.startswith("text/"):
87
+ return ("text", "none")
88
+ if "json" in mime_type:
89
+ return ("code", "json")
90
+ if "html" in mime_type:
91
+ return ("html", "markup")
92
+ if "svg" in mime_type:
93
+ return ("svg", "markup")
94
+ return ("binary", "none")
95
+
96
+
97
+ def render_artifact_page(
98
+ *,
99
+ file_id: str,
100
+ filename: str,
101
+ mime_type: str,
102
+ size_bytes: int,
103
+ content_bytes: bytes,
104
+ ) -> str:
105
+ """Build the single-page viewer HTML for one artifact."""
106
+ kind, prism = _classify(filename, mime_type)
107
+
108
+ # The /files/<id>/content URL — used by iframes / image / audio
109
+ raw_url = f"/files/{file_id}/content"
110
+
111
+ # For text-y kinds, decode and stash raw text for source view + JS render
112
+ raw_text = ""
113
+ if kind in ("html", "svg", "code", "markdown", "text"):
114
+ try:
115
+ raw_text = content_bytes.decode("utf-8", errors="replace")
116
+ except Exception:
117
+ raw_text = ""
118
+
119
+ safe_filename = html.escape(filename)
120
+ safe_size = _humanize_bytes(size_bytes)
121
+ type_badge = _badge_for_kind(kind, prism)
122
+
123
+ # Body section depends on kind
124
+ body = _render_body(kind, prism, raw_url, raw_text, filename)
125
+
126
+ # Embed raw text in a JSON-encoded JS variable so toggles can read
127
+ # it without a second fetch. Limit size to avoid huge pages —
128
+ # >2 MB falls back to "use the download button".
129
+ embedded_text = json.dumps(raw_text) if len(raw_text) < 2_000_000 else "null"
130
+
131
+ return f"""<!DOCTYPE html>
132
+ <html lang="en">
133
+ <head>
134
+ <meta charset="UTF-8">
135
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
136
+ <title>{safe_filename} — Cognos artifact</title>
137
+ <link rel="stylesheet"
138
+ href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css">
139
+ <style>
140
+ :root {{
141
+ --bg: #0f1115;
142
+ --bg-elev: #181b21;
143
+ --bg-pre: #0c0e12;
144
+ --text: #e6e7eb;
145
+ --text-dim: #8a8e98;
146
+ --border: #262a32;
147
+ --accent: #7aa2f7;
148
+ }}
149
+ * {{ box-sizing: border-box; margin: 0; padding: 0; }}
150
+ html, body {{ height: 100%; }}
151
+ body {{
152
+ background: var(--bg);
153
+ color: var(--text);
154
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
155
+ font-size: 14px;
156
+ line-height: 1.55;
157
+ display: flex; flex-direction: column;
158
+ }}
159
+ header {{
160
+ display: flex; align-items: center; gap: 12px;
161
+ padding: 10px 16px;
162
+ background: var(--bg-elev);
163
+ border-bottom: 1px solid var(--border);
164
+ }}
165
+ header .filename {{ font-weight: 600; font-size: 13px; }}
166
+ header .meta {{ color: var(--text-dim); font-size: 11px; }}
167
+ header .badge {{
168
+ background: var(--bg);
169
+ border: 1px solid var(--border);
170
+ padding: 2px 8px; border-radius: 10px;
171
+ font-size: 11px; color: var(--accent);
172
+ text-transform: uppercase; letter-spacing: 0.5px;
173
+ }}
174
+ header .controls {{ margin-left: auto; display: flex; gap: 6px; }}
175
+ button, .btn {{
176
+ background: var(--bg);
177
+ color: var(--text);
178
+ border: 1px solid var(--border);
179
+ padding: 6px 12px;
180
+ font-size: 12px; cursor: pointer;
181
+ border-radius: 4px; text-decoration: none;
182
+ font-family: inherit;
183
+ }}
184
+ button:hover, .btn:hover {{ border-color: var(--accent); color: var(--accent); }}
185
+ main {{
186
+ flex: 1; min-height: 0; overflow: auto;
187
+ padding: 16px 22px;
188
+ }}
189
+ iframe.preview {{
190
+ width: 100%; height: calc(100vh - 110px);
191
+ border: 1px solid var(--border); border-radius: 6px;
192
+ background: white;
193
+ }}
194
+ img.preview {{ max-width: 100%; max-height: calc(100vh - 110px); border-radius: 6px; }}
195
+ audio.preview, video.preview {{ width: 100%; }}
196
+ .svg-container {{ background: white; padding: 20px; border-radius: 6px;
197
+ max-width: 100%; overflow: auto; }}
198
+ .svg-container svg {{ max-width: 100%; height: auto; }}
199
+ .markdown-rendered {{
200
+ background: var(--bg-elev); border: 1px solid var(--border);
201
+ padding: 22px 28px; border-radius: 6px; max-width: 760px;
202
+ }}
203
+ .markdown-rendered h1 {{ margin: 0 0 14px; font-size: 22px; border-bottom: 1px solid var(--border); padding-bottom: 6px; }}
204
+ .markdown-rendered h2 {{ margin: 18px 0 10px; font-size: 17px; }}
205
+ .markdown-rendered h3 {{ margin: 14px 0 8px; font-size: 14px; color: var(--text-dim); }}
206
+ .markdown-rendered p, .markdown-rendered li {{ margin: 4px 0 8px; }}
207
+ .markdown-rendered ul, .markdown-rendered ol {{ padding-left: 24px; }}
208
+ .markdown-rendered code {{ background: var(--bg-pre); padding: 1px 5px; border-radius: 3px; font-size: 12px; }}
209
+ .markdown-rendered pre {{ background: var(--bg-pre); padding: 12px; border-radius: 4px; overflow-x: auto; }}
210
+ .markdown-rendered pre code {{ background: transparent; padding: 0; font-size: 12px; }}
211
+ .markdown-rendered table {{ border-collapse: collapse; margin: 8px 0; }}
212
+ .markdown-rendered th, .markdown-rendered td {{ border: 1px solid var(--border); padding: 4px 8px; }}
213
+ .markdown-rendered a {{ color: var(--accent); }}
214
+ pre.source {{
215
+ background: var(--bg-pre); border: 1px solid var(--border);
216
+ border-radius: 4px; padding: 14px;
217
+ overflow-x: auto; max-height: calc(100vh - 110px);
218
+ font-size: 12.5px;
219
+ }}
220
+ .text-content {{
221
+ background: var(--bg-elev); border: 1px solid var(--border);
222
+ padding: 14px; border-radius: 4px;
223
+ white-space: pre-wrap; word-wrap: break-word;
224
+ font-family: ui-monospace, "SF Mono", Consolas, monospace;
225
+ font-size: 12.5px;
226
+ }}
227
+ .hidden {{ display: none !important; }}
228
+ .empty {{ color: var(--text-dim); padding: 20px; text-align: center; font-style: italic; }}
229
+ </style>
230
+ </head>
231
+ <body>
232
+ <header>
233
+ <span class="filename">{safe_filename}</span>
234
+ <span class="badge">{type_badge}</span>
235
+ <span class="meta">{safe_size}</span>
236
+ <div class="controls">
237
+ {_render_view_toggle(kind)}
238
+ <a class="btn" href="{raw_url}" download="{safe_filename}">Download</a>
239
+ </div>
240
+ </header>
241
+ <main>
242
+ {body}
243
+ </main>
244
+ <script>
245
+ window.__ARTIFACT__ = {{
246
+ kind: {json.dumps(kind)},
247
+ filename: {json.dumps(filename)},
248
+ rawText: {embedded_text},
249
+ rawUrl: {json.dumps(raw_url)},
250
+ }};
251
+ {_VIEW_TOGGLE_JS}
252
+ </script>
253
+ {_RENDERER_SCRIPTS_FOR_KIND.get(kind, "")}
254
+ </body>
255
+ </html>"""
256
+
257
+
258
+ def _humanize_bytes(n: int) -> str:
259
+ for unit in ("B", "KB", "MB", "GB"):
260
+ if n < 1024 or unit == "GB":
261
+ return f"{n:.0f}{unit}" if unit == "B" else f"{n:.1f}{unit}"
262
+ n /= 1024
263
+ return f"{n}TB"
264
+
265
+
266
+ def _badge_for_kind(kind: str, prism: str) -> str:
267
+ if kind == "code":
268
+ return prism
269
+ return kind
270
+
271
+
272
+ def _render_view_toggle(kind: str) -> str:
273
+ """Render the source/preview toggle button — only shown for kinds
274
+ that have a meaningful split between rendered preview and raw."""
275
+ if kind in ("html", "svg", "markdown"):
276
+ return '<button id="toggle-source-btn">View source</button>'
277
+ return ''
278
+
279
+
280
+ def _render_body(kind: str, prism: str, raw_url: str,
281
+ raw_text: str, filename: str) -> str:
282
+ """Per-type body markup."""
283
+ safe_filename = html.escape(filename)
284
+ if kind == "html":
285
+ return f"""
286
+ <iframe id="preview-frame" class="preview" src="{raw_url}" sandbox="allow-scripts"></iframe>
287
+ <pre id="source-view" class="source hidden"><code class="language-markup">{html.escape(raw_text)}</code></pre>
288
+ """
289
+ if kind == "svg":
290
+ return f"""
291
+ <div id="preview-frame" class="svg-container">{raw_text}</div>
292
+ <pre id="source-view" class="source hidden"><code class="language-markup">{html.escape(raw_text)}</code></pre>
293
+ """
294
+ if kind == "markdown":
295
+ return f"""
296
+ <div id="preview-frame" class="markdown-rendered"></div>
297
+ <pre id="source-view" class="source hidden"><code class="language-markdown">{html.escape(raw_text)}</code></pre>
298
+ """
299
+ if kind == "code":
300
+ return f"""
301
+ <pre class="source"><code class="language-{prism}">{html.escape(raw_text)}</code></pre>
302
+ """
303
+ if kind == "text":
304
+ return f'<div class="text-content">{html.escape(raw_text)}</div>'
305
+ if kind == "image":
306
+ return f'<img class="preview" src="{raw_url}" alt="{safe_filename}">'
307
+ if kind == "audio":
308
+ return f'<audio class="preview" controls src="{raw_url}"></audio>'
309
+ if kind == "video":
310
+ return f'<video class="preview" controls src="{raw_url}"></video>'
311
+ # binary / unknown
312
+ return (
313
+ '<div class="empty">'
314
+ f'No inline preview for this file type. '
315
+ f'<a class="btn" href="{raw_url}" download="{safe_filename}">Download</a>'
316
+ '</div>'
317
+ )
318
+
319
+
320
+ _VIEW_TOGGLE_JS = r"""
321
+ (function() {
322
+ const btn = document.getElementById('toggle-source-btn');
323
+ const preview = document.getElementById('preview-frame');
324
+ const source = document.getElementById('source-view');
325
+ if (!btn || !preview || !source) return;
326
+ let showingSource = false;
327
+ btn.addEventListener('click', () => {
328
+ showingSource = !showingSource;
329
+ preview.classList.toggle('hidden', showingSource);
330
+ source.classList.toggle('hidden', !showingSource);
331
+ btn.textContent = showingSource ? 'View preview' : 'View source';
332
+ });
333
+ })();
334
+ """
335
+
336
+ # Per-kind external scripts (Prism + marked + initial render)
337
+ _RENDERER_SCRIPTS_FOR_KIND = {
338
+ "code": (
339
+ '<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>'
340
+ '<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-python.min.js"></script>'
341
+ '<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-typescript.min.js"></script>'
342
+ '<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-bash.min.js"></script>'
343
+ '<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-json.min.js"></script>'
344
+ '<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-yaml.min.js"></script>'
345
+ '<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-rust.min.js"></script>'
346
+ '<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-go.min.js"></script>'
347
+ '<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-css.min.js"></script>'
348
+ ),
349
+ "html": (
350
+ '<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>'
351
+ ),
352
+ "svg": (
353
+ '<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>'
354
+ ),
355
+ "markdown": (
356
+ '<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/14.1.2/marked.min.js"></script>'
357
+ '<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>'
358
+ '<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-markdown.min.js"></script>'
359
+ '<script>'
360
+ ' if (window.__ARTIFACT__.rawText && window.marked) {'
361
+ ' document.getElementById("preview-frame").innerHTML = '
362
+ ' window.marked.parse(window.__ARTIFACT__.rawText);'
363
+ ' }'
364
+ '</script>'
365
+ ),
366
+ }