cli-wikia 0.10.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 (127) hide show
  1. cli_wikia/__init__.py +5 -0
  2. cli_wikia/cli.py +456 -0
  3. cli_wikia/hooks.py +310 -0
  4. cli_wikia/schedule.py +206 -0
  5. cli_wikia/wikis/antigravity/README.md +142 -0
  6. cli_wikia/wikis/antigravity/agentic-model.md +130 -0
  7. cli_wikia/wikis/antigravity/cli-reference.md +184 -0
  8. cli_wikia/wikis/antigravity/cli-vs-api.md +33 -0
  9. cli_wikia/wikis/antigravity/configuration.md +124 -0
  10. cli_wikia/wikis/antigravity/customization.md +119 -0
  11. cli_wikia/wikis/antigravity/hooks.md +64 -0
  12. cli_wikia/wikis/antigravity/ide-and-app.md +110 -0
  13. cli_wikia/wikis/antigravity/mcp.md +98 -0
  14. cli_wikia/wikis/antigravity/models.md +92 -0
  15. cli_wikia/wikis/antigravity/overview.md +80 -0
  16. cli_wikia/wikis/antigravity/permissions.md +134 -0
  17. cli_wikia/wikis/antigravity/plugins.md +115 -0
  18. cli_wikia/wikis/antigravity/projects-sessions-conversations.md +111 -0
  19. cli_wikia/wikis/antigravity/sandbox.md +90 -0
  20. cli_wikia/wikis/antigravity/sdk.md +120 -0
  21. cli_wikia/wikis/chatgpt/README.md +80 -0
  22. cli_wikia/wikis/chatgpt/cli-reference.md +90 -0
  23. cli_wikia/wikis/chatgpt/cli-vs-api.md +34 -0
  24. cli_wikia/wikis/chatgpt/codex-agents-md.md +90 -0
  25. cli_wikia/wikis/chatgpt/codex-approvals-sandbox.md +136 -0
  26. cli_wikia/wikis/chatgpt/codex-auth.md +111 -0
  27. cli_wikia/wikis/chatgpt/codex-cli-reference.md +136 -0
  28. cli_wikia/wikis/chatgpt/codex-config.md +276 -0
  29. cli_wikia/wikis/chatgpt/codex-exec.md +104 -0
  30. cli_wikia/wikis/chatgpt/codex-mcp.md +114 -0
  31. cli_wikia/wikis/chatgpt/codex-models.md +82 -0
  32. cli_wikia/wikis/chatgpt/codex-overview.md +136 -0
  33. cli_wikia/wikis/chatgpt/codex-slash-commands.md +97 -0
  34. cli_wikia/wikis/chatgpt/configuration.md +55 -0
  35. cli_wikia/wikis/chatgpt/models.md +43 -0
  36. cli_wikia/wikis/claude/README.md +163 -0
  37. cli_wikia/wikis/claude/advisor.md +127 -0
  38. cli_wikia/wikis/claude/agent-teams.md +310 -0
  39. cli_wikia/wikis/claude/agents.md +329 -0
  40. cli_wikia/wikis/claude/channels.md +193 -0
  41. cli_wikia/wikis/claude/claude-code-web.md +317 -0
  42. cli_wikia/wikis/claude/cli-reference.md +240 -0
  43. cli_wikia/wikis/claude/cli-vs-api.md +27 -0
  44. cli_wikia/wikis/claude/environment-variables.md +176 -0
  45. cli_wikia/wikis/claude/headless-sdk.md +206 -0
  46. cli_wikia/wikis/claude/hooks.md +380 -0
  47. cli_wikia/wikis/claude/ide-integrations.md +117 -0
  48. cli_wikia/wikis/claude/marketplaces.md +133 -0
  49. cli_wikia/wikis/claude/mcp.md +369 -0
  50. cli_wikia/wikis/claude/memory.md +193 -0
  51. cli_wikia/wikis/claude/models.md +179 -0
  52. cli_wikia/wikis/claude/monitors.md +121 -0
  53. cli_wikia/wikis/claude/output-styles.md +110 -0
  54. cli_wikia/wikis/claude/permission-modes.md +219 -0
  55. cli_wikia/wikis/claude/permissions.md +249 -0
  56. cli_wikia/wikis/claude/plugins.md +335 -0
  57. cli_wikia/wikis/claude/remote-control.md +169 -0
  58. cli_wikia/wikis/claude/sandboxing.md +259 -0
  59. cli_wikia/wikis/claude/settings.md +342 -0
  60. cli_wikia/wikis/claude/skills.md +338 -0
  61. cli_wikia/wikis/claude/slash-commands.md +150 -0
  62. cli_wikia/wikis/claude/stacking.md +301 -0
  63. cli_wikia/wikis/claude/statusline.md +109 -0
  64. cli_wikia/wikis/copilot/README.md +109 -0
  65. cli_wikia/wikis/copilot/billing.md +48 -0
  66. cli_wikia/wikis/copilot/cli-reference.md +193 -0
  67. cli_wikia/wikis/copilot/cli-vs-api.md +32 -0
  68. cli_wikia/wikis/copilot/configuration.md +140 -0
  69. cli_wikia/wikis/copilot/custom-agents.md +95 -0
  70. cli_wikia/wikis/copilot/custom-instructions.md +96 -0
  71. cli_wikia/wikis/copilot/environment-variables.md +100 -0
  72. cli_wikia/wikis/copilot/getting-started.md +117 -0
  73. cli_wikia/wikis/copilot/hooks.md +50 -0
  74. cli_wikia/wikis/copilot/logging.md +56 -0
  75. cli_wikia/wikis/copilot/mcp.md +127 -0
  76. cli_wikia/wikis/copilot/models.md +77 -0
  77. cli_wikia/wikis/copilot/modes.md +80 -0
  78. cli_wikia/wikis/copilot/monitoring.md +94 -0
  79. cli_wikia/wikis/copilot/permissions.md +112 -0
  80. cli_wikia/wikis/copilot/plugins.md +83 -0
  81. cli_wikia/wikis/copilot/providers-byok.md +89 -0
  82. cli_wikia/wikis/copilot/sessions.md +93 -0
  83. cli_wikia/wikis/copilot/skills.md +59 -0
  84. cli_wikia/wikis/copilot/slash-commands.md +112 -0
  85. cli_wikia/wikis/deepseek/README.md +137 -0
  86. cli_wikia/wikis/deepseek/agents.md +203 -0
  87. cli_wikia/wikis/deepseek/architecture.md +205 -0
  88. cli_wikia/wikis/deepseek/cli-reference.md +182 -0
  89. cli_wikia/wikis/deepseek/cli-vs-api.md +28 -0
  90. cli_wikia/wikis/deepseek/configuration.md +175 -0
  91. cli_wikia/wikis/deepseek/hooks.md +232 -0
  92. cli_wikia/wikis/deepseek/models.md +136 -0
  93. cli_wikia/wikis/deepseek/permissions.md +160 -0
  94. cli_wikia/wikis/deepseek/plugins.md +107 -0
  95. cli_wikia/wikis/deepseek/sessions.md +128 -0
  96. cli_wikia/wikis/deepseek/skills.md +184 -0
  97. cli_wikia/wikis/gemini/README.md +161 -0
  98. cli_wikia/wikis/gemini/checkpointing.md +104 -0
  99. cli_wikia/wikis/gemini/cli-reference.md +201 -0
  100. cli_wikia/wikis/gemini/cli-vs-api.md +27 -0
  101. cli_wikia/wikis/gemini/commands.md +150 -0
  102. cli_wikia/wikis/gemini/configuration.md +129 -0
  103. cli_wikia/wikis/gemini/context-files.md +191 -0
  104. cli_wikia/wikis/gemini/custom-commands.md +169 -0
  105. cli_wikia/wikis/gemini/enterprise.md +395 -0
  106. cli_wikia/wikis/gemini/environment-variables.md +142 -0
  107. cli_wikia/wikis/gemini/extensions.md +305 -0
  108. cli_wikia/wikis/gemini/getting-started.md +145 -0
  109. cli_wikia/wikis/gemini/git-worktrees.md +74 -0
  110. cli_wikia/wikis/gemini/headless.md +208 -0
  111. cli_wikia/wikis/gemini/hooks.md +448 -0
  112. cli_wikia/wikis/gemini/ide-integration.md +129 -0
  113. cli_wikia/wikis/gemini/mcp.md +484 -0
  114. cli_wikia/wikis/gemini/models.md +267 -0
  115. cli_wikia/wikis/gemini/permissions.md +252 -0
  116. cli_wikia/wikis/gemini/sandboxing.md +275 -0
  117. cli_wikia/wikis/gemini/sessions.md +161 -0
  118. cli_wikia/wikis/gemini/settings.md +238 -0
  119. cli_wikia/wikis/gemini/skills.md +228 -0
  120. cli_wikia/wikis/gemini/subagents.md +424 -0
  121. cli_wikia/wikis/gemini/themes.md +204 -0
  122. cli_wikia/wikis/gemini/tools.md +468 -0
  123. cli_wikia-0.10.0.dist-info/METADATA +90 -0
  124. cli_wikia-0.10.0.dist-info/RECORD +127 -0
  125. cli_wikia-0.10.0.dist-info/WHEEL +4 -0
  126. cli_wikia-0.10.0.dist-info/entry_points.txt +2 -0
  127. cli_wikia-0.10.0.dist-info/licenses/LICENSE +21 -0
