awkno 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 (106) hide show
  1. awkno/__init__.py +25 -0
  2. awkno/_doctor.py +139 -0
  3. awkno/cli.py +355 -0
  4. awkno/corpus.py +216 -0
  5. awkno/generate.py +549 -0
  6. awkno/pages/agent-senses.json +16 -0
  7. awkno/pages/agent-vm.json +14 -0
  8. awkno/pages/aitherconnect.json +11 -0
  9. awkno/pages/aitherkvcache.json +11 -0
  10. awkno/pages/aitherzero.json +11 -0
  11. awkno/pages/awask.json +15 -0
  12. awkno/pages/awbac.json +11 -0
  13. awkno/pages/awbeads.json +13 -0
  14. awkno/pages/awbonsai.json +13 -0
  15. awkno/pages/awbrowse.json +13 -0
  16. awkno/pages/awdit.json +12 -0
  17. awkno/pages/awdk.json +17 -0
  18. awkno/pages/awevolve.json +15 -0
  19. awkno/pages/awfind.json +13 -0
  20. awkno/pages/awgit.json +12 -0
  21. awkno/pages/awgraph.json +13 -0
  22. awkno/pages/awiam.json +12 -0
  23. awkno/pages/awkit.json +13 -0
  24. awkno/pages/awkno.json +13 -0
  25. awkno/pages/awknowledge.json +12 -0
  26. awkno/pages/awm.json +13 -0
  27. awkno/pages/awmail.json +18 -0
  28. awkno/pages/awnboard.json +20 -0
  29. awkno/pages/awnest.json +19 -0
  30. awkno/pages/awnet.json +14 -0
  31. awkno/pages/awnix.json +20 -0
  32. awkno/pages/awnode.json +12 -0
  33. awkno/pages/awpack.json +14 -0
  34. awkno/pages/awpredict.json +11 -0
  35. awkno/pages/awprism.json +14 -0
  36. awkno/pages/awreason.json +14 -0
  37. awkno/pages/awrecover.json +13 -0
  38. awkno/pages/awrecurse.json +13 -0
  39. awkno/pages/awrelay.json +12 -0
  40. awkno/pages/awrena.json +14 -0
  41. awkno/pages/awrepl.json +13 -0
  42. awkno/pages/awresearch.json +14 -0
  43. awkno/pages/awrise.json +15 -0
  44. awkno/pages/awrun.json +14 -0
  45. awkno/pages/awscreen.json +13 -0
  46. awkno/pages/awseal.json +13 -0
  47. awkno/pages/awsh.json +13 -0
  48. awkno/pages/awshare.json +12 -0
  49. awkno/pages/awskills.json +12 -0
  50. awkno/pages/awsprite.json +14 -0
  51. awkno/pages/awsync.json +16 -0
  52. awkno/pages/awtoll.json +13 -0
  53. awkno/pages/awtunnel.json +12 -0
  54. awkno/pages/awvision.json +13 -0
  55. awkno/pages/awvoice.json +13 -0
  56. awkno/pages/awwall.json +19 -0
  57. awkno/pages/bead-space.json +12 -0
  58. awkno/pages/browser-companion-graduation.json +15 -0
  59. awkno/pages/cited-research.json +14 -0
  60. awkno/pages/gobbonet-agentic.json +12 -0
  61. awkno/pages/gobbonet.json +13 -0
  62. awkno/pages/guide-00.json +16 -0
  63. awkno/pages/guide-01.json +14 -0
  64. awkno/pages/guide-02.json +14 -0
  65. awkno/pages/guide-03.json +14 -0
  66. awkno/pages/guide-04.json +15 -0
  67. awkno/pages/guide-05.json +16 -0
  68. awkno/pages/guide-06.json +15 -0
  69. awkno/pages/guide-07.json +15 -0
  70. awkno/pages/guide-08.json +17 -0
  71. awkno/pages/guide-09.json +13 -0
  72. awkno/pages/guide.json +22 -0
  73. awkno/pages/law-01.json +11 -0
  74. awkno/pages/law-02.json +11 -0
  75. awkno/pages/law-03.json +11 -0
  76. awkno/pages/law-04.json +11 -0
  77. awkno/pages/law-05.json +11 -0
  78. awkno/pages/law-06.json +11 -0
  79. awkno/pages/law-07.json +11 -0
  80. awkno/pages/law-08.json +11 -0
  81. awkno/pages/law-09.json +11 -0
  82. awkno/pages/law-10.json +11 -0
  83. awkno/pages/law-11.json +11 -0
  84. awkno/pages/law-12.json +11 -0
  85. awkno/pages/law-13.json +11 -0
  86. awkno/pages/law-14.json +11 -0
  87. awkno/pages/law-15.json +11 -0
  88. awkno/pages/law-16.json +11 -0
  89. awkno/pages/law-17.json +11 -0
  90. awkno/pages/law-18.json +11 -0
  91. awkno/pages/law-19.json +11 -0
  92. awkno/pages/law-20.json +11 -0
  93. awkno/pages/one-surface.json +15 -0
  94. awkno/pages/persona.json +12 -0
  95. awkno/pages/personal-mesh-pool.json +13 -0
  96. awkno/pages/provenance.json +14 -0
  97. awkno/pages/shared-worktree.json +14 -0
  98. awkno/pages/the-front-door.json +15 -0
  99. awkno/pages/the-reasoning-loop.json +14 -0
  100. awkno/pages/who-what-did.json +13 -0
  101. awkno-0.1.0.dist-info/METADATA +336 -0
  102. awkno-0.1.0.dist-info/RECORD +106 -0
  103. awkno-0.1.0.dist-info/WHEEL +5 -0
  104. awkno-0.1.0.dist-info/entry_points.txt +2 -0
  105. awkno-0.1.0.dist-info/licenses/LICENSE +118 -0
  106. awkno-0.1.0.dist-info/top_level.txt +1 -0
