@agentikos/omega-os 0.19.23 → 0.19.25

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.
@@ -564,6 +564,15 @@ for entry in missing:
564
564
  if shutil.which(binary):
565
565
  print(f" ok {cli_id:<14} (installed via {attempt})")
566
566
  installed_now.append(cli_id)
567
+ # Optional post-install command (e.g. `scrapling install`
568
+ # downloads its browser deps after pip install).
569
+ post = entry.get("post_install") or []
570
+ if post and shutil.which(post[0]):
571
+ rc2, _ = _run(post, timeout=600)
572
+ if rc2 == 0:
573
+ print(f" post-install: {' '.join(post)} ok")
574
+ else:
575
+ print(f" post-install: {' '.join(post)} rc={rc2} (non-fatal)")
567
576
  else:
568
577
  print(f" fail {cli_id}: {attempt} returned rc={rc}")
569
578
  failed_now.append(cli_id)
@@ -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.23"
191
+ __version__ = "0.19.25"
192
192
 
193
193
  __all__ = [
194
194
  "__version__",
@@ -2798,145 +2798,249 @@ def _session_age(unix_ts: int) -> str:
2798
2798
 
2799
2799
 
2800
2800
  def cmd_menu(_args: argparse.Namespace) -> int:
2801
- """`omega` (no args) — open the OmegaOS session manager.
2802
-
2803
- v0.19.19 redesign per the user's spec: bare `omega` no longer
2804
- auto-attaches to AISB-chat. Instead it shows a fzf-driven menu:
2805
-
2806
- ┌─ Omega — Session Manager ──────────────────┐
2807
- > AISB-chat now │
2808
- │ Hermes-chat 5m ago │
2809
- │ Causio-oracle 2h ago │
2810
- ───────────────────────────────────────── │
2811
- + New Claude Code session (AISB master) │
2812
- + New Hermès session │
2813
- └────────────────────────────────────────────┘
2814
-
2815
- Pick a session attach. Pick "+ New …" → spawn + attach. Esc / q
2816
- exit cleanly.
2817
-
2818
- Direct shortcuts kept:
2819
- * `omega aisb` → straight to AISB-chat (no menu)
2820
- * `omega hermes` → straight to Hermes-chat (no menu)
2821
- * `omega menu` → legacy whiptail picker (advanced ops)
2801
+ """`omega` (no args) — attach to the persistent Omega master tmux session.
2802
+
2803
+ v0.19.25 redesign per the user's spec: bare `omega` should feel like
2804
+ typing `claude` (opens the Claude Code TUI) or `hermes` (opens
2805
+ Hermès). It should TAKE OVER your terminal as the OmegaOS home.
2806
+
2807
+ Mechanism (tmux-based, since OmegaOS is multi-session by design):
2808
+ 1. Ensure the `Omega` tmux session exists (spawn if missing).
2809
+ The session's window 0 runs `omega menu-tui` — the fzf-driven
2810
+ action picker.
2811
+ 2. `os.execvp("tmux attach -t Omega")` your terminal now IS
2812
+ the Omega session. Detach with Ctrl-b d, attach again with
2813
+ `omega`.
2814
+
2815
+ Inside the Omega session you have:
2816
+ * window 0 (menu) — the action picker
2817
+ * spawned on demand: AISB-chat, Hermes-chat, projects, scratch shells
2818
+ * Ctrl-b w — tmux's own window picker
2819
+ * Ctrl-b z tmux-claude session manager (if installed)
2820
+
2821
+ If you're already inside tmux (e.g. iTerm tmux integration), we
2822
+ don't take over the current session — we tell you how to switch
2823
+ to the Omega session manually.
2822
2824
  """
2823
2825
  import os
2824
2826
  import shutil
2825
- import subprocess
2826
2827
  from omega_engine import tmux
2827
2828
 
2828
- # If we're already inside a tmux session, instruct the user — we can't
2829
- # take over the current pane from a subprocess, but `omega aisb` /
2830
- # `omega hermes` from inside tmux still print switch-client instructions.
2831
- if os.environ.get("TMUX"):
2832
- print(" You're already inside tmux. Use Ctrl+b z (tmux-claude")
2833
- print(" session manager) or one of:")
2834
- print(" tmux switch-client -t AISB-chat")
2835
- print(" tmux switch-client -t Hermes-chat")
2836
- return 0
2829
+ OMEGA_SESSION = "Omega"
2837
2830
 
2838
2831
  if not shutil.which("tmux"):
2839
2832
  print(" tmux not installed — `brew install tmux` (macOS) or "
2840
2833
  "`apt-get install tmux` (Linux)")
2841
2834
  return 2
2842
2835
 
2843
- sessions = tmux.list_sessions(_omega_home())
2844
- sessions.sort(key=lambda s: -int(s.created))
2845
-
2846
- NEW_AISB = "__new_aisb__"
2847
- NEW_HERMES = "__new_hermes__"
2848
-
2849
- lines: list[tuple[str, str]] = [] # (display, action_key)
2850
- if sessions:
2851
- for s in sessions:
2852
- attach_mark = " *" if s.attached else " "
2853
- line = f"{attach_mark} {s.name:<32} {_session_age(s.created):<10} {s.category}"
2854
- lines.append((line, s.name))
2855
- lines.append(("──────────────────────────────────────────────",
2856
- "__separator__"))
2857
- lines.append((" + New Claude Code session (AISB master)", NEW_AISB))
2858
- lines.append((" + New Hermès session", NEW_HERMES))
2859
-
2860
- chosen_key: str | None = None
2861
- if shutil.which("fzf"):
2862
- # fzf path — prettiest UX. Header shows the title, separator line
2863
- # is non-selectable (we filter it after).
2864
- fzf_input = "\n".join(disp for disp, _ in lines)
2836
+ if os.environ.get("TMUX"):
2837
+ # We're already in a tmux session. Don't try to attach (tmux
2838
+ # refuses nested attach). Tell the user how to switch.
2839
+ if not tmux.is_alive(OMEGA_SESSION):
2840
+ tmux.spawn_omega_master(_omega_home())
2841
+ print(f" Omega session is running. Switch to it with:")
2842
+ print(f" tmux switch-client -t {OMEGA_SESSION}")
2843
+ print(f" Or use Ctrl-b s to pick from the session list,")
2844
+ print(f" or Ctrl-b z for the tmux-claude session manager.")
2845
+ return 0
2846
+
2847
+ if not tmux.is_alive(OMEGA_SESSION):
2848
+ tmux.spawn_omega_master(_omega_home())
2849
+
2850
+ try:
2851
+ os.execvp("tmux", ["tmux", "attach", "-t", OMEGA_SESSION])
2852
+ except FileNotFoundError:
2853
+ print(" tmux not on PATH")
2854
+ return 2
2855
+ return 0
2856
+
2857
+
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)
2868
+ """
2869
+ import os
2870
+ import shlex
2871
+ import shutil
2872
+ import subprocess
2873
+ from omega_engine import tmux
2874
+ from omega_engine import __version__
2875
+
2876
+ HOME = _omega_home()
2877
+ OMEGA_BIN = str(HOME / "Agentik_Tools" / "bin" / "omega")
2878
+
2879
+ if not shutil.which("fzf"):
2880
+ print(" fzf not installed — install with `brew install fzf` "
2881
+ "(macOS) or `apt install fzf` (Linux)")
2882
+ return 2
2883
+
2884
+ # The menu actions. Each entry: (label, action_key).
2885
+ # Actions are dispatched in the if/elif block below.
2886
+ def _build_menu() -> list[tuple[str, str]]:
2887
+ active = _active_provider()
2888
+ 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"),
2913
+ ]
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
+ else:
2925
+ tmux._tmux("send-keys", "-t", f"Omega:{window}", command, "Enter")
2926
+ tmux._tmux("select-window", "-t", f"Omega:{window}")
2927
+
2928
+ print(f"\n Omega v{__version__} — type a number to refresh, Ctrl-c to exit.\n")
2929
+ while True:
2930
+ 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)
2939
+ fzf_input = "\n".join(lines)
2865
2940
  try:
