@agentikos/omega-os 0.19.35 → 0.19.37
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.
- package/bootstrap/lib/common.sh +65 -0
- package/install.sh +74 -21
- package/omega/Agentik_Engine/omega_engine/__init__.py +1 -1
- package/omega/Agentik_Engine/omega_engine/__pycache__/__init__.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/cli.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/paperclip_bridge.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/tui.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/cli.py +20 -0
- package/omega/Agentik_Engine/omega_engine/paperclip_bridge.py +76 -0
- package/omega/Agentik_Engine/omega_engine/tui.py +68 -12
- package/omega/Agentik_Engine/pyproject.toml +1 -1
- package/omega/Agentik_Engine/tests/__pycache__/test_tui_runtime.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_tui_runtime.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/test_tui_runtime.py +76 -0
- package/omega/Agentik_SSOT/VERSION +1 -1
- package/package.json +1 -1
package/bootstrap/lib/common.sh
CHANGED
|
@@ -75,12 +75,45 @@ record_state_version() {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
# run_step <name> <function>
|
|
78
|
+
#
|
|
79
|
+
# In NORMAL mode: prints `:: step <name>` headers + step output flows
|
|
80
|
+
# to the terminal in real time (the v0.19.34 behaviour).
|
|
81
|
+
#
|
|
82
|
+
# In FULL mode (INSTALL_FULL=1, set by `--full`): redirects each step's
|
|
83
|
+
# stdout+stderr to $LOG_FILE and updates a single progress bar line
|
|
84
|
+
# instead. No `:: step xx` chatter. Operator sees:
|
|
85
|
+
#
|
|
86
|
+
# Installing… ████████████░░░░░░░░░░ 14/21 66% 45-claude-plugins
|
|
87
|
+
#
|
|
88
|
+
# When the run finishes, the progress bar clears and the post-install
|
|
89
|
+
# card prints normally.
|
|
78
90
|
run_step() {
|
|
79
91
|
local name="$1" fn="$2"
|
|
80
92
|
if step_done "$name" && [ "${FORCE:-0}" != "1" ]; then
|
|
93
|
+
if [ "${INSTALL_FULL:-0}" = "1" ]; then
|
|
94
|
+
_full_progress "$name" "skip"
|
|
95
|
+
return 0
|
|
96
|
+
fi
|
|
81
97
|
log "${C_DIM}-- skip $name (already done)${C_RST}"
|
|
82
98
|
return 0
|
|
83
99
|
fi
|
|
100
|
+
|
|
101
|
+
if [ "${INSTALL_FULL:-0}" = "1" ]; then
|
|
102
|
+
# Redirect step output to the log; only the progress bar stays on screen.
|
|
103
|
+
_full_progress "$name" "run"
|
|
104
|
+
if "$fn" >>"$LOG_FILE" 2>&1; then
|
|
105
|
+
mark_done "$name"
|
|
106
|
+
_full_progress "$name" "ok"
|
|
107
|
+
else
|
|
108
|
+
# Clear the bar then surface the error inline.
|
|
109
|
+
printf "\r\033[K"
|
|
110
|
+
err "step $name failed — see $LOG_FILE"
|
|
111
|
+
die "step $name failed — fix the cause and re-run install.sh"
|
|
112
|
+
fi
|
|
113
|
+
return 0
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
# Normal verbose mode (default).
|
|
84
117
|
info "step $name"
|
|
85
118
|
if "$fn"; then
|
|
86
119
|
mark_done "$name"
|
|
@@ -90,6 +123,38 @@ run_step() {
|
|
|
90
123
|
fi
|
|
91
124
|
}
|
|
92
125
|
|
|
126
|
+
# _full_progress <step_name> <phase=run|ok|skip>
|
|
127
|
+
# Renders one line: "Installing… <bar> N/M % <current step>"
|
|
128
|
+
# Uses \r to overwrite — no scrollback noise.
|
|
129
|
+
_full_progress() {
|
|
130
|
+
local step="$1" phase="$2"
|
|
131
|
+
STEP_COUNT=$((${STEP_COUNT:-0} + 1))
|
|
132
|
+
local total="${STEP_TOTAL:-21}"
|
|
133
|
+
local current="$STEP_COUNT"
|
|
134
|
+
# Decrement displayed count for "skip" so the bar represents real progress
|
|
135
|
+
if [ "$phase" = "skip" ]; then
|
|
136
|
+
: # leave count as-is; skips still count as progress
|
|
137
|
+
fi
|
|
138
|
+
local pct=$((current * 100 / total))
|
|
139
|
+
local bar_width=30
|
|
140
|
+
local filled=$((current * bar_width / total))
|
|
141
|
+
local empty=$((bar_width - filled))
|
|
142
|
+
local bar=""
|
|
143
|
+
if [ "$filled" -gt 0 ]; then
|
|
144
|
+
bar="$(printf '█%.0s' $(seq 1 "$filled"))"
|
|
145
|
+
fi
|
|
146
|
+
if [ "$empty" -gt 0 ]; then
|
|
147
|
+
bar="${bar}$(printf '░%.0s' $(seq 1 "$empty"))"
|
|
148
|
+
fi
|
|
149
|
+
# \033[K clears to end of line so the previous step name doesn't bleed.
|
|
150
|
+
printf "\r ${C_ORANGE}Installing…${C_RST} ${bar} ${C_BOLD}%2d/%d${C_RST} %3d%% ${C_DIM}%-32s${C_RST}\033[K" \
|
|
151
|
+
"$current" "$total" "$pct" "$step"
|
|
152
|
+
# Final newline only when we hit the last step.
|
|
153
|
+
if [ "$current" -ge "$total" ]; then
|
|
154
|
+
printf "\n"
|
|
155
|
+
fi
|
|
156
|
+
}
|
|
157
|
+
|
|
93
158
|
# --- user resolution ---------------------------------------------------------
|
|
94
159
|
#
|
|
95
160
|
# The installer is modular about WHICH user it runs as. Three modes, decided
|
package/install.sh
CHANGED
|
@@ -25,6 +25,7 @@ source "$REPO_DIR/bootstrap/lib/steps.sh"
|
|
|
25
25
|
PROFILE="vps"; MANIFEST=""; NONINTERACTIVE=0; FORCE=0
|
|
26
26
|
USER_REQUESTED=""; CREATE_USER=0; CREATE_USER_NAME=""
|
|
27
27
|
PROFILE_EXPLICIT=0; NO_PATH_SETUP=0; DRY_RUN=0
|
|
28
|
+
INSTALL_FULL=0; FULL_WANT_TELEGRAM=""
|
|
28
29
|
[ "$(uname -s)" = "Darwin" ] && PROFILE="workstation"
|
|
29
30
|
|
|
30
31
|
while [ $# -gt 0 ]; do
|
|
@@ -41,6 +42,14 @@ while [ $# -gt 0 ]; do
|
|
|
41
42
|
--force) FORCE=1; shift ;;
|
|
42
43
|
--no-path-setup) NO_PATH_SETUP=1; shift ;;
|
|
43
44
|
--dry-run) DRY_RUN=1; shift ;;
|
|
45
|
+
# `--full` — silent full-recommended install. One prompt
|
|
46
|
+
# (Telegram yes/no), then progress bar only. Selects every
|
|
47
|
+
# recommended LLM CLI / MCP / Claude plugin / system CLI.
|
|
48
|
+
--full) INSTALL_FULL=1; NONINTERACTIVE=1; shift ;;
|
|
49
|
+
--full-no-telegram) INSTALL_FULL=1; NONINTERACTIVE=1
|
|
50
|
+
FULL_WANT_TELEGRAM=no; shift ;;
|
|
51
|
+
--full-with-telegram) INSTALL_FULL=1; NONINTERACTIVE=1
|
|
52
|
+
FULL_WANT_TELEGRAM=yes; shift ;;
|
|
44
53
|
--user) USER_REQUESTED="${2:?}"; shift 2 ;;
|
|
45
54
|
--create-user)
|
|
46
55
|
CREATE_USER=1
|
|
@@ -123,15 +132,41 @@ fi
|
|
|
123
132
|
|
|
124
133
|
# ───── UX header ─────
|
|
125
134
|
install_banner
|
|
126
|
-
|
|
135
|
+
# In --full mode skip the heavy preflight card; we'll show the progress
|
|
136
|
+
# bar instead. Banner still prints so the user sees "Omega OS vX.Y.Z".
|
|
137
|
+
if [ "${INSTALL_FULL:-0}" != "1" ]; then
|
|
138
|
+
preflight_card
|
|
139
|
+
fi
|
|
127
140
|
|
|
128
141
|
# Interactive profile picker if the operator did not pass --profile.
|
|
129
|
-
|
|
142
|
+
# (Skipped in --full mode — uses the default profile for the OS.)
|
|
143
|
+
if [ "${INSTALL_FULL:-0}" != "1" ]; then
|
|
144
|
+
pick_profile_interactive
|
|
145
|
+
fi
|
|
130
146
|
|
|
131
147
|
# Final pre-run summary (one tight line so the operator knows what they
|
|
132
|
-
# confirmed).
|
|
133
|
-
|
|
134
|
-
|
|
148
|
+
# confirmed). Skipped in --full mode.
|
|
149
|
+
if [ "${INSTALL_FULL:-0}" != "1" ]; then
|
|
150
|
+
log "${C_DIM}::${C_RST} starting install — profile=${C_BOLD}$PROFILE${C_RST} os=$OMEGA_OS user=$OMEGA_USER home=$OMEGA_HOME"
|
|
151
|
+
echo
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
# In --full mode we ask EXACTLY one question before anything else:
|
|
155
|
+
# "install Telegram?" If they pass --full-with-telegram or
|
|
156
|
+
# --full-no-telegram on the CLI, we skip even this prompt.
|
|
157
|
+
if [ "${INSTALL_FULL:-0}" = "1" ] && [ -z "${FULL_WANT_TELEGRAM:-}" ]; then
|
|
158
|
+
if [ -t 0 ] && [ "${NONINTERACTIVE:-0}" != "1" ]; then
|
|
159
|
+
if prompt_yes_no " Install the Telegram bot?" "no"; then
|
|
160
|
+
FULL_WANT_TELEGRAM=yes
|
|
161
|
+
else
|
|
162
|
+
FULL_WANT_TELEGRAM=no
|
|
163
|
+
fi
|
|
164
|
+
else
|
|
165
|
+
# Truly headless (piped stdin) — default to NO Telegram.
|
|
166
|
+
FULL_WANT_TELEGRAM=no
|
|
167
|
+
fi
|
|
168
|
+
echo
|
|
169
|
+
fi
|
|
135
170
|
|
|
136
171
|
# Define the sequence ONCE so we can count it for the progress header.
|
|
137
172
|
STEPS=(
|
|
@@ -157,7 +192,11 @@ STEPS=(
|
|
|
157
192
|
"45-claude-plugins:step_claude_plugins"
|
|
158
193
|
)
|
|
159
194
|
if [ "$PROFILE" != "minimal" ]; then
|
|
160
|
-
|
|
195
|
+
# In --full mode the Telegram step is gated by the single yes/no asked
|
|
196
|
+
# at the very top. Skip the step entirely when the user said no.
|
|
197
|
+
if [ "${INSTALL_FULL:-0}" != "1" ] || [ "${FULL_WANT_TELEGRAM:-}" != "no" ]; then
|
|
198
|
+
STEPS+=("50-telegram:step_telegram")
|
|
199
|
+
fi
|
|
161
200
|
fi
|
|
162
201
|
STEPS+=(
|
|
163
202
|
"55-autonomous:step_autonomous"
|
|
@@ -170,25 +209,27 @@ STEPS+=(
|
|
|
170
209
|
export STEP_TOTAL="${#STEPS[@]}"
|
|
171
210
|
export STEP_COUNT=0
|
|
172
211
|
|
|
173
|
-
# ───── Plan mode ─────
|
|
174
|
-
#
|
|
175
|
-
#
|
|
176
|
-
|
|
177
|
-
echo
|
|
178
|
-
log "${C_DIM}─── Install plan (${STEP_TOTAL} steps, ~2-3 min total) ───${C_RST}"
|
|
179
|
-
_idx=0
|
|
180
|
-
for entry in "${STEPS[@]}"; do
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
done
|
|
185
|
-
log "${C_DIM}─────────────────────────────────────────${C_RST}"
|
|
186
|
-
echo
|
|
212
|
+
# ───── Plan mode (verbose only) ─────
|
|
213
|
+
# In NORMAL mode: show every step BEFORE running + ask for confirmation.
|
|
214
|
+
# In --full mode: skip the plan — operator already opted in via the flag.
|
|
215
|
+
if [ "${INSTALL_FULL:-0}" != "1" ]; then
|
|
216
|
+
echo
|
|
217
|
+
log "${C_DIM}─── Install plan (${STEP_TOTAL} steps, ~2-3 min total) ───${C_RST}"
|
|
218
|
+
_idx=0
|
|
219
|
+
for entry in "${STEPS[@]}"; do
|
|
220
|
+
_idx=$((_idx + 1))
|
|
221
|
+
_name="${entry%%:*}"
|
|
222
|
+
log " ${C_DIM}${_idx}/${STEP_TOTAL}${C_RST} ${_name}"
|
|
223
|
+
done
|
|
224
|
+
log "${C_DIM}─────────────────────────────────────────${C_RST}"
|
|
225
|
+
echo
|
|
226
|
+
fi
|
|
187
227
|
|
|
188
228
|
if [ "${DRY_RUN:-0}" = "1" ]; then
|
|
189
229
|
ok "dry-run — exiting without running any step"
|
|
190
230
|
exit 0
|
|
191
231
|
fi
|
|
232
|
+
|
|
192
233
|
if [ "${NONINTERACTIVE:-0}" != "1" ] && [ -t 0 ]; then
|
|
193
234
|
if ! prompt_yes_no "Proceed with the install?" "yes"; then
|
|
194
235
|
log "aborted by operator — no changes made"
|
|
@@ -196,10 +237,22 @@ if [ "${NONINTERACTIVE:-0}" != "1" ] && [ -t 0 ]; then
|
|
|
196
237
|
fi
|
|
197
238
|
fi
|
|
198
239
|
|
|
240
|
+
# In --full mode, print the single header line that introduces the
|
|
241
|
+
# progress bar so the user knows what's happening.
|
|
242
|
+
if [ "${INSTALL_FULL:-0}" = "1" ]; then
|
|
243
|
+
echo
|
|
244
|
+
log " ${C_DIM}${STEP_TOTAL} steps · output piped to ${LOG_FILE} · Ctrl-c to abort${C_RST}"
|
|
245
|
+
echo
|
|
246
|
+
fi
|
|
247
|
+
|
|
199
248
|
for entry in "${STEPS[@]}"; do
|
|
200
249
|
name="${entry%%:*}"
|
|
201
250
|
fn="${entry##*:}"
|
|
202
|
-
|
|
251
|
+
# The verbose per-step header is suppressed in --full mode (progress
|
|
252
|
+
# bar replaces it).
|
|
253
|
+
if [ "${INSTALL_FULL:-0}" != "1" ]; then
|
|
254
|
+
step_progress_header "$name"
|
|
255
|
+
fi
|
|
203
256
|
run_step "$name" "$fn"
|
|
204
257
|
done
|
|
205
258
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -3208,6 +3208,17 @@ def cmd_paperclip(args: argparse.Namespace) -> int:
|
|
|
3208
3208
|
print(f" start failed: {exc}")
|
|
3209
3209
|
return 2
|
|
3210
3210
|
|
|
3211
|
+
if sub == "url":
|
|
3212
|
+
bind = getattr(args, "bind", None) or "loopback"
|
|
3213
|
+
host = getattr(args, "host", None)
|
|
3214
|
+
url = PB.dashboard_url(bind=bind, host=host)
|
|
3215
|
+
print(f" paperclip dashboard: {url}")
|
|
3216
|
+
return 0
|
|
3217
|
+
|
|
3218
|
+
if sub == "remote-setup":
|
|
3219
|
+
print(PB.remote_setup_help())
|
|
3220
|
+
return 0
|
|
3221
|
+
|
|
3211
3222
|
if sub == "heartbeat":
|
|
3212
3223
|
# Drop a heartbeat from the current shell — used by `dispatch-to-
|
|
3213
3224
|
# session.sh` style scripts so Paperclip sees workers come up + report.
|
|
@@ -4527,6 +4538,15 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
4527
4538
|
help="launch the Paperclip web dashboard (npx onboard)")
|
|
4528
4539
|
p_pcs.add_argument("--bind", choices=["loopback", "lan", "tailnet"],
|
|
4529
4540
|
default="loopback")
|
|
4541
|
+
p_pcu = pc_sub.add_parser("url",
|
|
4542
|
+
help="print the URL where the dashboard is reachable")
|
|
4543
|
+
p_pcu.add_argument("--bind", choices=["loopback", "lan", "tailnet"],
|
|
4544
|
+
default="loopback")
|
|
4545
|
+
p_pcu.add_argument("--host", default=None,
|
|
4546
|
+
help="override host (e.g. for a Cloudflare Tunnel URL)")
|
|
4547
|
+
pc_sub.add_parser("remote-setup",
|
|
4548
|
+
help="how to reach a VPS-hosted Paperclip from your Mac/PC "
|
|
4549
|
+
"(SSH port-forward / Tailscale / Cloudflare Tunnel)")
|
|
4530
4550
|
p_pch = pc_sub.add_parser("heartbeat",
|
|
4531
4551
|
help="send a heartbeat from a tmux session (script callable)")
|
|
4532
4552
|
p_pch.add_argument("--agent-id", required=True,
|
|
@@ -488,6 +488,82 @@ def status() -> BridgeStatus:
|
|
|
488
488
|
# ---------------------------------------------------------------------------
|
|
489
489
|
|
|
490
490
|
|
|
491
|
+
def dashboard_url(bind: str = "loopback", port: int = 8080,
|
|
492
|
+
host: str | None = None) -> str:
|
|
493
|
+
"""Return the URL where the Paperclip dashboard is reachable.
|
|
494
|
+
|
|
495
|
+
Resolution:
|
|
496
|
+
- loopback → http://localhost:<port>
|
|
497
|
+
- lan → http://<lan-ip>:<port> (best-effort interface IP)
|
|
498
|
+
- tailnet → http://<tailscale-ip>:<port> (reads `tailscale ip -4`)
|
|
499
|
+
- host → http://<host>:<port> (explicit override)
|
|
500
|
+
"""
|
|
501
|
+
if host:
|
|
502
|
+
return f"http://{host}:{port}"
|
|
503
|
+
if bind == "tailnet":
|
|
504
|
+
try:
|
|
505
|
+
r = subprocess.run(["tailscale", "ip", "-4"],
|
|
506
|
+
capture_output=True, text=True, timeout=5)
|
|
507
|
+
ip = (r.stdout or "").strip().splitlines()
|
|
508
|
+
if ip:
|
|
509
|
+
return f"http://{ip[0]}:{port}"
|
|
510
|
+
except (FileNotFoundError, subprocess.SubprocessError):
|
|
511
|
+
pass
|
|
512
|
+
if bind == "lan":
|
|
513
|
+
# Best-effort: use `hostname -I` (Linux) or `ipconfig getifaddr en0` (Mac).
|
|
514
|
+
for cmd in [["hostname", "-I"],
|
|
515
|
+
["ipconfig", "getifaddr", "en0"]]:
|
|
516
|
+
try:
|
|
517
|
+
r = subprocess.run(cmd, capture_output=True, text=True, timeout=5)
|
|
518
|
+
ip = (r.stdout or "").strip().split()
|
|
519
|
+
if ip:
|
|
520
|
+
return f"http://{ip[0]}:{port}"
|
|
521
|
+
except (FileNotFoundError, subprocess.SubprocessError):
|
|
522
|
+
continue
|
|
523
|
+
return f"http://localhost:{port}"
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def remote_setup_help() -> str:
|
|
527
|
+
"""Return setup instructions for accessing a VPS-hosted Paperclip
|
|
528
|
+
dashboard from the operator's Mac/PC.
|
|
529
|
+
|
|
530
|
+
Three paths, from simplest to most secure:
|
|
531
|
+
|
|
532
|
+
1. SSH port-forward (no extra software needed)
|
|
533
|
+
2. Tailscale (zero-config VPN, recommended)
|
|
534
|
+
3. Cloudflare Tunnel (public URL with auth)
|
|
535
|
+
"""
|
|
536
|
+
return (
|
|
537
|
+
" Three ways to reach a VPS-hosted Paperclip dashboard from\n"
|
|
538
|
+
" your Mac/PC. Pick what fits your setup:\n"
|
|
539
|
+
"\n"
|
|
540
|
+
" ─── (1) SSH PORT-FORWARD (no extra install) ───\n"
|
|
541
|
+
" From your Mac/PC, run ONCE per session:\n"
|
|
542
|
+
" ssh -p 42820 -N -L 8080:localhost:8080 hacker@<vps-ip>\n"
|
|
543
|
+
" Then open http://localhost:8080 on your Mac browser.\n"
|
|
544
|
+
" Leave the SSH window open while you use the dashboard.\n"
|
|
545
|
+
"\n"
|
|
546
|
+
" ─── (2) TAILSCALE (recommended) ───\n"
|
|
547
|
+
" On the VPS:\n"
|
|
548
|
+
" curl -fsSL https://tailscale.com/install.sh | sh\n"
|
|
549
|
+
" sudo tailscale up\n"
|
|
550
|
+
" omega paperclip start --bind tailnet\n"
|
|
551
|
+
" On your Mac/PC: install Tailscale, log into the same account,\n"
|
|
552
|
+
" open http://<vps-tailscale-name>:8080.\n"
|
|
553
|
+
" Auth handled by Tailscale ACLs; no public exposure.\n"
|
|
554
|
+
"\n"
|
|
555
|
+
" ─── (3) CLOUDFLARE TUNNEL (public URL) ───\n"
|
|
556
|
+
" On the VPS:\n"
|
|
557
|
+
" brew install cloudflared (or apt-get install cloudflared)\n"
|
|
558
|
+
" cloudflared tunnel --url http://localhost:8080\n"
|
|
559
|
+
" cloudflared prints a public *.trycloudflare.com URL.\n"
|
|
560
|
+
" For a stable subdomain, use a named tunnel + DNS.\n"
|
|
561
|
+
" Combine with Cloudflare Access for auth.\n"
|
|
562
|
+
"\n"
|
|
563
|
+
" Currently bound: see `omega paperclip status`\n"
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
|
|
491
567
|
def start_server(bind: str = "loopback", *, detach: bool = True) -> int:
|
|
492
568
|
"""Launch the Paperclip onboard server.
|
|
493
569
|
|
|
@@ -493,6 +493,11 @@ def _arrow_menu() -> int:
|
|
|
493
493
|
(_label("Stealth scrape", "CloakBrowser"), "scrape:cloak"),
|
|
494
494
|
(_label("Fast scrape", "Scrapling"), "scrape:scrapling"),
|
|
495
495
|
("", "__sep__"),
|
|
496
|
+
(_section("GOVERNANCE (Paperclip L0)"), "__sep__"),
|
|
497
|
+
(_label("Paperclip dashboard","web UI for 14 agents"), "paperclip:dashboard"),
|
|
498
|
+
(_label("Paperclip status", "bridge health"), "paperclip:status"),
|
|
499
|
+
(_label("Paperclip register","(re)sync OmegaOS company"), "paperclip:register"),
|
|
500
|
+
("", "__sep__"),
|
|
496
501
|
(_section("EXIT"), "__sep__"),
|
|
497
502
|
(_label("Detach", "session keeps running"), "detach"),
|
|
498
503
|
(_label("Quit Omega", "kills the tmux session"), "quit:kill"),
|
|
@@ -522,20 +527,27 @@ def _arrow_menu() -> int:
|
|
|
522
527
|
|
|
523
528
|
while True:
|
|
524
529
|
items = _build_items()
|
|
525
|
-
|
|
530
|
+
# v0.19.37 — bulletproof matching via tab-delimited index column.
|
|
531
|
+
# fzf with --ansi STRIPS escape codes from stdout, which broke
|
|
532
|
+
# the old `display == pick` exact-string match (every pick fell
|
|
533
|
+
# through, menu just reloaded). Now we prefix each line with a
|
|
534
|
+
# stable index (`<i>\t<display>`) and use --with-nth=2.. so fzf
|
|
535
|
+
# only DISPLAYS the second column but RETURNS the full line.
|
|
536
|
+
# We split off the index and look up the action — survives any
|
|
537
|
+
# ANSI/whitespace transformation fzf might apply.
|
|
538
|
+
lines = []
|
|
539
|
+
for i, (disp, key) in enumerate(items):
|
|
540
|
+
lines.append(f"{i}\t{disp}")
|
|
526
541
|
header = (
|
|
527
542
|
f"{ORANGE}{BOLD}Ω Omega OS v{__version__}{RST} "
|
|
528
543
|
f"{MUTED}• ↑↓ navigate • ↵ pick • / search • Esc refresh{RST}"
|
|
529
544
|
)
|
|
530
545
|
try:
|
|
531
|
-
# Claude Code LIGHT theme (white-paper) — bg cream #FAFAF7,
|
|
532
|
-
# fg dark slate #3D3929, accent orange #D97757, muted warm
|
|
533
|
-
# gray #88837A, soft border #A8A29E. Reads cleanly under
|
|
534
|
-
# both light and dark terminal backgrounds (we override
|
|
535
|
-
# fzf's bg explicitly).
|
|
536
546
|
proc = subprocess.run(
|
|
537
547
|
["fzf",
|
|
538
548
|
"--ansi",
|
|
549
|
+
"--delimiter=\t",
|
|
550
|
+
"--with-nth=2..",
|
|
539
551
|
"--prompt=Ω › ",
|
|
540
552
|
f"--header={header}",
|
|
541
553
|
"--header-first",
|
|
@@ -570,12 +582,21 @@ def _arrow_menu() -> int:
|
|
|
570
582
|
# Esc → refresh.
|
|
571
583
|
continue
|
|
572
584
|
pick = proc.stdout.rstrip("\n")
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
585
|
+
# Parse the index column. The tab is always the first \t in the
|
|
586
|
+
# picked line (the rest of the display may contain colour codes
|
|
587
|
+
# but no tabs).
|
|
588
|
+
if "\t" not in pick:
|
|
589
|
+
continue
|
|
590
|
+
idx_str, _disp_part = pick.split("\t", 1)
|
|
591
|
+
try:
|
|
592
|
+
idx = int(idx_str)
|
|
593
|
+
except ValueError:
|
|
594
|
+
continue
|
|
595
|
+
if not (0 <= idx < len(items)):
|
|
596
|
+
continue
|
|
597
|
+
action = items[idx][1]
|
|
598
|
+
if action == "__sep__":
|
|
599
|
+
# User picked a section header — silent reroll.
|
|
579
600
|
continue
|
|
580
601
|
|
|
581
602
|
# === Dispatch ===
|
|
@@ -665,6 +686,41 @@ def _arrow_menu() -> int:
|
|
|
665
686
|
_run_inline([OMEGA_BIN, "scrape", url, "--engine", "scrapling"])
|
|
666
687
|
continue
|
|
667
688
|
|
|
689
|
+
if action == "paperclip:dashboard":
|
|
690
|
+
# Show status, then offer to start if not running and open
|
|
691
|
+
# the URL. Reuses `omega paperclip start` which handles npx.
|
|
692
|
+
_run_inline([OMEGA_BIN, "paperclip", "status"])
|
|
693
|
+
try:
|
|
694
|
+
ans = input(f" {ORANGE}start the Paperclip dashboard now? "
|
|
695
|
+
f"[y/N]:{RST} ").strip().lower()
|
|
696
|
+
except (EOFError, KeyboardInterrupt):
|
|
697
|
+
ans = ""
|
|
698
|
+
if ans in ("y", "yes"):
|
|
699
|
+
bind = "loopback"
|
|
700
|
+
try:
|
|
701
|
+
bind_ans = input(
|
|
702
|
+
f" {ORANGE}bind mode [loopback/lan/tailnet] "
|
|
703
|
+
f"(default loopback):{RST} "
|
|
704
|
+
).strip().lower() or "loopback"
|
|
705
|
+
if bind_ans in ("loopback", "lan", "tailnet"):
|
|
706
|
+
bind = bind_ans
|
|
707
|
+
except (EOFError, KeyboardInterrupt):
|
|
708
|
+
pass
|
|
709
|
+
_run_inline([OMEGA_BIN, "paperclip", "start", "--bind", bind])
|
|
710
|
+
# Try to open the browser at the default URL.
|
|
711
|
+
import shutil as _sh
|
|
712
|
+
opener = _sh.which("open") or _sh.which("xdg-open")
|
|
713
|
+
if opener:
|
|
714
|
+
subprocess.run([opener, "http://localhost:8080"],
|
|
715
|
+
capture_output=True)
|
|
716
|
+
continue
|
|
717
|
+
if action == "paperclip:status":
|
|
718
|
+
_run_inline([OMEGA_BIN, "paperclip", "status"])
|
|
719
|
+
continue
|
|
720
|
+
if action == "paperclip:register":
|
|
721
|
+
_run_inline([OMEGA_BIN, "paperclip", "register"])
|
|
722
|
+
continue
|
|
723
|
+
|
|
668
724
|
|
|
669
725
|
def run_tui(prefer_textual: bool = False,
|
|
670
726
|
force_repl: bool = False) -> int:
|
package/omega/Agentik_Engine/tests/__pycache__/test_tui_runtime.cpython-313-pytest-8.4.2.pyc
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -100,5 +100,81 @@ class TestProviderStateSharedModule(unittest.TestCase):
|
|
|
100
100
|
provider_state.set_active_provider)
|
|
101
101
|
|
|
102
102
|
|
|
103
|
+
class TestFzfMenuMatching(unittest.TestCase):
|
|
104
|
+
"""v0.19.37 — the menu was broken because fzf with --ansi STRIPS
|
|
105
|
+
ANSI codes from its stdout, so the old `display == pick` exact
|
|
106
|
+
match was always False. Every pick fell through and the menu just
|
|
107
|
+
re-rendered. These tests lock in the index-based matching that
|
|
108
|
+
survives any ANSI/whitespace transformation."""
|
|
109
|
+
|
|
110
|
+
def test_fzf_ansi_strips_codes_from_stdout(self):
|
|
111
|
+
"""Documents the upstream behaviour we're working around."""
|
|
112
|
+
import shutil
|
|
113
|
+
import subprocess
|
|
114
|
+
if not shutil.which("fzf"):
|
|
115
|
+
self.skipTest("fzf not installed")
|
|
116
|
+
ORANGE = "\033[38;2;217;119;87m"
|
|
117
|
+
RST = "\033[0m"
|
|
118
|
+
input_line = f" {ORANGE}AISB master chat{RST} hint"
|
|
119
|
+
proc = subprocess.run(
|
|
120
|
+
["fzf", "--ansi", "--filter=AISB"],
|
|
121
|
+
input=input_line, capture_output=True, text=True,
|
|
122
|
+
)
|
|
123
|
+
pick = proc.stdout.rstrip("\n")
|
|
124
|
+
# The stdout has the SAME visible characters but NO escape codes.
|
|
125
|
+
self.assertEqual(pick, " AISB master chat hint")
|
|
126
|
+
self.assertNotEqual(pick, input_line,
|
|
127
|
+
"if these match, fzf preserves ANSI — our workaround was unnecessary "
|
|
128
|
+
"but harmless; if not (current behaviour), the workaround is required")
|
|
129
|
+
|
|
130
|
+
def test_arrow_menu_uses_tab_delimited_index(self):
|
|
131
|
+
"""Inspect the source of `_arrow_menu` to confirm it builds
|
|
132
|
+
tab-delimited input + parses the index column. If someone reverts
|
|
133
|
+
to display-equality matching, this test fails LOUDLY."""
|
|
134
|
+
import inspect
|
|
135
|
+
from omega_engine.tui import _arrow_menu
|
|
136
|
+
src = inspect.getsource(_arrow_menu)
|
|
137
|
+
# The fix must use --delimiter=\t + --with-nth=2.. + split("\t", 1).
|
|
138
|
+
self.assertIn("--delimiter=", src,
|
|
139
|
+
"menu must use tab-delimited input to survive fzf's --ansi strip")
|
|
140
|
+
self.assertIn("--with-nth", src,
|
|
141
|
+
"menu must use --with-nth to display only the visual column")
|
|
142
|
+
self.assertIn('split("\\t", 1)', src,
|
|
143
|
+
"menu must parse the index column from the tab-delimited pick")
|
|
144
|
+
self.assertNotIn("if disp == pick", src,
|
|
145
|
+
"REGRESSION: don't bring back display-equality matching — "
|
|
146
|
+
"fzf strips ANSI from stdout, the equality always fails")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class TestPaperclipMenuIntegration(unittest.TestCase):
|
|
150
|
+
"""The menu must surface the Paperclip governance actions
|
|
151
|
+
(dashboard / status / register) the user explicitly asked for in
|
|
152
|
+
v0.19.37. Also lock in the URL helper + remote-setup help string."""
|
|
153
|
+
|
|
154
|
+
def test_paperclip_dashboard_url_returns_string(self):
|
|
155
|
+
from omega_engine.paperclip_bridge import dashboard_url
|
|
156
|
+
self.assertTrue(dashboard_url("loopback").startswith("http://"))
|
|
157
|
+
# Explicit host override.
|
|
158
|
+
self.assertEqual(dashboard_url("loopback", host="vps.example.com"),
|
|
159
|
+
"http://vps.example.com:8080")
|
|
160
|
+
|
|
161
|
+
def test_remote_setup_help_lists_three_paths(self):
|
|
162
|
+
from omega_engine.paperclip_bridge import remote_setup_help
|
|
163
|
+
text = remote_setup_help()
|
|
164
|
+
# The three documented paths must all appear.
|
|
165
|
+
for kw in ("SSH PORT-FORWARD", "TAILSCALE", "CLOUDFLARE TUNNEL"):
|
|
166
|
+
self.assertIn(kw, text,
|
|
167
|
+
f"remote_setup_help must document the {kw} path")
|
|
168
|
+
|
|
169
|
+
def test_arrow_menu_contains_paperclip_actions(self):
|
|
170
|
+
import inspect
|
|
171
|
+
from omega_engine.tui import _arrow_menu
|
|
172
|
+
src = inspect.getsource(_arrow_menu)
|
|
173
|
+
for action in ("paperclip:dashboard", "paperclip:status",
|
|
174
|
+
"paperclip:register"):
|
|
175
|
+
self.assertIn(action, src,
|
|
176
|
+
f"arrow menu must wire {action} (user asked for it in v0.19.37)")
|
|
177
|
+
|
|
178
|
+
|
|
103
179
|
if __name__ == "__main__":
|
|
104
180
|
unittest.main()
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.19.
|
|
1
|
+
0.19.37
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentikos/omega-os",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.37",
|
|
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"
|