awkno/corpus.py ADDED
@@ -0,0 +1,216 @@
1
+ """Load and query the offline corpus.
2
+
3
+ Pages are stored as JSON files under awkno/pages/. This module handles the
4
+ loading, searching, and formatting of those pages.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from dataclasses import dataclass
11
+ from difflib import SequenceMatcher
12
+ from pathlib import Path
13
+ from typing import Optional
14
+
15
+
16
+ class NotFoundError(Exception):
17
+ """Raised when a topic is not found."""
18
+
19
+ pass
20
+
21
+
22
+ @dataclass
23
+ class AwknoPage:
24
+ """A single man page."""
25
+
26
+ topic: str
27
+ category: str
28
+ synopsis: str
29
+ description: str
30
+ adopt: Optional[str] = None
31
+ status: Optional[str] = None
32
+ see_also: Optional[list[str]] = None
33
+ body: Optional[str] = None
34
+ # The law filename's own slug (`design-for-the-silence`). Carried rather
35
+ # than re-derived from the synopsis: the generator already computes it, and
36
+ # a slug inferred from a title silently stops matching the moment a law is
37
+ # retitled.
38
+ slug: Optional[str] = None
39
+
40
+ def render(self, plain: bool = False) -> str:
41
+ """Render the page as text.
42
+
43
+ Args:
44
+ plain: If True, no ANSI codes. If False, use formatting (when TTY).
45
+
46
+ Returns:
47
+ The formatted page text.
48
+ """
49
+ lines = []
50
+ lines.append("NAME")
51
+ lines.append(f" {self.topic} — {self.synopsis}")
52
+ lines.append("")
53
+
54
+ if self.adopt:
55
+ lines.append("ADOPT")
56
+ for line in self.adopt.split("\n"):
57
+ lines.append(f" {line}")
58
+ lines.append("")
59
+
60
+ if self.description:
61
+ lines.append("DESCRIPTION")
62
+ for line in self.description.split("\n"):
63
+ lines.append(f" {line}")
64
+ lines.append("")
65
+
66
+ if self.body:
67
+ lines.append("DETAILS")
68
+ for line in self.body.split("\n"):
69
+ lines.append(f" {line}")
70
+ lines.append("")
71
+
72
+ if self.status:
73
+ lines.append("STATUS")
74
+ lines.append(f" {self.status}")
75
+ lines.append("")
76
+
77
+ if self.see_also:
78
+ lines.append("SEE ALSO")
79
+ for item in self.see_also:
80
+ lines.append(f" {item}")
81
+
82
+ text = "\n".join(lines)
83
+
84
+ if not plain:
85
+ # Check if stdout is a TTY for formatting
86
+ import sys
87
+
88
+ if hasattr(sys.stdout, "isatty") and sys.stdout.isatty():
89
+ # Apply ANSI formatting
90
+ text = text.replace("NAME\n", "\033[1mNAME\033[0m\n")
91
+ text = text.replace("ADOPT\n", "\033[1mADOPT\033[0m\n")
92
+ text = text.replace("DESCRIPTION\n", "\033[1mDESCRIPTION\033[0m\n")
93
+ text = text.replace("DETAILS\n", "\033[1mDETAILS\033[0m\n")
94
+ text = text.replace("STATUS\n", "\033[1mSTATUS\033[0m\n")
95
+ text = text.replace("SEE ALSO\n", "\033[1mSEE ALSO\033[0m\n")
96
+
97
+ return text
98
+
99
+ def to_dict(self) -> dict:
100
+ """Convert to dict for JSON serialization."""
101
+ return {
102
+ "topic": self.topic,
103
+ "category": self.category,
104
+ "synopsis": self.synopsis,
105
+ "description": self.description,
106
+ "adopt": self.adopt,
107
+ "status": self.status,
108
+ "see_also": self.see_also,
109
+ "body": self.body,
110
+ "slug": self.slug,
111
+ }
112
+
113
+ @classmethod
114
+ def from_dict(cls, data: dict) -> AwknoPage:
115
+ """Create from dict (JSON deserialization)."""
116
+ return cls(**data)
117
+
118
+
119
+ class AwknoRegistry:
120
+ """Load and query all pages."""
121
+
122
+ def __init__(self):
123
+ """Load all pages from the corpus directory."""
124
+ self.pages: dict[str, AwknoPage] = {}
125
+ self._load_pages()
126
+
127
+ def _load_pages(self) -> None:
128
+ """Load all JSON files from awkno/pages/."""
129
+ pages_dir = Path(__file__).parent / "pages"
130
+
131
+ if not pages_dir.exists():
132
+ return
133
+
134
+ for json_file in pages_dir.glob("*.json"):
135
+ try:
136
+ with open(json_file) as f:
137
+ data = json.load(f)
138
+ page = AwknoPage.from_dict(data)
139
+ self.pages[page.topic.lower()] = page
140
+ except (json.JSONDecodeError, ValueError):
141
+ pass
142
+
143
+ def get(self, topic: str) -> AwknoPage:
144
+ """Get a page by topic name.
145
+
146
+ Args:
147
+ topic: The topic name (case-insensitive).
148
+
149
+ Returns:
150
+ The AwknoPage.
151
+
152
+ Raises:
153
+ NotFoundError: If the topic is not found.
154
+ """
155
+ key = topic.lower()
156
+ if key in self.pages:
157
+ return self.pages[key]
158
+ raise NotFoundError(f"Topic '{topic}' not found")
159
+
160
+ def list_topics(self) -> list[str]:
161
+ """List all available topics."""
162
+ return sorted(self.pages.keys())
163
+
164
+ def list_by_category(self, category: str) -> list[AwknoPage]:
165
+ """List pages by category.
166
+
167
+ Args:
168
+ category: One of "brick", "stack", "law", "topic".
169
+
170
+ Returns:
171
+ List of pages in that category.
172
+ """
173
+ return [
174
+ page for page in self.pages.values() if page.category == category
175
+ ]
176
+
177
+ def search(self, term: str) -> list[tuple[AwknoPage, float]]:
178
+ """Search pages by keyword.
179
+
180
+ Args:
181
+ term: Search term (case-insensitive).
182
+
183
+ Returns:
184
+ List of (page, score) tuples sorted by score (highest first).
185
+ """
186
+ term_lower = term.lower()
187
+ results = []
188
+
189
+ for page in self.pages.values():
190
+ # Score based on where the term appears
191
+ score = 0.0
192
+
193
+ if term_lower in page.topic.lower():
194
+ score += 1.0
195
+
196
+ if page.synopsis and term_lower in page.synopsis.lower():
197
+ score += 0.8
198
+
199
+ if page.description and term_lower in page.description.lower():
200
+ score += 0.5
201
+
202
+ if page.adopt and term_lower in page.adopt.lower():
203
+ score += 0.3
204
+
205
+ if page.body and term_lower in page.body.lower():
206
+ score += 0.2
207
+
208
+ # Use SequenceMatcher for fuzzy matching on topic
209
+ ratio = SequenceMatcher(None, term_lower, page.topic.lower()).ratio()
210
+ score += ratio * 0.5
211
+
212
+ if score > 0:
213
+ results.append((page, score))
214
+
215
+ results.sort(key=lambda x: x[1], reverse=True)
216
+ return results