cli-web-codewiki 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.
- cli_web/codewiki/__init__.py +3 -0
- cli_web/codewiki/__main__.py +6 -0
- cli_web/codewiki/codewiki_cli.py +142 -0
- cli_web/codewiki/commands/__init__.py +0 -0
- cli_web/codewiki/commands/chat.py +46 -0
- cli_web/codewiki/commands/repos.py +90 -0
- cli_web/codewiki/commands/wiki.py +267 -0
- cli_web/codewiki/core/__init__.py +0 -0
- cli_web/codewiki/core/client.py +224 -0
- cli_web/codewiki/core/exceptions.py +74 -0
- cli_web/codewiki/core/models.py +91 -0
- cli_web/codewiki/core/rpc/__init__.py +0 -0
- cli_web/codewiki/core/rpc/decoder.py +86 -0
- cli_web/codewiki/core/rpc/encoder.py +32 -0
- cli_web/codewiki/core/rpc/types.py +27 -0
- cli_web/codewiki/tests/__init__.py +0 -0
- cli_web/codewiki/tests/test_core.py +725 -0
- cli_web/codewiki/tests/test_e2e.py +411 -0
- cli_web/codewiki/utils/__init__.py +0 -0
- cli_web/codewiki/utils/config.py +14 -0
- cli_web/codewiki/utils/doctor.py +188 -0
- cli_web/codewiki/utils/helpers.py +67 -0
- cli_web/codewiki/utils/mcp_server.py +290 -0
- cli_web/codewiki/utils/output.py +11 -0
- cli_web/codewiki/utils/repl_skin.py +486 -0
- cli_web_codewiki-0.1.0.dist-info/METADATA +14 -0
- cli_web_codewiki-0.1.0.dist-info/RECORD +30 -0
- cli_web_codewiki-0.1.0.dist-info/WHEEL +5 -0
- cli_web_codewiki-0.1.0.dist-info/entry_points.txt +2 -0
- cli_web_codewiki-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
"""cli-web REPL Skin — Unified terminal interface for all cli-web-* CLIs.
|
|
2
|
+
|
|
3
|
+
CANONICAL SOURCE: cli-web-core/cli_web_core/repl_skin.py
|
|
4
|
+
This file is vendored into every generated CLI at
|
|
5
|
+
cli_web/<app>/utils/repl_skin.py
|
|
6
|
+
by `cli-web-devkit resync` / scaffold-cli.py. Do not edit vendored copies
|
|
7
|
+
by hand — change the canonical source and resync. App-specific needs are
|
|
8
|
+
covered by constructor parameters (`display_name`, `accent`), not by
|
|
9
|
+
editing this file.
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
from cli_web.<app>.utils.repl_skin import ReplSkin
|
|
13
|
+
|
|
14
|
+
skin = ReplSkin("monday", version="1.0.0")
|
|
15
|
+
skin.print_banner()
|
|
16
|
+
prompt_text = skin.prompt(context="Board: Sprint 42")
|
|
17
|
+
skin.success("Items created")
|
|
18
|
+
skin.error("Auth token expired")
|
|
19
|
+
skin.warning("Rate limit approaching")
|
|
20
|
+
skin.info("Fetching 24 items...")
|
|
21
|
+
skin.status("Workspace", "my-team")
|
|
22
|
+
skin.table(headers, rows)
|
|
23
|
+
skin.print_goodbye()
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import os
|
|
27
|
+
import sys
|
|
28
|
+
|
|
29
|
+
SKIN_VERSION = "2.0.0"
|
|
30
|
+
|
|
31
|
+
# ── ANSI color codes (no external deps for core styling) ──────────────
|
|
32
|
+
|
|
33
|
+
_RESET = "\033[0m"
|
|
34
|
+
_BOLD = "\033[1m"
|
|
35
|
+
_DIM = "\033[2m"
|
|
36
|
+
_ITALIC = "\033[3m"
|
|
37
|
+
_UNDERLINE = "\033[4m"
|
|
38
|
+
|
|
39
|
+
# Brand colors
|
|
40
|
+
_CYAN = "\033[38;5;80m" # cli-web brand cyan
|
|
41
|
+
_CYAN_BG = "\033[48;5;80m"
|
|
42
|
+
_WHITE = "\033[97m"
|
|
43
|
+
_GRAY = "\033[38;5;245m"
|
|
44
|
+
_DARK_GRAY = "\033[38;5;240m"
|
|
45
|
+
_LIGHT_GRAY = "\033[38;5;250m"
|
|
46
|
+
|
|
47
|
+
# Web app accent colors — each app gets a unique accent.
|
|
48
|
+
# Prefer passing `accent=` to ReplSkin for new apps; this dict exists so
|
|
49
|
+
# vendored copies stay byte-identical across the fleet.
|
|
50
|
+
_ACCENT_COLORS = {
|
|
51
|
+
"monday": "\033[38;5;214m", # warm orange (Monday.com brand)
|
|
52
|
+
"notion": "\033[38;5;255m", # near-white (Notion brand)
|
|
53
|
+
"linear": "\033[38;5;99m", # purple (Linear brand)
|
|
54
|
+
"jira": "\033[38;5;27m", # blue (Jira brand)
|
|
55
|
+
"slack": "\033[38;5;55m", # aubergine (Slack brand)
|
|
56
|
+
"github": "\033[38;5;240m", # dark gray (GitHub brand)
|
|
57
|
+
"figma": "\033[38;5;213m", # pink (Figma brand)
|
|
58
|
+
"airtable": "\033[38;5;35m", # green (Airtable brand)
|
|
59
|
+
"asana": "\033[38;5;196m", # red (Asana brand)
|
|
60
|
+
"trello": "\033[38;5;39m", # blue (Trello brand)
|
|
61
|
+
# Fleet apps
|
|
62
|
+
"amazon": "\033[38;5;214m", # Amazon orange (#FF9900)
|
|
63
|
+
"airbnb": "\033[38;5;197m", # Airbnb rausch pink
|
|
64
|
+
"linkedin": "\033[38;5;32m", # LinkedIn blue
|
|
65
|
+
"reddit": "\033[38;5;202m", # Reddit orangered
|
|
66
|
+
"youtube": "\033[38;5;196m", # YouTube red
|
|
67
|
+
"hackernews": "\033[38;5;208m", # HN orange
|
|
68
|
+
"unsplash": "\033[38;5;240m", # Unsplash dark gray
|
|
69
|
+
"pexels": "\033[38;5;36m", # Pexels teal
|
|
70
|
+
"producthunt": "\033[38;5;209m", # Product Hunt coral
|
|
71
|
+
"booking": "\033[38;5;26m", # Booking blue
|
|
72
|
+
"tripadvisor": "\033[38;5;40m", # TripAdvisor green
|
|
73
|
+
"futbin": "\033[38;5;70m", # FUTBIN pitch green
|
|
74
|
+
"capitoltrades": "\033[38;5;30m", # CapitolTrades teal
|
|
75
|
+
}
|
|
76
|
+
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
|
|
77
|
+
|
|
78
|
+
# Status colors
|
|
79
|
+
_GREEN = "\033[38;5;78m"
|
|
80
|
+
_YELLOW = "\033[38;5;220m"
|
|
81
|
+
_RED = "\033[38;5;196m"
|
|
82
|
+
_BLUE = "\033[38;5;75m"
|
|
83
|
+
_MAGENTA = "\033[38;5;176m"
|
|
84
|
+
|
|
85
|
+
# ── Brand icon ────────────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
_ICON = f"{_CYAN}{_BOLD}◆{_RESET}"
|
|
88
|
+
_ICON_SMALL = f"{_CYAN}▸{_RESET}"
|
|
89
|
+
|
|
90
|
+
# ── Box drawing characters ────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
_H_LINE = "─"
|
|
93
|
+
_V_LINE = "│"
|
|
94
|
+
_TL = "╭"
|
|
95
|
+
_TR = "╮"
|
|
96
|
+
_BL = "╰"
|
|
97
|
+
_BR = "╯"
|
|
98
|
+
_T_DOWN = "┬"
|
|
99
|
+
_T_UP = "┴"
|
|
100
|
+
_T_RIGHT = "├"
|
|
101
|
+
_T_LEFT = "┤"
|
|
102
|
+
_CROSS = "┼"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _strip_ansi(text: str) -> str:
|
|
106
|
+
"""Remove ANSI escape codes for length calculation."""
|
|
107
|
+
import re
|
|
108
|
+
|
|
109
|
+
return re.sub(r"\033\[[^m]*m", "", text)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _visible_len(text: str) -> int:
|
|
113
|
+
"""Get visible length of text (excluding ANSI codes)."""
|
|
114
|
+
return len(_strip_ansi(text))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class ReplSkin:
|
|
118
|
+
"""Unified REPL skin for cli-web-* CLIs.
|
|
119
|
+
|
|
120
|
+
Provides consistent branding, prompts, and message formatting
|
|
121
|
+
across all CLI harnesses built with the cli-anything-web methodology.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
def __init__(
|
|
125
|
+
self,
|
|
126
|
+
app: str,
|
|
127
|
+
version: str = "1.0.0",
|
|
128
|
+
history_file: str | None = None,
|
|
129
|
+
display_name: str | None = None,
|
|
130
|
+
accent: str | None = None,
|
|
131
|
+
):
|
|
132
|
+
"""Initialize the REPL skin.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
app: App name (e.g., "monday", "notion", "jira").
|
|
136
|
+
version: CLI version string.
|
|
137
|
+
history_file: Path for persistent command history.
|
|
138
|
+
Defaults to ~/.cli-web-<app>/history
|
|
139
|
+
display_name: Override for the banner display name.
|
|
140
|
+
accent: ANSI escape for the brand accent color (overrides
|
|
141
|
+
the built-in per-app table).
|
|
142
|
+
"""
|
|
143
|
+
self.app = app.lower().replace("-", "_")
|
|
144
|
+
self.display_name = display_name or app.replace("_", " ").title()
|
|
145
|
+
self.version = version
|
|
146
|
+
self.accent = accent or _ACCENT_COLORS.get(self.app, _DEFAULT_ACCENT)
|
|
147
|
+
|
|
148
|
+
# History file path. The directory is created lazily in
|
|
149
|
+
# create_prompt_session() — CLIs construct ReplSkin at module import
|
|
150
|
+
# time, and one-shot commands must not pay (or fail on) filesystem
|
|
151
|
+
# writes they never use.
|
|
152
|
+
if history_file is None:
|
|
153
|
+
from pathlib import Path
|
|
154
|
+
|
|
155
|
+
hist_dir = Path.home() / f".cli-web-{self.app}"
|
|
156
|
+
self.history_file = str(hist_dir / "history")
|
|
157
|
+
else:
|
|
158
|
+
self.history_file = history_file
|
|
159
|
+
|
|
160
|
+
# Detect terminal capabilities
|
|
161
|
+
self._color = self._detect_color_support()
|
|
162
|
+
|
|
163
|
+
def _detect_color_support(self) -> bool:
|
|
164
|
+
"""Check if terminal supports color."""
|
|
165
|
+
if os.environ.get("NO_COLOR"):
|
|
166
|
+
return False
|
|
167
|
+
if os.environ.get("CLI_WEB_NO_COLOR"):
|
|
168
|
+
return False
|
|
169
|
+
if not hasattr(sys.stdout, "isatty"):
|
|
170
|
+
return False
|
|
171
|
+
return sys.stdout.isatty()
|
|
172
|
+
|
|
173
|
+
def _c(self, code: str, text: str) -> str:
|
|
174
|
+
"""Apply color code if colors are supported."""
|
|
175
|
+
if not self._color:
|
|
176
|
+
return text
|
|
177
|
+
return f"{code}{text}{_RESET}"
|
|
178
|
+
|
|
179
|
+
# ── Banner ────────────────────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
def print_banner(self):
|
|
182
|
+
"""Print the startup banner with branding."""
|
|
183
|
+
inner = 54
|
|
184
|
+
|
|
185
|
+
def _box_line(content: str) -> str:
|
|
186
|
+
"""Wrap content in box drawing, padding to inner width."""
|
|
187
|
+
pad = inner - _visible_len(content)
|
|
188
|
+
vl = self._c(_DARK_GRAY, _V_LINE)
|
|
189
|
+
return f"{vl}{content}{' ' * max(0, pad)}{vl}"
|
|
190
|
+
|
|
191
|
+
top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
|
|
192
|
+
bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
|
|
193
|
+
|
|
194
|
+
# Title: ◆ cli-web · Monday
|
|
195
|
+
icon = self._c(_CYAN + _BOLD, "◆")
|
|
196
|
+
brand = self._c(_CYAN + _BOLD, "cli-web")
|
|
197
|
+
dot = self._c(_DARK_GRAY, "·")
|
|
198
|
+
name = self._c(self.accent + _BOLD, self.display_name)
|
|
199
|
+
title = f" {icon} {brand} {dot} {name}"
|
|
200
|
+
|
|
201
|
+
ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
|
|
202
|
+
tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
|
|
203
|
+
empty = ""
|
|
204
|
+
|
|
205
|
+
print(top)
|
|
206
|
+
print(_box_line(title))
|
|
207
|
+
print(_box_line(ver))
|
|
208
|
+
print(_box_line(empty))
|
|
209
|
+
print(_box_line(tip))
|
|
210
|
+
print(bot)
|
|
211
|
+
print()
|
|
212
|
+
|
|
213
|
+
# ── Prompt ────────────────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
def prompt(self, project_name: str = "", modified: bool = False, context: str = "") -> str:
|
|
216
|
+
"""Build a styled prompt string for prompt_toolkit or input()."""
|
|
217
|
+
parts = []
|
|
218
|
+
|
|
219
|
+
if self._color:
|
|
220
|
+
parts.append(f"{_CYAN}◆{_RESET} ")
|
|
221
|
+
else:
|
|
222
|
+
parts.append("> ")
|
|
223
|
+
|
|
224
|
+
parts.append(self._c(self.accent + _BOLD, self.app))
|
|
225
|
+
|
|
226
|
+
if project_name or context:
|
|
227
|
+
ctx = context or project_name
|
|
228
|
+
mod = "*" if modified else ""
|
|
229
|
+
parts.append(f" {self._c(_DARK_GRAY, '[')}")
|
|
230
|
+
parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
|
|
231
|
+
parts.append(self._c(_DARK_GRAY, "]"))
|
|
232
|
+
|
|
233
|
+
parts.append(self._c(_GRAY, " ❯ "))
|
|
234
|
+
|
|
235
|
+
return "".join(parts)
|
|
236
|
+
|
|
237
|
+
def prompt_tokens(self, project_name: str = "", modified: bool = False, context: str = ""):
|
|
238
|
+
"""Build prompt_toolkit formatted text tokens for the prompt."""
|
|
239
|
+
tokens = []
|
|
240
|
+
|
|
241
|
+
tokens.append(("class:icon", "◆ "))
|
|
242
|
+
tokens.append(("class:app", self.app))
|
|
243
|
+
|
|
244
|
+
if project_name or context:
|
|
245
|
+
ctx = context or project_name
|
|
246
|
+
mod = "*" if modified else ""
|
|
247
|
+
tokens.append(("class:bracket", " ["))
|
|
248
|
+
tokens.append(("class:context", f"{ctx}{mod}"))
|
|
249
|
+
tokens.append(("class:bracket", "]"))
|
|
250
|
+
|
|
251
|
+
tokens.append(("class:arrow", " ❯ "))
|
|
252
|
+
|
|
253
|
+
return tokens
|
|
254
|
+
|
|
255
|
+
def get_prompt_style(self):
|
|
256
|
+
"""Get a prompt_toolkit Style object matching the skin."""
|
|
257
|
+
try:
|
|
258
|
+
from prompt_toolkit.styles import Style
|
|
259
|
+
except Exception: # noqa: BLE001 — optional dep must never break the REPL
|
|
260
|
+
return None
|
|
261
|
+
|
|
262
|
+
accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
|
|
263
|
+
|
|
264
|
+
return Style.from_dict(
|
|
265
|
+
{
|
|
266
|
+
"icon": "#5fdfdf bold",
|
|
267
|
+
"app": f"{accent_hex} bold",
|
|
268
|
+
"bracket": "#585858",
|
|
269
|
+
"context": "#bcbcbc",
|
|
270
|
+
"arrow": "#808080",
|
|
271
|
+
"completion-menu.completion": "bg:#303030 #bcbcbc",
|
|
272
|
+
"completion-menu.completion.current": f"bg:{accent_hex} #000000",
|
|
273
|
+
"completion-menu.meta.completion": "bg:#303030 #808080",
|
|
274
|
+
"completion-menu.meta.completion.current": f"bg:{accent_hex} #000000",
|
|
275
|
+
"auto-suggest": "#585858",
|
|
276
|
+
"bottom-toolbar": "bg:#1c1c1c #808080",
|
|
277
|
+
"bottom-toolbar.text": "#808080",
|
|
278
|
+
}
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# ── Messages ──────────────────────────────────────────────────────
|
|
282
|
+
|
|
283
|
+
def success(self, message: str):
|
|
284
|
+
"""Print a success message with green checkmark."""
|
|
285
|
+
icon = self._c(_GREEN + _BOLD, "✓")
|
|
286
|
+
print(f" {icon} {self._c(_GREEN, message)}")
|
|
287
|
+
|
|
288
|
+
def error(self, message: str):
|
|
289
|
+
"""Print an error message with red cross."""
|
|
290
|
+
icon = self._c(_RED + _BOLD, "✗")
|
|
291
|
+
print(f" {icon} {self._c(_RED, message)}", file=sys.stderr)
|
|
292
|
+
|
|
293
|
+
def warning(self, message: str):
|
|
294
|
+
"""Print a warning message with yellow triangle."""
|
|
295
|
+
icon = self._c(_YELLOW + _BOLD, "⚠")
|
|
296
|
+
print(f" {icon} {self._c(_YELLOW, message)}")
|
|
297
|
+
|
|
298
|
+
def info(self, message: str):
|
|
299
|
+
"""Print an info message with blue dot."""
|
|
300
|
+
icon = self._c(_BLUE, "●")
|
|
301
|
+
print(f" {icon} {self._c(_LIGHT_GRAY, message)}")
|
|
302
|
+
|
|
303
|
+
def hint(self, message: str):
|
|
304
|
+
"""Print a subtle hint message."""
|
|
305
|
+
print(f" {self._c(_DARK_GRAY, message)}")
|
|
306
|
+
|
|
307
|
+
def section(self, title: str):
|
|
308
|
+
"""Print a section header."""
|
|
309
|
+
print()
|
|
310
|
+
print(f" {self._c(self.accent + _BOLD, title)}")
|
|
311
|
+
print(f" {self._c(_DARK_GRAY, _H_LINE * len(title))}")
|
|
312
|
+
|
|
313
|
+
# ── Status display ────────────────────────────────────────────────
|
|
314
|
+
|
|
315
|
+
def status(self, label: str, value: str):
|
|
316
|
+
"""Print a key-value status line."""
|
|
317
|
+
lbl = self._c(_GRAY, f" {label}:")
|
|
318
|
+
val = self._c(_WHITE, f" {value}")
|
|
319
|
+
print(f"{lbl}{val}")
|
|
320
|
+
|
|
321
|
+
def status_block(self, items: dict[str, str], title: str = ""):
|
|
322
|
+
"""Print a block of status key-value pairs."""
|
|
323
|
+
if title:
|
|
324
|
+
self.section(title)
|
|
325
|
+
|
|
326
|
+
max_key = max(len(k) for k in items) if items else 0
|
|
327
|
+
for label, value in items.items():
|
|
328
|
+
lbl = self._c(_GRAY, f" {label:<{max_key}}")
|
|
329
|
+
val = self._c(_WHITE, f" {value}")
|
|
330
|
+
print(f"{lbl}{val}")
|
|
331
|
+
|
|
332
|
+
def progress(self, current: int, total: int, label: str = ""):
|
|
333
|
+
"""Print a simple progress indicator."""
|
|
334
|
+
pct = int(current / total * 100) if total > 0 else 0
|
|
335
|
+
bar_width = 20
|
|
336
|
+
filled = int(bar_width * current / total) if total > 0 else 0
|
|
337
|
+
bar = "█" * filled + "░" * (bar_width - filled)
|
|
338
|
+
text = f" {self._c(_CYAN, bar)} {self._c(_GRAY, f'{pct:3d}%')}"
|
|
339
|
+
if label:
|
|
340
|
+
text += f" {self._c(_LIGHT_GRAY, label)}"
|
|
341
|
+
print(text)
|
|
342
|
+
|
|
343
|
+
# ── Table display ─────────────────────────────────────────────────
|
|
344
|
+
|
|
345
|
+
def table(self, headers: list[str], rows: list[list[str]], max_col_width: int = 40):
|
|
346
|
+
"""Print a formatted table with box-drawing characters."""
|
|
347
|
+
if not headers:
|
|
348
|
+
return
|
|
349
|
+
|
|
350
|
+
col_widths = [min(len(h), max_col_width) for h in headers]
|
|
351
|
+
for row in rows:
|
|
352
|
+
for i, cell in enumerate(row):
|
|
353
|
+
if i < len(col_widths):
|
|
354
|
+
col_widths[i] = min(max(col_widths[i], len(str(cell))), max_col_width)
|
|
355
|
+
|
|
356
|
+
def pad(text: str, width: int) -> str:
|
|
357
|
+
t = str(text)[:width]
|
|
358
|
+
return t + " " * (width - len(t))
|
|
359
|
+
|
|
360
|
+
header_cells = [
|
|
361
|
+
self._c(_CYAN + _BOLD, pad(h, col_widths[i])) for i, h in enumerate(headers)
|
|
362
|
+
]
|
|
363
|
+
sep = self._c(_DARK_GRAY, f" {_V_LINE} ")
|
|
364
|
+
print(f" {sep.join(header_cells)}")
|
|
365
|
+
|
|
366
|
+
sep_line = self._c(_DARK_GRAY, f" {'───'.join([_H_LINE * w for w in col_widths])}")
|
|
367
|
+
print(sep_line)
|
|
368
|
+
|
|
369
|
+
for row in rows:
|
|
370
|
+
cells = []
|
|
371
|
+
for i, cell in enumerate(row):
|
|
372
|
+
if i < len(col_widths):
|
|
373
|
+
cells.append(self._c(_LIGHT_GRAY, pad(str(cell), col_widths[i])))
|
|
374
|
+
row_sep = self._c(_DARK_GRAY, f" {_V_LINE} ")
|
|
375
|
+
print(f" {row_sep.join(cells)}")
|
|
376
|
+
|
|
377
|
+
# ── Help display ──────────────────────────────────────────────────
|
|
378
|
+
|
|
379
|
+
def help(self, commands: dict[str, str]):
|
|
380
|
+
"""Print a formatted help listing."""
|
|
381
|
+
self.section("Commands")
|
|
382
|
+
max_cmd = max(len(c) for c in commands) if commands else 0
|
|
383
|
+
for cmd, desc in commands.items():
|
|
384
|
+
cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
|
|
385
|
+
desc_styled = self._c(_GRAY, f" {desc}")
|
|
386
|
+
print(f"{cmd_styled}{desc_styled}")
|
|
387
|
+
print()
|
|
388
|
+
|
|
389
|
+
# ── Goodbye ───────────────────────────────────────────────────────
|
|
390
|
+
|
|
391
|
+
def print_goodbye(self):
|
|
392
|
+
"""Print a styled goodbye message."""
|
|
393
|
+
print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
|
|
394
|
+
|
|
395
|
+
# ── Prompt toolkit session factory ────────────────────────────────
|
|
396
|
+
|
|
397
|
+
def create_prompt_session(self):
|
|
398
|
+
"""Create a prompt_toolkit PromptSession with skin styling."""
|
|
399
|
+
try:
|
|
400
|
+
from pathlib import Path
|
|
401
|
+
|
|
402
|
+
from prompt_toolkit import PromptSession
|
|
403
|
+
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
|
|
404
|
+
from prompt_toolkit.history import FileHistory, InMemoryHistory
|
|
405
|
+
|
|
406
|
+
style = self.get_prompt_style()
|
|
407
|
+
|
|
408
|
+
try:
|
|
409
|
+
Path(self.history_file).parent.mkdir(parents=True, exist_ok=True)
|
|
410
|
+
history = FileHistory(self.history_file)
|
|
411
|
+
except OSError: # read-only HOME — run without persistent history
|
|
412
|
+
history = InMemoryHistory()
|
|
413
|
+
|
|
414
|
+
session = PromptSession(
|
|
415
|
+
history=history,
|
|
416
|
+
auto_suggest=AutoSuggestFromHistory(),
|
|
417
|
+
style=style,
|
|
418
|
+
enable_history_search=True,
|
|
419
|
+
)
|
|
420
|
+
return session
|
|
421
|
+
except Exception: # noqa: BLE001 — optional dep must never break the REPL
|
|
422
|
+
return None
|
|
423
|
+
|
|
424
|
+
def get_input(
|
|
425
|
+
self, pt_session, project_name: str = "", modified: bool = False, context: str = ""
|
|
426
|
+
) -> str:
|
|
427
|
+
"""Get input from user using prompt_toolkit or fallback."""
|
|
428
|
+
if pt_session is not None:
|
|
429
|
+
from prompt_toolkit.formatted_text import FormattedText
|
|
430
|
+
|
|
431
|
+
tokens = self.prompt_tokens(project_name, modified, context)
|
|
432
|
+
return pt_session.prompt(FormattedText(tokens)).strip()
|
|
433
|
+
else:
|
|
434
|
+
raw_prompt = self.prompt(project_name, modified, context)
|
|
435
|
+
return input(raw_prompt).strip()
|
|
436
|
+
|
|
437
|
+
# ── Toolbar builder ───────────────────────────────────────────────
|
|
438
|
+
|
|
439
|
+
def bottom_toolbar(self, items: dict[str, str]):
|
|
440
|
+
"""Create a bottom toolbar callback for prompt_toolkit."""
|
|
441
|
+
|
|
442
|
+
def toolbar():
|
|
443
|
+
from prompt_toolkit.formatted_text import FormattedText
|
|
444
|
+
|
|
445
|
+
parts = []
|
|
446
|
+
for i, (k, v) in enumerate(items.items()):
|
|
447
|
+
if i > 0:
|
|
448
|
+
parts.append(("class:bottom-toolbar.text", " │ "))
|
|
449
|
+
parts.append(("class:bottom-toolbar.text", f" {k}: "))
|
|
450
|
+
parts.append(("class:bottom-toolbar", v))
|
|
451
|
+
return FormattedText(parts)
|
|
452
|
+
|
|
453
|
+
return toolbar
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
# ── ANSI 256-color to hex mapping (for prompt_toolkit styles) ─────────
|
|
457
|
+
|
|
458
|
+
_ANSI_256_TO_HEX = {
|
|
459
|
+
# Base entries (from reference implementation)
|
|
460
|
+
"\033[38;5;33m": "#0087ff",
|
|
461
|
+
"\033[38;5;35m": "#00af5f",
|
|
462
|
+
"\033[38;5;39m": "#00afff",
|
|
463
|
+
"\033[38;5;40m": "#00d700",
|
|
464
|
+
"\033[38;5;55m": "#5f00af",
|
|
465
|
+
"\033[38;5;69m": "#5f87ff",
|
|
466
|
+
"\033[38;5;75m": "#5fafff",
|
|
467
|
+
"\033[38;5;80m": "#5fd7d7",
|
|
468
|
+
"\033[38;5;208m": "#ff8700",
|
|
469
|
+
"\033[38;5;214m": "#ffaf00",
|
|
470
|
+
# Web app accent colors
|
|
471
|
+
"\033[38;5;255m": "#eeeeee", # notion
|
|
472
|
+
"\033[38;5;99m": "#875fff", # linear
|
|
473
|
+
"\033[38;5;27m": "#005fff", # jira
|
|
474
|
+
"\033[38;5;240m": "#585858", # github
|
|
475
|
+
"\033[38;5;213m": "#ff87ff", # figma
|
|
476
|
+
"\033[38;5;196m": "#ff0000", # asana
|
|
477
|
+
# Fleet apps
|
|
478
|
+
"\033[38;5;197m": "#ff005f", # airbnb
|
|
479
|
+
"\033[38;5;32m": "#0087d7", # linkedin
|
|
480
|
+
"\033[38;5;202m": "#ff5f00", # reddit
|
|
481
|
+
"\033[38;5;36m": "#00af87", # pexels
|
|
482
|
+
"\033[38;5;209m": "#ff875f", # producthunt
|
|
483
|
+
"\033[38;5;26m": "#005fd7", # booking
|
|
484
|
+
"\033[38;5;70m": "#5faf00", # futbin
|
|
485
|
+
"\033[38;5;30m": "#008787", # capitoltrades
|
|
486
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cli-web-codewiki
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI for Google Code Wiki — AI-generated documentation for open source repos
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: click>=8.0
|
|
7
|
+
Requires-Dist: httpx>=0.24
|
|
8
|
+
Requires-Dist: rich>=13.0
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
11
|
+
Dynamic: provides-extra
|
|
12
|
+
Dynamic: requires-dist
|
|
13
|
+
Dynamic: requires-python
|
|
14
|
+
Dynamic: summary
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
cli_web/codewiki/__init__.py,sha256=0BAnckUl3p09NdXpaBElwXJrWZc5mCoor_YneFwrxwI,76
|
|
2
|
+
cli_web/codewiki/__main__.py,sha256=UqAs7_5Rqpr63ulBhHqrvUYBCnIOdKaWgi70oFJlBpc,122
|
|
3
|
+
cli_web/codewiki/codewiki_cli.py,sha256=eOd9w8sMfIyrpt62udKkiDp5WsoZHjGAnraZ1VCSP5o,4555
|
|
4
|
+
cli_web/codewiki/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
cli_web/codewiki/commands/chat.py,sha256=P8ZbaADZnQ07mAfOw6_ONRclpOMitr6FAjleIvoqLqo,1344
|
|
6
|
+
cli_web/codewiki/commands/repos.py,sha256=s_YsBNZVk66CEJA06TqoXOgzrEmzRXcFTxyEHIfW4ak,2704
|
|
7
|
+
cli_web/codewiki/commands/wiki.py,sha256=QE_poAeGbk-Ug4FkCIjlOtxj1CaDIJ1YI4O8AjWwHpk,9217
|
|
8
|
+
cli_web/codewiki/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
cli_web/codewiki/core/client.py,sha256=drY7lNQshDZfpNsotkH8NnZnKLTPP3jpl6z5rAONr-s,8343
|
|
10
|
+
cli_web/codewiki/core/exceptions.py,sha256=ZPv98rkxoHC9Q3E-VJ3nGAtekpithhhLJc4DI4DXTrw,2012
|
|
11
|
+
cli_web/codewiki/core/models.py,sha256=uUcG2OlSdah1yEwZpvdIjgvgtpBKuvvBkiQ-pgvnWI0,2167
|
|
12
|
+
cli_web/codewiki/core/rpc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
cli_web/codewiki/core/rpc/decoder.py,sha256=PitmIH9YY89jCIqOCey6dJ6MSqChq9CuOPugO2zqtqg,2611
|
|
14
|
+
cli_web/codewiki/core/rpc/encoder.py,sha256=EAaaXj2mqe0VvD91VrfCts2kowowiHtyLsTis8mhR9Y,862
|
|
15
|
+
cli_web/codewiki/core/rpc/types.py,sha256=ncNd4aiofEwVG6fkPocGAhA_Qqa83FZb-_qxH9tshWc,711
|
|
16
|
+
cli_web/codewiki/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
cli_web/codewiki/tests/test_core.py,sha256=0HG7nXx5XAL-x1nHQr_vegcfhicEk4xU6bu04hUZVqc,26466
|
|
18
|
+
cli_web/codewiki/tests/test_e2e.py,sha256=kTbjESpoKbpDC2bkq_KG_UXtMu4FBKYcAuX6loFL8TA,15165
|
|
19
|
+
cli_web/codewiki/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
+
cli_web/codewiki/utils/config.py,sha256=3S7LPPxJWdln0MH1aeLAYPYnD8CyGWuwvZCyjYoVak0,356
|
|
21
|
+
cli_web/codewiki/utils/doctor.py,sha256=HI3u85HWruCjG2VK8cwel_TSX3zejPTWqAibiPHCTAQ,6552
|
|
22
|
+
cli_web/codewiki/utils/helpers.py,sha256=j9lLulm2eaxWwlEsNBMSDIA3_8IcZQQr2p0FHDxB5B0,1827
|
|
23
|
+
cli_web/codewiki/utils/mcp_server.py,sha256=5rDTR5487yE08gAIgKC8Bqs3RwR2JiSQUfCATYV7r9w,10768
|
|
24
|
+
cli_web/codewiki/utils/output.py,sha256=B5XxI5gnyIBVNjsn4YFfh3NLHnHmgEyKFaHbVMSDiO0,309
|
|
25
|
+
cli_web/codewiki/utils/repl_skin.py,sha256=fIqMbQ5SBYSGzPOoTDT6VE67sFSw0pfS7VPGIdeBmJU,18749
|
|
26
|
+
cli_web_codewiki-0.1.0.dist-info/METADATA,sha256=tLhypiWZhhKytikRnThATeNLvEvVYX9tA1r07w5r2TA,402
|
|
27
|
+
cli_web_codewiki-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
28
|
+
cli_web_codewiki-0.1.0.dist-info/entry_points.txt,sha256=pgsSTex33MK17dzpog-NvjtnSaUb0PblFQwnMRGVHBE,72
|
|
29
|
+
cli_web_codewiki-0.1.0.dist-info/top_level.txt,sha256=nqEsw6-b5Wtzip1g13XiADPkuClajm6oyFis-CZv-wY,8
|
|
30
|
+
cli_web_codewiki-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cli_web
|