omega-code 0.4.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 (73) hide show
  1. omega/__init__.py +0 -0
  2. omega/__main__.py +589 -0
  3. omega/artifacts.py +151 -0
  4. omega/checkpoint.py +246 -0
  5. omega/compact.py +106 -0
  6. omega/config.py +285 -0
  7. omega/eval/__init__.py +3 -0
  8. omega/eval/cli.py +127 -0
  9. omega/eval/examples/plan-version-flag.yaml +11 -0
  10. omega/eval/examples/relative-age-negative-delta.yaml +14 -0
  11. omega/eval/examples/version-flag.yaml +10 -0
  12. omega/eval/manifest.py +129 -0
  13. omega/eval/prices.py +29 -0
  14. omega/eval/report.py +135 -0
  15. omega/eval/runner.py +199 -0
  16. omega/eval/tasks.py +97 -0
  17. omega/events.py +145 -0
  18. omega/export.py +80 -0
  19. omega/gitlog.py +229 -0
  20. omega/hooks.py +63 -0
  21. omega/instructions.py +103 -0
  22. omega/integrations.py +284 -0
  23. omega/keys.py +173 -0
  24. omega/llm.py +442 -0
  25. omega/loop.py +510 -0
  26. omega/mcp.py +490 -0
  27. omega/memory/__init__.py +5 -0
  28. omega/memory/consolidate.py +103 -0
  29. omega/memory/curate.py +69 -0
  30. omega/memory/store.py +321 -0
  31. omega/memory/tools.py +175 -0
  32. omega/migrate.py +40 -0
  33. omega/onboarding.py +242 -0
  34. omega/permissions.py +137 -0
  35. omega/secrets.py +173 -0
  36. omega/server/__init__.py +7 -0
  37. omega/server/__main__.py +18 -0
  38. omega/server/app.py +71 -0
  39. omega/server/auth.py +73 -0
  40. omega/server/manager.py +287 -0
  41. omega/server/models.py +123 -0
  42. omega/server/tasks_api.py +311 -0
  43. omega/server/terminals.py +245 -0
  44. omega/server/worker.py +186 -0
  45. omega/session.py +209 -0
  46. omega/setup.html +281 -0
  47. omega/setup_server.py +452 -0
  48. omega/skills.py +158 -0
  49. omega/subagent.py +98 -0
  50. omega/tasks.py +195 -0
  51. omega/tools.py +590 -0
  52. omega/trace.py +156 -0
  53. omega/trajectory.py +146 -0
  54. omega/ui/__init__.py +0 -0
  55. omega/ui/composer.py +140 -0
  56. omega/ui/format.py +708 -0
  57. omega/ui/plain.py +141 -0
  58. omega/ui/tui/__init__.py +9 -0
  59. omega/ui/tui/app.py +958 -0
  60. omega/ui/tui/history.py +50 -0
  61. omega/ui/tui/modals.py +292 -0
  62. omega/ui/tui/onboarding.py +367 -0
  63. omega/ui/tui/prefs.py +25 -0
  64. omega/ui/tui/sidebar.py +510 -0
  65. omega/ui/tui/status.py +115 -0
  66. omega/ui/tui/theme.py +91 -0
  67. omega/ui/tui/transcript.py +783 -0
  68. omega/verify.py +133 -0
  69. omega_code-0.4.0.dist-info/METADATA +479 -0
  70. omega_code-0.4.0.dist-info/RECORD +73 -0
  71. omega_code-0.4.0.dist-info/WHEEL +4 -0
  72. omega_code-0.4.0.dist-info/entry_points.txt +2 -0
  73. omega_code-0.4.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,367 @@
