@agentikos/omega-os 0.19.25 → 0.19.27

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.
@@ -188,7 +188,7 @@ from omega_engine.genesis import (
188
188
  )
189
189
  from omega_engine import plan as plan_v7
190
190
 
191
- __version__ = "0.19.25"
191
+ __version__ = "0.19.27"
192
192
 
193
193
  __all__ = [
194
194
  "__version__",
@@ -2856,15 +2856,27 @@ def cmd_menu(_args: argparse.Namespace) -> int:
2856
2856
 
2857
2857
 
2858
2858
  def cmd_menu_tui(_args: argparse.Namespace) -> int:
2859
- """`omega menu-tui` — the fzf action picker that runs INSIDE the
2860
- Omega master tmux session. Don't invoke this directly; use bare
2861
- `omega` which spawns the Omega session and lands you here.
2862
-
2863
- Loops until you pick Quit or hit Esc. Each action either:
2864
- - Spawns a new tmux window in the Omega session (AISB chat,
2865
- Hermes chat, scratch shell)
2866
- - Runs a sub-command inline (status, doctor, vault list)
2867
- - Opens a sub-menu (switch provider, manage account)
2859
+ """`omega menu-tui` — the OmegaOS Textual TUI (v0.19.27+).
2860
+
2861
+ Modern boxed-layout TUI with slash commands (Hermès-style), sidebar
2862
+ categories, scrollable content log, footer keybindings. Falls back
2863
+ to a plain slash REPL if `textual` isn't installed (e.g. headless
2864
+ or before the engine venv finishes installing deps).
2865
+
2866
+ Delegates to `omega_engine.tui.run_tui()` which encapsulates both
2867
+ the Textual app and the plain REPL fallback.
2868
+ """
2869
+ from omega_engine.tui import run_tui
2870
+ return run_tui()
2871
+
2872
+
2873
+ def _legacy_fzf_menu(_args: argparse.Namespace) -> int:
2874
+ """v0.19.26 fzf menu — kept as fallback. Reachable via `omega menu-fzf`.
2875
+
2876
+ Actions run INLINE in the menu pane (not in dead background tmux
2877
+ windows). After every action, "press Enter to return to menu".
2878
+ Sessions (AISB/Hermes) still spawn as separate tmux sessions because
2879
+ they're meant to be talked to long-running.
2868
2880
  """
2869
2881
  import os
2870
2882
  import shlex
@@ -2881,102 +2893,101 @@ def cmd_menu_tui(_args: argparse.Namespace) -> int:
2881
2893
  "(macOS) or `apt install fzf` (Linux)")
2882
2894
  return 2
2883
2895
 
2884
- # The menu actions. Each entry: (label, action_key).
2885
- # Actions are dispatched in the if/elif block below.
2886
2896
  def _build_menu() -> list[tuple[str, str]]:
2887
2897
  active = _active_provider()
2888
2898
  sessions = tmux.list_sessions(HOME)
2889
- items: list[tuple[str, str]] = [
2890
- ("CHAT", "__separator__"),
2891
- (f" AISB master chat (Claude Max OAuth)", "open:aisb"),
2892
- (f" Hermès meta-companion (Anthropic API)", "open:hermes"),
2893
- (f" Switch LLM provider ({active})", "switch:provider"),
2894
- ("PROJECTS", "__separator__"),
2895
- (f" New project (Genesis pipeline)", "genesis:new"),
2896
- (f" Open project shell", "project:open"),
2897
- ("AUDITS & MISSIONS", "__separator__"),
2898
- (f" Run a mission ({active})", "run:mission"),
2899
- (f" Quality Arsenal — pick an audit", "audit:menu"),
2900
- ("INFRASTRUCTURE", "__separator__"),
2901
- (f" Sessions ({len(sessions)} active) — pick to attach", "sessions:list"),
2902
- (f" Accounts — Claude Max + per-provider auth", "accounts:menu"),
2903
- (f" Vault — secrets", "vault:menu"),
2904
- ("HEALTH", "__separator__"),
2905
- (f" omega doctor", "cmd:doctor"),
2906
- (f" omega status", "cmd:status"),
2907
- ("SCRAPE & SEARCH", "__separator__"),
2908
- (f" Stealth scrape a URL (CloakBrowser)", "scrape:cloak"),
2909
- (f" Fast scrape a URL (Scrapling)", "scrape:scrapling"),
2910
- ("", "__separator__"),
2911
- (f" Detach (Ctrl-b d also works)", "detach"),
2912
- (f" Quit Omega session (kills the tmux session)", "quit:kill"),
2899
+ return [
2900
+ ("── CHAT ─────────────────────", "__sep__"),
2901
+ (f"AISB master chat (Claude Max OAuth)", "open:aisb"),
2902
+ (f"Hermès meta-companion (Anthropic API)", "open:hermes"),
2903
+ (f"Switch LLM provider [{active}]", "switch:provider"),
2904
+ ("── PROJECTS ─────────────────", "__sep__"),
2905
+ (f"New project (Genesis pipeline)", "genesis:new"),
2906
+ (f"Open project shell", "project:open"),
2907
+ ("── AUDITS & MISSIONS ────────", "__sep__"),
2908
+ (f"Run a mission", "run:mission"),
2909
+ (f"Quality Arsenal — list audits", "audit:menu"),
2910
+ ("── INFRASTRUCTURE ───────────", "__sep__"),
2911
+ (f"Sessions ({len(sessions)} active) — pick to attach", "sessions:list"),
2912
+ (f"Accounts — Claude Max + per-provider auth", "accounts:menu"),
2913
+ (f"Vault — secrets", "vault:menu"),
2914
+ ("── HEALTH ───────────────────", "__sep__"),
2915
+ (f"omega doctor", "cmd:doctor"),
2916
+ (f"omega status", "cmd:status"),
2917
+ ("── SCRAPE & SEARCH ──────────", "__sep__"),
2918
+ (f"Stealth scrape a URL (CloakBrowser)", "scrape:cloak"),
2919
+ (f"Fast scrape a URL (Scrapling)", "scrape:scrapling"),
2920
+ ("── EXIT ─────────────────────", "__sep__"),
2921
+ (f"Detach (Ctrl-b d also works)", "detach"),
2922
+ (f"Quit Omega session (kills the tmux session)", "quit:kill"),
2913
2923
  ]
2914
- return items
2915
-
2916
- def _send_in_window(window: str, command: str) -> None:
2917
- """Open (or focus) a window in the Omega session and run command."""
2918
- _, list_out = tmux._tmux("list-windows", "-t", "Omega", "-F", "#W")
2919
- windows = list_out.splitlines() if list_out else []
2920
- if window not in windows:
2921
- tmux._tmux("new-window", "-t", "Omega", "-n", window,
2922
- "-c", str(Path.home()))
2923
- tmux._tmux("send-keys", "-t", f"Omega:{window}", command, "Enter")
2924
+
2925
+ def _run_inline(cmd_argv: list[str] | str, *, shell: bool = False) -> None:
2926
+ """Run a command in the current pane, stream stdout/stderr,
2927
+ then prompt the user to return to the menu."""
2928
+ os.system("clear")
2929
+ print(f" Ω running: {cmd_argv if shell else ' '.join(cmd_argv)}")
2930
+ print(" " + "─" * 60)
2931
+ if shell:
2932
+ subprocess.run(cmd_argv, shell=True)
2924
2933
  else:
2925
- tmux._tmux("send-keys", "-t", f"Omega:{window}", command, "Enter")
2926
- tmux._tmux("select-window", "-t", f"Omega:{window}")
2934
+ subprocess.run(cmd_argv)
2935
+ print()
2936
+ print(" " + "─" * 60)
2937
+ try:
2938
+ input(" press Enter to return to Omega menu… ")
2939
+ except (EOFError, KeyboardInterrupt):
2940
+ pass
2941
+
2942
+ def _prompt(label: str) -> str:
2943
+ try:
2944
+ return input(f" {label}: ").strip()
2945
+ except (EOFError, KeyboardInterrupt):
2946
+ return ""
2927
2947
 
2928
- print(f"\n Omega v{__version__} — type a number to refresh, Ctrl-c to exit.\n")
2929
2948
  while True:
2930
2949
  items = _build_menu()
2931
- # Build fzf input separators are unselectable category headers.
2932
- lines = []
2933
- for label, key in items:
2934
- if key == "__separator__":
2935
- # Visual section header line.
2936
- lines.append(f"── {label} " + "─" * max(0, 50 - len(label)))
2937
- else:
2938
- lines.append(label)
2950
+ lines = [label for label, _ in items]
2939
2951
  fzf_input = "\n".join(lines)
2940
2952
  try:
2941
2953
  proc = subprocess.run(
2942
2954
  ["fzf",
2943
2955
  "--prompt=Ω › ",
2944
- "--header=Omega Session Manager — Enter to pick, Esc to refresh",
2956
+ "--header=Omega — Enter to pick, Esc to refresh, "
2957
+ "Ctrl-c to exit",
2945
2958
  "--layout=reverse",
2946
2959
  "--height=100%",
2947
2960
  "--border=rounded",
2948
2961
  "--no-multi",
2949
- "--info=inline"],
2962
+ "--info=inline",
2963
+ "--ansi"],
2950
2964
  input=fzf_input, capture_output=True, text=True,
2951
2965
  )
2952
2966
  except KeyboardInterrupt:
2953
2967
  return 0
2954
2968
  if proc.returncode != 0:
2955
- # Esc / Ctrl-c → refresh the menu rather than exit immediately.
2956
- # User picks "Quit" or "Detach" explicitly.
2957
- try:
2958
- input("\n (Esc pressed press Enter to refresh, Ctrl-c to quit)\n > ")
2959
- continue
2960
- except (EOFError, KeyboardInterrupt):
2961
- return 0
2962
- pick = proc.stdout.strip()
2969
+ # Esc → refresh.
2970
+ continue
2971
+ # FIX (v0.19.26): match label EXACTLY, no strip — fzf returns the
2972
+ # full line including any chars we put in. Just trim trailing \n.
2973
+ pick = proc.stdout.rstrip("\n")
2963
2974
  action = None
2964
2975
  for label, key in items:
2965
- display = (f"── {label} " + "" * max(0, 50 - len(label))
2966
- if key == "__separator__" else label)
2967
- if display == pick and key != "__separator__":
2976
+ if label == pick and key != "__sep__":
2968
2977
  action = key
2969
2978
  break
2970
2979
  if action is None:
2971
2980
  continue
2972
2981
 
2973
- # Dispatch.
2982
+ # === Dispatch ===
2974
2983
  if action == "detach":
2975
2984
  subprocess.run(["tmux", "detach-client"])
2976
2985
  return 0
2977
2986
  if action == "quit:kill":
2978
2987
  subprocess.run(["tmux", "kill-session", "-t", "Omega"])
2979
2988
  return 0
2989
+
2990
+ # Chat sessions — switch tmux client to the chat session.
2980
2991
  if action == "open:aisb":
2981
2992
  tmux.spawn_aisb_chat(HOME, force_replace=False)
2982
2993
  subprocess.run(["tmux", "switch-client", "-t", "AISB-chat"])
@@ -2985,57 +2996,62 @@ def cmd_menu_tui(_args: argparse.Namespace) -> int:
2985
2996
  tmux.spawn_hermes_chat(HOME, force_replace=False)
2986
2997
  subprocess.run(["tmux", "switch-client", "-t", "Hermes-chat"])
2987
2998
  continue
2999
+
3000
+ # Inline actions — run in the menu pane, prompt to return.
2988
3001
  if action == "switch:provider":
2989
- _send_in_window("switch", f"{shlex.quote(OMEGA_BIN)} switch")
3002
+ _run_inline([OMEGA_BIN, "switch"])
2990
3003
  continue
2991
3004
  if action == "genesis:new":
2992
- _send_in_window("genesis",
2993
- f"{shlex.quote(OMEGA_BIN)} genesis questions")
3005
+ slug = _prompt("project slug (alphanumeric, _-)")
3006
+ if slug:
3007
+ _run_inline([OMEGA_BIN, "genesis", "new", slug])
2994
3008
  continue
2995
3009
  if action == "project:open":
2996
- _send_in_window("project", "ls -la ~/Omega/Agentik_Coding/projects/")
3010
+ _run_inline("ls -la ~/Omega/Agentik_Coding/projects/ 2>/dev/null "
3011
+ "&& echo && echo '(cd into one to start working)'",
3012
+ shell=True)
2997
3013
  continue
2998
3014
  if action == "run:mission":
2999
- _send_in_window("mission",
3000
- f"echo 'usage: omega run \"<intent>\"' && "
3001
- f"echo 'examples: omega run \"fix all linear feedbacks\"'")
3015
+ intent = _prompt("mission intent (free-form)")
3016
+ if intent:
3017
+ _run_inline([OMEGA_BIN, "run", intent])
3002
3018
  continue
3003
3019
  if action == "audit:menu":
3004
- _send_in_window("audit",
3005
- f"echo 'Quality Arsenal — invoke an audit:' && "
3006
- f"echo ' /codeaudit /uiuxaudit /flowaudit /debugaudit' && "
3007
- f"echo ' /featureaudit /perfaudit /secaudit /a11yaudit' && "
3008
- f"echo ' /seoaudit /dataaudit /apiaudit /copyaudit' && "
3009
- f"echo ' /dxaudit /motionaudit /automationaudit' && "
3010
- f"echo ' /logicaudit /retentionaudit /refontaudit' && "
3011
- f"echo 'or: omega audit <id>'")
3020
+ _run_inline(
3021
+ "echo 'Quality Arsenal — 17 forensic audits:' && "
3022
+ "echo ' /codeaudit /uiuxaudit /flowaudit /debugaudit' && "
3023
+ "echo ' /featureaudit /perfaudit /secaudit /a11yaudit' && "
3024
+ "echo ' /seoaudit /dataaudit /apiaudit /copyaudit' && "
3025
+ "echo ' /dxaudit /motionaudit /automationaudit' && "
3026
+ "echo ' /logicaudit /retentionaudit /refontaudit' && "
3027
+ "echo && echo 'invoke from any claude session, or:' && "
3028
+ f"echo ' {OMEGA_BIN} audit <id>'",
3029
+ shell=True)
3012
3030
  continue
3013
3031
  if action == "sessions:list":
3014
- _send_in_window("sessions", "tmux ls")
3032
+ _run_inline("tmux ls", shell=True)
3015
3033
  continue
3016
3034
  if action == "accounts:menu":
3017
- _send_in_window("accounts",
3018
- f"{shlex.quote(OMEGA_BIN)} account list")
3035
+ _run_inline([OMEGA_BIN, "account", "list"])
3019
3036
  continue
3020
3037
  if action == "vault:menu":
3021
- _send_in_window("vault",
3022
- f"{shlex.quote(OMEGA_BIN)} vault status")
3038
+ _run_inline([OMEGA_BIN, "vault", "status"])
3023
3039
  continue
3024
3040
  if action == "cmd:doctor":
3025
- _send_in_window("doctor", f"{shlex.quote(OMEGA_BIN)} doctor")
3041
+ _run_inline([OMEGA_BIN, "doctor"])
3026
3042
  continue
3027
3043
  if action == "cmd:status":
3028
- _send_in_window("status", f"{shlex.quote(OMEGA_BIN)} status")
3044
+ _run_inline([OMEGA_BIN, "status"])
3029
3045
  continue
3030
3046
  if action == "scrape:cloak":
3031
- _send_in_window("scrape",
3032
- "read -p 'URL: ' u && "
3033
- f"{shlex.quote(OMEGA_BIN)} scrape \"$u\"")
3047
+ url = _prompt("URL")
3048
+ if url:
3049
+ _run_inline([OMEGA_BIN, "scrape", url])
3034
3050
  continue
3035
3051
  if action == "scrape:scrapling":
3036
- _send_in_window("scrape",
3037
- "read -p 'URL: ' u && "
3038
- f"{shlex.quote(OMEGA_BIN)} scrape \"$u\" --engine scrapling")
3052
+ url = _prompt("URL")
3053
+ if url:
3054
+ _run_inline([OMEGA_BIN, "scrape", url, "--engine", "scrapling"])
3039
3055
  continue
3040
3056
 
3041
3057
 
@@ -4328,9 +4344,12 @@ def _build_parser() -> argparse.ArgumentParser:
4328
4344
  help="open the interactive whiptail menu (legacy)"
4329
4345
  ).set_defaults(fn=cmd_menu_whiptail)
4330
4346
  sub.add_parser("menu-tui",
4331
- help="the fzf action picker that runs INSIDE the Omega tmux "
4332
- "session (don't call directly — bare `omega` lands you here)"
4347
+ help="the Textual TUI inside the Omega tmux session "
4348
+ "(don't call directly — bare `omega` lands you here)"
4333
4349
  ).set_defaults(fn=cmd_menu_tui)
4350
+ sub.add_parser("menu-fzf",
4351
+ help="legacy v0.19.26 fzf menu fallback"
4352
+ ).set_defaults(fn=_legacy_fzf_menu)
4334
4353
  sub.add_parser("version", help="print the engine version").set_defaults(fn=cmd_version)
4335
4354
 
4336
4355
  # `omega switch <provider>` — hot-swap the LLM CLI used for new chats.
@@ -0,0 +1,457 @@
1
+ """OmegaOS interactive Textual TUI — the v0.19.27 redesign.
2
+
3
+ User: "l'objectif c'est que ça fonctionne comme
4
+ https://www.agentik-os.com/services/agentik-coding-workflow ! avec notre
5
+ nouveau system omegaOS qui est noramlement une refont de tout ca ! et
6
+ aussi pour ta reponse - le TUI mets tout en place pour OmegaOS"
7
+
8
+ Architecture mirror of the Agentik Coding Workflow v2.5.1 doc:
9
+
10
+ - 4-level hierarchy visible (Human → AISB → Oracle → Worker)
11
+ - Telegram + terminal-native (no web dashboard)
12
+ - Terminal entry = this TUI
13
+ - Sessions exposed via tmux; TUI is the OmegaOS control surface
14
+
15
+ Layout (Textual, mouse + keyboard, slash-command driven like Hermès):
16
+
17
+ ┌── Ω Omega OS v0.19.27 • provider: claude_code • sessions: 3 ──┐
18
+ │ CATEGORY │ CONTENT │
19
+ │ ──────── │ ─────── │
20
+ │ ▶ Chat │ (selected category's content here │
21
+ │ Projects │ — sessions list, audit menu, │
22
+ │ Audits │ doctor output, etc.) │
23
+ │ Sessions │ │
24
+ │ Accounts │ │
25
+ │ Vault │ │
26
+ │ Health │ │
27
+ │ Scrape │ │
28
+ │ │ │
29
+ ├────────────────────────────┴─────────────────────────────────────┤
30
+ │ Ω › /<slash command> │
31
+ ├──────────────────────────────────────────────────────────────────┤
32
+ │ q quit • d detach • ? help • / focus slash input │
33
+ └──────────────────────────────────────────────────────────────────┘
34
+
35
+ Slash commands (Hermès-style):
36
+ /chat aisb /chat hermes /aisb /hermes
37
+ /switch <provider> /switch (show current)
38
+ /audit <id> /mission "<intent>" /scrape <url>
39
+ /genesis <slug> /doctor /status
40
+ /vault [<key>] /accounts /sessions
41
+ /quit /detach /help
42
+ """
43
+ from __future__ import annotations
44
+
45
+ import os
46
+ import subprocess
47
+ import shlex
48
+ from pathlib import Path
49
+ from typing import Any
50
+
51
+ try:
52
+ from textual.app import App, ComposeResult
53
+ from textual.widgets import (
54
+ Header, Footer, Input, RichLog, ListView, ListItem, Label,
55
+ )
56
+ from textual.containers import Horizontal, Vertical
57
+ from textual.binding import Binding
58
+ TEXTUAL_AVAILABLE = True
59
+ except ImportError:
60
+ TEXTUAL_AVAILABLE = False
61
+
62
+
63
+ # ---------------------------------------------------------------------------
64
+ # Sidebar category map
65
+ # ---------------------------------------------------------------------------
66
+
67
+ CATEGORIES: list[tuple[str, str]] = [
68
+ ("chat", "Chat — AISB & Hermès"),
69
+ ("projects", "Projects — Genesis & shells"),
70
+ ("audits", "Audits — Quality Arsenal (17 forensic)"),
71
+ ("missions", "Missions — run with verified completion"),
72
+ ("sessions", "Sessions — live tmux sessions"),
73
+ ("accounts", "Accounts — Claude Max + provider auth"),
74
+ ("vault", "Vault — encrypted secrets"),
75
+ ("health", "Health — doctor + status"),
76
+ ("scrape", "Scrape — CloakBrowser + Scrapling"),
77
+ ]
78
+
79
+
80
+ # ---------------------------------------------------------------------------
81
+ # Slash command dispatcher
82
+ # ---------------------------------------------------------------------------
83
+
84
+
85
+ class CommandResult:
86
+ """A command's effect — text to display + optional side-effects."""
87
+
88
+ def __init__(self, *, text: str = "", session_jump: str = "",
89
+ exit_tui: bool = False, detach: bool = False) -> None:
90
+ self.text = text
91
+ self.session_jump = session_jump
92
+ self.exit_tui = exit_tui
93
+ self.detach = detach
94
+
95
+
96
+ def _omega_bin() -> str:
97
+ home = Path(os.environ.get("OMEGA_HOME", str(Path.home() / "Omega")))
98
+ return str(home / "Agentik_Tools" / "bin" / "omega")
99
+
100
+
101
+ def _run_omega(*args: str) -> str:
102
+ """Shell out to the omega CLI and capture its stdout/stderr."""
103
+ try:
104
+ proc = subprocess.run([_omega_bin(), *args],
105
+ capture_output=True, text=True, timeout=120)
106
+ return (proc.stdout + proc.stderr).rstrip()
107
+ except subprocess.SubprocessError as exc:
108
+ return f" error: {exc}"
109
+
110
+
111
+ def _switch_client(target: str) -> None:
112
+ subprocess.run(["tmux", "switch-client", "-t", target],
113
+ capture_output=True)
114
+
115
+
116
+ def dispatch_slash(line: str) -> CommandResult:
117
+ """Parse a slash command and return the effect to apply."""
118
+ line = line.strip()
119
+ if not line:
120
+ return CommandResult(text="(empty)")
121
+ # Allow both "/foo bar" and "foo bar" inputs.
122
+ if line.startswith("/"):
123
+ line = line[1:]
124
+ try:
125
+ argv = shlex.split(line)
126
+ except ValueError as exc:
127
+ return CommandResult(text=f" parse error: {exc}")
128
+ if not argv:
129
+ return CommandResult(text="(empty)")
130
+ cmd, *rest = argv
131
+
132
+ # Quit / detach
133
+ if cmd in ("quit", "q", "exit"):
134
+ return CommandResult(exit_tui=True)
135
+ if cmd in ("detach", "d"):
136
+ return CommandResult(detach=True)
137
+
138
+ # Chat sessions
139
+ if cmd == "aisb" or (cmd == "chat" and rest and rest[0] == "aisb"):
140
+ from omega_engine import tmux as _tx
141
+ _tx.spawn_aisb_chat(Path(os.environ.get(
142
+ "OMEGA_HOME", str(Path.home() / "Omega"))))
143
+ return CommandResult(
144
+ text=" AISB-chat spawned — switching client…",
145
+ session_jump="AISB-chat",
146
+ )
147
+ if cmd == "hermes" or (cmd == "chat" and rest and rest[0] == "hermes"):
148
+ from omega_engine import tmux as _tx
149
+ _tx.spawn_hermes_chat(Path(os.environ.get(
150
+ "OMEGA_HOME", str(Path.home() / "Omega"))))
151
+ return CommandResult(
152
+ text=" Hermes-chat spawned — switching client…",
153
+ session_jump="Hermes-chat",
154
+ )
155
+
156
+ # Provider hot-swap
157
+ if cmd == "switch":
158
+ if rest:
159
+ return CommandResult(text=_run_omega("switch", rest[0]))
160
+ return CommandResult(text=_run_omega("switch"))
161
+
162
+ # Audits + missions
163
+ if cmd == "audit":
164
+ if rest:
165
+ return CommandResult(text=_run_omega("audit", *rest))
166
+ return CommandResult(text=(
167
+ " Quality Arsenal — 17 forensic audits:\n"
168
+ " /codeaudit /uiuxaudit /flowaudit /debugaudit\n"
169
+ " /featureaudit /perfaudit /secaudit /a11yaudit\n"
170
+ " /seoaudit /dataaudit /apiaudit /copyaudit\n"
171
+ " /dxaudit /motionaudit /automationaudit\n"
172
+ " /logicaudit /retentionaudit /refontaudit\n"
173
+ " invoke from any claude session, or:\n"
174
+ f" omega audit <id>"
175
+ ))
176
+ if cmd in ("mission", "run"):
177
+ if not rest:
178
+ return CommandResult(text=" usage: /mission \"<intent>\"")
179
+ return CommandResult(text=_run_omega("run", " ".join(rest)))
180
+
181
+ # Genesis (new project)
182
+ if cmd == "genesis":
183
+ if not rest:
184
+ return CommandResult(text=_run_omega("genesis", "questions"))
185
+ return CommandResult(text=_run_omega("genesis", "new", rest[0]))
186
+
187
+ # Vault
188
+ if cmd == "vault":
189
+ if not rest:
190
+ return CommandResult(text=_run_omega("vault", "status"))
191
+ return CommandResult(text=_run_omega("vault", *rest))
192
+
193
+ # Accounts + sessions
194
+ if cmd == "accounts":
195
+ return CommandResult(text=_run_omega("account", "list"))
196
+ if cmd == "sessions":
197
+ try:
198
+ proc = subprocess.run(["tmux", "ls"],
199
+ capture_output=True, text=True, timeout=10)
200
+ return CommandResult(text=proc.stdout + proc.stderr)
201
+ except subprocess.SubprocessError as exc:
202
+ return CommandResult(text=f" error: {exc}")
203
+
204
+ # Health
205
+ if cmd == "doctor":
206
+ return CommandResult(text=_run_omega("doctor"))
207
+ if cmd == "status":
208
+ return CommandResult(text=_run_omega("status"))
209
+
210
+ # Scrape
211
+ if cmd == "scrape":
212
+ if not rest:
213
+ return CommandResult(text=" usage: /scrape <url> [--engine scrapling]")
214
+ return CommandResult(text=_run_omega("scrape", *rest))
215
+
216
+ # Help
217
+ if cmd in ("help", "?"):
218
+ return CommandResult(text=(
219
+ " SLASH COMMANDS\n"
220
+ " ──────────────\n"
221
+ " /aisb switch to AISB master chat (Max OAuth)\n"
222
+ " /hermes switch to Hermès chat (Anthropic API)\n"
223
+ " /switch <provider> hot-swap LLM provider (Hermes-style)\n"
224
+ " /mission \"<intent>\" run a verified-completion mission\n"
225
+ " /audit <id> run a forensic audit (codeaudit, …)\n"
226
+ " /genesis <slug> start a new project via Genesis pipeline\n"
227
+ " /scrape <url> stealth scrape (CloakBrowser default)\n"
228
+ " /scrape <url> --engine scrapling fast scrape (Scrapling)\n"
229
+ " /sessions list active tmux sessions\n"
230
+ " /accounts list Claude Max account pool\n"
231
+ " /vault [key] vault status or read key\n"
232
+ " /doctor full health check (omega doctor)\n"
233
+ " /status task state (omega status)\n"
234
+ " /detach detach from Omega tmux session\n"
235
+ " /quit quit the TUI (kill Omega session)\n"
236
+ " /help show this help\n"
237
+ "\n"
238
+ " KEYBINDINGS\n"
239
+ " ───────────\n"
240
+ " / focus the slash input\n"
241
+ " ? this help\n"
242
+ " d detach\n"
243
+ " q quit\n"
244
+ " Tab navigate categories"
245
+ ))
246
+
247
+ return CommandResult(text=(
248
+ f" unknown command: /{cmd}\n"
249
+ f" type /help for the list of commands"
250
+ ))
251
+
252
+
253
+ # ---------------------------------------------------------------------------
254
+ # The Textual app
255
+ # ---------------------------------------------------------------------------
256
+
257
+
258
+ if TEXTUAL_AVAILABLE:
259
+
260
+ class OmegaTUI(App):
261
+ """OmegaOS Textual TUI — the v0.19.27 control surface."""
262
+
263
+ CSS = """
264
+ Screen {
265
+ background: $background;
266
+ }
267
+ #sidebar {
268
+ width: 32;
269
+ border-right: tall $primary;
270
+ padding: 1 1;
271
+ }
272
+ #content {
273
+ padding: 1 2;
274
+ }
275
+ #slash {
276
+ border-top: tall $primary;
277
+ background: $surface;
278
+ }
279
+ Header {
280
+ background: $primary;
281
+ color: $text;
282
+ }
283
+ ListView > ListItem.--highlight {
284
+ background: $primary;
285
+ color: $text;
286
+ }
287
+ """
288
+
289
+ BINDINGS = [
290
+ Binding("q", "quit_omega", "Quit"),
291
+ Binding("d", "detach_omega", "Detach"),
292
+ Binding("question_mark", "help_omega", "Help"),
293
+ Binding("slash", "focus_slash", "Slash"),
294
+ ]
295
+
296
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
297
+ super().__init__(*args, **kwargs)
298
+ self._content: RichLog | None = None
299
+ self._slash: Input | None = None
300
+
301
+ def compose(self) -> ComposeResult:
302
+ from omega_engine import __version__
303
+ yield Header(name=f"Ω Omega OS v{__version__}", show_clock=True)
304
+ with Horizontal():
305
+ with Vertical(id="sidebar"):
306
+ yield Label("Categories", id="sidebar-title")
307
+ items = [ListItem(Label(name)) for _id, name in CATEGORIES]
308
+ yield ListView(*items, id="cats")
309
+ with Vertical(id="content-wrap"):
310
+ log = RichLog(id="content", highlight=True, markup=True,
311
+ wrap=True)
312
+ self._content = log
313
+ yield log
314
+ self._slash = Input(placeholder="Ω › type a slash command "
315
+ "(/help for list, /quit to exit)",
316
+ id="slash")
317
+ yield self._slash
318
+ yield Footer()
319
+
320
+ def on_mount(self) -> None:
321
+ self._write_welcome()
322
+ if self._slash:
323
+ self._slash.focus()
324
+
325
+ def _write_welcome(self) -> None:
326
+ if not self._content:
327
+ return
328
+ from omega_engine import __version__
329
+ self._content.write(
330
+ f"[bold #D97757]Ω Omega OS v{__version__}[/]\n"
331
+ "Type /help to see all slash commands.\n"
332
+ "\n"
333
+ "Quick start:\n"
334
+ " [bold]/aisb[/] chat with the AISB master agent\n"
335
+ " [bold]/hermes[/] chat with Hermès (Anthropic API)\n"
336
+ " [bold]/switch[/] see + hot-swap LLM provider\n"
337
+ " [bold]/mission \"...\"[/] run a verified-completion mission\n"
338
+ " [bold]/scrape <url>[/] stealth scrape via CloakBrowser\n"
339
+ " [bold]/audit <id>[/] run a Quality Arsenal audit\n"
340
+ " [bold]/doctor[/] full health check\n"
341
+ "\n"
342
+ "Click a category in the sidebar, or type a / command.\n"
343
+ )
344
+
345
+ def action_quit_omega(self) -> None:
346
+ subprocess.run(["tmux", "kill-session", "-t", "Omega"],
347
+ capture_output=True)
348
+ self.exit()
349
+
350
+ def action_detach_omega(self) -> None:
351
+ subprocess.run(["tmux", "detach-client"], capture_output=True)
352
+ self.exit()
353
+
354
+ def action_help_omega(self) -> None:
355
+ res = dispatch_slash("/help")
356
+ if self._content:
357
+ self._content.write(res.text)
358
+
359
+ def action_focus_slash(self) -> None:
360
+ if self._slash:
361
+ self._slash.focus()
362
+
363
+ def on_list_view_selected(self, event: Any) -> None:
364
+ """User clicked a sidebar category — render that category's content."""
365
+ idx = event.list_view.index or 0
366
+ if idx >= len(CATEGORIES):
367
+ return
368
+ cat_id = CATEGORIES[idx][0]
369
+ self._render_category(cat_id)
370
+
371
+ def _render_category(self, cat_id: str) -> None:
372
+ if not self._content:
373
+ return
374
+ mapping = {
375
+ "chat": "/help", # show overall help — chat actions in help
376
+ "projects": "/genesis",
377
+ "audits": "/audit",
378
+ "missions": "/help",
379
+ "sessions": "/sessions",
380
+ "accounts": "/accounts",
381
+ "vault": "/vault",
382
+ "health": "/doctor",
383
+ "scrape": "/help",
384
+ }
385
+ cmd = mapping.get(cat_id, "/help")
386
+ res = dispatch_slash(cmd)
387
+ self._content.write(f"\n[bold #D97757]── {cat_id} ──[/]\n")
388
+ self._content.write(res.text)
389
+
390
+ def on_input_submitted(self, event: Any) -> None:
391
+ """User typed a slash command."""
392
+ value = event.value
393
+ if not value:
394
+ return
395
+ if self._content:
396
+ self._content.write(f"\n[bold #D97757]Ω ›[/] {value}\n")
397
+ res = dispatch_slash(value)
398
+ if event.input:
399
+ event.input.value = ""
400
+ if res.exit_tui:
401
+ self.action_quit_omega()
402
+ return
403
+ if res.detach:
404
+ self.action_detach_omega()
405
+ return
406
+ if res.text and self._content:
407
+ self._content.write(res.text)
408
+ if res.session_jump:
409
+ _switch_client(res.session_jump)
410
+
411
+
412
+ def run_tui() -> int:
413
+ """Entry point — `omega menu-tui` calls this."""
414
+ if not TEXTUAL_AVAILABLE:
415
+ print(" textual not installed — `omega upgrade` or run:")
416
+ print(" pip install textual")
417
+ print()
418
+ print(" Falling back to plain prompt mode.")
419
+ return _plain_repl()
420
+ app = OmegaTUI()
421
+ app.run()
422
+ return 0
423
+
424
+
425
+ # ---------------------------------------------------------------------------
426
+ # Plain REPL fallback (when textual isn't installed)
427
+ # ---------------------------------------------------------------------------
428
+
429
+
430
+ def _plain_repl() -> int:
431
+ """Slash-command REPL with no textual TUI — same command set."""
432
+ from omega_engine import __version__
433
+ print(f"\n Ω Omega OS v{__version__} — plain REPL mode")
434
+ print(" Type /help for commands, /quit to exit.\n")
435
+ while True:
436
+ try:
437
+ line = input(" Ω › ").strip()
438
+ except (EOFError, KeyboardInterrupt):
439
+ print()
440
+ return 0
441
+ if not line:
442
+ continue
443
+ res = dispatch_slash(line)
444
+ if res.exit_tui:
445
+ subprocess.run(["tmux", "kill-session", "-t", "Omega"],
446
+ capture_output=True)
447
+ return 0
448
+ if res.detach:
449
+ subprocess.run(["tmux", "detach-client"], capture_output=True)
450
+ return 0
451
+ if res.text:
452
+ print()
453
+ for ln in res.text.splitlines():
454
+ print(f" {ln}")
455
+ print()
456
+ if res.session_jump:
457
+ _switch_client(res.session_jump)
@@ -1,12 +1,13 @@
1
1
  [project]
2
2
  name = "omega-engine"
3
- version = "0.19.25"
3
+ version = "0.19.27"
4
4
  description = "The Omega OS orchestration engine — event-sourced, verified-completion agent graphs."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
7
7
  license = { text = "MIT" }
8
8
  dependencies = [
9
9
  "pyyaml>=6.0",
10
+ "textual>=1.0",
10
11
  ]
11
12
 
12
13
  [project.optional-dependencies]
@@ -1 +1 @@
1
- 0.19.25
1
+ 0.19.27
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentikos/omega-os",
3
- "version": "0.19.25",
3
+ "version": "0.19.27",
4
4
  "description": "Omega OS — installable agentic operating system with verified-completion orchestration. Event-sourced engine, 8-block rack, autonomous agents, MCP.",
5
5
  "bin": {
6
6
  "omega-os": "bin/omega-os.js"