[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
819
components/terminal/osc7Setup.ts
Normal file
819
components/terminal/osc7Setup.ts
Normal file
@@ -0,0 +1,819 @@
|
||||
export const OSC7_MARKER = "Netcatty OSC 7 cwd tracking";
|
||||
|
||||
export const OSC7_SETUP_TARGETS = [
|
||||
"~/.bashrc",
|
||||
"${ZDOTDIR:-~}/.zshrc",
|
||||
"~/.config/fish/config.fish",
|
||||
] as const;
|
||||
|
||||
export const OSC7_SETUP_SHELL_MARKER = "__NETCATTY_OSC7_SETUP_SHELL__=";
|
||||
export const OSC7_SETUP_CONFIG_MARKER = "__NETCATTY_OSC7_SETUP_CONFIG__=";
|
||||
// Emitted when the silent exec-channel setup finds the active terminal shell
|
||||
// owned by another user (after `su` / `sudo su`); the exec channel cannot
|
||||
// configure that shell, so the renderer retypes the setup inside the terminal
|
||||
// where it runs as the target user (#1942).
|
||||
export const OSC7_SETUP_OTHER_USER_MARKER = "__NETCATTY_OSC7_SETUP_OTHER_USER_SHELL__=";
|
||||
|
||||
export type Osc7SetupActionContext = {
|
||||
protocol?: string;
|
||||
isLocalConnection?: boolean;
|
||||
isSerialConnection?: boolean;
|
||||
isNetworkDevice?: boolean;
|
||||
};
|
||||
|
||||
export type Osc7SetupShell = "bash" | "zsh" | "fish";
|
||||
|
||||
export type Osc7SetupMetadata = {
|
||||
shell: Osc7SetupShell;
|
||||
configPath: string;
|
||||
};
|
||||
|
||||
export type Osc7SetupRunResult = {
|
||||
success: boolean;
|
||||
pending?: boolean;
|
||||
stdout?: string;
|
||||
stderr?: string;
|
||||
code?: number | null;
|
||||
error?: string;
|
||||
reloadCommand?: string;
|
||||
/** Setup was retyped into the terminal for a user-switched (su/sudo) shell. */
|
||||
sentToTerminal?: boolean;
|
||||
};
|
||||
|
||||
export type RunOsc7SetupActionOptions = {
|
||||
status: string;
|
||||
sessionId: string;
|
||||
setupCommand: string;
|
||||
setupOsc7Tracking?: (
|
||||
sessionId: string,
|
||||
command: string,
|
||||
) => Promise<Osc7SetupRunResult>;
|
||||
writeToSession: (
|
||||
sessionId: string,
|
||||
data: string,
|
||||
options?: {
|
||||
automated?: boolean;
|
||||
logRewrite?: { sentCommand: string; displayCommand: string };
|
||||
},
|
||||
) => void;
|
||||
writeLocalTerminalData?: (data: string) => void;
|
||||
};
|
||||
|
||||
export const shouldOfferOsc7SetupAction = ({
|
||||
protocol,
|
||||
isLocalConnection,
|
||||
isSerialConnection,
|
||||
isNetworkDevice,
|
||||
}: Osc7SetupActionContext): boolean =>
|
||||
!isLocalConnection
|
||||
&& !isSerialConnection
|
||||
&& !isNetworkDevice
|
||||
&& protocol !== "telnet";
|
||||
|
||||
const DOLLAR = "$";
|
||||
|
||||
const URL_PATH_AWK_SCRIPT = String.raw`BEGIN {
|
||||
for (i = 0; i < 256; i++) {
|
||||
c = sprintf("%c", i)
|
||||
ord[c] = i
|
||||
}
|
||||
}
|
||||
{
|
||||
if (NR > 1) encode("\n")
|
||||
for (i = 1; i <= length($0); i++) {
|
||||
encode(substr($0, i, 1))
|
||||
}
|
||||
}
|
||||
function encode(c, o) {
|
||||
o = ord[c]
|
||||
if ((o >= 48 && o <= 57) || (o >= 65 && o <= 90) || (o >= 97 && o <= 122) || c == "/" || c == "-" || c == "." || c == "_" || c == "~") {
|
||||
printf "%s", c
|
||||
} else {
|
||||
printf "%%%02X", o
|
||||
}
|
||||
}`;
|
||||
|
||||
const quoteForSingleQuotedShellString = (value: string): string =>
|
||||
`'${value.replace(/'/g, `'\\''`)}'`;
|
||||
|
||||
const URL_PATH_AWK_SCRIPT_QUOTED = quoteForSingleQuotedShellString(URL_PATH_AWK_SCRIPT);
|
||||
|
||||
const BASH_DELETE_MARKED_HISTORY_COMMAND = String.raw`if test -n "${DOLLAR}{BASH_VERSION-}"; then __netcatty_osc7_history_cleanup_marker__=1; __netcatty_osc7_history_line=$(HISTTIMEFORMAT= builtin history 1 2>/dev/null) || __netcatty_osc7_history_line=""; case "$__netcatty_osc7_history_line" in *__netcatty_osc7_history_cleanup_marker__=1*) __netcatty_osc7_history_number=$(printf "%s\n" "$__netcatty_osc7_history_line" | sed "s/^ *\([0-9][0-9]*\).*/\1/"); case "$__netcatty_osc7_history_number" in ""|*[!0-9]*) ;; *) builtin history -d "$__netcatty_osc7_history_number" 2>/dev/null || true;; esac;; esac; unset __netcatty_osc7_history_cleanup_marker__ __netcatty_osc7_history_line __netcatty_osc7_history_number 2>/dev/null || true; fi`;
|
||||
|
||||
const POSIX_SETUP_SCRIPT = String.raw`set -eu
|
||||
marker="# >>> Netcatty OSC 7 cwd tracking >>>"
|
||||
SELF=$$
|
||||
expected_cwd="${DOLLAR}{NETCATTY_OSC7_EXPECTED_CWD:-}"
|
||||
forced_shell="${DOLLAR}{NETCATTY_OSC7_FORCE_SHELL:-}"
|
||||
|
||||
find_login_shell() {
|
||||
_shell=$(ps -e -o pid=,ppid=,tty=,comm= 2>/dev/null | awk -v pp="$1" -v self="$SELF" '
|
||||
$1 != self && $2 == pp && $4 ~ /^-?(ba|z|fi|k|da|a)?sh$/ {
|
||||
if ($3 != "?") { print $1; found=1; exit }
|
||||
if (any == "") any=$1
|
||||
}
|
||||
END { if (!found && any != "") print any }
|
||||
')
|
||||
[ -n "$_shell" ] && { echo "$_shell"; return; }
|
||||
[ -r "/proc/$SELF/environ" ] || return
|
||||
_conn=$(tr '\0' '\n' < "/proc/$SELF/environ" 2>/dev/null | sed -n 's/^SSH_CONNECTION=//p' | head -n1)
|
||||
[ -z "$_conn" ] && return
|
||||
_any=""
|
||||
for _d in /proc/[0-9]*; do
|
||||
_pid=$(basename "$_d")
|
||||
[ "$_pid" = "$SELF" ] && continue
|
||||
[ -r "$_d/environ" ] || continue
|
||||
_conn2=$(tr '\0' '\n' < "$_d/environ" 2>/dev/null | sed -n 's/^SSH_CONNECTION=//p' | head -n1)
|
||||
[ "$_conn2" = "$_conn" ] || continue
|
||||
_comm=$(cat "$_d/comm" 2>/dev/null)
|
||||
case "$_comm" in
|
||||
sh|bash|zsh|fish|ksh|dash|ash) ;;
|
||||
*) continue ;;
|
||||
esac
|
||||
_tty=$(ps -p "$_pid" -o tty= 2>/dev/null | tr -d '[:space:]')
|
||||
if [ "$_tty" != "?" ] && [ -n "$_tty" ]; then
|
||||
echo "$_pid"
|
||||
return
|
||||
fi
|
||||
[ -z "$_any" ] && _any="$_pid"
|
||||
done
|
||||
[ -n "$_any" ] && echo "$_any"
|
||||
}
|
||||
|
||||
find_active_shell() {
|
||||
ps -e -o pid=,ppid=,stat=,comm= 2>/dev/null | awk -v start="$1" '
|
||||
{ pp[$1]=$2; st[$1]=$3; cm[$1]=$4; ord[NR]=$1 }
|
||||
function isshell(c) { return c ~ /^-?(ba|z|fi|k|da|a)?sh$/ }
|
||||
function depth(p, d) { d=0; while (p != "" && d < 64) { if (p == start) return d; p=pp[p]; d++ } return -1 }
|
||||
END {
|
||||
best=-1; bp="";
|
||||
for (i=1; i<=NR; i++) {
|
||||
p=ord[i];
|
||||
if (!isshell(cm[p])) continue;
|
||||
if (index(st[p], "+") == 0) continue;
|
||||
d=depth(p); if (d < 0) continue;
|
||||
if (d > best) { best=d; bp=p }
|
||||
}
|
||||
print (bp != "" ? bp : start)
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
read_proc_env_value() {
|
||||
[ -r "$1" ] || return 1
|
||||
tr '\0' '\n' < "$1" 2>/dev/null | sed -n "s/^$2=//p" | head -n1
|
||||
}
|
||||
|
||||
active_shell_pid=""
|
||||
login_shell_pid=""
|
||||
if [ -z "$forced_shell" ] && [ -d /proc ]; then
|
||||
login_shell_pid=$(find_login_shell "$PPID" || true)
|
||||
if [ -n "$login_shell_pid" ]; then
|
||||
active_shell_pid=$(find_active_shell "$login_shell_pid" || true)
|
||||
[ -n "$active_shell_pid" ] || active_shell_pid="$login_shell_pid"
|
||||
fi
|
||||
fi
|
||||
|
||||
# After su / sudo su the foreground shell belongs to another user. This exec
|
||||
# channel runs as the login user, so it can neither verify nor configure that
|
||||
# shell (ptrace-scoped /proc access). Report the target shell so the caller
|
||||
# can retype the setup inside the terminal, where it runs as that user (#1942).
|
||||
if [ -n "$active_shell_pid" ]; then
|
||||
active_uid=$(sed -n "s/^Uid:[[:space:]]*\([0-9]*\).*/\1/p" "/proc/$active_shell_pid/status" 2>/dev/null | head -n1 || true)
|
||||
self_uid=$(id -u 2>/dev/null || true)
|
||||
if [ -n "$active_uid" ] && [ -n "$self_uid" ] && [ "$active_uid" != "$self_uid" ]; then
|
||||
other_shell=$(cat "/proc/$active_shell_pid/comm" 2>/dev/null | sed "s/^-//" | tr -d "[:space:]" || true)
|
||||
printf '%s%s\n' '${OSC7_SETUP_OTHER_USER_MARKER}' "${DOLLAR}{other_shell:-unknown}"
|
||||
printf "Netcatty OSC 7 setup: the active terminal shell belongs to another user\n" >&2
|
||||
exit 5
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -d /proc ] && [ -n "$expected_cwd" ]; then
|
||||
if [ -z "$active_shell_pid" ]; then
|
||||
printf "Netcatty OSC 7 setup: could not identify the active terminal shell\n" >&2
|
||||
exit 4
|
||||
fi
|
||||
active_cwd=$(readlink "/proc/$active_shell_pid/cwd" 2>/dev/null || true)
|
||||
if [ "$active_cwd" != "$expected_cwd" ]; then
|
||||
printf "Netcatty OSC 7 setup: active terminal shell did not match the current tab\n" >&2
|
||||
exit 4
|
||||
fi
|
||||
fi
|
||||
|
||||
active_comm=""
|
||||
active_home=""
|
||||
active_shell_env=""
|
||||
active_zdotdir=""
|
||||
active_xdg_config_home=""
|
||||
if [ -n "$active_shell_pid" ]; then
|
||||
active_comm=$(cat "/proc/$active_shell_pid/comm" 2>/dev/null | sed "s/^-//" | tr -d "[:space:]")
|
||||
active_env_file="/proc/$active_shell_pid/environ"
|
||||
if [ -r "$active_env_file" ]; then
|
||||
active_home=$(read_proc_env_value "$active_env_file" HOME || true)
|
||||
active_shell_env=$(read_proc_env_value "$active_env_file" SHELL || true)
|
||||
active_zdotdir=$(read_proc_env_value "$active_env_file" ZDOTDIR || true)
|
||||
active_xdg_config_home=$(read_proc_env_value "$active_env_file" XDG_CONFIG_HOME || true)
|
||||
elif [ "$active_shell_pid" != "$login_shell_pid" ]; then
|
||||
printf "Netcatty OSC 7 setup: cannot silently configure an active shell owned by another user\n" >&2
|
||||
exit 3
|
||||
fi
|
||||
fi
|
||||
|
||||
parent_shell=$(ps -p "$PPID" -o comm= 2>/dev/null | sed "s/^-//" | tr -d "[:space:]")
|
||||
login_shell=$(basename "${DOLLAR}{active_shell_env:-${DOLLAR}{SHELL:-sh}}" | sed "s/^-//")
|
||||
shell_name="$login_shell"
|
||||
case "$parent_shell" in
|
||||
bash|zsh|fish) shell_name="$parent_shell" ;;
|
||||
esac
|
||||
case "$active_comm" in
|
||||
bash|zsh|fish) shell_name="$active_comm" ;;
|
||||
esac
|
||||
case "$forced_shell" in
|
||||
bash|zsh|fish) shell_name="$forced_shell" ;;
|
||||
esac
|
||||
|
||||
home_dir="${DOLLAR}{active_home:-$HOME}"
|
||||
zdotdir="${DOLLAR}{active_zdotdir:-${DOLLAR}{NETCATTY_ZDOTDIR:-${DOLLAR}{ZDOTDIR:-$home_dir}}}"
|
||||
xdg_config_home="${DOLLAR}{active_xdg_config_home:-${DOLLAR}{NETCATTY_XDG_CONFIG_HOME:-${DOLLAR}{XDG_CONFIG_HOME:-$home_dir/.config}}}"
|
||||
|
||||
case "$shell_name" in
|
||||
bash) config="$home_dir/.bashrc" ;;
|
||||
zsh) config="$zdotdir/.zshrc" ;;
|
||||
fish) config="$xdg_config_home/fish/config.fish" ;;
|
||||
*)
|
||||
printf "Netcatty OSC 7 setup: unsupported shell %s\n" "$shell_name" >&2
|
||||
printf "Supported shells: bash, zsh, fish\n" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
__netcatty_osc7_url_path() {
|
||||
printf "%s" "$1" | LC_ALL=C awk ${URL_PATH_AWK_SCRIPT_QUOTED}
|
||||
}
|
||||
|
||||
mkdir -p "$(dirname "$config")"
|
||||
touch "$config"
|
||||
# Snippet v2: guarded prompt entry + unexport PROMPT_COMMAND so su without
|
||||
# login does not inherit a bare osc7_cwd call into another user's shell.
|
||||
snippet_version_marker="netcatty-osc7-version: 2"
|
||||
end_marker="# <<< Netcatty OSC 7 cwd tracking <<<"
|
||||
# Returns 0 when every start marker is closed by a matching end marker.
|
||||
# Markers must be whole lines (optional indent) so an echo of the marker text
|
||||
# does not count as a block boundary.
|
||||
netcatty_osc7_markers_balanced() {
|
||||
awk -v start="$marker" -v end="$end_marker" '
|
||||
function trim(s) {
|
||||
sub(/^[ \t]+/, "", s)
|
||||
sub(/[ \t]+$/, "", s)
|
||||
return s
|
||||
}
|
||||
{
|
||||
t = trim($0)
|
||||
if (t == start) {
|
||||
if (skip) incomplete = 1
|
||||
else skip = 1
|
||||
next
|
||||
}
|
||||
if (t == end) {
|
||||
if (!skip) incomplete = 1
|
||||
else skip = 0
|
||||
next
|
||||
}
|
||||
}
|
||||
END {
|
||||
if (skip) incomplete = 1
|
||||
exit incomplete ? 1 : 0
|
||||
}
|
||||
' "$1"
|
||||
}
|
||||
|
||||
# Returns 0 when at least one contiguous start..end region contains the v2
|
||||
# version marker line. Older malformed markers outside that region are ignored
|
||||
# so recovery appends stay idempotent.
|
||||
netcatty_osc7_has_complete_v2_block() {
|
||||
awk -v start="$marker" -v end="$end_marker" -v ver_line="# $snippet_version_marker" '
|
||||
function trim(s) {
|
||||
sub(/^[ \t]+/, "", s)
|
||||
sub(/[ \t]+$/, "", s)
|
||||
return s
|
||||
}
|
||||
{
|
||||
t = trim($0)
|
||||
if (t == start) {
|
||||
skip = 1
|
||||
has_ver = 0
|
||||
next
|
||||
}
|
||||
if (skip && t == ver_line) has_ver = 1
|
||||
if (t == end) {
|
||||
if (skip && has_ver) found = 1
|
||||
skip = 0
|
||||
has_ver = 0
|
||||
next
|
||||
}
|
||||
}
|
||||
END { exit found ? 0 : 1 }
|
||||
' "$1"
|
||||
}
|
||||
|
||||
# Resolve config to a real path so upgrades rewrite the final target of a
|
||||
# symlink chain without replacing intermediate links.
|
||||
netcatty_osc7_resolve_path() {
|
||||
_path="$1"
|
||||
if command -v realpath >/dev/null 2>&1; then
|
||||
_resolved=$(realpath "$_path" 2>/dev/null || true)
|
||||
if [ -n "$_resolved" ]; then
|
||||
printf '%s\n' "$_resolved"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
_depth=0
|
||||
while [ -L "$_path" ] && [ "$_depth" -lt 32 ]; do
|
||||
_link=$(readlink "$_path" 2>/dev/null || true)
|
||||
[ -n "$_link" ] || break
|
||||
case "$_link" in
|
||||
/*) _path="$_link" ;;
|
||||
*) _path="$(dirname "$_path")/$_link" ;;
|
||||
esac
|
||||
_depth=$((_depth + 1))
|
||||
done
|
||||
printf '%s\n' "$_path"
|
||||
}
|
||||
|
||||
# Best-effort portable mode bits for chmod after atomic replace.
|
||||
netcatty_osc7_file_mode() {
|
||||
if stat -c '%a' "$1" >/dev/null 2>&1; then
|
||||
stat -c '%a' "$1"
|
||||
elif stat -f '%OLp' "$1" >/dev/null 2>&1; then
|
||||
stat -f '%OLp' "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
# Best-effort portable owner:group for chown after atomic replace.
|
||||
netcatty_osc7_file_owner() {
|
||||
if stat -c '%u:%g' "$1" >/dev/null 2>&1; then
|
||||
stat -c '%u:%g' "$1"
|
||||
elif stat -f '%u:%g' "$1" >/dev/null 2>&1; then
|
||||
stat -f '%u:%g' "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
# Append the v2 snippet to the given file path ($1).
|
||||
netcatty_osc7_append_v2() {
|
||||
__netcatty_osc7_dest="$1"
|
||||
case "$shell_name" in
|
||||
bash)
|
||||
cat >> "$__netcatty_osc7_dest" <<'NETCATTY_OSC7_BASH'
|
||||
|
||||
# >>> Netcatty OSC 7 cwd tracking >>>
|
||||
# netcatty-osc7-version: 2
|
||||
__netcatty_osc7_url_path() {
|
||||
printf "%s" "$1" | LC_ALL=C awk '${URL_PATH_AWK_SCRIPT}'
|
||||
}
|
||||
osc7_cwd() {
|
||||
printf '\033]7;file://%s%s\a' "${DOLLAR}{HOSTNAME:-localhost}" "$(__netcatty_osc7_url_path "$PWD")"
|
||||
}
|
||||
# Safe prompt hook: no-op when helpers are missing (PROMPT_COMMAND string may
|
||||
# be inherited across su without this rc file).
|
||||
__netcatty_osc7_prompt() {
|
||||
if declare -F osc7_cwd >/dev/null 2>&1; then
|
||||
osc7_cwd
|
||||
fi
|
||||
}
|
||||
# Install/dedupe the guarded prompt hook for both scalar and array PROMPT_COMMAND.
|
||||
__netcatty_osc7_hook='declare -F __netcatty_osc7_prompt >/dev/null 2>&1 && __netcatty_osc7_prompt'
|
||||
# Match declare -a / -ax / -ar etc. (array flag may appear with other flags).
|
||||
if declare -p PROMPT_COMMAND 2>/dev/null | grep -Eq 'declare -[A-Za-z]*a'; then
|
||||
__netcatty_osc7_new=()
|
||||
__netcatty_osc7_has_hook=0
|
||||
for __netcatty_osc7_el in "${DOLLAR}{PROMPT_COMMAND[@]}"; do
|
||||
case "${DOLLAR}{__netcatty_osc7_el}" in
|
||||
osc7_cwd|__netcatty_osc7_prompt) continue ;;
|
||||
*'declare -F __netcatty_osc7_prompt'*)
|
||||
if [ "${DOLLAR}{__netcatty_osc7_has_hook}" -eq 0 ]; then
|
||||
__netcatty_osc7_new+=("${DOLLAR}{__netcatty_osc7_hook}")
|
||||
__netcatty_osc7_has_hook=1
|
||||
fi
|
||||
;;
|
||||
*) __netcatty_osc7_new+=("${DOLLAR}{__netcatty_osc7_el}") ;;
|
||||
esac
|
||||
done
|
||||
if [ "${DOLLAR}{__netcatty_osc7_has_hook}" -eq 0 ]; then
|
||||
__netcatty_osc7_new+=("${DOLLAR}{__netcatty_osc7_hook}")
|
||||
fi
|
||||
PROMPT_COMMAND=("${DOLLAR}{__netcatty_osc7_new[@]}")
|
||||
unset __netcatty_osc7_new __netcatty_osc7_has_hook __netcatty_osc7_el 2>/dev/null || true
|
||||
else
|
||||
if [ -n "${DOLLAR}{PROMPT_COMMAND+x}" ]; then
|
||||
__netcatty_osc7_pc=""
|
||||
__netcatty_osc7_sep=""
|
||||
while IFS= read -r __netcatty_osc7_line || [ -n "${DOLLAR}{__netcatty_osc7_line}" ]; do
|
||||
case "${DOLLAR}{__netcatty_osc7_line}" in
|
||||
osc7_cwd|__netcatty_osc7_prompt) continue ;;
|
||||
*'declare -F __netcatty_osc7_prompt'*) continue ;;
|
||||
*)
|
||||
__netcatty_osc7_pc="${DOLLAR}{__netcatty_osc7_pc}${DOLLAR}{__netcatty_osc7_sep}${DOLLAR}{__netcatty_osc7_line}"
|
||||
__netcatty_osc7_sep="
|
||||
"
|
||||
;;
|
||||
esac
|
||||
done <<EOF
|
||||
${DOLLAR}{PROMPT_COMMAND-}
|
||||
EOF
|
||||
if [ -n "${DOLLAR}{__netcatty_osc7_pc}" ]; then
|
||||
PROMPT_COMMAND="${DOLLAR}{__netcatty_osc7_pc}
|
||||
${DOLLAR}{__netcatty_osc7_hook}"
|
||||
else
|
||||
PROMPT_COMMAND="${DOLLAR}{__netcatty_osc7_hook}"
|
||||
fi
|
||||
unset __netcatty_osc7_pc __netcatty_osc7_sep __netcatty_osc7_line 2>/dev/null || true
|
||||
else
|
||||
PROMPT_COMMAND="${DOLLAR}{__netcatty_osc7_hook}"
|
||||
fi
|
||||
fi
|
||||
unset __netcatty_osc7_hook 2>/dev/null || true
|
||||
# Do not force-unexport PROMPT_COMMAND: the guarded hook is safe if inherited
|
||||
# across non-login su (declare -F fails quietly), and users may intentionally
|
||||
# export PROMPT_COMMAND for child shells.
|
||||
# <<< Netcatty OSC 7 cwd tracking <<<
|
||||
NETCATTY_OSC7_BASH
|
||||
;;
|
||||
zsh)
|
||||
cat >> "$__netcatty_osc7_dest" <<'NETCATTY_OSC7_ZSH'
|
||||
|
||||
# >>> Netcatty OSC 7 cwd tracking >>>
|
||||
# netcatty-osc7-version: 2
|
||||
__netcatty_osc7_url_path() {
|
||||
printf "%s" "$1" | LC_ALL=C awk '${URL_PATH_AWK_SCRIPT}'
|
||||
}
|
||||
osc7_cwd() {
|
||||
printf '\033]7;file://%s%s\a' "${DOLLAR}{HOST:-${DOLLAR}{HOSTNAME:-localhost}}" "$(__netcatty_osc7_url_path "$PWD")"
|
||||
}
|
||||
__netcatty_osc7_prompt() {
|
||||
if typeset -f osc7_cwd >/dev/null 2>&1; then
|
||||
osc7_cwd
|
||||
fi
|
||||
}
|
||||
if (( ${DOLLAR}{+precmd_functions} )); then
|
||||
precmd_functions=(${DOLLAR}{precmd_functions:#osc7_cwd})
|
||||
case " ${DOLLAR}{precmd_functions[*]} " in
|
||||
*" __netcatty_osc7_prompt "*) ;;
|
||||
*) precmd_functions+=(__netcatty_osc7_prompt) ;;
|
||||
esac
|
||||
else
|
||||
precmd_functions=(__netcatty_osc7_prompt)
|
||||
fi
|
||||
# <<< Netcatty OSC 7 cwd tracking <<<
|
||||
NETCATTY_OSC7_ZSH
|
||||
;;
|
||||
fish)
|
||||
cat >> "$__netcatty_osc7_dest" <<'NETCATTY_OSC7_FISH'
|
||||
|
||||
# >>> Netcatty OSC 7 cwd tracking >>>
|
||||
# netcatty-osc7-version: 2
|
||||
function __netcatty_osc7_url_path
|
||||
printf "%s" "$argv[1]" | LC_ALL=C awk '${URL_PATH_AWK_SCRIPT}'
|
||||
end
|
||||
function __netcatty_osc7_cwd --on-event fish_prompt
|
||||
printf '\033]7;file://%s%s\a' (hostname 2>/dev/null; or printf localhost) (__netcatty_osc7_url_path "$PWD")
|
||||
end
|
||||
# <<< Netcatty OSC 7 cwd tracking <<<
|
||||
NETCATTY_OSC7_FISH
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
need_write=1
|
||||
# Whole-line marker presence only (not substring matches inside echo etc.).
|
||||
if awk -v start="$marker" '
|
||||
function trim(s) { sub(/^[ \t]+/, "", s); sub(/[ \t]+$/, "", s); return s }
|
||||
trim($0) == start { found = 1; exit }
|
||||
END { exit found ? 0 : 1 }
|
||||
' "$config"; then
|
||||
if netcatty_osc7_has_complete_v2_block "$config"; then
|
||||
# At least one complete v2 block exists (even if older junk markers remain).
|
||||
need_write=0
|
||||
elif netcatty_osc7_markers_balanced "$config"; then
|
||||
# Complete balanced block without v2 (legacy). Replace the marked region
|
||||
# in place (not strip-then-append-at-EOF) so surrounding control flow such
|
||||
# as if/then/fi wrappers stay valid. Build the full file in a temp, then
|
||||
# atomically replace so read-only modes cannot lose the block without the
|
||||
# replacement.
|
||||
__netcatty_osc7_target=$(netcatty_osc7_resolve_path "$config")
|
||||
__netcatty_osc7_dir=$(dirname "$__netcatty_osc7_target")
|
||||
__netcatty_osc7_mode=$(netcatty_osc7_file_mode "$__netcatty_osc7_target" || true)
|
||||
__netcatty_osc7_owner=$(netcatty_osc7_file_owner "$__netcatty_osc7_target" || true)
|
||||
# Prefer same-dir atomic replace. If the directory is not writable
|
||||
# (managed homes), fall back to append without aborting setup.
|
||||
__netcatty_osc7_tmp=$(mktemp "$__netcatty_osc7_dir/.netcatty-osc7.XXXXXX" 2>/dev/null || true)
|
||||
__netcatty_osc7_snip=$(mktemp "$__netcatty_osc7_dir/.netcatty-osc7-snip.XXXXXX" 2>/dev/null || true)
|
||||
if [ -z "${DOLLAR}{__netcatty_osc7_tmp:-}" ] || [ -z "${DOLLAR}{__netcatty_osc7_snip:-}" ]; then
|
||||
rm -f "$__netcatty_osc7_tmp" "$__netcatty_osc7_snip" 2>/dev/null || true
|
||||
need_write=1
|
||||
else
|
||||
: > "$__netcatty_osc7_snip"
|
||||
netcatty_osc7_append_v2 "$__netcatty_osc7_snip"
|
||||
if awk -v start="$marker" -v end="$end_marker" -v snip="$__netcatty_osc7_snip" '
|
||||
function trim(s) {
|
||||
sub(/^[ \t]+/, "", s)
|
||||
sub(/[ \t]+$/, "", s)
|
||||
return s
|
||||
}
|
||||
{
|
||||
t = trim($0)
|
||||
if (t == start) {
|
||||
while ((getline line < snip) > 0) print line
|
||||
close(snip)
|
||||
skip = 1
|
||||
next
|
||||
}
|
||||
if (t == end) {
|
||||
skip = 0
|
||||
next
|
||||
}
|
||||
if (!skip) print
|
||||
}
|
||||
' "$config" > "$__netcatty_osc7_tmp"
|
||||
then
|
||||
rm -f "$__netcatty_osc7_snip"
|
||||
if [ -n "${DOLLAR}{__netcatty_osc7_mode:-}" ]; then
|
||||
chmod "$__netcatty_osc7_mode" "$__netcatty_osc7_tmp" 2>/dev/null || true
|
||||
fi
|
||||
if [ -n "${DOLLAR}{__netcatty_osc7_owner:-}" ]; then
|
||||
# If we cannot restore ownership, leave the original file untouched.
|
||||
if ! chown "$__netcatty_osc7_owner" "$__netcatty_osc7_tmp" 2>/dev/null; then
|
||||
rm -f "$__netcatty_osc7_tmp"
|
||||
need_write=1
|
||||
else
|
||||
mv -f "$__netcatty_osc7_tmp" "$__netcatty_osc7_target"
|
||||
need_write=0
|
||||
fi
|
||||
else
|
||||
mv -f "$__netcatty_osc7_tmp" "$__netcatty_osc7_target"
|
||||
need_write=0
|
||||
fi
|
||||
else
|
||||
rm -f "$__netcatty_osc7_tmp" "$__netcatty_osc7_snip"
|
||||
need_write=1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
# Incomplete or unbalanced markers and no complete v2 yet.
|
||||
# Never rewrite/truncate: append a complete v2 block only.
|
||||
need_write=1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$need_write" = 1 ]; then
|
||||
netcatty_osc7_append_v2 "$config"
|
||||
fi
|
||||
|
||||
if [ -z "$forced_shell" ]; then
|
||||
printf '%s%s\n' '${OSC7_SETUP_SHELL_MARKER}' "$shell_name"
|
||||
printf '%s%s\n' '${OSC7_SETUP_CONFIG_MARKER}' "$config"
|
||||
fi
|
||||
host=$(hostname 2>/dev/null || printf localhost)
|
||||
printf '\033]7;file://%s%s\a' "$host" "$(__netcatty_osc7_url_path "$PWD")"`;
|
||||
|
||||
export const buildOsc7SetupCommand = (): string =>
|
||||
`set +u 2>/dev/null || true; printf "%s\\n" ${quoteForSingleQuotedShellString(POSIX_SETUP_SCRIPT)} | env NETCATTY_ZDOTDIR="$ZDOTDIR" NETCATTY_XDG_CONFIG_HOME="$XDG_CONFIG_HOME" sh\n`;
|
||||
|
||||
export const buildOsc7SetupExecCommand = (expectedCwd?: string): string => {
|
||||
const envPrefix = expectedCwd
|
||||
? `env NETCATTY_OSC7_EXPECTED_CWD=${quoteForSingleQuotedShellString(expectedCwd)} `
|
||||
: "";
|
||||
return `exec ${envPrefix}sh -c ${quoteForSingleQuotedShellString(POSIX_SETUP_SCRIPT)}\n`;
|
||||
};
|
||||
|
||||
export const OSC7_SETUP_STAGED_MARKER = "__NETCATTY_OSC7_SETUP_STAGED__=";
|
||||
|
||||
/** Exact bytes the stage command writes (printf '%s\n' appends the newline). */
|
||||
const STAGED_SETUP_SCRIPT_BYTES = `${POSIX_SETUP_SCRIPT}\n`;
|
||||
|
||||
let stagedScriptSha256Promise: Promise<string> | null = null;
|
||||
|
||||
/**
|
||||
* SHA-256 (hex) of the staged setup script bytes, computed locally so the
|
||||
* typed runner can verify the remote file was not tampered with.
|
||||
*/
|
||||
export const getOsc7StagedScriptSha256 = (): Promise<string> => {
|
||||
if (!stagedScriptSha256Promise) {
|
||||
stagedScriptSha256Promise = crypto.subtle
|
||||
.digest("SHA-256", new TextEncoder().encode(STAGED_SETUP_SCRIPT_BYTES))
|
||||
.then((digest) =>
|
||||
Array.from(new Uint8Array(digest))
|
||||
.map((byte) => byte.toString(16).padStart(2, "0"))
|
||||
.join(""),
|
||||
);
|
||||
}
|
||||
return stagedScriptSha256Promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Exec-channel command that stages the setup script into a world-readable
|
||||
* remote temp file. Typed fallback (#1942) runs that file instead of pasting
|
||||
* the multi-line script into the terminal: the typed command stays a single
|
||||
* line, so it is one history entry that the bash cleanup reliably deletes.
|
||||
*/
|
||||
export const buildOsc7StageScriptCommand = (): string => {
|
||||
const stageScript = `set -eu
|
||||
umask 022
|
||||
file=$(mktemp /tmp/.netcatty-osc7-setup.XXXXXX)
|
||||
printf '%s\\n' ${quoteForSingleQuotedShellString(POSIX_SETUP_SCRIPT)} > "$file"
|
||||
chmod 644 "$file"
|
||||
printf '%s%s\\n' '${OSC7_SETUP_STAGED_MARKER}' "$file"`;
|
||||
return `exec sh -c ${quoteForSingleQuotedShellString(stageScript)}\n`;
|
||||
};
|
||||
|
||||
/**
|
||||
* POSIX runner that executes the staged script without a TOCTOU window: it
|
||||
* reads the file into memory ONCE, deletes it, verifies the local SHA-256 of
|
||||
* the in-memory copy, and only then pipes that same copy to sh. The staged
|
||||
* file stays writable by the login user until this runs, so a same-uid
|
||||
* process could otherwise swap its contents and have the su-target shell
|
||||
* (e.g. root) execute them.
|
||||
*/
|
||||
const buildVerifiedStagedRunner = (contentSha256: string): string =>
|
||||
`c=$(cat -- "$1" 2>/dev/null); rm -f -- "$1" 2>/dev/null; `
|
||||
+ `h=$(printf "%s\\n" "$c" | sha256sum 2>/dev/null | cut -d" " -f1); `
|
||||
+ `[ -n "$h" ] || h=$(printf "%s\\n" "$c" | shasum -a 256 2>/dev/null | cut -d" " -f1); `
|
||||
+ `[ -n "$h" ] || h=$(printf "%s\\n" "$c" | openssl dgst -sha256 2>/dev/null | sed "s/^.* //"); `
|
||||
+ `if [ "x$h" = "x${contentSha256}" ]; then printf "%s\\n" "$c" | sh; `
|
||||
+ `else printf "%s\\n" "Netcatty OSC 7 setup: staged script verification failed" >&2; fi`;
|
||||
|
||||
/**
|
||||
* Setup command typed into the interactive terminal itself. Used when the
|
||||
* silent exec-channel setup cannot configure the active shell because it is
|
||||
* owned by another user (after `su` / `sudo su`, #1942): typed input runs as
|
||||
* that user, so the config lands in the target user's rc file and OSC 7
|
||||
* reporting resumes for this and every future user-switched shell.
|
||||
*
|
||||
* The command hash-verifies and runs the staged script (see
|
||||
* buildOsc7StageScriptCommand / buildVerifiedStagedRunner) inside a POSIX
|
||||
* `sh -c` child, and forwards shell-local (possibly unexported) ZDOTDIR /
|
||||
* XDG_CONFIG_HOME via the NETCATTY_* overrides the setup script already
|
||||
* honors, mirroring buildOsc7SetupCommand.
|
||||
*/
|
||||
export const buildOsc7TypedSetupCommand = (
|
||||
shell: Osc7SetupShell,
|
||||
scriptPath: string,
|
||||
contentSha256: string,
|
||||
): string => {
|
||||
const quotedPath = quoteForSingleQuotedShellString(scriptPath);
|
||||
const quotedRunner = quoteForSingleQuotedShellString(buildVerifiedStagedRunner(contentSha256));
|
||||
if (shell === "bash") {
|
||||
const run = `env NETCATTY_OSC7_FORCE_SHELL=bash sh -c ${quotedRunner} sh ${quotedPath}`;
|
||||
return `${run}; . "${DOLLAR}HOME/.bashrc" >/dev/null 2>&1; osc7_cwd 2>/dev/null; true; ${BASH_DELETE_MARKED_HISTORY_COMMAND}\r`;
|
||||
}
|
||||
if (shell === "zsh") {
|
||||
const run = `env NETCATTY_OSC7_FORCE_SHELL=zsh NETCATTY_ZDOTDIR="${DOLLAR}{ZDOTDIR:-}" sh -c ${quotedRunner} sh ${quotedPath}`;
|
||||
// Leading space keeps the command out of history when HIST_IGNORE_SPACE is set.
|
||||
return ` ${run}; . "${DOLLAR}{ZDOTDIR:-${DOLLAR}HOME}/.zshrc" >/dev/null 2>&1; osc7_cwd 2>/dev/null; true\r`;
|
||||
}
|
||||
const run = `env NETCATTY_OSC7_FORCE_SHELL=fish NETCATTY_XDG_CONFIG_HOME="${DOLLAR}XDG_CONFIG_HOME" sh -c ${quotedRunner} sh ${quotedPath}`;
|
||||
return ` ${run}; source (test -n "${DOLLAR}XDG_CONFIG_HOME"; and echo "${DOLLAR}XDG_CONFIG_HOME"; or echo "${DOLLAR}HOME/.config")/fish/config.fish >/dev/null 2>&1; __netcatty_osc7_cwd 2>/dev/null; true\r`;
|
||||
};
|
||||
|
||||
const isOsc7SetupShell = (value: string): value is Osc7SetupShell =>
|
||||
value === "bash" || value === "zsh" || value === "fish";
|
||||
|
||||
const readMarkerLine = (stdout: string, marker: string): string | null => {
|
||||
const line = stdout.split(/\r?\n/).find((entry) => entry.startsWith(marker));
|
||||
return line ? line.slice(marker.length).trim() : null;
|
||||
};
|
||||
|
||||
export const parseOsc7SetupMetadata = (stdout: string): Osc7SetupMetadata | null => {
|
||||
const shell = readMarkerLine(stdout, OSC7_SETUP_SHELL_MARKER);
|
||||
const configPath = readMarkerLine(stdout, OSC7_SETUP_CONFIG_MARKER);
|
||||
if (!shell || !isOsc7SetupShell(shell) || !configPath) return null;
|
||||
return { shell, configPath };
|
||||
};
|
||||
|
||||
/** Shell of the other-user foreground shell reported by the setup script, if any. */
|
||||
export const parseOsc7SetupOtherUserShell = (stdout: string): string | null =>
|
||||
readMarkerLine(stdout, OSC7_SETUP_OTHER_USER_MARKER);
|
||||
|
||||
/** Remote path of the staged setup script, if the stage command reported one. */
|
||||
export const parseOsc7SetupStagedPath = (stdout: string): string | null => {
|
||||
const path = readMarkerLine(stdout, OSC7_SETUP_STAGED_MARKER);
|
||||
return path && path.startsWith("/") ? path : null;
|
||||
};
|
||||
|
||||
export const extractOsc7SetupTerminalData = (stdout: string): string => {
|
||||
const escape = String.fromCharCode(0x1b);
|
||||
const bell = String.fromCharCode(0x07);
|
||||
const prefix = `${escape}]7;`;
|
||||
let offset = 0;
|
||||
let output = "";
|
||||
|
||||
while (offset < stdout.length) {
|
||||
const start = stdout.indexOf(prefix, offset);
|
||||
if (start < 0) break;
|
||||
const bodyStart = start + prefix.length;
|
||||
const bellEnd = stdout.indexOf(bell, bodyStart);
|
||||
const stEnd = stdout.indexOf(`${escape}\\`, bodyStart);
|
||||
const hasBellEnd = bellEnd >= 0;
|
||||
const hasStEnd = stEnd >= 0;
|
||||
if (!hasBellEnd && !hasStEnd) break;
|
||||
|
||||
const useBell = hasBellEnd && (!hasStEnd || bellEnd < stEnd);
|
||||
const end = useBell ? bellEnd : stEnd;
|
||||
const terminatorLength = useBell ? 1 : 2;
|
||||
output += stdout.slice(start, end + terminatorLength);
|
||||
offset = end + terminatorLength;
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
export const buildOsc7ReloadCommand = (metadata: Osc7SetupMetadata | null): string | null => {
|
||||
if (!metadata) return null;
|
||||
const sourceCommand = `source ${quoteForSingleQuotedShellString(metadata.configPath)} >/dev/null 2>&1`;
|
||||
const emitCommand = metadata.shell === "fish" ? "__netcatty_osc7_cwd" : "osc7_cwd";
|
||||
if (metadata.shell === "bash") {
|
||||
return `${sourceCommand}; ${emitCommand} 2>/dev/null; true; ${BASH_DELETE_MARKED_HISTORY_COMMAND}\r`;
|
||||
}
|
||||
return ` ${sourceCommand}; ${emitCommand} 2>/dev/null; true\r`;
|
||||
};
|
||||
|
||||
export const runOsc7SetupAction = async ({
|
||||
status,
|
||||
sessionId,
|
||||
setupCommand,
|
||||
setupOsc7Tracking,
|
||||
writeToSession,
|
||||
writeLocalTerminalData,
|
||||
}: RunOsc7SetupActionOptions): Promise<Osc7SetupRunResult> => {
|
||||
if (status !== "connected") {
|
||||
return { success: false, error: "Terminal is not connected" };
|
||||
}
|
||||
if (!setupOsc7Tracking) {
|
||||
return { success: false, error: "Directory tracking setup is unavailable" };
|
||||
}
|
||||
|
||||
const result = await setupOsc7Tracking(sessionId, setupCommand);
|
||||
|
||||
// The exec channel cannot configure a shell owned by another user (after
|
||||
// su / sudo su). Stage the setup script into a remote temp file over the
|
||||
// exec channel, then type a short single-line command into the terminal to
|
||||
// run it as that user (#1942).
|
||||
const otherUserShell = parseOsc7SetupOtherUserShell(result.stdout || "");
|
||||
if (otherUserShell) {
|
||||
if (!isOsc7SetupShell(otherUserShell)) {
|
||||
return {
|
||||
...result,
|
||||
success: false,
|
||||
error: `Directory tracking does not support the current shell (${otherUserShell})`,
|
||||
};
|
||||
}
|
||||
const stageResult = await setupOsc7Tracking(sessionId, buildOsc7StageScriptCommand());
|
||||
const stagedPath =
|
||||
stageResult.success && (typeof stageResult.code !== "number" || stageResult.code === 0)
|
||||
? parseOsc7SetupStagedPath(stageResult.stdout || "")
|
||||
: null;
|
||||
if (!stagedPath) {
|
||||
return {
|
||||
...stageResult,
|
||||
success: false,
|
||||
error: stageResult.error || stageResult.stderr?.trim() || "Directory tracking setup failed",
|
||||
};
|
||||
}
|
||||
const contentSha256 = await getOsc7StagedScriptSha256();
|
||||
const typedCommand = buildOsc7TypedSetupCommand(otherUserShell, stagedPath, contentSha256);
|
||||
writeToSession(sessionId, typedCommand, {
|
||||
automated: true,
|
||||
logRewrite: { sentCommand: typedCommand, displayCommand: "" },
|
||||
});
|
||||
return { ...result, success: true, sentToTerminal: true };
|
||||
}
|
||||
|
||||
if (!result.success || (typeof result.code === "number" && result.code !== 0)) {
|
||||
return {
|
||||
...result,
|
||||
success: false,
|
||||
error: result.error || result.stderr?.trim() || "Directory tracking setup failed",
|
||||
};
|
||||
}
|
||||
|
||||
const metadata = parseOsc7SetupMetadata(result.stdout || "");
|
||||
const reloadCommand = buildOsc7ReloadCommand(metadata);
|
||||
if (!reloadCommand) {
|
||||
return {
|
||||
...result,
|
||||
success: false,
|
||||
error: "Directory tracking setup did not return reload metadata",
|
||||
};
|
||||
}
|
||||
|
||||
const terminalData = extractOsc7SetupTerminalData(result.stdout || "");
|
||||
if (terminalData) {
|
||||
writeLocalTerminalData?.(terminalData);
|
||||
}
|
||||
writeToSession(sessionId, reloadCommand, {
|
||||
automated: true,
|
||||
logRewrite: { sentCommand: reloadCommand, displayCommand: "" },
|
||||
});
|
||||
|
||||
return { ...result, success: true, reloadCommand };
|
||||
};
|
||||
Reference in New Issue
Block a user