1
+ """The Textual onboarding wizard: one Screen per step, pushed forward as the
2
+ user completes each one. All provider/catalog/config logic lives in
3
+ `omega/onboarding.py`; this module only renders it and reads the answers back
4
+ into `OnboardingApp`'s state."""
5
+ from __future__ import annotations
6
+
7
+ import asyncio
8
+ import os
9
+ import time
10
+ from typing import Any, cast
11
+
12
+ from textual.app import App, ComposeResult
13
+ from textual.binding import Binding
14
+ from textual.containers import VerticalScroll
15
+ from textual.screen import Screen
16
+ from textual.widgets import Input, OptionList, Static
17
+ from textual.widgets.option_list import Option
18
+
19
+ from ... import config, events, loop, onboarding
20
+ from .. import format
21
+ from . import theme
22
+ from .status import SPINNER_FRAMES
23
+
24
+ Body = dict[str, Any]
25
+
26
+ TOTAL_STEPS = 6
27
+
28
+
29
+ class StepIndicator(Static):
30
+ def __init__(self, step: int) -> None:
31
+ dots = " ".join("●" if i < step else "○" for i in range(TOTAL_STEPS))
32
+ super().__init__(f"[dim]{dots}[/dim]")
33
+
34
+
35
+ class _Spinner(Static):
36
+ """The part-A spinner (same frames/tempo as the status bar), reused here
37
+ for key validation and the live probe turn."""
38
+
39
+ def __init__(self, label: str) -> None:
40
+ super().__init__("")
41
+ self._label = label
42
+ self._frame = 0
43
+ self._stopped = False
44
+ self._timer: Any = None
45
+
46
+ def on_mount(self) -> None:
47
+ self._tick()
48
+ if not self._stopped:
49
+ self._timer = self.set_interval(0.08, self._tick)
50
+
51
+ def _tick(self) -> None:
52
+ if self._stopped:
53
+ return
54
+ glyph = SPINNER_FRAMES[self._frame % len(SPINNER_FRAMES)]
55
+ self._frame += 1
56
+ self.update(f"[dim]{glyph} {self._label}[/dim]")
57
+
58
+ def stop(self, final: str = "") -> None:
59
+ self._stopped = True
60
+ if self._timer is not None:
61
+ self._timer.stop()
62
+ self.update(final)
63
+
64
+
65
+ def _base_screen_css(title: str) -> str:
66
+ return f"""
67
+ {title} {{ align: center middle; }}
68
+ {title} #panel {{ width: 76; height: auto; padding: 1 2; }}
69
+ """
70
+
71
+
72
+ class WizardScreen(Screen[None]):
73
+ """Shared escape-to-quit behavior for every step."""
74
+
75
+ BINDINGS = [Binding("escape", "quit_wizard", show=False)]
76
+
77
+ def action_quit_wizard(self) -> None:
78
+ cast(OnboardingApp, self.app).exit(False)
79
+
80
+
81
+ class WelcomeScreen(WizardScreen):
82
+ DEFAULT_CSS = _base_screen_css("WelcomeScreen")
83
+ BINDINGS = [*WizardScreen.BINDINGS, Binding("enter", "continue_step", show=False),
84
+ Binding("q", "quit_wizard", show=False)]
85
+
86
+ def compose(self) -> ComposeResult:
87
+ with VerticalScroll(id="panel"):
88
+ yield StepIndicator(1)
89
+ yield Static("[bold]omega[/bold]")
90
+ yield Static("A fast, small coding agent for your terminal. Bring your own models.")
91
+ yield Static("Three quick steps: provider → key → model. "
92
+ "Then we run one real turn to prove it works.")
93
+ yield Static("[dim]enter to continue · q/esc to quit[/dim]")
94
+
95
+ def action_continue_step(self) -> None:
96
+ self.app.push_screen(ProviderScreen())
97
+
98
+
99
+ class ProviderScreen(WizardScreen):
100
+ DEFAULT_CSS = _base_screen_css("ProviderScreen")
101
+
102
+ def compose(self) -> ComposeResult:
103
+ with VerticalScroll(id="panel"):
104
+ yield StepIndicator(2)
105
+ yield Static("[bold]Provider[/bold]")
106
+ yield OptionList(*self._options(), id="providers")
107
+ yield Static("[red]base URL must start with http:// or https://[/red]", id="url-error")
108
+ yield Input(placeholder="https://api.example.com/v1", id="base-url")
109
+
110
+ def on_mount(self) -> None:
111
+ self.query_one("#url-error", Static).display = False
112
+ self.query_one("#base-url", Input).display = False
113
+ options = self.query_one("#providers", OptionList)
114
+ options.focus()
115
+ options.highlighted = self._env_preselect()
116
+
117
+ def _env_preselect(self) -> int:
118
+ for i, (_key, _label, _desc, env) in enumerate(onboarding.PROVIDER_INFO):
119
+ if env and os.environ.get(env):
120
+ return i
121
+ return 0
122
+
123
+ def _options(self) -> list[Option]:
124
+ rendered = []
125
+ for key, label, desc, env in onboarding.PROVIDER_INFO:
126
+ found = " [green]· key found in environment[/green]" if env and os.environ.get(env) else ""
127
+ rendered.append(Option(f"{label}{found}\n[dim]{desc}[/dim]", id=key))
128
+ return rendered
129
+
130
+ def on_option_list_option_selected(self, message: OptionList.OptionSelected) -> None:
131
+ key = message.option_id
132
+ if key == "other":
133
+ self.query_one("#base-url", Input).display = True
134
+ self.query_one("#base-url", Input).focus()
135
+ return
136
+ app = cast(OnboardingApp, self.app)
137
+ app.choice = onboarding.choice_for(key or "anthropic")
138
+ app.push_screen(KeyScreen())
139
+
140
+ def on_input_submitted(self, message: Input.Submitted) -> None:
141
+ url = message.value.strip()
142
+ if not (url.startswith("http://") or url.startswith("https://")):
143
+ self.query_one("#url-error", Static).display = True
144
+ return
145
+ app = cast(OnboardingApp, self.app)
146
+ app.choice = onboarding.choice_for("other", url)
147
+ app.push_screen(KeyScreen())
148
+
149
+
150
+ class KeyScreen(WizardScreen):
151
+ DEFAULT_CSS = _base_screen_css("KeyScreen")
152
+
153
+ def compose(self) -> ComposeResult:
154
+ with VerticalScroll(id="panel"):
155
+ yield StepIndicator(3)
156
+ yield Static("[bold]API key[/bold]")
157
+ yield Static("", id="key-hint")
158
+ yield Input(password=True, id="key-input")
159
+ yield Static("", id="key-result")
160
+
161
+ def on_mount(self) -> None:
162
+ app = cast(OnboardingApp, self.app)
163
+ choice = app.choice
164
+ assert choice is not None
165
+ env = onboarding.env_var_for(choice.provider_key)
166
+ env_val = os.environ.get(env) if env else ""
167
+ self._env_key = env_val or None
168
+ if self._env_key:
169
+ self.query_one("#key-hint", Static).update(
170
+ f"[dim]•••• from ${env} — enter to use, or paste a different one[/dim]")
171
+ self.query_one("#key-input", Input).focus()
172
+
173
+ def on_input_submitted(self, message: Input.Submitted) -> None:
174
+ key = message.value.strip() or (self._env_key or "")
175
+ if not key:
176
+ self.query_one("#key-result", Static).update("[red]enter a key[/red]")
177
+ return
178
+ self.query_one("#key-result", Static).update("")
179
+ self.run_worker(self._validate(key), exclusive=True, thread=False)
180
+
181
+ async def _validate(self, key: str) -> None:
182
+ app = cast(OnboardingApp, self.app)
183
+ result = self.query_one("#key-result", Static)
184
+ spinner = _Spinner("waiting")
185
+ await result.remove()
186
+ panel = self.query_one("#panel", VerticalScroll)
187
+ panel.mount(spinner)
188
+ assert app.choice is not None
189
+ ok, msg = await onboarding.validate_key(app.choice, key)
190
+ if ok:
191
+ spinner.stop("[green]✓ connected[/green]")
192
+ app.choice.provider["apiKey"] = key
193
+ app.push_screen(ModelScreen())
194
+ else:
195
+ spinner.stop(f"[red]✗ {msg[:120]}[/red]")
196
+ panel.mount(Static("", id="key-result"))
197
+
198
+
199
+ class ModelScreen(WizardScreen):
200
+ DEFAULT_CSS = _base_screen_css("ModelScreen")
201
+
202
+ def compose(self) -> ComposeResult:
203
+ app = cast(OnboardingApp, self.app)
204
+ choice = app.choice
205
+ assert choice is not None
206
+ with VerticalScroll(id="panel"):
207
+ yield StepIndicator(4)
208
+ yield Static("[bold]Model[/bold]")
209
+ if choice.catalog:
210
+ yield OptionList(*self._options(choice), id="models")
211
+ yield Static(f"[dim]Background roles (subagents, compaction, memory) will use "
212
+ f"{choice.cheap_alias} automatically — change any of this later "
213
+ f"with /model or `omega setup`.[/dim]")
214
+ else:
215
+ yield Input(placeholder="model id served by this endpoint", id="model-id")
216
+
217
+ def on_mount(self) -> None:
218
+ app = cast(OnboardingApp, self.app)
219
+ choice = app.choice
220
+ assert choice is not None
221
+ if choice.catalog:
222
+ options = self.query_one("#models", OptionList)
223
+ options.focus()
224
+ aliases = sorted(choice.catalog)
225
+ options.highlighted = aliases.index(choice.default_alias)
226
+ else:
227
+ self.query_one("#model-id", Input).focus()
228
+
229
+ def _options(self, choice: onboarding._Choice) -> list[Option]:
230
+ rendered = []
231
+ for alias in sorted(choice.catalog):
232
+ m = choice.catalog[alias]
233
+ price_in, price_out = onboarding.PRICES.get(alias, (0.0, 0.0))
234
+ purpose = onboarding.PURPOSES.get(alias, "")
235
+ text = (f"{alias:<11} {m['model']:<26} {m['context']:>10,} "
236
+ f"${price_in:g}/${price_out:g} per M\n[dim]{purpose}[/dim]")
237
+ rendered.append(Option(text, id=alias))
238
+ return rendered
239
+
240
+ def on_option_list_option_selected(self, message: OptionList.OptionSelected) -> None:
241
+ app = cast(OnboardingApp, self.app)
242
+ assert app.choice is not None
243
+ app.main_alias = message.option_id or app.choice.default_alias
244
+ app.cheap_alias = app.choice.cheap_alias
245
+ app.models = dict(app.choice.catalog)
246
+ app.push_screen(ProveScreen())
247
+
248
+ def on_input_submitted(self, message: Input.Submitted) -> None:
249
+ model_id = message.value.strip()
250
+ if not model_id:
251
+ return
252
+ app = cast(OnboardingApp, self.app)
253
+ assert app.choice is not None
254
+ app.main_alias = app.cheap_alias = "main-model"
255
+ app.models = {"main-model": {"model": model_id, "provider": app.choice.provider_key,
256
+ "context": 128000}}
257
+ app.push_screen(ProveScreen())
258
+
259
+
260
+ class ProveScreen(WizardScreen):
261
+ DEFAULT_CSS = _base_screen_css("ProveScreen") + """
262
+ ProveScreen #panel { width: 90; }
263
+ ProveScreen #prove-log { height: auto; max-height: 16; }
264
+ """
265
+ BINDINGS = [*WizardScreen.BINDINGS, Binding("r", "retry", show=False),
266
+ Binding("enter", "continue_anyway", show=False)]
267
+
268
+ def compose(self) -> ComposeResult:
269
+ with VerticalScroll(id="panel"):
270
+ yield StepIndicator(5)
271
+ yield Static("[bold]Prove it[/bold]")
272
+ yield VerticalScroll(id="prove-log")
273
+ yield Static("", id="prove-hint")
274
+
275
+ def on_mount(self) -> None:
276
+ self._start()
277
+
278
+ def _start(self) -> None:
279
+ self.query_one("#prove-hint", Static).update("")
280
+ log = self.query_one("#prove-log", VerticalScroll)
281
+ log.remove_children()
282
+ app = cast(OnboardingApp, self.app)
283
+ assert app.choice is not None
284
+ raw = onboarding.build_config(app.choice, app.main_alias, app.cheap_alias, app.models)
285
+ onboarding.save_config(raw)
286
+ self.run_worker(self._probe(), exclusive=True, thread=False)
287
+
288
+ async def _probe(self) -> None:
289
+ log = self.query_one("#prove-log", VerticalScroll)
290
+ spinner = _Spinner("waiting")
291
+ log.mount(spinner)
292
+ started = time.monotonic()
293
+ tool_calls = 0
294
+ tokens_used = 0
295
+ state: dict[str, Any] = {"text_widget": None, "live_text": ""}
296
+
297
+ def emit(ev: events.Event) -> None:
298
+ nonlocal tool_calls, tokens_used
299
+ if isinstance(ev, events.Phase):
300
+ spinner._label = ev.state
301
+ elif isinstance(ev, events.ToolStart):
302
+ tool_calls += 1
303
+ log.mount(Static(format.tool_start(ev)))
304
+ elif isinstance(ev, events.TextDelta):
305
+ if state["text_widget"] is None:
306
+ widget = Static("")
307
+ log.mount(widget)
308
+ state["text_widget"] = widget
309
+ state["live_text"] += ev.text
310
+ cast(Static, state["text_widget"]).update(cast(str, state["live_text"]))
311
+ elif isinstance(ev, events.Usage):
312
+ tokens_used = ev.used
313
+
314
+ try:
315
+ cfg = config.load()
316
+ history: list[dict[str, Any]] = [{"role": "user", "content": onboarding.PROBE_PROMPT}]
317
+ await loop.run_agent(cfg, "main", loop.BUILD_SYSTEM, history, emit=emit)
318
+ elapsed = time.monotonic() - started
319
+ spinner.stop(f"[green]✓ working — {tool_calls} tool calls · "
320
+ f"{tokens_used} tokens · {elapsed:.0f}s[/green]")
321
+ await asyncio.sleep(0.6)
322
+ cast(OnboardingApp, self.app).push_screen(DoneScreen())
323
+ except Exception as e:
324
+ spinner.stop(f"[red]✗ {type(e).__name__}: {e}[/red]"[:200])
325
+ self.query_one("#prove-hint", Static).update(
326
+ "[dim]config is saved; press r to retry or enter to continue anyway[/dim]")
327
+
328
+ def action_retry(self) -> None:
329
+ self._start()
330
+
331
+ def action_continue_anyway(self) -> None:
332
+ cast(OnboardingApp, self.app).push_screen(DoneScreen())
333
+
334
+
335
+ class DoneScreen(WizardScreen):
336
+ DEFAULT_CSS = _base_screen_css("DoneScreen")
337
+ BINDINGS = [*WizardScreen.BINDINGS, Binding("enter", "finish", show=False)]
338
+
339
+ CHEAT_SHEET = (
340
+ "omega open the TUI\n"
341
+ "omega \"fix the test\" one-shot\n"
342
+ "/model or ctrl+o switch models /plan · /build switch modes\n"
343
+ "omega setup full browser setup: more providers, roles, MCP servers")
344
+
345
+ def compose(self) -> ComposeResult:
346
+ with VerticalScroll(id="panel"):
347
+ yield StepIndicator(6)
348
+ yield Static("[bold]You're set up[/bold]")
349
+ yield Static(self.CHEAT_SHEET)
350
+ yield Static("[dim]enter to start[/dim]")
351
+
352
+ def action_finish(self) -> None:
353
+ cast(OnboardingApp, self.app).exit(True)
354
+
355
+
356
+ class OnboardingApp(App[bool]):
357
+ def __init__(self) -> None:
358
+ super().__init__()
359
+ self.register_theme(theme.SYSTEM_THEME)
360
+ self.theme = theme.SYSTEM_THEME.name
361
+ self.choice: onboarding._Choice | None = None
362
+ self.main_alias = ""
363
+ self.cheap_alias = ""
364
+ self.models: Body = {}
365
+
366
+ def on_mount(self) -> None:
367
+ self.push_screen(WelcomeScreen())
omega/ui/tui/prefs.py ADDED
@@ -0,0 +1,25 @@
1
+ """Per-machine TUI preferences persisted at ~/.omega/ui.json -- separate from
2
+ ~/.omega/config.json, which holds provider/model/role configuration, not UI
3
+ state like whether the side panel is open."""
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ import os
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ PATH = Path(os.environ.get("OMEGA_UI_PREFS", Path.home() / ".omega" / "ui.json"))
12
+
13
+
14
+ def load() -> dict[str, Any]:
15
+ if not PATH.exists():
16
+ return {}
17
+ try:
18
+ return dict(json.loads(PATH.read_text()))
19
+ except (json.JSONDecodeError, OSError):
20
+ return {}
21
+
22
+
23
+ def save(data: dict[str, Any]) -> None:
24
+ PATH.parent.mkdir(parents=True, exist_ok=True)
25
+ PATH.write_text(json.dumps(data, indent=2) + "\n")