2866
2941
  proc = subprocess.run(
2867
2942
  ["fzf",
2868
- "--prompt=omega › ",
2869
- "--header=Omega Session Manager — Enter to attach, Esc to quit",
2943
+ "--prompt › ",
2944
+ "--header=Omega Session Manager — Enter to pick, Esc to refresh",
2870
2945
  "--layout=reverse",
2871
- "--height=70%",
2872
- "--border",
2873
- "--no-multi"],
2946
+ "--height=100%",
2947
+ "--border=rounded",
2948
+ "--no-multi",
2949
+ "--info=inline"],
2874
2950
  input=fzf_input, capture_output=True, text=True,
2875
2951
  )
2876
- if proc.returncode == 0:
2877
- pick = proc.stdout.strip()
2878
- for disp, key in lines:
2879
- if disp == pick and key != "__separator__":
2880
- chosen_key = key
2881
- break
2882
- except Exception as exc: # noqa: BLE001
2883
- print(f" fzf failed: {exc}")
2884
- return 2
2885
- else:
2886
- # Plain stdin fallback — numbered list.
2887
- print()
2888
- print(" Omega Session Manager")
2889
- for i, (disp, _) in enumerate(lines, 1):
2890
- print(f" {i:2}) {disp}")
2891
- print()
2892
- try:
2893
- raw = input(" pick a number (Enter to quit): ").strip()
2894
- except (EOFError, KeyboardInterrupt):
2952
+ except KeyboardInterrupt:
2895
2953
  return 0
