chore(theme): swap ⛧ sigil for † across UI, scripts, docs
CI / rust client (hh) (macos-latest) (push) Waiting to run
CI / rust client (hh) (ubuntu-latest) (push) Waiting to run
CI / rust coverage (push) Waiting to run
CI / python server (3.10) (push) Waiting to run
CI / python server (3.11) (push) Waiting to run
CI / python server (3.12) (push) Waiting to run
CI / headless e2e smoke (push) Waiting to run
CI / dependency audit (push) Waiting to run
CI / secret scanning (push) Waiting to run
CI / rust client (hh) (macos-latest) (push) Waiting to run
CI / rust client (hh) (ubuntu-latest) (push) Waiting to run
CI / rust coverage (push) Waiting to run
CI / python server (3.10) (push) Waiting to run
CI / python server (3.11) (push) Waiting to run
CI / python server (3.12) (push) Waiting to run
CI / headless e2e smoke (push) Waiting to run
CI / dependency audit (push) Waiting to run
CI / secret scanning (push) Waiting to run
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -63,7 +63,7 @@ stats:
|
|||||||
["GUI", "browser"]
|
["GUI", "browser"]
|
||||||
```
|
```
|
||||||
Spoken/caption outro: "A shared Kali rig your whole crew drives — saved, restored,
|
Spoken/caption outro: "A shared Kali rig your whole crew drives — saved, restored,
|
||||||
end-to-end encrypted. ⛧ link in bio."
|
end-to-end encrypted. † link in bio."
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ stats:
|
|||||||
["bind", "loopbk"]
|
["bind", "loopbk"]
|
||||||
```
|
```
|
||||||
Outro: "A portable Parrot desktop you can hand to a teammate as a single file —
|
Outro: "A portable Parrot desktop you can hand to a teammate as a single file —
|
||||||
no host mounts, loopback-only. ⛧"
|
no host mounts, loopback-only. †"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ stats:
|
|||||||
["model", "local"]
|
["model", "local"]
|
||||||
```
|
```
|
||||||
Outro: "An autonomous agent whose blast radius is one container — gated by a single
|
Outro: "An autonomous agent whose blast radius is one container — gated by a single
|
||||||
grant, running a local model. ⛧"
|
grant, running a local model. †"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ cd tools/video-forge
|
|||||||
# 4. Vertical reframe + ship
|
# 4. Vertical reframe + ship
|
||||||
cd ~/coding/video-toolkit
|
cd ~/coding/video-toolkit
|
||||||
bin/social-reframe.sh output/hh-<reel>-cut.mp4 --out output/hh-<reel>-reel.mp4 --size 1080x1920
|
bin/social-reframe.sh output/hh-<reel>-cut.mp4 --out output/hh-<reel>-reel.mp4 --size 1080x1920
|
||||||
bin/tg-send.sh output/hh-<reel>-reel.mp4 andre "hack-house — <reel> ⛧"
|
bin/tg-send.sh output/hh-<reel>-reel.mp4 andre "hack-house — <reel> †"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Gotchas (from prior demo work)
|
### Gotchas (from prior demo work)
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ the chat provider is Ollama and a `qwen2.5-coder` is present (it is — pulled).
|
|||||||
→ agent drives the shared shell; `fib.py` is written and executed; the
|
→ agent drives the shared shell; `fib.py` is written and executed; the
|
||||||
sandbox pane shows the Fibonacci output.
|
sandbox pane shows the Fibonacci output.
|
||||||
5. **Freeze it** — alice: `/sbx save buildbox` →
|
5. **Freeze it** — alice: `/sbx save buildbox` →
|
||||||
`⛧ saved sandbox → image hh-snap:buildbox · reload with /sbx load buildbox`.
|
`† saved sandbox → image hh-snap:buildbox · reload with /sbx load buildbox`.
|
||||||
6. **Walk away** — alice: `/sbx stop` (or quits the client entirely). Container is
|
6. **Walk away** — alice: `/sbx stop` (or quits the client entirely). Container is
|
||||||
purged; prove it: `docker ps -a` shows no `hack-house`, but
|
purged; prove it: `docker ps -a` shows no `hack-house`, but
|
||||||
`docker images hh-snap` still lists `buildbox`.
|
`docker images hh-snap` still lists `buildbox`.
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ sleep 2
|
|||||||
ok "recording → $CAST"
|
ok "recording → $CAST"
|
||||||
|
|
||||||
# ---- 3. title + join --------------------------------------------------------
|
# ---- 3. title + join --------------------------------------------------------
|
||||||
say "echo '⛧ hack-house — ephemeral by default, persistent on demand'"
|
say "echo '† hack-house — ephemeral by default, persistent on demand'"
|
||||||
sleep 1.2
|
sleep 1.2
|
||||||
say "$BIN connect 127.0.0.1 $PORT alice --password '$PW' --no-tls"
|
say "$BIN connect 127.0.0.1 $PORT alice --password '$PW' --no-tls"
|
||||||
wait_for 'alice|roster|hack-house|owner' 20 && ok "alice joined" || fail "alice never joined"
|
wait_for 'alice|roster|hack-house|owner' 20 && ok "alice joined" || fail "alice never joined"
|
||||||
@@ -196,7 +196,7 @@ docker images "$SNAP" --format '{{.Tag}}' | grep -qx "$LABEL" && ok "image survi
|
|||||||
sleep 1
|
sleep 1
|
||||||
say "docker ps -a --format '{{.Names}}' | grep hack-house || echo '(no hack-house container — purged)'"
|
say "docker ps -a --format '{{.Names}}' | grep hack-house || echo '(no hack-house container — purged)'"
|
||||||
sleep 1.5
|
sleep 1.5
|
||||||
say "docker images hh-snap --format '⛧ {{.Repository}}:{{.Tag}}'"
|
say "docker images hh-snap --format '† {{.Repository}}:{{.Tag}}'"
|
||||||
sleep 2
|
sleep 2
|
||||||
|
|
||||||
# ---- 9. fresh client → load -------------------------------------------------
|
# ---- 9. fresh client → load -------------------------------------------------
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ sleep 2
|
|||||||
ok "recording → $CAST"
|
ok "recording → $CAST"
|
||||||
|
|
||||||
# ---- 3. both parties join (alice = owner-side, bob = guest/client) ----------
|
# ---- 3. both parties join (alice = owner-side, bob = guest/client) ----------
|
||||||
asay "echo '⛧ alice — host'"; bsay "echo '⛧ bob — guest'"
|
asay "echo '† alice — host'"; bsay "echo '† bob — guest'"
|
||||||
sleep 0.8
|
sleep 0.8
|
||||||
asay "$BIN connect 127.0.0.1 $PORT alice --password '$PW' --no-tls"
|
asay "$BIN connect 127.0.0.1 $PORT alice --password '$PW' --no-tls"
|
||||||
await_for 'alice|roster|hack-house|owner' 20 && ok "alice joined" || fail "alice never joined"
|
await_for 'alice|roster|hack-house|owner' 20 && ok "alice joined" || fail "alice never joined"
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ HOST="${HOST:-$DEFAULT_HOST}"
|
|||||||
|
|
||||||
# No password yet? Prompt with no echo — straight into RAM, out of history.
|
# No password yet? Prompt with no echo — straight into RAM, out of history.
|
||||||
if [[ -z "$PASSWORD" ]]; then
|
if [[ -z "$PASSWORD" ]]; then
|
||||||
read -rsp "⛧ room password: " PASSWORD < /dev/tty
|
read -rsp "† room password: " PASSWORD < /dev/tty
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
[[ -n "$PASSWORD" ]] || { echo "✖ a password is required" >&2; exit 1; }
|
[[ -n "$PASSWORD" ]] || { echo "✖ a password is required" >&2; exit 1; }
|
||||||
@@ -71,7 +71,7 @@ if [[ "$SYNC" -eq 1 ]]; then
|
|||||||
TO=""; command -v timeout >/dev/null 2>&1 && TO="timeout 10"
|
TO=""; command -v timeout >/dev/null 2>&1 && TO="timeout 10"
|
||||||
for remote in gitea origin; do
|
for remote in gitea origin; do
|
||||||
if git remote get-url "$remote" >/dev/null 2>&1; then
|
if git remote get-url "$remote" >/dev/null 2>&1; then
|
||||||
echo "⛧ syncing $BRANCH from $remote…" >&2
|
echo "† syncing $BRANCH from $remote…" >&2
|
||||||
GIT_TERMINAL_PROMPT=0 $TO git pull --ff-only "$remote" "$BRANCH" 2>&1 \
|
GIT_TERMINAL_PROMPT=0 $TO git pull --ff-only "$remote" "$BRANCH" 2>&1 \
|
||||||
|| echo " (skipped — couldn't fast-forward from $remote/$BRANCH)" >&2
|
|| echo " (skipped — couldn't fast-forward from $remote/$BRANCH)" >&2
|
||||||
fi
|
fi
|
||||||
@@ -83,7 +83,7 @@ fi
|
|||||||
# runs a prebuilt binary as-is (handy for remote joiners without a toolchain),
|
# runs a prebuilt binary as-is (handy for remote joiners without a toolchain),
|
||||||
# preferring release, then debug.
|
# preferring release, then debug.
|
||||||
if [[ "$NO_BUILD" -eq 0 ]]; then
|
if [[ "$NO_BUILD" -eq 0 ]]; then
|
||||||
echo "⛧ building client (use --no-build to skip)…" >&2
|
echo "† building client (use --no-build to skip)…" >&2
|
||||||
cargo build --quiet || { echo "✖ build failed" >&2; exit 1; }
|
cargo build --quiet || { echo "✖ build failed" >&2; exit 1; }
|
||||||
BIN=./target/debug/hack-house
|
BIN=./target/debug/hack-house
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ ensure_branch() {
|
|||||||
echo " commit/stash first, or run with BRANCH= to build '$cur' as-is." >&2
|
echo " commit/stash first, or run with BRANCH= to build '$cur' as-is." >&2
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
echo "⛧ switching $cur → $BRANCH before build"
|
echo "† switching $cur → $BRANCH before build"
|
||||||
git -C "$ROOT" switch "$BRANCH" || { echo "✖ couldn't switch to '$BRANCH'" >&2; exit 2; }
|
git -C "$ROOT" switch "$BRANCH" || { echo "✖ couldn't switch to '$BRANCH'" >&2; exit 2; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ LAN_IP="$(ip -4 -o addr show scope global 2>/dev/null | awk '{print $4}' | cut -
|
|||||||
JOIN_IP="${TS_IP:-${LAN_IP:-$HOST}}"
|
JOIN_IP="${TS_IP:-${LAN_IP:-$HOST}}"
|
||||||
|
|
||||||
echo "═══════════════════════════════════════════════"
|
echo "═══════════════════════════════════════════════"
|
||||||
echo " ⛧ hack-house room — $PROTO://$HOST:$PORT"
|
echo " † hack-house room — $PROTO://$HOST:$PORT"
|
||||||
echo "═══════════════════════════════════════════════"
|
echo "═══════════════════════════════════════════════"
|
||||||
[[ -n "$TS_IP" ]] && echo " tailscale : $TS_IP (recommended — encrypted)"
|
[[ -n "$TS_IP" ]] && echo " tailscale : $TS_IP (recommended — encrypted)"
|
||||||
[[ -n "$LAN_IP" ]] && echo " lan : $LAN_IP"
|
[[ -n "$LAN_IP" ]] && echo " lan : $LAN_IP"
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ fi
|
|||||||
|
|
||||||
# cloudimg: the existing, tested unattended path.
|
# cloudimg: the existing, tested unattended path.
|
||||||
if [[ "$KIND" == "cloudimg" ]]; then
|
if [[ "$KIND" == "cloudimg" ]]; then
|
||||||
echo "⛧ building '$NAME' via vbox-new.sh (cloud-init, unattended)…" >&2
|
echo "† building '$NAME' via vbox-new.sh (cloud-init, unattended)…" >&2
|
||||||
args=(--name "$NAME" --release "${VERSION:-24.04}" --cpus "$CPUS" --mem "$MEM" --disk "$DISK")
|
args=(--name "$NAME" --release "${VERSION:-24.04}" --cpus "$CPUS" --mem "$MEM" --disk "$DISK")
|
||||||
[[ $ASSUME_YES -eq 1 ]] && args+=(--yes)
|
[[ $ASSUME_YES -eq 1 ]] && args+=(--yes)
|
||||||
[[ $DO_BOOT -eq 0 ]] && args+=(--no-boot)
|
[[ $DO_BOOT -eq 0 ]] && args+=(--no-boot)
|
||||||
@@ -216,7 +216,7 @@ fi
|
|||||||
|
|
||||||
# ── ova: import the appliance and we're done ─────────────────────────────────
|
# ── ova: import the appliance and we're done ─────────────────────────────────
|
||||||
if [[ "$KIND" == "ova" ]]; then
|
if [[ "$KIND" == "ova" ]]; then
|
||||||
echo "⛧ importing appliance '$NAME' …" >&2
|
echo "† importing appliance '$NAME' …" >&2
|
||||||
if VBoxManage import "$SRC" >/dev/null; then
|
if VBoxManage import "$SRC" >/dev/null; then
|
||||||
echo "✓ imported '$NAME' — boot it with /sbx vbox \"$NAME\"" >&2
|
echo "✓ imported '$NAME' — boot it with /sbx vbox \"$NAME\"" >&2
|
||||||
exit 0
|
exit 0
|
||||||
@@ -225,7 +225,7 @@ if [[ "$KIND" == "ova" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# ── iso: create a VM, attach the installer, boot it ──────────────────────────
|
# ── iso: create a VM, attach the installer, boot it ──────────────────────────
|
||||||
echo "⛧ creating VM '$NAME' (${OSTYPE:-Other_64}) …" >&2
|
echo "† creating VM '$NAME' (${OSTYPE:-Other_64}) …" >&2
|
||||||
VBoxManage createvm --name "$NAME" --ostype "${OSTYPE:-Other_64}" --register >/dev/null
|
VBoxManage createvm --name "$NAME" --ostype "${OSTYPE:-Other_64}" --register >/dev/null
|
||||||
|
|
||||||
# Roll the half-built VM back if any step below fails.
|
# Roll the half-built VM back if any step below fails.
|
||||||
@@ -259,7 +259,7 @@ echo "✓ VM '$NAME' created — the installer ISO is attached." >&2
|
|||||||
echo " finish setup inside the VirtualBox window (it boots into the installer)." >&2
|
echo " finish setup inside the VirtualBox window (it boots into the installer)." >&2
|
||||||
|
|
||||||
if [[ $DO_BOOT -eq 1 ]]; then
|
if [[ $DO_BOOT -eq 1 ]]; then
|
||||||
echo "⛧ booting '$NAME' (GUI) …" >&2
|
echo "† booting '$NAME' (GUI) …" >&2
|
||||||
VBoxManage startvm "$NAME" --type gui >/dev/null
|
VBoxManage startvm "$NAME" --type gui >/dev/null
|
||||||
echo "✓ launched $NAME" >&2
|
echo "✓ launched $NAME" >&2
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ if [[ ! -f "$IMG_PATH" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# ── 2. register the VM (creates its folder) ──────────────────────────────────
|
# ── 2. register the VM (creates its folder) ──────────────────────────────────
|
||||||
echo "⛧ creating VM '$NAME' …" >&2
|
echo "† creating VM '$NAME' …" >&2
|
||||||
VBoxManage createvm --name "$NAME" --ostype Ubuntu_64 --register >/dev/null
|
VBoxManage createvm --name "$NAME" --ostype Ubuntu_64 --register >/dev/null
|
||||||
MACHINE_FOLDER="$(VBoxManage list systemproperties | sed -n 's/^Default machine folder: *//p')"
|
MACHINE_FOLDER="$(VBoxManage list systemproperties | sed -n 's/^Default machine folder: *//p')"
|
||||||
VMDIR="$MACHINE_FOLDER/$NAME"
|
VMDIR="$MACHINE_FOLDER/$NAME"
|
||||||
@@ -159,7 +159,7 @@ trap 'echo "✖ build failed — rolling back VM" >&2; cleanup' ERR
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
# ── 3. cloud-image → VDI, grown to the requested size ────────────────────────
|
# ── 3. cloud-image → VDI, grown to the requested size ────────────────────────
|
||||||
echo "⛧ converting cloud image → VDI …" >&2
|
echo "† converting cloud image → VDI …" >&2
|
||||||
qemu-img convert -O vdi "$IMG_PATH" "$VDI"
|
qemu-img convert -O vdi "$IMG_PATH" "$VDI"
|
||||||
VBoxManage modifymedium disk "$VDI" --resize "$((DISK * 1024))" >/dev/null
|
VBoxManage modifymedium disk "$VDI" --resize "$((DISK * 1024))" >/dev/null
|
||||||
|
|
||||||
@@ -208,7 +208,7 @@ trap 'echo "✖ build failed — rolling back VM" >&2; cleanup; rm -rf "$WORK"'
|
|||||||
for p in "${PKG_LIST[@]}"; do echo " - $p"; done
|
for p in "${PKG_LIST[@]}"; do echo " - $p"; done
|
||||||
} > "$WORK/user-data"
|
} > "$WORK/user-data"
|
||||||
|
|
||||||
echo "⛧ building cloud-init seed ($SEED_TOOL) …" >&2
|
echo "† building cloud-init seed ($SEED_TOOL) …" >&2
|
||||||
case "$SEED_TOOL" in
|
case "$SEED_TOOL" in
|
||||||
cloud-localds)
|
cloud-localds)
|
||||||
cloud-localds "$SEED_ISO" "$WORK/user-data" "$WORK/meta-data"
|
cloud-localds "$SEED_ISO" "$WORK/user-data" "$WORK/meta-data"
|
||||||
@@ -245,7 +245,7 @@ echo " cloud-init runs the toolchain install on first boot (give it a minute)."
|
|||||||
|
|
||||||
# ── 6. boot ──────────────────────────────────────────────────────────────────
|
# ── 6. boot ──────────────────────────────────────────────────────────────────
|
||||||
if [[ $DO_BOOT -eq 1 ]]; then
|
if [[ $DO_BOOT -eq 1 ]]; then
|
||||||
echo "⛧ booting '$NAME' (GUI) …" >&2
|
echo "† booting '$NAME' (GUI) …" >&2
|
||||||
VBoxManage startvm "$NAME" --type gui >/dev/null
|
VBoxManage startvm "$NAME" --type gui >/dev/null
|
||||||
echo "✓ launched $NAME" >&2
|
echo "✓ launched $NAME" >&2
|
||||||
else
|
else
|
||||||
|
|||||||
+47
-47
@@ -430,7 +430,7 @@ impl App {
|
|||||||
self.users = users;
|
self.users = users;
|
||||||
self.connected = true;
|
self.connected = true;
|
||||||
self.chat_scroll = 0;
|
self.chat_scroll = 0;
|
||||||
self.sys(format!("joined as {} ⛧", self.me));
|
self.sys(format!("joined as {} †", self.me));
|
||||||
self.sys("/sbx <docker|podman|multipass|vbox|local> · /drive (F2 releases) · /ai start · /ai <question> · /send <user> <file> · /sendroom <file> · /pw show password · /help full command list · PgUp/PgDn scroll chat · ctrl-q quit");
|
self.sys("/sbx <docker|podman|multipass|vbox|local> · /drive (F2 releases) · /ai start · /ai <question> · /send <user> <file> · /sendroom <file> · /pw show password · /help full command list · PgUp/PgDn scroll chat · ctrl-q quit");
|
||||||
}
|
}
|
||||||
Net::Message(l) => self.push_line(l),
|
Net::Message(l) => self.push_line(l),
|
||||||
@@ -487,7 +487,7 @@ impl App {
|
|||||||
parser: vt100::Parser::new(rows.max(1), cols.max(1), 2000),
|
parser: vt100::Parser::new(rows.max(1), cols.max(1), 2000),
|
||||||
backend: backend.clone(),
|
backend: backend.clone(),
|
||||||
});
|
});
|
||||||
self.sys(format!("⛧ sandbox summoned ({backend}) — F2 to drive"));
|
self.sys(format!("† sandbox summoned ({backend}) — F2 to drive"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -497,7 +497,7 @@ impl App {
|
|||||||
self.owner = None;
|
self.owner = None;
|
||||||
self.drivers.clear();
|
self.drivers.clear();
|
||||||
self.sudoers.clear();
|
self.sudoers.clear();
|
||||||
self.sys("⛧ sandbox dismissed");
|
self.sys("† sandbox dismissed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Net::SbxResize { rows, cols } => {
|
Net::SbxResize { rows, cols } => {
|
||||||
@@ -519,22 +519,22 @@ impl App {
|
|||||||
let new: std::collections::HashSet<String> = drivers.into_iter().collect();
|
let new: std::collections::HashSet<String> = drivers.into_iter().collect();
|
||||||
let sudo: std::collections::HashSet<String> = sudoers.into_iter().collect();
|
let sudo: std::collections::HashSet<String> = sudoers.into_iter().collect();
|
||||||
if !owner.is_empty() && self.owner.as_deref() != Some(owner.as_str()) {
|
if !owner.is_empty() && self.owner.as_deref() != Some(owner.as_str()) {
|
||||||
self.sys(format!("⛧ {owner} is the superuser (sandbox owner)"));
|
self.sys(format!("† {owner} is the superuser (sandbox owner)"));
|
||||||
}
|
}
|
||||||
if new.contains(&self.me)
|
if new.contains(&self.me)
|
||||||
&& !self.drivers.contains(&self.me)
|
&& !self.drivers.contains(&self.me)
|
||||||
&& self.owner.is_some()
|
&& self.owner.is_some()
|
||||||
{
|
{
|
||||||
self.sys("⛧ you were granted drive (F2 to take the shell)");
|
self.sys("† you were granted drive (F2 to take the shell)");
|
||||||
} else if !new.contains(&self.me) && self.drivers.contains(&self.me) {
|
} else if !new.contains(&self.me) && self.drivers.contains(&self.me) {
|
||||||
self.driving = false;
|
self.driving = false;
|
||||||
self.sys("⛧ your drive permission was revoked");
|
self.sys("† your drive permission was revoked");
|
||||||
}
|
}
|
||||||
if sudo.contains(&self.me)
|
if sudo.contains(&self.me)
|
||||||
&& !self.sudoers.contains(&self.me)
|
&& !self.sudoers.contains(&self.me)
|
||||||
&& self.owner.is_some()
|
&& self.owner.is_some()
|
||||||
{
|
{
|
||||||
self.sys("⛧ you were granted sudo (superuser) in the VM");
|
self.sys("† you were granted sudo (superuser) in the VM");
|
||||||
}
|
}
|
||||||
self.owner = Some(owner).filter(|o| !o.is_empty());
|
self.owner = Some(owner).filter(|o| !o.is_empty());
|
||||||
self.drivers = new;
|
self.drivers = new;
|
||||||
@@ -548,7 +548,7 @@ impl App {
|
|||||||
// Skip our own echo — we already saw the local "launched" line.
|
// Skip our own echo — we already saw the local "launched" line.
|
||||||
if by != self.me {
|
if by != self.me {
|
||||||
self.sys(format!(
|
self.sys(format!(
|
||||||
"⛧ {by} opened ‘{vm}’ locally — `/sbx gui {vm}` to open your own copy"
|
"† {by} opened ‘{vm}’ locally — `/sbx gui {vm}` to open your own copy"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -771,7 +771,7 @@ fn handle_ft(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
app.sys(format!(
|
app.sys(format!(
|
||||||
"⛧ {} offers {} ({}{}){} — /accept or /reject",
|
"† {} offers {} ({}{}){} — /accept or /reject",
|
||||||
o.from,
|
o.from,
|
||||||
o.name,
|
o.name,
|
||||||
ft::human(o.size as usize),
|
ft::human(o.size as usize),
|
||||||
@@ -868,7 +868,7 @@ fn handle_ft(
|
|||||||
match ft::commit(downloads, &t.meta, &tmp) {
|
match ft::commit(downloads, &t.meta, &tmp) {
|
||||||
Ok(p) => {
|
Ok(p) => {
|
||||||
app.sys(format!(
|
app.sys(format!(
|
||||||
"⛧ saved {} ({}) — verified ✓",
|
"† saved {} ({}) — verified ✓",
|
||||||
p.display(),
|
p.display(),
|
||||||
ft::human(t.meta.size as usize)
|
ft::human(t.meta.size as usize)
|
||||||
));
|
));
|
||||||
@@ -1044,7 +1044,7 @@ pub async fn run(params: net::ConnParams, mut session: Session, mut theme: Theme
|
|||||||
let SudoPrompt { password, pending } =
|
let SudoPrompt { password, pending } =
|
||||||
app.sudo_prompt.take().unwrap();
|
app.sudo_prompt.take().unwrap();
|
||||||
if password.is_empty() {
|
if password.is_empty() {
|
||||||
app.sys("⛧ cancelled — empty password");
|
app.sys("† cancelled — empty password");
|
||||||
} else {
|
} else {
|
||||||
match pending {
|
match pending {
|
||||||
PendingPrivileged::Launch(pending) => {
|
PendingPrivileged::Launch(pending) => {
|
||||||
@@ -1089,7 +1089,7 @@ pub async fn run(params: net::ConnParams, mut session: Session, mut theme: Theme
|
|||||||
}
|
}
|
||||||
KeyCode::Esc => {
|
KeyCode::Esc => {
|
||||||
app.sudo_prompt = None;
|
app.sudo_prompt = None;
|
||||||
app.sys("⛧ sudo cancelled — launch aborted");
|
app.sys("† sudo cancelled — launch aborted");
|
||||||
}
|
}
|
||||||
KeyCode::Backspace => {
|
KeyCode::Backspace => {
|
||||||
if let Some(p) = app.sudo_prompt.as_mut() {
|
if let Some(p) = app.sudo_prompt.as_mut() {
|
||||||
@@ -1121,7 +1121,7 @@ pub async fn run(params: net::ConnParams, mut session: Session, mut theme: Theme
|
|||||||
app.agent_sbx_allow = false;
|
app.agent_sbx_allow = false;
|
||||||
let _ = sb.write_input(&[0x03]); // Ctrl-C into the shell
|
let _ = sb.write_input(&[0x03]); // Ctrl-C into the shell
|
||||||
broadcast_acl(&out_tx, &session.room, &app);
|
broadcast_acl(&out_tx, &session.room, &app);
|
||||||
app.sys("⛧ kill switch — revoked all drive + interrupted the shell");
|
app.sys("† kill switch — revoked all drive + interrupted the shell");
|
||||||
} else {
|
} else {
|
||||||
app.sys("kill switch is for the sandbox owner (you don't hold the PTY)");
|
app.sys("kill switch is for the sandbox owner (you don't hold the PTY)");
|
||||||
}
|
}
|
||||||
@@ -1144,7 +1144,7 @@ pub async fn run(params: net::ConnParams, mut session: Session, mut theme: Theme
|
|||||||
// stays responsive, then re-attach the websocket on success.
|
// stays responsive, then re-attach the websocket on success.
|
||||||
if !app.reconnecting {
|
if !app.reconnecting {
|
||||||
app.reconnecting = true;
|
app.reconnecting = true;
|
||||||
app.sys("⛧ reconnecting…");
|
app.sys("† reconnecting…");
|
||||||
let p = params.clone();
|
let p = params.clone();
|
||||||
let rtx = recon_tx.clone();
|
let rtx = recon_tx.clone();
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
@@ -1186,7 +1186,7 @@ pub async fn run(params: net::ConnParams, mut session: Session, mut theme: Theme
|
|||||||
}
|
}
|
||||||
KeyCode::Esc => {
|
KeyCode::Esc => {
|
||||||
app.vbox_picker = None;
|
app.vbox_picker = None;
|
||||||
app.sys("⛧ picker dismissed");
|
app.sys("† picker dismissed");
|
||||||
}
|
}
|
||||||
_ => {} // ignore other keys so the picker stays put
|
_ => {} // ignore other keys so the picker stays put
|
||||||
}
|
}
|
||||||
@@ -1260,7 +1260,7 @@ pub async fn run(params: net::ConnParams, mut session: Session, mut theme: Theme
|
|||||||
if app.sandbox.is_some() {
|
if app.sandbox.is_some() {
|
||||||
app.layout.cycle_zoom();
|
app.layout.cycle_zoom();
|
||||||
announced_dims = None; // re-sync PTY to the new height
|
announced_dims = None; // re-sync PTY to the new height
|
||||||
app.sys(format!("⛧ {}", app.layout.describe()));
|
app.sys(format!("† {}", app.layout.describe()));
|
||||||
} else {
|
} else {
|
||||||
app.sys("no sandbox to fullscreen — /sbx <type> first");
|
app.sys("no sandbox to fullscreen — /sbx <type> first");
|
||||||
}
|
}
|
||||||
@@ -1271,10 +1271,10 @@ pub async fn run(params: net::ConnParams, mut session: Session, mut theme: Theme
|
|||||||
app.cycle_focus();
|
app.cycle_focus();
|
||||||
match app.focused_pane {
|
match app.focused_pane {
|
||||||
Some(p) => app.sys(format!(
|
Some(p) => app.sys(format!(
|
||||||
"⛧ editing {} — arrows resize · Esc done",
|
"† editing {} — arrows resize · Esc done",
|
||||||
pane_label(p)
|
pane_label(p)
|
||||||
)),
|
)),
|
||||||
None => app.sys("⛧ layout editing off"),
|
None => app.sys("† layout editing off"),
|
||||||
}
|
}
|
||||||
} else if app.focused_pane.is_some() && !app.driving {
|
} else if app.focused_pane.is_some() && !app.driving {
|
||||||
// Interactive layout editing: a pane is selected (via click
|
// Interactive layout editing: a pane is selected (via click
|
||||||
@@ -1293,7 +1293,7 @@ pub async fn run(params: net::ConnParams, mut session: Session, mut theme: Theme
|
|||||||
match k.code {
|
match k.code {
|
||||||
KeyCode::Esc | KeyCode::Enter => {
|
KeyCode::Esc | KeyCode::Enter => {
|
||||||
app.focused_pane = None;
|
app.focused_pane = None;
|
||||||
app.sys(format!("⛧ {}", app.layout.describe()));
|
app.sys(format!("† {}", app.layout.describe()));
|
||||||
}
|
}
|
||||||
_ if dir.is_some() => {
|
_ if dir.is_some() => {
|
||||||
// Every pane is resizable on both axes wherever a
|
// Every pane is resizable on both axes wherever a
|
||||||
@@ -1305,7 +1305,7 @@ pub async fn run(params: net::ConnParams, mut session: Session, mut theme: Theme
|
|||||||
// rect (height *or* width now), so force a
|
// rect (height *or* width now), so force a
|
||||||
// PTY re-sync to rebroadcast the new grid.
|
// PTY re-sync to rebroadcast the new grid.
|
||||||
announced_dims = None;
|
announced_dims = None;
|
||||||
app.sys(format!("⛧ {}", app.layout.describe()));
|
app.sys(format!("† {}", app.layout.describe()));
|
||||||
}
|
}
|
||||||
Resize::NoAxisHere => {
|
Resize::NoAxisHere => {
|
||||||
app.sys(
|
app.sys(
|
||||||
@@ -1426,7 +1426,7 @@ pub async fn run(params: net::ConnParams, mut session: Session, mut theme: Theme
|
|||||||
Some(p) if p != Pane::Input => {
|
Some(p) if p != Pane::Input => {
|
||||||
app.focused_pane = Some(p);
|
app.focused_pane = Some(p);
|
||||||
app.sys(format!(
|
app.sys(format!(
|
||||||
"⛧ editing {} — arrows resize · Esc done",
|
"† editing {} — arrows resize · Esc done",
|
||||||
pane_label(p)
|
pane_label(p)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -1489,7 +1489,7 @@ pub async fn run(params: net::ConnParams, mut session: Session, mut theme: Theme
|
|||||||
.await;
|
.await;
|
||||||
let _ = match res {
|
let _ = match res {
|
||||||
Ok(Ok(vm)) => tx.send(Net::Sys(format!(
|
Ok(Ok(vm)) => tx.send(Net::Sys(format!(
|
||||||
"⛧ imported VM ‘{vm}’ — `/sbx vbox gui {vm}` to boot it"
|
"† imported VM ‘{vm}’ — `/sbx vbox gui {vm}` to boot it"
|
||||||
))),
|
))),
|
||||||
Ok(Err(e)) => tx.send(Net::Sys(format!(
|
Ok(Err(e)) => tx.send(Net::Sys(format!(
|
||||||
"(received .ova not auto-imported: {e})"
|
"(received .ova not auto-imported: {e})"
|
||||||
@@ -1513,7 +1513,7 @@ pub async fn run(params: net::ConnParams, mut session: Session, mut theme: Theme
|
|||||||
.await;
|
.await;
|
||||||
let _ = match res {
|
let _ = match res {
|
||||||
Ok(Ok(dest)) => tx.send(Net::Sys(format!(
|
Ok(Ok(dest)) => tx.send(Net::Sys(format!(
|
||||||
"⛧ bridged into sandbox → {dest}"
|
"† bridged into sandbox → {dest}"
|
||||||
))),
|
))),
|
||||||
Ok(Err(e)) => tx.send(Net::Sys(format!(
|
Ok(Err(e)) => tx.send(Net::Sys(format!(
|
||||||
"(received file not bridged into sandbox: {e})"
|
"(received file not bridged into sandbox: {e})"
|
||||||
@@ -1594,7 +1594,7 @@ pub async fn run(params: net::ConnParams, mut session: Session, mut theme: Theme
|
|||||||
parser: vt100::Parser::new(rows.max(1), cols.max(1), 2000),
|
parser: vt100::Parser::new(rows.max(1), cols.max(1), 2000),
|
||||||
backend: backend.label().to_string(),
|
backend: backend.label().to_string(),
|
||||||
});
|
});
|
||||||
app.sys(format!("⛧ sandbox summoned ({}) — /drive to take the shell", backend.label()));
|
app.sys(format!("† sandbox summoned ({}) — /drive to take the shell", backend.label()));
|
||||||
app.owner = Some(app.me.clone());
|
app.owner = Some(app.me.clone());
|
||||||
app.drivers.clear();
|
app.drivers.clear();
|
||||||
app.drivers.insert(app.me.clone());
|
app.drivers.insert(app.me.clone());
|
||||||
@@ -1625,7 +1625,7 @@ pub async fn run(params: net::ConnParams, mut session: Session, mut theme: Theme
|
|||||||
match net::open(&session, tx.clone()).await {
|
match net::open(&session, tx.clone()).await {
|
||||||
Ok(w) => {
|
Ok(w) => {
|
||||||
let _ = sink_tx.send(w);
|
let _ = sink_tx.send(w);
|
||||||
app.sys("⛧ websocket re-attached — syncing…");
|
app.sys("† websocket re-attached — syncing…");
|
||||||
// If we host the sandbox, re-announce it so the
|
// If we host the sandbox, re-announce it so the
|
||||||
// rest of the house re-syncs the shared shell.
|
// rest of the house re-syncs the shared shell.
|
||||||
if let Some((be, sbx_name)) = &broker_meta {
|
if let Some((be, sbx_name)) = &broker_meta {
|
||||||
@@ -1774,15 +1774,15 @@ fn handle_command(
|
|||||||
// room — other peers keep their own history.
|
// room — other peers keep their own history.
|
||||||
app.lines.clear();
|
app.lines.clear();
|
||||||
app.chat_scroll = 0;
|
app.chat_scroll = 0;
|
||||||
app.sys("⛧ chat cleared");
|
app.sys("† chat cleared");
|
||||||
} else if line == "/pw" || line == "/password" {
|
} else if line == "/pw" || line == "/password" {
|
||||||
// Show the room password locally (never broadcast). Handy when the
|
// Show the room password locally (never broadcast). Handy when the
|
||||||
// server's password was autogenerated and you need to read it off / share
|
// server's password was autogenerated and you need to read it off / share
|
||||||
// it out-of-band to invite someone into the room.
|
// it out-of-band to invite someone into the room.
|
||||||
if app.password.is_empty() {
|
if app.password.is_empty() {
|
||||||
app.sys("⛧ no room password (joined without one)");
|
app.sys("† no room password (joined without one)");
|
||||||
} else {
|
} else {
|
||||||
app.sys(format!("⛧ room password: {}", app.password));
|
app.sys(format!("† room password: {}", app.password));
|
||||||
}
|
}
|
||||||
} else if let Some(rest) = line.strip_prefix("/theme") {
|
} else if let Some(rest) = line.strip_prefix("/theme") {
|
||||||
// Live vestment switch: `/theme <name>`, or bare `/theme` to list options.
|
// Live vestment switch: `/theme <name>`, or bare `/theme` to list options.
|
||||||
@@ -1842,7 +1842,7 @@ fn handle_command(
|
|||||||
let mut resized = false;
|
let mut resized = false;
|
||||||
match cmd {
|
match cmd {
|
||||||
"" => {
|
"" => {
|
||||||
app.sys(format!("⛧ layout: {}", app.layout.describe()));
|
app.sys(format!("† layout: {}", app.layout.describe()));
|
||||||
app.sys(" resize live: F4 fullscreen · F5 or click a pane, then arrows · Esc done");
|
app.sys(" resize live: F4 fullscreen · F5 or click a pane, then arrows · Esc done");
|
||||||
app.sys(format!(
|
app.sys(format!(
|
||||||
" presets: {} — /layout save <name> · load <name> · rm <name>",
|
" presets: {} — /layout save <name> · load <name> · rm <name>",
|
||||||
@@ -1854,11 +1854,11 @@ fn handle_command(
|
|||||||
app.layout = Layout::default();
|
app.layout = Layout::default();
|
||||||
app.layout.set_roster_width(roster); // keep your roster choice on reset
|
app.layout.set_roster_width(roster); // keep your roster choice on reset
|
||||||
resized = true;
|
resized = true;
|
||||||
app.sys(format!("⛧ layout reset — {}", app.layout.describe()));
|
app.sys(format!("† layout reset — {}", app.layout.describe()));
|
||||||
}
|
}
|
||||||
"save" if !arg.is_empty() => match app.layout.save(arg) {
|
"save" if !arg.is_empty() => match app.layout.save(arg) {
|
||||||
Ok(slug) => app.sys(format!(
|
Ok(slug) => app.sys(format!(
|
||||||
"⛧ saved layout '{slug}' — re-apply anytime with /layout load {slug}"
|
"† saved layout '{slug}' — re-apply anytime with /layout load {slug}"
|
||||||
)),
|
)),
|
||||||
Err(e) => app.err(format!("couldn't save layout: {e}")),
|
Err(e) => app.err(format!("couldn't save layout: {e}")),
|
||||||
},
|
},
|
||||||
@@ -1866,7 +1866,7 @@ fn handle_command(
|
|||||||
Ok(l) => {
|
Ok(l) => {
|
||||||
app.layout = l;
|
app.layout = l;
|
||||||
resized = true;
|
resized = true;
|
||||||
app.sys(format!("⛧ loaded layout '{arg}' — {}", app.layout.describe()));
|
app.sys(format!("† loaded layout '{arg}' — {}", app.layout.describe()));
|
||||||
}
|
}
|
||||||
Err(_) => app.err(format!(
|
Err(_) => app.err(format!(
|
||||||
"no saved layout '{arg}' — saved: {}",
|
"no saved layout '{arg}' — saved: {}",
|
||||||
@@ -1874,10 +1874,10 @@ fn handle_command(
|
|||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
"list" | "presets" | "ls" => {
|
"list" | "presets" | "ls" => {
|
||||||
app.sys(format!("⛧ saved layouts: {}", once_or_none(Layout::available())));
|
app.sys(format!("† saved layouts: {}", once_or_none(Layout::available())));
|
||||||
}
|
}
|
||||||
"rm" | "delete" | "del" if !arg.is_empty() => match Layout::remove(arg) {
|
"rm" | "delete" | "del" if !arg.is_empty() => match Layout::remove(arg) {
|
||||||
Ok(()) => app.sys(format!("⛧ deleted layout '{arg}'")),
|
Ok(()) => app.sys(format!("† deleted layout '{arg}'")),
|
||||||
Err(e) => app.err(format!("{e}")),
|
Err(e) => app.err(format!("{e}")),
|
||||||
},
|
},
|
||||||
// Bare `/layout <name>` → load a saved preset if it exists.
|
// Bare `/layout <name>` → load a saved preset if it exists.
|
||||||
@@ -1885,7 +1885,7 @@ fn handle_command(
|
|||||||
Ok(l) => {
|
Ok(l) => {
|
||||||
app.layout = l;
|
app.layout = l;
|
||||||
resized = true;
|
resized = true;
|
||||||
app.sys(format!("⛧ loaded layout '{name}' — {}", app.layout.describe()));
|
app.sys(format!("† loaded layout '{name}' — {}", app.layout.describe()));
|
||||||
}
|
}
|
||||||
Err(_) => app.sys(
|
Err(_) => app.sys(
|
||||||
"usage: /layout [save <name>|load <name>|list|rm <name>|reset] — resize live with F4/F5 or click",
|
"usage: /layout [save <name>|load <name>|list|rm <name>|reset] — resize live with F4/F5 or click",
|
||||||
@@ -1901,7 +1901,7 @@ fn handle_command(
|
|||||||
app.sys("no sandbox running — /sbx <type> first");
|
app.sys("no sandbox running — /sbx <type> first");
|
||||||
} else if app.can_drive() {
|
} else if app.can_drive() {
|
||||||
app.driving = true;
|
app.driving = true;
|
||||||
app.sys("⛧ drive mode ON — type into the shell (Esc reaches vim etc.) · press F2 to release");
|
app.sys("† drive mode ON — type into the shell (Esc reaches vim etc.) · press F2 to release");
|
||||||
} else {
|
} else {
|
||||||
app.sys("you don't have drive permission — the owner can /grant you");
|
app.sys("you don't have drive permission — the owner can /grant you");
|
||||||
}
|
}
|
||||||
@@ -2167,7 +2167,7 @@ fn handle_command(
|
|||||||
let res = tokio::task::spawn_blocking(move || sbx::save_state(be, &name, &label, local)).await;
|
let res = tokio::task::spawn_blocking(move || sbx::save_state(be, &name, &label, local)).await;
|
||||||
let _ = match res {
|
let _ = match res {
|
||||||
Ok(Ok(desc)) => tx.send(Net::Sys(format!(
|
Ok(Ok(desc)) => tx.send(Net::Sys(format!(
|
||||||
"⛧ saved sandbox → {desc} · reload with `/sbx load {lbl}`"))),
|
"† saved sandbox → {desc} · reload with `/sbx load {lbl}`"))),
|
||||||
Ok(Err(e)) => tx.send(Net::Err(format!("save failed: {e}"))),
|
Ok(Err(e)) => tx.send(Net::Err(format!("save failed: {e}"))),
|
||||||
Err(e) => tx.send(Net::Err(format!("save task: {e}"))),
|
Err(e) => tx.send(Net::Err(format!("save task: {e}"))),
|
||||||
};
|
};
|
||||||
@@ -2238,7 +2238,7 @@ fn handle_command(
|
|||||||
.await;
|
.await;
|
||||||
match res {
|
match res {
|
||||||
Ok(Ok(desc)) => {
|
Ok(Ok(desc)) => {
|
||||||
let _ = atx.send(Net::Sys(format!("⛧ {desc} · booting…")));
|
let _ = atx.send(Net::Sys(format!("† {desc} · booting…")));
|
||||||
spawn_launch(
|
spawn_launch(
|
||||||
sbx::Backend::Multipass,
|
sbx::Backend::Multipass,
|
||||||
sbx::Backend::Multipass.default_image().to_string(),
|
sbx::Backend::Multipass.default_image().to_string(),
|
||||||
@@ -2341,7 +2341,7 @@ fn handle_command(
|
|||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
let _ = match res {
|
let _ = match res {
|
||||||
Ok(Ok(desc)) => tx.send(Net::Sys(format!("⛧ saved VM → {desc}"))),
|
Ok(Ok(desc)) => tx.send(Net::Sys(format!("† saved VM → {desc}"))),
|
||||||
Ok(Err(e)) => tx.send(Net::Err(format!("vmsave failed: {e}"))),
|
Ok(Err(e)) => tx.send(Net::Err(format!("vmsave failed: {e}"))),
|
||||||
Err(e) => tx.send(Net::Err(format!("vmsave task: {e}"))),
|
Err(e) => tx.send(Net::Err(format!("vmsave task: {e}"))),
|
||||||
};
|
};
|
||||||
@@ -2400,7 +2400,7 @@ fn handle_command(
|
|||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
let _ = match res {
|
let _ = match res {
|
||||||
Ok(Ok(desc)) => tx.send(Net::Sys(format!("⛧ {desc}"))),
|
Ok(Ok(desc)) => tx.send(Net::Sys(format!("† {desc}"))),
|
||||||
Ok(Err(e)) => tx.send(Net::Err(format!("vmload failed: {e}"))),
|
Ok(Err(e)) => tx.send(Net::Err(format!("vmload failed: {e}"))),
|
||||||
Err(e) => tx.send(Net::Err(format!("vmload task: {e}"))),
|
Err(e) => tx.send(Net::Err(format!("vmload task: {e}"))),
|
||||||
};
|
};
|
||||||
@@ -2463,7 +2463,7 @@ fn handle_command(
|
|||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
let _ = match res {
|
let _ = match res {
|
||||||
Ok(Ok(desc)) => tx.send(Net::Sys(format!("⛧ {desc}"))),
|
Ok(Ok(desc)) => tx.send(Net::Sys(format!("† {desc}"))),
|
||||||
Ok(Err(e)) => tx.send(Net::Err(format!("vmlib install failed: {e}"))),
|
Ok(Err(e)) => tx.send(Net::Err(format!("vmlib install failed: {e}"))),
|
||||||
Err(e) => tx.send(Net::Err(format!("vmlib install task: {e}"))),
|
Err(e) => tx.send(Net::Err(format!("vmlib install task: {e}"))),
|
||||||
};
|
};
|
||||||
@@ -2596,7 +2596,7 @@ fn handle_command(
|
|||||||
if let Some(mut child) = agent.take() {
|
if let Some(mut child) = agent.take() {
|
||||||
let _ = child.kill();
|
let _ = child.kill();
|
||||||
let _ = child.wait();
|
let _ = child.wait();
|
||||||
app.sys("⛧ dismissed the AI agent");
|
app.sys("† dismissed the AI agent");
|
||||||
// Drop any sandbox drive the agent held so a dead handle can't act.
|
// Drop any sandbox drive the agent held so a dead handle can't act.
|
||||||
app.agent_sbx_allow = false;
|
app.agent_sbx_allow = false;
|
||||||
let revoked = app
|
let revoked = app
|
||||||
@@ -2680,7 +2680,7 @@ fn handle_command(
|
|||||||
None => String::new(),
|
None => String::new(),
|
||||||
};
|
};
|
||||||
app.sys(format!(
|
app.sys(format!(
|
||||||
"⛧ summoning {name} ({desc}{hdesc})… it will announce when online"
|
"† summoning {name} ({desc}{hdesc})… it will announce when online"
|
||||||
));
|
));
|
||||||
if grant_sbx {
|
if grant_sbx {
|
||||||
// Grant now if a sandbox is already running; otherwise the
|
// Grant now if a sandbox is already running; otherwise the
|
||||||
@@ -2690,7 +2690,7 @@ fn handle_command(
|
|||||||
broadcast_acl(out_tx, room, app);
|
broadcast_acl(out_tx, room, app);
|
||||||
}
|
}
|
||||||
app.sys(format!(
|
app.sys(format!(
|
||||||
"⛧ {name} will get sandbox drive — Ctrl-X kills all drive in a pinch"
|
"† {name} will get sandbox drive — Ctrl-X kills all drive in a pinch"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2885,7 +2885,7 @@ fn open_vbox_picker(app: &mut App) {
|
|||||||
match sbx::list_vms() {
|
match sbx::list_vms() {
|
||||||
Ok(vms) if !vms.is_empty() => {
|
Ok(vms) if !vms.is_empty() => {
|
||||||
app.vbox_picker = Some(VboxPicker { vms, selected: 0 });
|
app.vbox_picker = Some(VboxPicker { vms, selected: 0 });
|
||||||
app.sys("⛧ pick a VM — ↑/↓ move · Enter/Tab choose · Esc dismiss");
|
app.sys("† pick a VM — ↑/↓ move · Enter/Tab choose · Esc dismiss");
|
||||||
}
|
}
|
||||||
Ok(_) => app.sys(
|
Ok(_) => app.sys(
|
||||||
"no VirtualBox VMs registered — a host can /send you a .ova, then `/sbx vbox gui <vm> yes` to import it",
|
"no VirtualBox VMs registered — a host can /send you a .ova, then `/sbx vbox gui <vm> yes` to import it",
|
||||||
@@ -2931,7 +2931,7 @@ fn launch_vbox_new(app: &mut App, name: String, user: String, app_tx: &Unbounded
|
|||||||
let (n, u) = (name.clone(), user);
|
let (n, u) = (name.clone(), user);
|
||||||
let res = tokio::task::spawn_blocking(move || sbx::vbox_new(&n, &u)).await;
|
let res = tokio::task::spawn_blocking(move || sbx::vbox_new(&n, &u)).await;
|
||||||
let _ = match res {
|
let _ = match res {
|
||||||
Ok(Ok(desc)) => tx.send(Net::Sys(format!("⛧ {desc}"))),
|
Ok(Ok(desc)) => tx.send(Net::Sys(format!("† {desc}"))),
|
||||||
Ok(Err(e)) => tx.send(Net::Err(format!("vbox new failed: {e}"))),
|
Ok(Err(e)) => tx.send(Net::Err(format!("vbox new failed: {e}"))),
|
||||||
Err(e) => tx.send(Net::Err(format!("vbox new task: {e}"))),
|
Err(e) => tx.send(Net::Err(format!("vbox new task: {e}"))),
|
||||||
};
|
};
|
||||||
@@ -3092,7 +3092,7 @@ fn spawn_vm_execute(
|
|||||||
// Tell the room the shared VM is live (others can open their own).
|
// Tell the room the shared VM is live (others can open their own).
|
||||||
let frame = json!({"_sbx": "vm", "vm": announce_vm});
|
let frame = json!({"_sbx": "vm", "vm": announce_vm});
|
||||||
let _ = out.send(WsMsg::Text(room.encrypt(frame.to_string().as_bytes())));
|
let _ = out.send(WsMsg::Text(room.encrypt(frame.to_string().as_bytes())));
|
||||||
tx.send(Net::Sys(format!("⛧ {desc}")))
|
tx.send(Net::Sys(format!("† {desc}")))
|
||||||
}
|
}
|
||||||
Ok(Err(e)) => tx.send(Net::Err(e)),
|
Ok(Err(e)) => tx.send(Net::Err(e)),
|
||||||
Err(e) => tx.send(Net::Err(format!("gui task: {e}"))),
|
Err(e) => tx.send(Net::Err(format!("gui task: {e}"))),
|
||||||
|
|||||||
+2
-2
@@ -29,7 +29,7 @@ pub struct Theme {
|
|||||||
/// Width of the roster column.
|
/// Width of the roster column.
|
||||||
pub roster_width: u16,
|
pub roster_width: u16,
|
||||||
/// Glyph flanking the "hack-house" title (and used for occult accents).
|
/// Glyph flanking the "hack-house" title (and used for occult accents).
|
||||||
/// Each theme picks its own sigil (crypt: ✝, church: ⛧, …).
|
/// Each theme picks its own sigil (crypt: ✝, church: †, …).
|
||||||
pub sigil: String,
|
pub sigil: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +179,7 @@ fn slugify(name: &str) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Occult glyphs the randomizer can stamp as the title sigil.
|
/// Occult glyphs the randomizer can stamp as the title sigil.
|
||||||
const SIGILS: [&str; 12] = ["✝", "⛧", "☥", "†", "‡", "✟", "♰", "☩", "⸸", "⯐", "✠", "☦"];
|
const SIGILS: [&str; 12] = ["✝", "☧", "☥", "†", "‡", "✟", "♰", "☩", "⸸", "⯐", "✠", "☦"];
|
||||||
|
|
||||||
/// Arcane name fragments — `<adj>-<noun>` makes a memorable vestment name.
|
/// Arcane name fragments — `<adj>-<noun>` makes a memorable vestment name.
|
||||||
const NAME_ADJ: [&str; 16] = [
|
const NAME_ADJ: [&str; 16] = [
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
name = "church"
|
name = "church"
|
||||||
border = "#19b3ff" # cyan window-chrome
|
border = "#19b3ff" # cyan window-chrome
|
||||||
title = "#7df9ff" # bright cyan
|
title = "#7df9ff" # bright cyan
|
||||||
accent = "#39ff14" # acid green — ⛧ glyphs, prompt
|
accent = "#39ff14" # acid green — † glyphs, prompt
|
||||||
dim = "#475a7a" # muted slate-blue
|
dim = "#475a7a" # muted slate-blue
|
||||||
me = "#39ff14" # your messages
|
me = "#39ff14" # your messages
|
||||||
other = "#56c8ff" # others' messages (soft cyan)
|
other = "#56c8ff" # others' messages (soft cyan)
|
||||||
@@ -10,4 +10,4 @@ system = "#b46cff" # system / occult lines (purple)
|
|||||||
input = "#39ff14"
|
input = "#39ff14"
|
||||||
roster_me = "#ff39c0" # you / owner (hot magenta)
|
roster_me = "#ff39c0" # you / owner (hot magenta)
|
||||||
roster_width = 22
|
roster_width = 22
|
||||||
sigil = "⛧" # inverted pentagram
|
sigil = "†" # dagger
|
||||||
|
|||||||
Reference in New Issue
Block a user