opencontext-cli 0.3.0__tar.gz → 0.4.0b0__tar.gz
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.
- {opencontext_cli-0.3.0 → opencontext_cli-0.4.0b0}/PKG-INFO +1 -1
- {opencontext_cli-0.3.0 → opencontext_cli-0.4.0b0}/opencontext_cli/commands/config_cmd.py +12 -3
- opencontext_cli-0.4.0b0/opencontext_cli/commands/menu_cmd.py +442 -0
- {opencontext_cli-0.3.0 → opencontext_cli-0.4.0b0}/opencontext_cli/commands/setup_cmd.py +2 -2
- {opencontext_cli-0.3.0 → opencontext_cli-0.4.0b0}/opencontext_cli/commands/update_cmd.py +20 -9
- {opencontext_cli-0.3.0 → opencontext_cli-0.4.0b0}/opencontext_cli/main.py +59 -9
- {opencontext_cli-0.3.0 → opencontext_cli-0.4.0b0}/opencontext_cli.egg-info/PKG-INFO +1 -1
- {opencontext_cli-0.3.0 → opencontext_cli-0.4.0b0}/opencontext_cli.egg-info/SOURCES.txt +1 -0
- {opencontext_cli-0.3.0 → opencontext_cli-0.4.0b0}/pyproject.toml +1 -1
- {opencontext_cli-0.3.0 → opencontext_cli-0.4.0b0}/LICENSE +0 -0
- {opencontext_cli-0.3.0 → opencontext_cli-0.4.0b0}/README.md +0 -0
- {opencontext_cli-0.3.0 → opencontext_cli-0.4.0b0}/opencontext_cli/__init__.py +0 -0
- {opencontext_cli-0.3.0 → opencontext_cli-0.4.0b0}/opencontext_cli/__main__.py +0 -0
- {opencontext_cli-0.3.0 → opencontext_cli-0.4.0b0}/opencontext_cli/commands/__init__.py +0 -0
- {opencontext_cli-0.3.0 → opencontext_cli-0.4.0b0}/opencontext_cli/commands/ci_check_cmd.py +0 -0
- {opencontext_cli-0.3.0 → opencontext_cli-0.4.0b0}/opencontext_cli/commands/git_cmd.py +0 -0
- {opencontext_cli-0.3.0 → opencontext_cli-0.4.0b0}/opencontext_cli/commands/hints_cmd.py +0 -0
- {opencontext_cli-0.3.0 → opencontext_cli-0.4.0b0}/opencontext_cli/commands/kg_cmd.py +0 -0
- {opencontext_cli-0.3.0 → opencontext_cli-0.4.0b0}/opencontext_cli/commands/plugin_cmd.py +0 -0
- {opencontext_cli-0.3.0 → opencontext_cli-0.4.0b0}/opencontext_cli/commands/sync_cmd.py +0 -0
- {opencontext_cli-0.3.0 → opencontext_cli-0.4.0b0}/opencontext_cli/commands/verify_cmd.py +0 -0
- {opencontext_cli-0.3.0 → opencontext_cli-0.4.0b0}/opencontext_cli.egg-info/dependency_links.txt +0 -0
- {opencontext_cli-0.3.0 → opencontext_cli-0.4.0b0}/opencontext_cli.egg-info/entry_points.txt +0 -0
- {opencontext_cli-0.3.0 → opencontext_cli-0.4.0b0}/opencontext_cli.egg-info/requires.txt +0 -0
- {opencontext_cli-0.3.0 → opencontext_cli-0.4.0b0}/opencontext_cli.egg-info/top_level.txt +0 -0
- {opencontext_cli-0.3.0 → opencontext_cli-0.4.0b0}/setup.cfg +0 -0
|
@@ -16,7 +16,6 @@ from opencontext_core.user_prefs import UserConfigStore
|
|
|
16
16
|
from opencontext_core.wizard import (
|
|
17
17
|
reconfigure,
|
|
18
18
|
reset_config,
|
|
19
|
-
run_wizard,
|
|
20
19
|
show_config,
|
|
21
20
|
)
|
|
22
21
|
|
|
@@ -25,7 +24,7 @@ def add_config_parser(subparsers: Any) -> None:
|
|
|
25
24
|
"""Add config command parsers."""
|
|
26
25
|
|
|
27
26
|
config_parser = subparsers.add_parser("config", help="Manage OpenContext configuration.")
|
|
28
|
-
config_sub = config_parser.add_subparsers(dest="config_command"
|
|
27
|
+
config_sub = config_parser.add_subparsers(dest="config_command")
|
|
29
28
|
|
|
30
29
|
# Wizard
|
|
31
30
|
wizard_parser = config_sub.add_parser("wizard", help="Run configuration wizard.")
|
|
@@ -76,7 +75,17 @@ def add_config_parser(subparsers: Any) -> None:
|
|
|
76
75
|
def handle_config(args: Any) -> None:
|
|
77
76
|
"""Handle config commands."""
|
|
78
77
|
|
|
79
|
-
command = args
|
|
78
|
+
command = getattr(args, "config_command", None)
|
|
79
|
+
|
|
80
|
+
if command is None:
|
|
81
|
+
# No subcommand — run the interactive wizard by default
|
|
82
|
+
from opencontext_core.wizard import run_wizard, run_wizard_menu
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
run_wizard_menu()
|
|
86
|
+
except Exception:
|
|
87
|
+
run_wizard(non_interactive=True)
|
|
88
|
+
return
|
|
80
89
|
|
|
81
90
|
if command == "wizard":
|
|
82
91
|
use_tui = not getattr(args, "non_interactive", False)
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
"""Main TUI menu for OpenContext.
|
|
2
|
+
|
|
3
|
+
Run opencontext with no arguments to launch this interactive menu.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
from rich.prompt import Prompt
|
|
12
|
+
from rich.text import Text
|
|
13
|
+
|
|
14
|
+
from opencontext_cli.commands.update_cmd import handle_upgrade
|
|
15
|
+
from opencontext_core.dx.console_styles import console
|
|
16
|
+
|
|
17
|
+
# ── ANSI art: OpenContext logo ──────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
LOGO = [
|
|
20
|
+
" ⢀⣀⣤⣤⣶⣶⣶⣶⣦⣤⣤⣄⣀",
|
|
21
|
+
" ⣠⣶⣿⣿⠿⠛⠋⠉⠉⠉⠉⠙⠛⠿⣿⣿⣶⣄",
|
|
22
|
+
" ⣰⣿⣿⠟⠉ ⢀⣀⣀⣀ ⠉⠻⣿⣿⣆",
|
|
23
|
+
" ⣿⣿⡟⠁ ⢀⣴⣿⣿⠿⠿⣿⣿⣶⣄ ⢹⣿⣿",
|
|
24
|
+
" ⢸⣿⡟ ⣴⣿⣿⠋ ⢀⣀ ⠙⣿⣿⣦ ⢸⣿⡟",
|
|
25
|
+
" ⠸⣿⣿⣦ ⢿⣿⣿⣷⣾⣿⣿⣿⣷⣶⣾⣿⣿⡿ ⣰⣿⣿⠇",
|
|
26
|
+
" ⠻⣿⣿⣷⣤⣉⠛⠻⠿⠿⠿⠿⠿⠟⠛⣉⣤⣶⣿⣿⡿⠟",
|
|
27
|
+
" ⢀⣀⣤⣶⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣤⣄⣀",
|
|
28
|
+
" ⣠⣶⣿⣿⠿⠟⠛⠉⠉ ⠈⠉⠉⠙⠛⠻⢿⣿⣿⣶⣄⡀",
|
|
29
|
+
" ⣿⣿⡟⠁ ⢀⣀⣤⣶⣿⣿⡿⠿⢿⣿⣷",
|
|
30
|
+
" ⣿⣿⡇ ⢀⣀⣤⣤⣶⣶⣶⣿⣿⣿⣿⣿⣿⣿⣷⣶⣤⣀ ⢸⣿⣿",
|
|
31
|
+
" ⣿⣿⡇ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⢸⣿⣿",
|
|
32
|
+
" ⢻⣿⣿⣄ ⠙⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛ ⣠⣿⣿⡟",
|
|
33
|
+
" ⠻⣿⣿⣷⣤⣀⡀ ⢀⣀⣤⣴⣿⣿⣿⠟",
|
|
34
|
+
" ⠙⠛⠿⣿⣿⣿⣿⣷⣶⣶⣶⣶⣶⣶⣾⣿⣿⣿⣿⡿⠟⠋⠁",
|
|
35
|
+
" ⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
COMPACT_LOGO = [
|
|
39
|
+
" ╔══════════════════════════════╗",
|
|
40
|
+
" ║ OpenContext Runtime ║",
|
|
41
|
+
" ║ Context Engineering ║",
|
|
42
|
+
" ╚══════════════════════════════╝",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _show_logo() -> None:
|
|
47
|
+
"""Print the OpenContext logo, falling back to compact if terminal is small."""
|
|
48
|
+
try:
|
|
49
|
+
width = __import__("shutil").get_terminal_size().columns
|
|
50
|
+
height = __import__("shutil").get_terminal_size().lines
|
|
51
|
+
use_full = width >= 64 and height >= len(LOGO) + 18
|
|
52
|
+
except Exception:
|
|
53
|
+
use_full = False
|
|
54
|
+
|
|
55
|
+
logo_lines = LOGO if use_full else COMPACT_LOGO
|
|
56
|
+
for line in logo_lines:
|
|
57
|
+
console.print(Text(line, style="bold cyan"))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def run_main_menu() -> None:
|
|
61
|
+
"""Show the main OpenContext menu and delegate to the selected command."""
|
|
62
|
+
|
|
63
|
+
while True:
|
|
64
|
+
try:
|
|
65
|
+
console.clear()
|
|
66
|
+
except Exception:
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
_show_logo()
|
|
70
|
+
console.print()
|
|
71
|
+
console.print(
|
|
72
|
+
Panel(
|
|
73
|
+
"\n".join(
|
|
74
|
+
[
|
|
75
|
+
"[bold]Menu[/]",
|
|
76
|
+
"",
|
|
77
|
+
" [cyan]1[/] Start installation",
|
|
78
|
+
" [cyan]2[/] Upgrade tools",
|
|
79
|
+
" [cyan]3[/] Sync configs",
|
|
80
|
+
" [cyan]4[/] Upgrade + Sync",
|
|
81
|
+
" [cyan]5[/] Configure models",
|
|
82
|
+
" [cyan]6[/] Create your own Agent",
|
|
83
|
+
" [cyan]7[/] OpenCode Community Plugins",
|
|
84
|
+
" [cyan]8[/] OpenCode SDD Profiles",
|
|
85
|
+
" [cyan]9[/] Manage backups",
|
|
86
|
+
" [cyan]10[/] Managed uninstall",
|
|
87
|
+
" [cyan]q[/] Quit",
|
|
88
|
+
"",
|
|
89
|
+
"[dim]1-10: select • q: quit[/]",
|
|
90
|
+
]
|
|
91
|
+
),
|
|
92
|
+
border_style="cyan",
|
|
93
|
+
padding=(1, 2),
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
console.print()
|
|
97
|
+
|
|
98
|
+
choice = Prompt.ask(
|
|
99
|
+
"Select option",
|
|
100
|
+
choices=["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "q"],
|
|
101
|
+
default="q",
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if choice == "1":
|
|
105
|
+
_run_install()
|
|
106
|
+
elif choice == "2":
|
|
107
|
+
_run_upgrade()
|
|
108
|
+
elif choice == "3":
|
|
109
|
+
_run_sync()
|
|
110
|
+
elif choice == "4":
|
|
111
|
+
_run_upgrade_sync()
|
|
112
|
+
elif choice == "5":
|
|
113
|
+
_run_configure_models()
|
|
114
|
+
elif choice == "6":
|
|
115
|
+
_run_create_agent()
|
|
116
|
+
elif choice == "7":
|
|
117
|
+
_run_plugins()
|
|
118
|
+
elif choice == "8":
|
|
119
|
+
_run_sdd_profiles()
|
|
120
|
+
elif choice == "9":
|
|
121
|
+
_run_backups()
|
|
122
|
+
elif choice == "10":
|
|
123
|
+
_run_uninstall()
|
|
124
|
+
elif choice == "q":
|
|
125
|
+
console.print("[dim]Goodbye.[/]")
|
|
126
|
+
break
|
|
127
|
+
|
|
128
|
+
console.print("\n[dim]Press Enter to return to menu...[/]")
|
|
129
|
+
try:
|
|
130
|
+
input()
|
|
131
|
+
except (EOFError, KeyboardInterrupt):
|
|
132
|
+
break
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# ── Menu action dispatchers ─────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _run_install() -> None:
|
|
139
|
+
"""Start installation — opencontext install."""
|
|
140
|
+
console.print("\n[bold]Starting installation...[/]")
|
|
141
|
+
try:
|
|
142
|
+
from opencontext_cli.main import _install
|
|
143
|
+
|
|
144
|
+
class _InstallArgs:
|
|
145
|
+
root: str = "."
|
|
146
|
+
yes: bool = False
|
|
147
|
+
|
|
148
|
+
_install(_InstallArgs())
|
|
149
|
+
except Exception as exc:
|
|
150
|
+
console.print(f"[red]Installation failed: {exc}[/]")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _run_upgrade() -> None:
|
|
154
|
+
"""Upgrade tools — opencontext upgrade."""
|
|
155
|
+
console.print("\n[bold]Checking for updates...[/]")
|
|
156
|
+
handle_upgrade(
|
|
157
|
+
type(
|
|
158
|
+
"Args",
|
|
159
|
+
(),
|
|
160
|
+
{},
|
|
161
|
+
)()
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _run_sync() -> None:
|
|
166
|
+
"""Sync configs — opencontext sync."""
|
|
167
|
+
console.print("\n[bold]Syncing configs...[/]")
|
|
168
|
+
try:
|
|
169
|
+
from opencontext_cli.commands.sync_cmd import handle_sync
|
|
170
|
+
|
|
171
|
+
handle_sync(type("Args", (), {"sync_command": None})())
|
|
172
|
+
console.print("[green]✓ Configs synced[/]")
|
|
173
|
+
except Exception as exc:
|
|
174
|
+
console.print(f"[red]Sync failed: {exc}[/]")
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _run_upgrade_sync() -> None:
|
|
178
|
+
"""Upgrade tools and sync configs."""
|
|
179
|
+
_run_upgrade()
|
|
180
|
+
console.print()
|
|
181
|
+
_run_sync()
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _run_configure_models() -> None:
|
|
185
|
+
"""Configure models — opencontext config wizard."""
|
|
186
|
+
console.print("\n[bold]Model configuration[/]")
|
|
187
|
+
try:
|
|
188
|
+
from opencontext_core.wizard import run_wizard_menu
|
|
189
|
+
|
|
190
|
+
run_wizard_menu()
|
|
191
|
+
return # wizard has its own loop
|
|
192
|
+
except Exception:
|
|
193
|
+
pass
|
|
194
|
+
|
|
195
|
+
# Fallback: simple prompts
|
|
196
|
+
from opencontext_core.user_prefs import UserConfigStore
|
|
197
|
+
|
|
198
|
+
store = UserConfigStore()
|
|
199
|
+
prefs = store.load()
|
|
200
|
+
|
|
201
|
+
from rich.prompt import Prompt as RPrompt
|
|
202
|
+
|
|
203
|
+
console.print("\n[bold]Current model configuration:[/]")
|
|
204
|
+
console.print(f" Default provider: {prefs.default_provider}")
|
|
205
|
+
console.print(f" Default model: {prefs.default_model}")
|
|
206
|
+
console.print()
|
|
207
|
+
|
|
208
|
+
provider = RPrompt.ask("Default provider", default=prefs.default_provider or "mock")
|
|
209
|
+
model = RPrompt.ask("Default model", default=prefs.default_model or "mock-llm")
|
|
210
|
+
prefs.default_provider = provider
|
|
211
|
+
prefs.default_model = model
|
|
212
|
+
store.save(prefs)
|
|
213
|
+
console.print("[green]✓ Model configuration saved[/]")
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _run_create_agent() -> None:
|
|
217
|
+
"""Create your own Agent — opencontext agent init."""
|
|
218
|
+
console.print("\n[bold]Creating agent integration...[/]")
|
|
219
|
+
try:
|
|
220
|
+
from opencontext_cli.main import _agent
|
|
221
|
+
|
|
222
|
+
_agent(
|
|
223
|
+
type(
|
|
224
|
+
"Args",
|
|
225
|
+
(),
|
|
226
|
+
{
|
|
227
|
+
"agent_command": "init",
|
|
228
|
+
"target": "generic",
|
|
229
|
+
"root": ".",
|
|
230
|
+
"force": False,
|
|
231
|
+
},
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
console.print("[green]✓ Agent integration created[/]")
|
|
235
|
+
except Exception as exc:
|
|
236
|
+
console.print(f"[red]Agent creation failed: {exc}[/]")
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _run_plugins() -> None:
|
|
240
|
+
"""Browse plugins — opencontext plugin."""
|
|
241
|
+
console.print("\n[bold]OpenCode Community Plugins[/]")
|
|
242
|
+
try:
|
|
243
|
+
from opencontext_cli.commands.plugin_cmd import handle_plugin
|
|
244
|
+
|
|
245
|
+
handle_plugin(
|
|
246
|
+
type(
|
|
247
|
+
"Args",
|
|
248
|
+
(),
|
|
249
|
+
{
|
|
250
|
+
"plugin_command": "search",
|
|
251
|
+
},
|
|
252
|
+
)
|
|
253
|
+
)
|
|
254
|
+
except Exception as exc:
|
|
255
|
+
console.print(f"[red]Plugin search failed: {exc}[/]")
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _run_sdd_profiles() -> None:
|
|
259
|
+
"""Configure SDD profiles — opencontext config wizard."""
|
|
260
|
+
console.print("\n[bold]OpenCode SDD Profiles[/]")
|
|
261
|
+
try:
|
|
262
|
+
from opencontext_core.user_prefs import UserConfigStore
|
|
263
|
+
|
|
264
|
+
store = UserConfigStore()
|
|
265
|
+
prefs = store.load()
|
|
266
|
+
from rich.prompt import Prompt as RPrompt
|
|
267
|
+
|
|
268
|
+
console.print(f" Current SDD profile: {prefs.sdd.sdd_model_profile}")
|
|
269
|
+
console.print(f" Current TDD mode: {prefs.sdd.tdd_mode}")
|
|
270
|
+
console.print()
|
|
271
|
+
|
|
272
|
+
profile = RPrompt.ask(
|
|
273
|
+
"SDD model profile",
|
|
274
|
+
choices=["default", "cheap", "hybrid", "premium"],
|
|
275
|
+
default=prefs.sdd.sdd_model_profile or "hybrid",
|
|
276
|
+
)
|
|
277
|
+
tdd = RPrompt.ask(
|
|
278
|
+
"TDD mode",
|
|
279
|
+
choices=["ask", "strict", "off"],
|
|
280
|
+
default=prefs.sdd.tdd_mode or "ask",
|
|
281
|
+
)
|
|
282
|
+
prefs.sdd.sdd_model_profile = profile
|
|
283
|
+
prefs.sdd.tdd_mode = tdd
|
|
284
|
+
store.save(prefs)
|
|
285
|
+
console.print("[green]✓ SDD profiles updated[/]")
|
|
286
|
+
except Exception as exc:
|
|
287
|
+
console.print(f"[red]Failed: {exc}[/]")
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _run_backups() -> None:
|
|
291
|
+
"""Manage backups — opencontext config backup/restore/backups."""
|
|
292
|
+
console.print("\n[bold]Backup Management[/]")
|
|
293
|
+
|
|
294
|
+
while True:
|
|
295
|
+
try:
|
|
296
|
+
console.clear()
|
|
297
|
+
except Exception:
|
|
298
|
+
pass
|
|
299
|
+
console.print(
|
|
300
|
+
Panel(
|
|
301
|
+
"\n".join(
|
|
302
|
+
[
|
|
303
|
+
"[bold]Backup Management[/]",
|
|
304
|
+
"",
|
|
305
|
+
" [cyan]1[/] Create backup",
|
|
306
|
+
" [cyan]2[/] List backups",
|
|
307
|
+
" [cyan]3[/] Restore backup",
|
|
308
|
+
" [cyan]4[/] Cleanup old backups",
|
|
309
|
+
" [cyan]b[/] Back to main menu",
|
|
310
|
+
" [cyan]q[/] Quit",
|
|
311
|
+
]
|
|
312
|
+
),
|
|
313
|
+
border_style="yellow",
|
|
314
|
+
padding=(1, 2),
|
|
315
|
+
)
|
|
316
|
+
)
|
|
317
|
+
console.print()
|
|
318
|
+
choice = Prompt.ask(
|
|
319
|
+
"Select option",
|
|
320
|
+
choices=["1", "2", "3", "4", "b", "q"],
|
|
321
|
+
default="b",
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
if choice == "1":
|
|
325
|
+
_create_backup()
|
|
326
|
+
elif choice == "2":
|
|
327
|
+
_list_backups()
|
|
328
|
+
elif choice == "3":
|
|
329
|
+
_restore_backup()
|
|
330
|
+
elif choice == "4":
|
|
331
|
+
_cleanup_backups()
|
|
332
|
+
elif choice == "b":
|
|
333
|
+
break
|
|
334
|
+
elif choice == "q":
|
|
335
|
+
console.print("[dim]Goodbye.[/]")
|
|
336
|
+
sys.exit(0)
|
|
337
|
+
|
|
338
|
+
console.print("\n[dim]Press Enter to continue...[/]")
|
|
339
|
+
try:
|
|
340
|
+
input()
|
|
341
|
+
except (EOFError, KeyboardInterrupt):
|
|
342
|
+
break
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def _create_backup() -> None:
|
|
346
|
+
"""Create a config backup."""
|
|
347
|
+
try:
|
|
348
|
+
from opencontext_core.state import ConfigBackupManager
|
|
349
|
+
|
|
350
|
+
backup_id = ConfigBackupManager.create_backup(description="manual")
|
|
351
|
+
console.print(f"[green]✓ Backup created: {backup_id}[/]")
|
|
352
|
+
except Exception as exc:
|
|
353
|
+
console.print(f"[red]Backup failed: {exc}[/]")
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _list_backups() -> None:
|
|
357
|
+
"""List all config backups."""
|
|
358
|
+
try:
|
|
359
|
+
from opencontext_core.state import ConfigBackupManager
|
|
360
|
+
|
|
361
|
+
backups = ConfigBackupManager.list_backups()
|
|
362
|
+
if not backups:
|
|
363
|
+
console.print("[yellow]No backups found.[/]")
|
|
364
|
+
return
|
|
365
|
+
console.print()
|
|
366
|
+
for b in backups:
|
|
367
|
+
console.print(f" {b.id} ({b.timestamp}) — {b.description}")
|
|
368
|
+
console.print(f"\n {len(backups)} backup(s) available")
|
|
369
|
+
except Exception as exc:
|
|
370
|
+
console.print(f"[red]Failed to list backups: {exc}[/]")
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _restore_backup() -> None:
|
|
374
|
+
"""Restore from a backup."""
|
|
375
|
+
try:
|
|
376
|
+
from opencontext_core.state import ConfigBackupManager
|
|
377
|
+
|
|
378
|
+
backups = ConfigBackupManager.list_backups()
|
|
379
|
+
if not backups:
|
|
380
|
+
console.print("[yellow]No backups to restore.[/]")
|
|
381
|
+
return
|
|
382
|
+
|
|
383
|
+
from rich.prompt import Prompt as RPrompt
|
|
384
|
+
|
|
385
|
+
console.print("\n[bold]Available backups:[/]")
|
|
386
|
+
for i, b in enumerate(backups, 1):
|
|
387
|
+
console.print(f" {i}. {b.id} ({b.timestamp})")
|
|
388
|
+
idx = RPrompt.ask(
|
|
389
|
+
"Select backup to restore",
|
|
390
|
+
choices=[str(i) for i in range(1, len(backups) + 1)],
|
|
391
|
+
)
|
|
392
|
+
backup_id = backups[int(idx) - 1].id
|
|
393
|
+
if ConfigBackupManager.restore_backup(backup_id):
|
|
394
|
+
console.print(f"[green]✓ Restored from: {backup_id}[/]")
|
|
395
|
+
else:
|
|
396
|
+
console.print(f"[red]Backup not found: {backup_id}[/]")
|
|
397
|
+
except Exception as exc:
|
|
398
|
+
console.print(f"[red]Restore failed: {exc}[/]")
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _cleanup_backups() -> None:
|
|
402
|
+
"""Clean up old backups."""
|
|
403
|
+
import shutil
|
|
404
|
+
from datetime import datetime, timedelta
|
|
405
|
+
|
|
406
|
+
from rich.prompt import IntPrompt
|
|
407
|
+
|
|
408
|
+
from opencontext_core.state import ConfigBackupManager
|
|
409
|
+
|
|
410
|
+
days = IntPrompt.ask("Keep backups newer than (days)", default=30)
|
|
411
|
+
backups = ConfigBackupManager.list_backups()
|
|
412
|
+
cutoff = datetime.now() - timedelta(days=days)
|
|
413
|
+
removed = 0
|
|
414
|
+
for b in backups:
|
|
415
|
+
try:
|
|
416
|
+
ts = datetime.strptime(b.timestamp, "%Y%m%dT%H%M%S")
|
|
417
|
+
if ts < cutoff:
|
|
418
|
+
backup_dir = ConfigBackupManager.BACKUP_DIR / b.id
|
|
419
|
+
if backup_dir.exists():
|
|
420
|
+
shutil.rmtree(backup_dir)
|
|
421
|
+
removed += 1
|
|
422
|
+
except (ValueError, OSError):
|
|
423
|
+
continue
|
|
424
|
+
console.print(f"[green]✓ Removed {removed} backup(s) older than {days} days[/]")
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def _run_uninstall() -> None:
|
|
428
|
+
"""Managed uninstall — opencontext clean."""
|
|
429
|
+
console.print("\n[bold]Uninstall OpenContext[/]")
|
|
430
|
+
from rich.prompt import Confirm
|
|
431
|
+
|
|
432
|
+
if not Confirm.ask("Remove OpenContext configuration from this project?", default=False):
|
|
433
|
+
console.print("[yellow]Uninstall cancelled.[/]")
|
|
434
|
+
return
|
|
435
|
+
|
|
436
|
+
try:
|
|
437
|
+
from opencontext_cli.main import _clean
|
|
438
|
+
|
|
439
|
+
_clean(".", dry_run=False, force=False)
|
|
440
|
+
console.print("[green]✓ OpenContext configuration removed[/]")
|
|
441
|
+
except Exception as exc:
|
|
442
|
+
console.print(f"[red]Uninstall failed: {exc}[/]")
|
|
@@ -546,8 +546,8 @@ def _execute_plan(
|
|
|
546
546
|
except ValueError:
|
|
547
547
|
console.print(f"[yellow]⚠ Unknown project-local agent target: {selected_agent}[/]")
|
|
548
548
|
|
|
549
|
-
# Global client config (MCP + profile files) for selected clients,
|
|
550
|
-
#
|
|
549
|
+
# Global client config (MCP + profile files) for selected clients, using
|
|
550
|
+
# OpenContext's local knowledge graph.
|
|
551
551
|
if "mcp-server" in plan.components or "knowledge-graph" in plan.components:
|
|
552
552
|
global_targets = []
|
|
553
553
|
for selected_agent in agents:
|
|
@@ -70,17 +70,28 @@ def handle_update(args: Any) -> None:
|
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
def handle_upgrade(args: Any) -> None:
|
|
73
|
-
"""Check and upgrade."""
|
|
73
|
+
"""Check and upgrade all OpenContext packages."""
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
print()
|
|
76
|
+
print(" Checking for OpenContext updates...")
|
|
77
|
+
print()
|
|
78
|
+
|
|
79
|
+
results = UpdateChecker.upgrade_all()
|
|
80
|
+
|
|
81
|
+
upgraded = [r for r in results if r["status"] == "upgraded"]
|
|
82
|
+
failed = [r for r in results if r["status"] == "failed"]
|
|
79
83
|
|
|
80
|
-
print(f"
|
|
81
|
-
|
|
84
|
+
print(f" {'Package':<25} {'Status':<12} {'Message'}")
|
|
85
|
+
print(f" {'─' * 25} {'─' * 12} {'─' * 40}")
|
|
86
|
+
for r in results:
|
|
87
|
+
icon = "✓" if r["status"] == "upgraded" else "✗"
|
|
88
|
+
print(f" {r['package']:<25} {icon + ' ' + r['status']:<12} {r['message']}")
|
|
82
89
|
print()
|
|
83
|
-
print(f" {result['status']}: {result['message']}")
|
|
84
90
|
|
|
85
|
-
if
|
|
91
|
+
if upgraded:
|
|
92
|
+
print(f" ✓ {len(upgraded)} package(s) upgraded.")
|
|
93
|
+
if failed:
|
|
94
|
+
print(f" ✗ {len(failed)} package(s) failed.")
|
|
86
95
|
sys.exit(1)
|
|
96
|
+
if not upgraded and not failed:
|
|
97
|
+
print(" ✓ All packages are up to date.")
|
|
@@ -134,7 +134,17 @@ def _technology_template_names() -> tuple[str, ...]:
|
|
|
134
134
|
TECHNOLOGY_TEMPLATE_NAMES = _technology_template_names()
|
|
135
135
|
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
def _get_version() -> str:
|
|
138
|
+
"""Get installed version via importlib.metadata, with fallback."""
|
|
139
|
+
try:
|
|
140
|
+
import importlib.metadata
|
|
141
|
+
|
|
142
|
+
return importlib.metadata.version("opencontext-cli")
|
|
143
|
+
except (importlib.metadata.PackageNotFoundError, ImportError):
|
|
144
|
+
return "0.0.0"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
__version__ = _get_version()
|
|
138
148
|
|
|
139
149
|
|
|
140
150
|
def main() -> None:
|
|
@@ -242,7 +252,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
242
252
|
version=f"%(prog)s {__version__}",
|
|
243
253
|
help="Show version and exit.",
|
|
244
254
|
)
|
|
245
|
-
subparsers = parser.add_subparsers(dest="command"
|
|
255
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
246
256
|
|
|
247
257
|
init_parser = subparsers.add_parser("init", help="Create a default OpenContext configuration.")
|
|
248
258
|
init_parser.add_argument(
|
|
@@ -792,16 +802,43 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
792
802
|
return parser
|
|
793
803
|
|
|
794
804
|
|
|
805
|
+
_config_path_cache: str | None = None
|
|
806
|
+
|
|
807
|
+
|
|
795
808
|
def _default_config_path() -> str:
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
if
|
|
799
|
-
return
|
|
800
|
-
|
|
809
|
+
"""Find opencontext.yaml in current dir or parent dirs, up to 10 levels."""
|
|
810
|
+
global _config_path_cache
|
|
811
|
+
if _config_path_cache is not None:
|
|
812
|
+
return _config_path_cache
|
|
813
|
+
|
|
814
|
+
candidates = ("opencontext.yaml", "configs/opencontext.yaml")
|
|
815
|
+
current = Path.cwd().resolve()
|
|
816
|
+
for _ in range(10):
|
|
817
|
+
for candidate in candidates:
|
|
818
|
+
path = current / candidate
|
|
819
|
+
if path.exists():
|
|
820
|
+
result = str(path)
|
|
821
|
+
_config_path_cache = result
|
|
822
|
+
return result
|
|
823
|
+
parent = current.parent
|
|
824
|
+
if parent == current:
|
|
825
|
+
break
|
|
826
|
+
current = parent
|
|
827
|
+
|
|
828
|
+
_config_path_cache = "opencontext.yaml"
|
|
829
|
+
return _config_path_cache
|
|
801
830
|
|
|
802
831
|
|
|
803
832
|
def _dispatch(args: argparse.Namespace) -> None:
|
|
804
|
-
command = args
|
|
833
|
+
command = getattr(args, "command", None)
|
|
834
|
+
|
|
835
|
+
if command is None:
|
|
836
|
+
# No command — launch the main TUI menu
|
|
837
|
+
from opencontext_cli.commands.menu_cmd import run_main_menu
|
|
838
|
+
|
|
839
|
+
run_main_menu()
|
|
840
|
+
return
|
|
841
|
+
|
|
805
842
|
if command == "init":
|
|
806
843
|
_init(args.config, args.template)
|
|
807
844
|
return
|
|
@@ -1081,10 +1118,23 @@ def _install(args: argparse.Namespace) -> None:
|
|
|
1081
1118
|
|
|
1082
1119
|
root = Path(args.root)
|
|
1083
1120
|
|
|
1121
|
+
# Check if already set up
|
|
1122
|
+
already_setup = (root / ".opencontext").exists() and (
|
|
1123
|
+
root / ".opencontext" / "sdd" / "context.json"
|
|
1124
|
+
).exists()
|
|
1125
|
+
|
|
1084
1126
|
console.header("OpenContext Install")
|
|
1085
1127
|
console.print("Detecting your project...")
|
|
1086
1128
|
console.print()
|
|
1087
1129
|
|
|
1130
|
+
if already_setup and not args.yes:
|
|
1131
|
+
console.print("[dim]OpenContext already configured for this project.[/]")
|
|
1132
|
+
proceed = Confirm.ask("Re-run setup?", default=False)
|
|
1133
|
+
if not proceed:
|
|
1134
|
+
console.print("[green]Nothing to do. Your project is ready.[/]")
|
|
1135
|
+
console.print(" Run [cyan]opencontext pack . --query 'Explain this'[/] to start.")
|
|
1136
|
+
return
|
|
1137
|
+
|
|
1088
1138
|
# Quick project detection (lightweight — no full index needed)
|
|
1089
1139
|
has_config = (root / "opencontext.yaml").exists()
|
|
1090
1140
|
has_git = (root / ".git").exists()
|
|
@@ -1117,7 +1167,7 @@ def _install(args: argparse.Namespace) -> None:
|
|
|
1117
1167
|
console.print()
|
|
1118
1168
|
|
|
1119
1169
|
if not args.yes:
|
|
1120
|
-
proceed = Confirm.ask("Proceed with setup?", default=
|
|
1170
|
+
proceed = Confirm.ask("Proceed with setup?", default=not already_setup)
|
|
1121
1171
|
if not proceed:
|
|
1122
1172
|
console.print("[yellow]Setup cancelled.[/]")
|
|
1123
1173
|
return
|
|
@@ -16,6 +16,7 @@ opencontext_cli/commands/config_cmd.py
|
|
|
16
16
|
opencontext_cli/commands/git_cmd.py
|
|
17
17
|
opencontext_cli/commands/hints_cmd.py
|
|
18
18
|
opencontext_cli/commands/kg_cmd.py
|
|
19
|
+
opencontext_cli/commands/menu_cmd.py
|
|
19
20
|
opencontext_cli/commands/plugin_cmd.py
|
|
20
21
|
opencontext_cli/commands/setup_cmd.py
|
|
21
22
|
opencontext_cli/commands/sync_cmd.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{opencontext_cli-0.3.0 → opencontext_cli-0.4.0b0}/opencontext_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|