2896
- if not raw:
2954
+ 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()
2963
+ action = None
2964
+ 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__":
2968
+ action = key
2969
+ break
2970
+ if action is None:
2971
+ continue
2972
+
2973
+ # Dispatch.
2974
+ if action == "detach":
2975
+ subprocess.run(["tmux", "detach-client"])
2897
2976
  return 0
2898
- try:
2899
- n = int(raw)
2900
- except ValueError:
2901
- print(f" not a number: {raw!r}")
2977
+ if action == "quit:kill":
2978
+ subprocess.run(["tmux", "kill-session", "-t", "Omega"])
2902
2979
  return 0
2903
- if 1 <= n <= len(lines):
2904
- chosen_key = lines[n - 1][1]
2905
- if chosen_key == "__separator__":
2906
- chosen_key = None
2907
-
2908
- if chosen_key is None:
2909
- return 0
2910
- if chosen_key == NEW_AISB:
2911
- # "+ New AISB" means the user wants a FRESH session — kill
2912
- # the existing AISB-chat first so we don't attach to a dead
2913
- # one (claude died, OAuth missing, etc.).
2914
- return _attach_or_spawn_chat(
2915
- "AISB-chat",
2916
- spawn_fn=lambda h: tmux.spawn_aisb_chat(h, force_replace=True),
2917
- label="AISB master (Claude Code on Max OAuth)",
2918
- )
2919
- if chosen_key == NEW_HERMES:
2920
- return _attach_or_spawn_chat(
2921
- "Hermes-chat",
2922
- spawn_fn=lambda h: tmux.spawn_hermes_chat(h, force_replace=True),
2923
- label="Hermès (Claude Code on Anthropic API)",
2924
- )
2925
- # Otherwise it's an existing session name — just attach.
2926
- print(f" attaching to {chosen_key}…")
2927
- try:
2928
- os.execvp("tmux", ["tmux", "attach", "-t", chosen_key])
2929
- except FileNotFoundError:
2930
- print(" tmux not on PATH")
2931
- return 2
2932
- return 0
2980
+ if action == "open:aisb":
2981
+ tmux.spawn_aisb_chat(HOME, force_replace=False)
2982
+ subprocess.run(["tmux", "switch-client", "-t", "AISB-chat"])
2983
+ continue
2984
+ if action == "open:hermes":
2985
+ tmux.spawn_hermes_chat(HOME, force_replace=False)
2986
+ subprocess.run(["tmux", "switch-client", "-t", "Hermes-chat"])
2987
+ continue
2988
+ if action == "switch:provider":
2989
+ _send_in_window("switch", f"{shlex.quote(OMEGA_BIN)} switch")
2990
+ continue
2991
+ if action == "genesis:new":
2992
+ _send_in_window("genesis",
2993
+ f"{shlex.quote(OMEGA_BIN)} genesis questions")
2994
+ continue
2995
+ if action == "project:open":
2996
+ _send_in_window("project", "ls -la ~/Omega/Agentik_Coding/projects/")
2997
+ continue
2998
+ 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\"'")
3002
+ continue
3003
+ 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>'")
3012
+ continue
3013
+ if action == "sessions:list":
3014
+ _send_in_window("sessions", "tmux ls")
3015
+ continue
3016
+ if action == "accounts:menu":
3017
+ _send_in_window("accounts",
3018
+ f"{shlex.quote(OMEGA_BIN)} account list")
3019
+ continue
3020
+ if action == "vault:menu":
3021
+ _send_in_window("vault",
3022
+ f"{shlex.quote(OMEGA_BIN)} vault status")
3023
+ continue
3024
+ if action == "cmd:doctor":
3025
+ _send_in_window("doctor", f"{shlex.quote(OMEGA_BIN)} doctor")
3026
+ continue
3027
+ if action == "cmd:status":
3028
+ _send_in_window("status", f"{shlex.quote(OMEGA_BIN)} status")
3029
+ continue
3030
+ if action == "scrape:cloak":
3031
+ _send_in_window("scrape",
3032
+ "read -p 'URL: ' u && "
3033
+ f"{shlex.quote(OMEGA_BIN)} scrape \"$u\"")
3034
+ continue
3035
+ if action == "scrape:scrapling":
3036
+ _send_in_window("scrape",
3037
+ "read -p 'URL: ' u && "
3038
+ f"{shlex.quote(OMEGA_BIN)} scrape \"$u\" --engine scrapling")
3039
+ continue
2933
3040
 
