okforge 0.6.2__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.
- okforge-0.6.2.dist-info/METADATA +144 -0
- okforge-0.6.2.dist-info/RECORD +54 -0
- okforge-0.6.2.dist-info/WHEEL +4 -0
- okforge-0.6.2.dist-info/entry_points.txt +3 -0
- okforge-0.6.2.dist-info/licenses/LICENSE +190 -0
- openkb/__init__.py +13 -0
- openkb/__main__.py +5 -0
- openkb/_skills/openkb-deck-editorial/SKILL.md +185 -0
- openkb/_skills/openkb-deck-neon/SKILL.md +300 -0
- openkb/_skills/openkb-html-critic/SKILL.md +140 -0
- openkb/agent/__init__.py +1 -0
- openkb/agent/_markdown.py +370 -0
- openkb/agent/chat.py +963 -0
- openkb/agent/chat_session.py +271 -0
- openkb/agent/compiler.py +2435 -0
- openkb/agent/linter.py +119 -0
- openkb/agent/query.py +470 -0
- openkb/agent/skill_runner.py +216 -0
- openkb/agent/skills.py +124 -0
- openkb/agent/tools.py +404 -0
- openkb/cli.py +3032 -0
- openkb/config.py +337 -0
- openkb/converter.py +270 -0
- openkb/deck/__init__.py +34 -0
- openkb/deck/creator.py +122 -0
- openkb/deck/validator.py +246 -0
- openkb/frontmatter.py +138 -0
- openkb/images.py +260 -0
- openkb/indexer.py +196 -0
- openkb/links.py +84 -0
- openkb/lint.py +694 -0
- openkb/locks.py +243 -0
- openkb/log.py +22 -0
- openkb/mutation.py +458 -0
- openkb/okf.py +120 -0
- openkb/prompts/__init__.py +22 -0
- openkb/prompts/skill_create.md +214 -0
- openkb/schema.py +93 -0
- openkb/skill/__init__.py +102 -0
- openkb/skill/creator.py +224 -0
- openkb/skill/evaluator.py +490 -0
- openkb/skill/generator.py +125 -0
- openkb/skill/marketplace.py +118 -0
- openkb/skill/tools.py +103 -0
- openkb/skill/validator.py +277 -0
- openkb/skill/workspace.py +188 -0
- openkb/state.py +127 -0
- openkb/templates/graph.html +907 -0
- openkb/topic_tree.py +229 -0
- openkb/topic_tree_llm.py +104 -0
- openkb/tree_renderer.py +170 -0
- openkb/url_ingest.py +282 -0
- openkb/visualize.py +79 -0
- openkb/watcher.py +101 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
"""Markdown rendering in Claude Code's terminal style.
|
|
2
|
+
|
|
3
|
+
Mirrors claude-code's utils/markdown.ts: parse with markdown-it, then map
|
|
4
|
+
each token to Rich primitives. No colors for plain text / bold / italic --
|
|
5
|
+
just terminal styling. Headings are left-aligned.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from markdown_it import MarkdownIt
|
|
13
|
+
from markdown_it.tree import SyntaxTreeNode
|
|
14
|
+
from rich.console import Group, RenderableType
|
|
15
|
+
from rich.syntax import Syntax
|
|
16
|
+
from rich.text import Text
|
|
17
|
+
|
|
18
|
+
INLINE_CODE_STYLE = "blue"
|
|
19
|
+
BLOCKQUOTE_BAR = "\u258e"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
_MD = MarkdownIt("commonmark").enable("table")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def render(content: str) -> RenderableType:
|
|
26
|
+
tokens = _MD.parse(content)
|
|
27
|
+
tree = SyntaxTreeNode(tokens)
|
|
28
|
+
|
|
29
|
+
blocks: list[RenderableType] = []
|
|
30
|
+
for child in tree.children:
|
|
31
|
+
rendered = _render_block(child)
|
|
32
|
+
if rendered is not None:
|
|
33
|
+
blocks.append(rendered)
|
|
34
|
+
|
|
35
|
+
if not blocks:
|
|
36
|
+
return Text("")
|
|
37
|
+
parts: list[RenderableType] = [blocks[0]]
|
|
38
|
+
for block in blocks[1:]:
|
|
39
|
+
parts.append(Text(""))
|
|
40
|
+
parts.append(block)
|
|
41
|
+
return Group(*parts)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _render_block(node: Any) -> RenderableType | None:
|
|
45
|
+
t = node.type
|
|
46
|
+
if t == "heading":
|
|
47
|
+
depth = int(node.tag[1:])
|
|
48
|
+
text = _render_inline_container(node)
|
|
49
|
+
if depth == 1:
|
|
50
|
+
text.stylize("bold italic underline")
|
|
51
|
+
else:
|
|
52
|
+
text.stylize("bold")
|
|
53
|
+
return text
|
|
54
|
+
if t == "paragraph":
|
|
55
|
+
return _render_inline_container(node)
|
|
56
|
+
if t == "fence":
|
|
57
|
+
info_parts = (node.info or "").strip().split()
|
|
58
|
+
lang = info_parts[0] if info_parts else ""
|
|
59
|
+
return Syntax(
|
|
60
|
+
node.content.rstrip("\n"),
|
|
61
|
+
lang or "text",
|
|
62
|
+
theme="monokai",
|
|
63
|
+
background_color="default",
|
|
64
|
+
word_wrap=True,
|
|
65
|
+
)
|
|
66
|
+
if t == "code_block":
|
|
67
|
+
return Syntax(
|
|
68
|
+
node.content.rstrip("\n"),
|
|
69
|
+
"text",
|
|
70
|
+
theme="monokai",
|
|
71
|
+
background_color="default",
|
|
72
|
+
word_wrap=True,
|
|
73
|
+
)
|
|
74
|
+
if t == "hr":
|
|
75
|
+
return Text("---")
|
|
76
|
+
if t in ("bullet_list", "ordered_list"):
|
|
77
|
+
return _render_list(node, ordered=(t == "ordered_list"), depth=0)
|
|
78
|
+
if t == "blockquote":
|
|
79
|
+
return _render_blockquote(node)
|
|
80
|
+
if t == "table":
|
|
81
|
+
return _render_table(node)
|
|
82
|
+
if t == "html_block":
|
|
83
|
+
return Text(node.content.rstrip("\n"))
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _render_inline_container(node: Any) -> Text:
|
|
88
|
+
if not node.children:
|
|
89
|
+
return Text("")
|
|
90
|
+
inline = node.children[0]
|
|
91
|
+
out = Text()
|
|
92
|
+
for child in inline.children or []:
|
|
93
|
+
_append_inline(child, out)
|
|
94
|
+
return out
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _append_inline(node: Any, out: Text) -> None:
|
|
98
|
+
t = node.type
|
|
99
|
+
if t == "text":
|
|
100
|
+
out.append(node.content)
|
|
101
|
+
elif t == "softbreak":
|
|
102
|
+
out.append("\n")
|
|
103
|
+
elif t == "hardbreak":
|
|
104
|
+
out.append("\n")
|
|
105
|
+
elif t == "strong":
|
|
106
|
+
piece = Text()
|
|
107
|
+
for child in node.children or []:
|
|
108
|
+
_append_inline(child, piece)
|
|
109
|
+
piece.stylize("bold")
|
|
110
|
+
out.append_text(piece)
|
|
111
|
+
elif t == "em":
|
|
112
|
+
piece = Text()
|
|
113
|
+
for child in node.children or []:
|
|
114
|
+
_append_inline(child, piece)
|
|
115
|
+
piece.stylize("italic")
|
|
116
|
+
out.append_text(piece)
|
|
117
|
+
elif t == "code_inline":
|
|
118
|
+
out.append(node.content, style=INLINE_CODE_STYLE)
|
|
119
|
+
elif t == "link":
|
|
120
|
+
href = node.attrGet("href") or ""
|
|
121
|
+
piece = Text()
|
|
122
|
+
for child in node.children or []:
|
|
123
|
+
_append_inline(child, piece)
|
|
124
|
+
if href.startswith("mailto:"):
|
|
125
|
+
email = href[len("mailto:") :]
|
|
126
|
+
plain = piece.plain
|
|
127
|
+
if plain and plain != email and plain != href:
|
|
128
|
+
piece.stylize(f"link {href}")
|
|
129
|
+
out.append_text(piece)
|
|
130
|
+
else:
|
|
131
|
+
out.append(email, style=f"link {href}")
|
|
132
|
+
return
|
|
133
|
+
if href:
|
|
134
|
+
plain = piece.plain
|
|
135
|
+
if plain and plain != href:
|
|
136
|
+
piece.stylize(f"link {href}")
|
|
137
|
+
out.append_text(piece)
|
|
138
|
+
else:
|
|
139
|
+
out.append(href, style=f"link {href}")
|
|
140
|
+
else:
|
|
141
|
+
out.append_text(piece)
|
|
142
|
+
elif t == "image":
|
|
143
|
+
href = node.attrGet("src") or ""
|
|
144
|
+
out.append(href)
|
|
145
|
+
elif t in ("html_inline", "html_block"):
|
|
146
|
+
out.append(node.content)
|
|
147
|
+
else:
|
|
148
|
+
content = getattr(node, "content", "")
|
|
149
|
+
if content:
|
|
150
|
+
out.append(content)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _append_with_cont_indent(target: Text, source: Text, cont_indent: str) -> None:
|
|
154
|
+
lines = source.split("\n", allow_blank=True)
|
|
155
|
+
for i, line in enumerate(lines):
|
|
156
|
+
if i > 0:
|
|
157
|
+
target.append("\n" + cont_indent)
|
|
158
|
+
target.append_text(line)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _render_code_as_text(node: Any) -> Text:
|
|
162
|
+
return Text(node.content.rstrip("\n"), style="dim")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _render_list(node: Any, ordered: bool, depth: int) -> Text:
|
|
166
|
+
result = Text()
|
|
167
|
+
items = list(node.children)
|
|
168
|
+
start = 1
|
|
169
|
+
if ordered:
|
|
170
|
+
try:
|
|
171
|
+
start = int(node.attrGet("start") or 1)
|
|
172
|
+
except (TypeError, ValueError):
|
|
173
|
+
start = 1
|
|
174
|
+
|
|
175
|
+
for i, item in enumerate(items):
|
|
176
|
+
indent = " " * depth
|
|
177
|
+
cont = indent + " "
|
|
178
|
+
if ordered:
|
|
179
|
+
prefix = f"{_list_number(depth, start + i)}. "
|
|
180
|
+
else:
|
|
181
|
+
prefix = "- "
|
|
182
|
+
result.append(indent + prefix)
|
|
183
|
+
first = True
|
|
184
|
+
for child in item.children or []:
|
|
185
|
+
if child.type == "paragraph":
|
|
186
|
+
if not first:
|
|
187
|
+
result.append("\n" + cont)
|
|
188
|
+
_append_with_cont_indent(result, _render_inline_container(child), cont)
|
|
189
|
+
first = False
|
|
190
|
+
elif child.type in ("bullet_list", "ordered_list"):
|
|
191
|
+
result.append("\n")
|
|
192
|
+
result.append_text(
|
|
193
|
+
_render_list(
|
|
194
|
+
child,
|
|
195
|
+
ordered=(child.type == "ordered_list"),
|
|
196
|
+
depth=depth + 1,
|
|
197
|
+
)
|
|
198
|
+
)
|
|
199
|
+
elif child.type in ("fence", "code_block"):
|
|
200
|
+
if not first:
|
|
201
|
+
result.append("\n" + cont)
|
|
202
|
+
_append_with_cont_indent(result, _render_code_as_text(child), cont)
|
|
203
|
+
first = False
|
|
204
|
+
else:
|
|
205
|
+
rendered = _render_block(child)
|
|
206
|
+
if rendered is None:
|
|
207
|
+
continue
|
|
208
|
+
if not first:
|
|
209
|
+
result.append("\n" + cont)
|
|
210
|
+
if isinstance(rendered, Text):
|
|
211
|
+
_append_with_cont_indent(result, rendered, cont)
|
|
212
|
+
first = False
|
|
213
|
+
if i < len(items) - 1:
|
|
214
|
+
result.append("\n")
|
|
215
|
+
return result
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _list_number(depth: int, n: int) -> str:
|
|
219
|
+
if depth == 0:
|
|
220
|
+
return str(n)
|
|
221
|
+
if depth == 1:
|
|
222
|
+
return _to_letters(n)
|
|
223
|
+
if depth == 2:
|
|
224
|
+
return _to_roman(n)
|
|
225
|
+
return str(n)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _to_letters(n: int) -> str:
|
|
229
|
+
result = ""
|
|
230
|
+
while n > 0:
|
|
231
|
+
n -= 1
|
|
232
|
+
result = chr(ord("a") + (n % 26)) + result
|
|
233
|
+
n //= 26
|
|
234
|
+
return result or "a"
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
_ROMAN = [
|
|
238
|
+
(1000, "m"),
|
|
239
|
+
(900, "cm"),
|
|
240
|
+
(500, "d"),
|
|
241
|
+
(400, "cd"),
|
|
242
|
+
(100, "c"),
|
|
243
|
+
(90, "xc"),
|
|
244
|
+
(50, "l"),
|
|
245
|
+
(40, "xl"),
|
|
246
|
+
(10, "x"),
|
|
247
|
+
(9, "ix"),
|
|
248
|
+
(5, "v"),
|
|
249
|
+
(4, "iv"),
|
|
250
|
+
(1, "i"),
|
|
251
|
+
]
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _to_roman(n: int) -> str:
|
|
255
|
+
out = ""
|
|
256
|
+
for value, numeral in _ROMAN:
|
|
257
|
+
while n >= value:
|
|
258
|
+
out += numeral
|
|
259
|
+
n -= value
|
|
260
|
+
return out
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _render_blockquote(node: Any) -> Text:
|
|
264
|
+
inner_blocks: list[Text] = []
|
|
265
|
+
for child in node.children or []:
|
|
266
|
+
if child.type in ("fence", "code_block"):
|
|
267
|
+
inner_blocks.append(_render_code_as_text(child))
|
|
268
|
+
continue
|
|
269
|
+
rendered = _render_block(child)
|
|
270
|
+
if isinstance(rendered, Text):
|
|
271
|
+
inner_blocks.append(rendered)
|
|
272
|
+
|
|
273
|
+
combined = Text()
|
|
274
|
+
for i, block in enumerate(inner_blocks):
|
|
275
|
+
if i > 0:
|
|
276
|
+
combined.append("\n\n")
|
|
277
|
+
combined.append_text(block)
|
|
278
|
+
combined.stylize("italic")
|
|
279
|
+
|
|
280
|
+
lines = combined.split("\n", allow_blank=True)
|
|
281
|
+
out = Text()
|
|
282
|
+
for i, line in enumerate(lines):
|
|
283
|
+
if i > 0:
|
|
284
|
+
out.append("\n")
|
|
285
|
+
if line.plain.strip():
|
|
286
|
+
out.append(f"{BLOCKQUOTE_BAR} ", style="dim")
|
|
287
|
+
out.append_text(line)
|
|
288
|
+
else:
|
|
289
|
+
out.append_text(line)
|
|
290
|
+
return out
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _render_table(node: Any) -> Text:
|
|
294
|
+
header_row: list[Text] = []
|
|
295
|
+
rows: list[list[Text]] = []
|
|
296
|
+
aligns: list[str | None] = []
|
|
297
|
+
|
|
298
|
+
thead = next((c for c in node.children if c.type == "thead"), None)
|
|
299
|
+
tbody = next((c for c in node.children if c.type == "tbody"), None)
|
|
300
|
+
|
|
301
|
+
if thead and thead.children:
|
|
302
|
+
tr = thead.children[0]
|
|
303
|
+
for th in tr.children or []:
|
|
304
|
+
header_row.append(_render_inline_container(th))
|
|
305
|
+
aligns.append(th.attrGet("style"))
|
|
306
|
+
if tbody:
|
|
307
|
+
for tr in tbody.children or []:
|
|
308
|
+
row: list[Text] = []
|
|
309
|
+
for td in tr.children or []:
|
|
310
|
+
row.append(_render_inline_container(td))
|
|
311
|
+
rows.append(row)
|
|
312
|
+
|
|
313
|
+
if not header_row:
|
|
314
|
+
return Text("")
|
|
315
|
+
|
|
316
|
+
widths = [max(3, cell.cell_len) for cell in header_row]
|
|
317
|
+
for row in rows:
|
|
318
|
+
for i, cell in enumerate(row):
|
|
319
|
+
if i < len(widths):
|
|
320
|
+
widths[i] = max(widths[i], cell.cell_len)
|
|
321
|
+
|
|
322
|
+
out = Text()
|
|
323
|
+
out.append("| ")
|
|
324
|
+
for i, cell in enumerate(header_row):
|
|
325
|
+
out.append_text(_pad(cell, widths[i], aligns[i] if i < len(aligns) else None))
|
|
326
|
+
out.append(" | ")
|
|
327
|
+
out = _rstrip_trailing_space(out)
|
|
328
|
+
out.append("\n|")
|
|
329
|
+
for w in widths:
|
|
330
|
+
out.append("-" * (w + 2))
|
|
331
|
+
out.append("|")
|
|
332
|
+
for row in rows:
|
|
333
|
+
out.append("\n| ")
|
|
334
|
+
for i, cell in enumerate(row):
|
|
335
|
+
width = widths[i] if i < len(widths) else cell.cell_len
|
|
336
|
+
align = aligns[i] if i < len(aligns) else None
|
|
337
|
+
out.append_text(_pad(cell, width, align))
|
|
338
|
+
out.append(" | ")
|
|
339
|
+
out = _rstrip_trailing_space(out)
|
|
340
|
+
return out
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _pad(cell: Text, width: int, align: str | None) -> Text:
|
|
344
|
+
padding = max(0, width - cell.cell_len)
|
|
345
|
+
if not padding:
|
|
346
|
+
return cell
|
|
347
|
+
if align and "center" in align:
|
|
348
|
+
left = padding // 2
|
|
349
|
+
right = padding - left
|
|
350
|
+
out = Text(" " * left)
|
|
351
|
+
out.append_text(cell)
|
|
352
|
+
out.append(" " * right)
|
|
353
|
+
return out
|
|
354
|
+
if align and "right" in align:
|
|
355
|
+
out = Text(" " * padding)
|
|
356
|
+
out.append_text(cell)
|
|
357
|
+
return out
|
|
358
|
+
out = Text()
|
|
359
|
+
out.append_text(cell)
|
|
360
|
+
out.append(" " * padding)
|
|
361
|
+
return out
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def _rstrip_trailing_space(text: Text) -> Text:
|
|
365
|
+
plain = text.plain
|
|
366
|
+
stripped = plain.rstrip(" ")
|
|
367
|
+
trim = len(plain) - len(stripped)
|
|
368
|
+
if trim:
|
|
369
|
+
return text[: len(plain) - trim]
|
|
370
|
+
return text
|