Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
All notable changes to okf-kit are documented here. This project adheres to
[Semantic Versioning](https://semver.org/).

## 0.3.1 — 2026-07-09

### Added
- **`GET /api/books/{name}/toc` now includes each concept's `resource`** (its
original page URL). This lets a GUI reader map an in-bundle link to the concept
it points at and navigate in-app, instead of opening the browser for links to
pages that are already in the bundle.

## 0.3.0 — 2026-07-08

### Added
Expand Down Expand Up @@ -189,6 +197,7 @@ okf chat docs-okf --provider ollama # chat offline, no key
Install `[js]` in its own environment for now. Tracked in
[#6](https://github.com/vinodborole/okf-kit/issues/6), fix planned for 0.1.1.

[0.3.1]: https://github.com/vinodborole/okf-kit/releases/tag/v0.3.1
[0.3.0]: https://github.com/vinodborole/okf-kit/releases/tag/v0.3.0
[0.2.0]: https://github.com/vinodborole/okf-kit/releases/tag/v0.2.0
[0.1.8]: https://github.com/vinodborole/okf-kit/releases/tag/v0.1.8
Expand Down
2 changes: 1 addition & 1 deletion okf_kit/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""okf-kit — turn any website into a portable, agent-ready OKF bundle."""

__version__ = "0.3.0"
__version__ = "0.3.1"
9 changes: 7 additions & 2 deletions okf_kit/serve/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ def ordered_concepts(bundle_dir) -> list[dict]:
for p in files:
cid = p.relative_to(root).as_posix()[:-3]
fm, _ = _split_frontmatter(p.read_text(encoding="utf8"))
out.append({"id": cid, "title": fm.get("title") or cid.split("/")[-1]})
out.append({
"id": cid,
"title": fm.get("title") or cid.split("/")[-1],
"resource": fm.get("resource") or "", # original page URL — lets a GUI
}) # map in-bundle links to concepts
return out


Expand All @@ -51,7 +55,8 @@ def to_list(node: dict) -> list[dict]:
items = []
for (kind, name), val in node.items():
if kind == "file":
items.append({"kind": "concept", "id": val["id"], "title": val["title"]})
items.append({"kind": "concept", "id": val["id"], "title": val["title"],
"resource": val.get("resource", "")})
else:
items.append({"kind": "section", "title": _prettify(name), "children": to_list(val)})
return items
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "okf-kit"
version = "0.3.0"
version = "0.3.1"
description = "Turn any website into a portable, agent-ready Open Knowledge Format (OKF) bundle — build, sync, and chat, no LLM required to start."
readme = "README.md"
requires-python = ">=3.10"
Expand Down
18 changes: 16 additions & 2 deletions tests/test_serve.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ def client(tmp_path, monkeypatch):
"---\ntype: Web Page\ntitle: Introduction\n---\n# Introduction\n\nWelcome.\n", encoding="utf8"
)
(b / "pages" / "ownership.md").write_text(
"---\ntype: Web Page\ntitle: Ownership\n---\n# Ownership\n\n"
"## Borrowing\n\nOwnership means every value has exactly one owner.\n",
"---\ntype: Web Page\ntitle: Ownership\nresource: https://doc.rust-lang.org/book/ch04-01.html\n---\n"
"# Ownership\n\n## Borrowing\n\nOwnership means every value has exactly one owner.\n",
encoding="utf8",
)
(b / ".okf-kit").mkdir()
Expand Down Expand Up @@ -72,6 +72,9 @@ def test_toc_and_concept(client):
toc = client.get("/api/books/testbook/toc", headers=AUTH).json()
ids = _concept_ids(toc)
assert "pages/intro" in ids and "pages/ownership" in ids
# toc carries each concept's original URL so a GUI can map links → concepts
own = _find_concept(toc, "pages/ownership")
assert own["resource"] == "https://doc.rust-lang.org/book/ch04-01.html"

c = client.get("/api/books/testbook/concept", params={"id": "pages/ownership"}, headers=AUTH).json()
assert c["title"] == "Ownership"
Expand Down Expand Up @@ -125,6 +128,17 @@ def _concept_ids(nodes):
return out


def _find_concept(nodes, cid):
for n in nodes:
if n["kind"] == "concept" and n["id"] == cid:
return n
if n["kind"] == "section":
found = _find_concept(n["children"], cid)
if found:
return found
return None


def _parse_sse(text):
events = []
for block in text.strip().split("\n\n"):
Expand Down
Loading