2934
3041
 
2935
3042
  def cmd_menu_whiptail(_args: argparse.Namespace) -> int:
2936
- """`omega menu` — the explicit whiptail picker.
2937
-
2938
- The old bare-omega behaviour, moved behind an explicit subcommand
2939
- because the default is now the AISB chat (v0.19.15)."""
3043
+ """`omega menu` — the explicit whiptail picker (legacy)."""
2940
3044
  from omega_engine.menu import run_menu
2941
3045
  return run_menu()
2942
3046
 
@@ -2966,28 +3070,74 @@ def _set_active_provider(provider_id: str) -> None:
2966
3070
 
2967
3071
 
2968
3072
  def cmd_scrape(args: argparse.Namespace) -> int:
2969
- """`omega scrape <url> [--out file] [--humanize]` — stealth scraper.
2970
-
2971
- Thin wrapper over CloakBrowser (https://github.com/CloakHQ/CloakBrowser).
2972
- Bypasses Cloudflare/Turnstile/FingerprintJS by patching the browser at
2973
- the C++ level. Default output = markdown to stdout so any LLM CLI can
2974
- pipe it directly into a context. The wrapper is what makes
2975
- CloakBrowser usable as a SHELL CLI for Claude/Gemini/Codex/etc.
2976
-
2977
- Usage:
2978
- omega scrape https://example.com # → markdown to stdout
2979
- omega scrape https://example.com --out page.md # file
2980
- omega scrape https://example.com --humanize # mouse curves + scroll
2981
- omega scrape https://example.com --proxy <url> # SOCKS5/HTTP
2982
-
2983
- Inside any Claude Code chat session: ``omega scrape <url>`` returns the
2984
- cleaned page content. No MCP server, no Playwright dance.
3073
+ """`omega scrape <url> [--engine cloak|scrapling] [--out file]` — scraper.
3074
+
3075
+ Two engines available since v0.19.24:
3076
+ * ``cloak`` (default) CloakBrowser, heavy stealth Chromium, 58
3077
+ C++ patches, passes Cloudflare/Turnstile/FingerprintJS. Slow
3078
+ but invincible.
3079
+ * ``scrapling`` D4Vinci/Scrapling, HTTP-first with optional
3080
+ browser, adaptive element tracking, concurrent crawl with
3081
+ pause/resume. Fast for non-protected sites.
3082
+
3083
+ Output: cleaned text to stdout (or --out FILE). Wraps both engines
3084
+ behind a consistent CLI so any LLM (Claude/Gemini/Codex/OpenCode/
3085
+ Aider) can `omega scrape <url>` via Bash without caring which
3086
+ Python library is doing the work.
3087
+
3088
+ Examples:
3089
+ omega scrape https://example.com # CloakBrowser
3090
+ omega scrape https://example.com --engine scrapling # Scrapling
3091
+ omega scrape https://news.ycombinator.com --engine scrapling --css ".titleline"
2985
3092
  """
2986
3093
  import shutil
2987
3094
  import subprocess
2988
3095
  if not args.url:
2989
- print("usage: omega scrape <url> [--out FILE] [--humanize] [--proxy URL]")
3096
+ print("usage: omega scrape <url> "
3097
+ "[--engine cloak|scrapling] [--out FILE] [--humanize] "
3098
+ "[--proxy URL] [--css SELECTOR]")
2990
3099
  return 2
3100
+ engine = getattr(args, "engine", None) or "cloak"
3101
+ if engine == "scrapling":
3102
+ # Scrapling path — uses its own CLI `scrapling extract`.
3103
+ if not shutil.which("scrapling"):
3104
+ print(" scrapling not installed — "
3105
+ "run `omega tool install scrapling` or "
3106
+ "`uv tool install 'scrapling[fetchers]' && scrapling install`")
3107
+ return 2
3108
+ out_file = getattr(args, "out", None)
3109
+ # Scrapling writes to a file (it doesn't print to stdout by
3110
+ # default). If user didn't specify --out, use a tmpfile and
3111
+ # cat it after.
3112
+ import tempfile
3113
+ from pathlib import Path
3114
+ tmp_out = out_file or tempfile.mktemp(suffix=".md")
3115
+ scrapling_cmd = ["scrapling", "extract",
3116
+ "stealthy-fetch" if getattr(args, "humanize", False)
3117
+ else "fetch",
3118
+ args.url, tmp_out]
3119
+ if getattr(args, "css", None):
3120
+ scrapling_cmd += ["--css-selector", args.css]
3121
+ try:
3122
+ proc = subprocess.run(scrapling_cmd, capture_output=True,
3123
+ text=True, timeout=120)
3124
+ except subprocess.TimeoutExpired:
3125
+ print(" scrapling timed out after 120s")
3126
+ return 2
3127
+ if proc.returncode != 0:
3128
+ print(f" scrapling error: {proc.stderr[:400]}")
3129
+ return proc.returncode
3130
+ if not out_file:
3131
+ try:
3132
+ print(Path(tmp_out).read_text())
3133
+ Path(tmp_out).unlink(missing_ok=True)
3134
+ except OSError as exc:
3135
+ print(f" read scrapling output failed: {exc}")
3136
+ return 2
3137
+ else:
3138
+ print(f" wrote → {out_file}")
3139
+ return 0
3140
+ # Default engine = CloakBrowser (kept verbatim from v0.19.23).
2991
3141
  # CloakBrowser is installed as a uv tool (`cloakbrowser`) but we need
2992
3142
  # to drive it from Python — the canonical path is `python -m
2993
3143
  # cloakbrowser` after `pip install cloakbrowser`, OR we invoke the
@@ -4175,8 +4325,12 @@ def _build_parser() -> argparse.ArgumentParser:
4175
4325
  parser.set_defaults(fn=cmd_menu)
4176
4326
  sub = parser.add_subparsers(dest="cmd")
4177
4327
  sub.add_parser("menu",
4178
- help="open the interactive whiptail menu (the old `omega` behaviour)"
4328
+ help="open the interactive whiptail menu (legacy)"
4179
4329
  ).set_defaults(fn=cmd_menu_whiptail)
4330
+ 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)"
4333
+ ).set_defaults(fn=cmd_menu_tui)
4180
4334
  sub.add_parser("version", help="print the engine version").set_defaults(fn=cmd_version)
4181
4335
 
4182
4336
  # `omega switch <provider>` — hot-swap the LLM CLI used for new chats.
@@ -4190,20 +4344,28 @@ def _build_parser() -> argparse.ArgumentParser:
4190
4344
  help="provider id (omit to print current + available)")
4191
4345
  p_sw.set_defaults(fn=cmd_switch)
4192
4346
 
4193
- # `omega scrape <url>` — official OmegaOS web scraper (CloakBrowser).
4347
+ # `omega scrape <url>` — official OmegaOS web scraper (CloakBrowser
4348
+ # default, Scrapling optional). Both engines pre-installed at step 40
4349
+ # when their catalog entry is `recommended: true`.
4194
4350
  p_sc = sub.add_parser(
4195
4351
  "scrape",
4196
- help="stealth scrape a URL via CloakBrowser bypasses Cloudflare, "
4197
- "Turnstile, FingerprintJS. Output: markdown to stdout (or --out FILE)",
4352
+ help="scrape a URL CloakBrowser (default, stealth) or Scrapling "
4353
+ "(--engine scrapling, fast). Output: stdout or --out FILE",
4198
4354
  )
4199
4355
  p_sc.add_argument("url", nargs="?", default=None,
4200
4356
  help="URL to fetch")
4357
+ p_sc.add_argument("--engine", default="cloak",
4358
+ choices=["cloak", "scrapling"],
4359
+ help="which scraper to use (default: cloak = CloakBrowser)")
4201
4360
  p_sc.add_argument("--out", default=None,
4202
4361
  help="write result to file instead of stdout")
4203
4362
  p_sc.add_argument("--humanize", action="store_true",
4204
- help="mouse curves + keyboard timing + scroll patterns")
4363
+ help="mouse curves + scroll patterns (cloak) "
4364
+ "or stealthy-fetch mode (scrapling)")
4205
4365
  p_sc.add_argument("--proxy", default=None,
4206
- help="HTTP or SOCKS5 proxy (with inline creds)")
4366
+ help="HTTP or SOCKS5 proxy (cloak engine only)")
4367
+ p_sc.add_argument("--css", default=None,
4368
+ help="CSS selector to scope output (scrapling engine only)")
4207
4369
  p_sc.set_defaults(fn=cmd_scrape)
4208
4370
  p_doc = sub.add_parser("doctor", help="validate the deployment")
4209
4371
  p_doc.add_argument("--json", action="store_true",
@@ -353,6 +353,41 @@ def spawn_aisb_chat(omega_home: str | Path | None = None,
353
353
  )
354
354
 
355
355
 
356
+ def spawn_omega_master(omega_home: str | Path | None = None) -> str:
357
+ """Spawn the master Omega tmux session — your persistent home.
358
+
359
+ v0.19.25 redesign: when you type `omega`, you ENTER this session.
360
+ Like `claude` opens the Claude Code TUI in your terminal, `omega`
361
+ attaches to a persistent tmux session called `Omega` which acts as
362
+ your OS-level dashboard. Inside, window 0 runs the Omega menu TUI
363
+ (`omega menu-tui` — the fzf-driven action picker). Other windows
364
+ open on demand:
365
+ - window 1: AISB-chat (spawned when user picks it)
366
+ - window 2: Hermes-chat
367
+ - window 3+: per-project oracles / workers / shells
368
+
369
+ Survives across terminal restarts. `tmux kill-server` is the only
370
+ way to nuke it.
371
+ """
372
+ name = "Omega"
373
+ home = Path(omega_home or os.environ.get("OMEGA_HOME")
374
+ or Path.home() / "Omega")
375
+ if is_alive(name):
376
+ return name
377
+ omega_bin = home / "Agentik_Tools" / "bin" / "omega"
378
+ # Window 0 = menu TUI. We launch the user's shell first then send
379
+ # the menu command — so if the TUI exits, the shell stays alive
380
+ # and the user can manually `omega menu-tui` again or run anything
381
+ # else. Same shell-stays-alive pattern as spawn_aisb_chat.
382
+ rc, _ = _tmux("new-session", "-d", "-s", name,
383
+ "-n", "menu", "-c", str(Path.home()))
384
+ if rc != 0:
385
+ return name
386
+ _tmux("send-keys", "-t", f"{name}:menu",
387
+ f"{omega_bin} menu-tui", "Enter")
388
+ return name
389
+
390
+
356
391
  def spawn_hermes_chat(omega_home: str | Path | None = None,
357
392
  force_replace: bool = False) -> str:
358
393
  """Spawn the Hermès chat tmux session — REAL Claude Code TUI, but on
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "omega-engine"
3
- version = "0.19.23"
3
+ version = "0.19.25"
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"
@@ -1 +1 @@
1
- 0.19.23
1
+ 0.19.25
@@ -74,6 +74,19 @@ native:
74
74
  secrets: []
75
75
  recommended: true
76
76
 
77
+ - id: scrapling
78
+ name: Scrapling (optional fast scraper — HTTP-first, adaptive elements)
79
+ # Complementary to CloakBrowser. Scrapling shines for non-protected
80
+ # sites + multi-page crawls (concurrent + pause/resume + adaptive
81
+ # element tracking that survives page redesigns). CloakBrowser stays
82
+ # the default for hard bot-walls; Scrapling for everything else.
83
+ # `omega scrape --engine scrapling` selects it explicitly.
84
+ install: { uv_tool: "scrapling[fetchers]" }
85
+ binary: scrapling
86
+ secrets: []
87
+ recommended: false
88
+ post_install: ["scrapling", "install"] # downloads browser deps once
89
+
77
90
  # --- 2. Printing Press CLIs --------------------------------------------
78
91
  # Installed via `npx -y @mvanhorn/printing-press-library install <name>`.
79
92
  # Each ships with a local SQLite mirror + Claude Code skill.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentikos/omega-os",
3
- "version": "0.19.23",
3
+ "version": "0.19.25",
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"