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.
Files changed (54) hide show
  1. okforge-0.6.2.dist-info/METADATA +144 -0
  2. okforge-0.6.2.dist-info/RECORD +54 -0
  3. okforge-0.6.2.dist-info/WHEEL +4 -0
  4. okforge-0.6.2.dist-info/entry_points.txt +3 -0
  5. okforge-0.6.2.dist-info/licenses/LICENSE +190 -0
  6. openkb/__init__.py +13 -0
  7. openkb/__main__.py +5 -0
  8. openkb/_skills/openkb-deck-editorial/SKILL.md +185 -0
  9. openkb/_skills/openkb-deck-neon/SKILL.md +300 -0
  10. openkb/_skills/openkb-html-critic/SKILL.md +140 -0
  11. openkb/agent/__init__.py +1 -0
  12. openkb/agent/_markdown.py +370 -0
  13. openkb/agent/chat.py +963 -0
  14. openkb/agent/chat_session.py +271 -0
  15. openkb/agent/compiler.py +2435 -0
  16. openkb/agent/linter.py +119 -0
  17. openkb/agent/query.py +470 -0
  18. openkb/agent/skill_runner.py +216 -0
  19. openkb/agent/skills.py +124 -0
  20. openkb/agent/tools.py +404 -0
  21. openkb/cli.py +3032 -0
  22. openkb/config.py +337 -0
  23. openkb/converter.py +270 -0
  24. openkb/deck/__init__.py +34 -0
  25. openkb/deck/creator.py +122 -0
  26. openkb/deck/validator.py +246 -0
  27. openkb/frontmatter.py +138 -0
  28. openkb/images.py +260 -0
  29. openkb/indexer.py +196 -0
  30. openkb/links.py +84 -0
  31. openkb/lint.py +694 -0
  32. openkb/locks.py +243 -0
  33. openkb/log.py +22 -0
  34. openkb/mutation.py +458 -0
  35. openkb/okf.py +120 -0
  36. openkb/prompts/__init__.py +22 -0
  37. openkb/prompts/skill_create.md +214 -0
  38. openkb/schema.py +93 -0
  39. openkb/skill/__init__.py +102 -0
  40. openkb/skill/creator.py +224 -0
  41. openkb/skill/evaluator.py +490 -0
  42. openkb/skill/generator.py +125 -0
  43. openkb/skill/marketplace.py +118 -0
  44. openkb/skill/tools.py +103 -0
  45. openkb/skill/validator.py +277 -0
  46. openkb/skill/workspace.py +188 -0
  47. openkb/state.py +127 -0
  48. openkb/templates/graph.html +907 -0
  49. openkb/topic_tree.py +229 -0
  50. openkb/topic_tree_llm.py +104 -0
  51. openkb/tree_renderer.py +170 -0
  52. openkb/url_ingest.py +282 -0
  53. openkb/visualize.py +79 -0
  54. 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