cli_wikia/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """cli-wikia: offline reference wiki for AI coding CLIs."""
2
+
3
+ __version__ = "0.10.0"
4
+
5
+ MODELS = ["claude", "deepseek", "copilot", "chatgpt", "gemini", "antigravity"]
cli_wikia/cli.py ADDED
@@ -0,0 +1,456 @@
1
+ """Command-line interface for cli-wikia.
2
+
3
+ A small, dependency-free CLI to browse, search, read and edit an offline
4
+ reference wiki for AI coding CLIs. Run `wikia --help` for usage.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import argparse
9
+ import difflib
10
+ import os
11
+ import re
12
+ import shutil
13
+ import subprocess
14
+ import sys
15
+ import urllib.request
16
+ from importlib import resources
17
+
18
+ from . import MODELS, __version__
19
+
20
+ # Which installed CLI can answer questions / provide updates for each model.
21
+ MODEL_CLIS = {
22
+ "claude": "claude",
23
+ "deepseek": "deepseek-code",
24
+ "copilot": "copilot",
25
+ "chatgpt": "codex", # OpenAI Codex CLI (may not be installed yet)
26
+ "gemini": "gemini",
27
+ "antigravity": "agy", # Google Antigravity CLI
28
+ }
29
+
30
+ # Sources the `update` command checks for changes, per model. The real signal
31
+ # comes from (1) the official docs and (2) asking the model itself — NOT a
32
+ # `--help` dump. `version` is just a cheap authoritative version string.
33
+ # - version: read-only version probe (no side effects).
34
+ # - docs: official documentation URL (best-effort; edit if a tool moves its docs).
35
+ # - ask: argv template to query the model in one-shot mode; "{q}" = the question.
36
+ # None means the model can't be queried that way (e.g. tool not installed).
37
+ MODEL_SOURCES = {
38
+ "claude": {
39
+ "version": ["--version"],
40
+ "docs": "https://docs.claude.com/en/docs/claude-code/overview",
41
+ "ask": ["-p", "{q}"],
42
+ },
43
+ "deepseek": {
44
+ "version": ["--version"],
45
+ "docs": "https://api-docs.deepseek.com/",
46
+ "ask": ["-p", "{q}"],
47
+ },
48
+ "copilot": {
49
+ "version": ["--version"],
50
+ "docs": "https://docs.github.com/en/copilot/concepts/agents/about-copilot-cli",
51
+ "ask": ["-p", "{q}", "--allow-all-tools"],
52
+ },
53
+ "chatgpt": {
54
+ "version": ["--version"],
55
+ "docs": "https://developers.openai.com/codex/cli/",
56
+ "ask": None, # codex isn't installed; openai CLI isn't a one-shot agent
57
+ },
58
+ "gemini": {
59
+ "version": ["--version"],
60
+ "docs": "https://google-gemini.github.io/gemini-cli/",
61
+ "ask": ["-p", "{q}"],
62
+ },
63
+ "antigravity": {
64
+ "version": ["--version"],
65
+ "docs": "https://antigravity.google/docs",
66
+ "ask": ["-p", "{q}"],
67
+ },
68
+ }
69
+
70
+ # What `update` asks each model about itself.
71
+ WHATS_NEW_Q = (
72
+ "What is your exact version, and what are your most recent features, "
73
+ "commands, or changes? Be specific and concise."
74
+ )
75
+
76
+
77
+ def wikis_root():
78
+ """Filesystem path to the bundled wikis/ directory."""
79
+ return resources.files("cli_wikia") / "wikis"
80
+
81
+
82
+ def model_dir(model):
83
+ return wikis_root() / model
84
+
85
+
86
+ def topics(model):
87
+ """Sorted list of topic names (filenames without .md) for a model."""
88
+ d = model_dir(model)
89
+ if not d.is_dir():
90
+ return []
91
+ return sorted(p.name[:-3] for p in d.iterdir() if p.name.endswith(".md"))
92
+
93
+
94
+ def resolve_model(model):
95
+ if model not in MODELS:
96
+ sys.exit(f"unknown model '{model}'. choose from: {', '.join(MODELS)}")
97
+ return model
98
+
99
+
100
+ # --------------------------------------------------------------------------- #
101
+ # commands
102
+ # --------------------------------------------------------------------------- #
103
+ def cmd_models(args):
104
+ for m in MODELS:
105
+ n = len(topics(m))
106
+ cli = MODEL_CLIS.get(m)
107
+ cli_state = f"cli: {cli}" if cli and shutil.which(cli) else "cli: not installed"
108
+ print(f"{m:12} {n:3} topics ({cli_state})")
109
+
110
+
111
+ def cmd_list(args):
112
+ models = [resolve_model(args.model)] if args.model else MODELS
113
+ for m in models:
114
+ ts = topics(m)
115
+ print(f"\n# {m} ({len(ts)} topics)")
116
+ for t in ts:
117
+ print(f" {t}")
118
+
119
+
120
+ def cmd_read(args):
121
+ m = resolve_model(args.model)
122
+ f = model_dir(m) / f"{args.topic}.md"
123
+ if not f.is_file():
124
+ avail = ", ".join(topics(m)) or "(none yet)"
125
+ sys.exit(f"no topic '{args.topic}' in {m}.\navailable: {avail}")
126
+ sys.stdout.write(f.read_text(encoding="utf-8"))
127
+
128
+
129
+ def cmd_search(args):
130
+ needle = args.query.lower()
131
+ models = [resolve_model(args.model)] if args.model else MODELS
132
+ hits = 0
133
+ for m in models:
134
+ d = model_dir(m)
135
+ if not d.is_dir():
136
+ continue
137
+ for p in sorted(d.iterdir(), key=lambda x: x.name):
138
+ if not p.name.endswith(".md"):
139
+ continue
140
+ for i, line in enumerate(p.read_text(encoding="utf-8").splitlines(), 1):
141
+ if needle in line.lower():
142
+ hits += 1
143
+ print(f"{m}/{p.name[:-3]}:{i}: {line.strip()}")
144
+ if not hits:
145
+ print(f"no matches for '{args.query}'")
146
+
147
+
148
+ def cmd_path(args):
149
+ if args.model:
150
+ print(model_dir(resolve_model(args.model)))
151
+ else:
152
+ print(wikis_root())
153
+
154
+
155
+ def cmd_ask(args):
156
+ """Use a local model CLI to answer a question grounded in the wiki docs."""
157
+ m = resolve_model(args.model)
158
+ cli = MODEL_CLIS.get(m)
159
+ runner = cli if (cli and shutil.which(cli)) else ("ollama" if shutil.which("ollama") else None)
160
+ if not runner:
161
+ sys.exit(
162
+ "no local model CLI available to answer. install one of: "
163
+ f"{cli or 'ollama'}, or read the docs with `wikia read {m} <topic>`."
164
+ )
165
+ # Build context from the model's docs (capped to keep the prompt sane).
166
+ context = ""
167
+ for t in topics(m):
168
+ context += f"\n\n## {t}\n" + (model_dir(m) / f"{t}.md").read_text(encoding="utf-8")
169
+ context = context[: args.max_context]
170
+ prompt = (
171
+ "Answer the question using ONLY the reference docs below. "
172
+ "If the answer is not in them, say so.\n"
173
+ f"=== REFERENCE DOCS ({m}) ===\n{context}\n=== QUESTION ===\n{args.question}\n"
174
+ )
175
+ print(f"(asking via: {runner})\n", file=sys.stderr)
176
+ try:
177
+ if runner == "ollama":
178
+ subprocess.run(["ollama", "run", args.ollama_model, prompt], check=False)
179
+ else:
180
+ subprocess.run([runner, prompt], check=False)
181
+ except FileNotFoundError:
182
+ sys.exit(f"could not run '{runner}'.")
183
+
184
+
185
+ def snapshot_dir():
186
+ """Writable per-user dir where CLI ground-truth snapshots are stored."""
187
+ base = os.environ.get("XDG_STATE_HOME") or os.path.join(
188
+ os.path.expanduser("~"), ".local", "state"
189
+ )
190
+ return os.path.join(base, "cli-wikia", "snapshots")
191
+
192
+
193
+ def _run_cli(cli, probe, timeout=30):
194
+ """Run one read-only CLI probe and return its labelled output."""
195
+ try:
196
+ r = subprocess.run(
197
+ [cli, *probe],
198
+ capture_output=True,
199
+ text=True,
200
+ timeout=timeout,
201
+ stdin=subprocess.DEVNULL,
202
+ )
203
+ return f"$ {cli} {' '.join(probe)}\n{r.stdout}{r.stderr}".strip()
204
+ except (subprocess.TimeoutExpired, OSError) as e:
205
+ return f"$ {cli} {' '.join(probe)}\n<error: {e}>"
206
+
207
+
208
+ def fetch_docs(url):
209
+ """Fetch an official docs page and reduce it to text for change detection.
210
+
211
+ Uses only the standard library. Returns a text block, or an error note if
212
+ offline / the page is unreachable (so update still works offline).
213
+ """
214
+ try:
215
+ req = urllib.request.Request(url, headers={"User-Agent": "cli-wikia/update"})
216
+ with urllib.request.urlopen(req, timeout=20) as resp:
217
+ html = resp.read(500_000).decode("utf-8", "replace")
218
+ except Exception as e: # noqa: BLE001 - network can fail many ways; degrade gracefully
219
+ return f"# docs: {url}\n<unreachable: {e}>"
220
+ text = re.sub(r"(?is)<(script|style).*?</\1>", " ", html)
221
+ text = re.sub(r"(?s)<[^>]+>", " ", text)
222
+ text = re.sub(r"\s+", " ", text).strip()
223
+ return f"# docs: {url}\n{text[:30000]}"
224
+
225
+
226
+ def wiki_doc_urls(model, limit=2):
227
+ """Official doc URLs for a model, discovered FROM its own wiki pages
228
+ (dynamic — not a hardcoded list). Prefers documentation-looking links."""
229
+ d = model_dir(model)
230
+ if not d.is_dir():
231
+ return []
232
+ text = "".join(
233
+ p.read_text(encoding="utf-8") for p in sorted(d.iterdir()) if p.name.endswith(".md")
234
+ )
235
+ urls = [u.rstrip(".,);]`") for u in re.findall(r"https?://[^\s)\]\"'>`]+", text)]
236
+ bad = ("example.com", "example.org", "localhost", "127.0.0.1", "your-", "git-scm.com")
237
+ urls = [u for u in urls if not any(b in u for b in bad)]
238
+ docish = [u for u in urls if re.search(r"docs?[./]|/docs|developers?\.|\.github\.io|/guide", u)]
239
+ out = []
240
+ for u in (docish or urls):
241
+ if u not in out:
242
+ out.append(u)
243
+ if len(out) >= limit:
244
+ break
245
+ return out
246
+
247
+
248
+ def query_model(cli, ask_template, question):
249
+ """Ask the model itself, in one-shot mode, via its per-model invocation."""
250
+ if not ask_template:
251
+ return None
252
+ argv = [question if tok == "{q}" else tok for tok in ask_template]
253
+ out = _run_cli(cli, argv, timeout=180) # models take a while
254
+ return "# model self-report (the model's own answer about itself):\n" + out
255
+
256
+
257
+ def capture_sources(m, cli, use_docs, use_model):
258
+ """Gather sources for a model into one snapshot blob, leaning on the docs.
259
+ Order = priority: (1) OFFICIAL DOCUMENTATION first (URLs discovered from the
260
+ model's own wiki, plus a seed), (2) the CLI's own facts (version + help),
261
+ (3) the model's self-report (secondary — models are unreliable about
262
+ themselves). Use --no-docs / --no-model to drop a source."""
263
+ src = MODEL_SOURCES.get(m, {})
264
+ parts = []
265
+ if use_docs:
266
+ urls = wiki_doc_urls(m)
267
+ seed = src.get("docs")
268
+ if seed and seed not in urls:
269
+ urls.append(seed)
270
+ for url in urls[:2]:
271
+ parts.append(fetch_docs(url))
272
+ parts.append(_run_cli(cli, src.get("version", ["--version"])))
273
+ parts.append(_run_cli(cli, ["--help"]))
274
+ if use_model and src.get("ask"):
275
+ mq = query_model(cli, src["ask"], WHATS_NEW_Q)
276
+ if mq:
277
+ parts.append(mq)
278
+ return "\n\n".join(p for p in parts if p) + "\n"
279
+
280
+
281
+ def cmd_update(args):
282
+ """Check each model's sources (CLI --help/--version, official docs, and
283
+ optionally the model itself) for changes vs the last saved snapshot.
284
+
285
+ Reports what changed so curated docs can be refreshed. No API keys. Never
286
+ overwrites the curated .md files; snapshots live in the user state dir.
287
+ """
288
+ if not args.all and not args.model:
289
+ sys.exit("specify a model (e.g. `wikia update gemini`) or use `--all`.")
290
+ models = MODELS if args.all else [resolve_model(args.model)]
291
+ use_docs = not args.no_docs
292
+ use_model = not args.no_model
293
+ sdir = snapshot_dir()
294
+ os.makedirs(sdir, exist_ok=True)
295
+ changed_any = False
296
+ for m in models:
297
+ cli = MODEL_CLIS.get(m)
298
+ if not cli:
299
+ print(f"{m:12} no associated CLI — skip")
300
+ continue
301
+ if not shutil.which(cli):
302
+ print(f"{m:12} '{cli}' not installed — can't check for updates")
303
+ continue
304
+ sources = "help/version" + (" + docs" if use_docs else "") + (" + model" if use_model else "")
305
+ current = capture_sources(m, cli, use_docs, use_model)
306
+ snap = os.path.join(sdir, f"{m}.txt")
307
+ if not os.path.exists(snap):
308
+ with open(snap, "w", encoding="utf-8") as f:
309
+ f.write(current)
310
+ print(f"{m:12} baseline snapshot saved ({sources}). Run again later to detect changes.")
311
+ continue
312
+ with open(snap, encoding="utf-8") as f:
313
+ prev = f.read()
314
+ if prev == current:
315
+ print(f"{m:12} up to date ({sources}, no change)")
316
+ continue
317
+ changed_any = True
318
+ diff = [
319
+ ln
320
+ for ln in difflib.unified_diff(
321
+ prev.splitlines(), current.splitlines(), lineterm="", n=0
322
+ )
323
+ if ln and ln[0] in "+-" and not ln.startswith(("+++", "---"))
324
+ ]
325
+ print(f"{m:12} CHANGED ({sources}) — {len(diff)} differing lines:")
326
+ for ln in diff[:30]:
327
+ print(f" {ln}")
328
+ if len(diff) > 30:
329
+ print(f" … (+{len(diff) - 30} more)")
330
+ print(f" review/update curated docs in: {model_dir(m)}")
331
+ if args.write:
332
+ with open(snap, "w", encoding="utf-8") as f:
333
+ f.write(current)
334
+ print(f" snapshot updated (acknowledged).")
335
+ else:
336
+ print(f" re-run with --write to accept this as the new baseline.")
337
+ if changed_any and not args.write:
338
+ print("\nTip: `wikia update --all --write` after you've refreshed the docs.")
339
+
340
+
341
+ def build_parser():
342
+ p = argparse.ArgumentParser(
343
+ prog="wikia",
344
+ description="Offline reference wiki for AI coding CLIs "
345
+ "(claude, deepseek, copilot, chatgpt, gemini).",
346
+ )
347
+ p.add_argument("--version", action="version", version=f"cli-wikia {__version__}")
348
+ sub = p.add_subparsers(dest="cmd", required=True)
349
+
350
+ sub.add_parser("models", help="list models and how many topics each has").set_defaults(func=cmd_models)
351
+
352
+ sp = sub.add_parser("list", help="list topics (optionally for one model)")
353
+ sp.add_argument("model", nargs="?", help="model name (default: all)")
354
+ sp.set_defaults(func=cmd_list)
355
+
356
+ sp = sub.add_parser("read", help="print a topic")
357
+ sp.add_argument("model")
358
+ sp.add_argument("topic")
359
+ sp.set_defaults(func=cmd_read)
360
+
361
+ sp = sub.add_parser("search", help="search text across topics")
362
+ sp.add_argument("query")
363
+ sp.add_argument("--model", help="limit to one model")
364
+ sp.set_defaults(func=cmd_search)
365
+
366
+ sp = sub.add_parser("path", help="print the on-disk path (for editing files)")
367
+ sp.add_argument("model", nargs="?")
368
+ sp.set_defaults(func=cmd_path)
369
+
370
+ sp = sub.add_parser("ask", help="ask a question answered from the docs via a local model")
371
+ sp.add_argument("model")
372
+ sp.add_argument("question")
373
+ sp.add_argument("--ollama-model", default="llama3", help="ollama model to use as fallback")
374
+ sp.add_argument("--max-context", type=int, default=24000, help="max chars of docs to feed")
375
+ sp.set_defaults(func=cmd_ask)
376
+
377
+ sp = sub.add_parser("update", help="check a model's sources (help, docs, model) for changes")
378
+ sp.add_argument("model", nargs="?", help="model name (omit and use --all for every model)")
379
+ sp.add_argument("--all", action="store_true", help="check every model")
380
+ sp.add_argument("--write", action="store_true", help="accept current state as the new baseline")
381
+ sp.add_argument("--no-docs", action="store_true", help="skip fetching official docs (offline / faster)")
382
+ sp.add_argument("--no-model", action="store_true", help="skip asking the model itself (faster)")
383
+ sp.set_defaults(func=cmd_update)
384
+
385
+ # hooks (Level 1 awareness + Level 2 tailored hooks) — see hooks.py
386
+ from . import hooks as H
387
+
388
+ hp = sub.add_parser("hooks", help="integrate the wiki into a model (awareness + real hooks)")
389
+ hsub = hp.add_subparsers(dest="hooks_cmd", required=True)
390
+
391
+ s = hsub.add_parser("status", help="show integration status per model")
392
+ s.add_argument("model", nargs="?")
393
+ s.add_argument("--all", action="store_true")
394
+ s.set_defaults(func=H.cmd_status)
395
+
396
+ s = hsub.add_parser("enable", help="Level 1: tell a model the wiki exists (dry-run unless --write)")
397
+ s.add_argument("model")
398
+ s.add_argument("--file", help="instructions file to write (default: per-model convention)")
399
+ s.add_argument("--write", action="store_true", help="actually write the change")
400
+ s.set_defaults(func=H.cmd_enable)
401
+
402
+ s = hsub.add_parser("disable", help="Level 1: remove the wiki awareness block")
403
+ s.add_argument("model")
404
+ s.add_argument("--file")
405
+ s.add_argument("--write", action="store_true")
406
+ s.set_defaults(func=H.cmd_disable)
407
+
408
+ s = hsub.add_parser("manifest", help="Level 2: generate the hook-positions doc from the wiki")
409
+ s.add_argument("model")
410
+ s.set_defaults(func=H.cmd_manifest)
411
+
412
+ s = hsub.add_parser("apply", help="Level 2: install the edited hooks (dry-run unless --write)")
413
+ s.add_argument("model")
414
+ s.add_argument("--file", help="target settings file (default: per-model)")
415
+ s.add_argument("--write", action="store_true", help="actually install the hooks")
416
+ s.set_defaults(func=H.cmd_apply)
417
+
418
+ # schedule — config-driven auto-update timer (see schedule.py)
419
+ from . import schedule as S
420
+
421
+ sc = sub.add_parser("schedule", help="auto-update on a timer, configured via a file")
422
+ scsub = sc.add_subparsers(dest="schedule_cmd", required=True)
423
+
424
+ c = scsub.add_parser("config", help="create/show the schedule config file (pick interval here)")
425
+ c.add_argument("--write", action="store_true", help="create the config file")
426
+ c.set_defaults(func=S.cmd_config)
427
+
428
+ c = scsub.add_parser("apply", help="make the timer match the config (dry-run unless --write)")
429
+ c.add_argument("--write", action="store_true", help="actually install/remove the timer")
430
+ c.set_defaults(func=S.cmd_apply)
431
+
432
+ c = scsub.add_parser("status", help="show config + installed timer")
433
+ c.set_defaults(func=S.cmd_status)
434
+
435
+ c = scsub.add_parser("remove", help="remove the scheduled timer (dry-run unless --write)")
436
+ c.add_argument("--write", action="store_true", help="actually remove")
437
+ c.set_defaults(func=S.cmd_remove)
438
+
439
+ return p
440
+
441
+
442
+ def main(argv=None):
443
+ # Behave like a normal Unix tool when output is piped into `head`/`less`
444
+ # and the reader closes early: die quietly instead of dumping a traceback.
445
+ try:
446
+ import signal
447
+
448
+ signal.signal(signal.SIGPIPE, signal.SIG_DFL)
449
+ except (ImportError, AttributeError):
450
+ pass # SIGPIPE not available (e.g. Windows)
451
+ args = build_parser().parse_args(argv)
452
+ args.func(args)
453
+
454
+
455
+ if __name__ == "__main__":
456
+ main()