@vibelord/lanes 1.1.1 → 1.3.0
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/README.md +36 -5
- package/bin/lanes-install.mjs +166 -13
- package/lib/plan.mjs +113 -3
- package/package.json +1 -1
- package/payload/lanes/README.md +47 -23
- package/payload/lanes/VERSION +1 -1
- package/payload/lanes/bin/lanes +2753 -202
- package/payload/lanes/client/ghostty/lanes-palette-light.conf +19 -19
- package/payload/lanes/client/ghostty/lanes-palette.conf +19 -19
- package/payload/lanes/client/lanes-launch +289 -40
- package/payload/lanes/client/lanes-window +15 -3
- package/payload/lanes/container/hooks/claude-settings.json +1 -0
- package/payload/lanes/container/lane +147 -92
- package/payload/lanes/container/lane-edit +100 -0
- package/payload/lanes/container/lane-mcp +1150 -0
- package/payload/lanes/container/lane-signal +109 -29
- package/payload/lanes/container/lane_core.py +1318 -0
- package/payload/lanes/container/vimrc +31 -0
- package/payload/lanes/docs/ARCHITECTURE.md +793 -16
- package/payload/lanes/docs/DESIGN.md +127 -46
- package/payload/lanes/docs/INSTALL.md +175 -18
- package/payload/lanes/docs/RUNBOOK.md +968 -76
- package/payload/lanes/docs/SHARING.md +3 -2
- package/payload/lanes/docs/USERGUIDE.md +967 -150
- package/payload/lanes/holders.lock +5 -0
- package/payload/lanes/install/enable-docs.sh +78 -0
- package/payload/lanes/install/enable-web.sh +2 -0
- package/payload/lanes/install/host-install.sh +197 -7
- package/payload/lanes/install/services.py +148 -18
- package/payload/lanes/install.sh +68 -7
- package/payload/lanes/libexec/agent_state.py +74 -1
- package/payload/lanes/libexec/cursor_sync.py +6 -0
- package/payload/lanes/libexec/device_sync.py +47 -10
- package/payload/lanes/libexec/docs_store.py +995 -0
- package/payload/lanes/libexec/docsd.py +2010 -0
- package/payload/lanes/libexec/economy_poll.py +1 -0
- package/payload/lanes/libexec/enrold.py +41 -22
- package/payload/lanes/libexec/keys.py +54 -23
- package/payload/lanes/libexec/lanes_lib.py +620 -22
- package/payload/lanes/libexec/mdlite.py +575 -0
- package/payload/lanes/libexec/members.py +21 -2
- package/payload/lanes/libexec/notify.py +169 -29
- package/payload/lanes/libexec/registrations.py +216 -18
- package/payload/lanes/libexec/session_actions.py +182 -14
- package/payload/lanes/libexec/sidebar +11 -3
- package/payload/lanes/libexec/sidebar.py +2232 -365
- package/payload/lanes/libexec/statusd.py +231 -12
- package/payload/lanes/libexec/studio_host.py +8 -1
- package/payload/lanes/libexec/tokens.py +148 -35
- package/payload/lanes/libexec/tokens.sh +86 -2
- package/payload/lanes/libexec/update.py +1044 -0
- package/payload/lanes/libexec/uploads.py +538 -0
- package/payload/lanes/libexec/users.py +614 -0
- package/payload/lanes/libexec/view.sh +34 -6
- package/payload/lanes/systemd/lanes-docsd.service +29 -0
- package/payload/lanes/systemd/lanes-ensure.service +3 -0
- package/payload/lanes/systemd/lanes-gc.service +1 -1
- package/payload/lanes/systemd/lanes-update.service +17 -0
- package/payload/lanes/systemd/lanes-update.timer +17 -0
- package/payload/lanes/web/Caddyfile +24 -1
- package/payload/lanes/web/README.md +165 -28
- package/payload/lanes/web/browser.js +4 -8
- package/payload/lanes/web/docs.css +52 -0
- package/payload/lanes/web/docs.js +36 -0
- package/payload/lanes/web/mobile.css +17 -17
- package/payload/lanes/web/mobile.js +45 -32
- package/payload/lanes/web/upload.css +17 -0
- package/payload/lanes/web/upload.js +84 -0
- package/payload/lanes/workspaces.example/README.md +3 -1
- package/payload/lanes/zellij/config.kdl +106 -56
- package/payload/manifest.json +6 -3
|
@@ -2,24 +2,24 @@
|
|
|
2
2
|
# Optional: the cockpit palette regardless of your everyday Ghostty theme (install.sh --client --palette).
|
|
3
3
|
# Slots 1-4 are the four tones, so 16-colour mode here equals truecolor mode. No font, padding or cursor keys.
|
|
4
4
|
background = #f3f5f2
|
|
5
|
-
foreground = #
|
|
6
|
-
cursor-color = #
|
|
5
|
+
foreground = #111a14
|
|
6
|
+
cursor-color = #1f6f3f
|
|
7
7
|
selection-background = #d5ded6
|
|
8
|
-
selection-foreground = #
|
|
9
|
-
palette = 0=#
|
|
10
|
-
palette = 1=#
|
|
11
|
-
palette = 2=#
|
|
12
|
-
palette = 3=#
|
|
13
|
-
palette = 4=#
|
|
14
|
-
palette = 5=#
|
|
15
|
-
palette = 6=#
|
|
16
|
-
palette = 7=#
|
|
17
|
-
palette = 8=#
|
|
18
|
-
palette = 9=#
|
|
19
|
-
palette = 10=#
|
|
20
|
-
palette = 11=#
|
|
21
|
-
palette = 12=#
|
|
22
|
-
palette = 13=#
|
|
23
|
-
palette = 14=#
|
|
24
|
-
palette = 15=#
|
|
8
|
+
selection-foreground = #111a14
|
|
9
|
+
palette = 0=#2f3b34
|
|
10
|
+
palette = 1=#c41c1c
|
|
11
|
+
palette = 2=#0d7338
|
|
12
|
+
palette = 3=#a04d00
|
|
13
|
+
palette = 4=#1a56c4
|
|
14
|
+
palette = 5=#8228c4
|
|
15
|
+
palette = 6=#006f73
|
|
16
|
+
palette = 7=#4a5661
|
|
17
|
+
palette = 8=#55625b
|
|
18
|
+
palette = 9=#d0221a
|
|
19
|
+
palette = 10=#0e7a3a
|
|
20
|
+
palette = 11=#a85200
|
|
21
|
+
palette = 12=#1f5fd8
|
|
22
|
+
palette = 13=#9133d0
|
|
23
|
+
palette = 14=#00797c
|
|
24
|
+
palette = 15=#111a14
|
|
25
25
|
# END lanes tokens
|
|
@@ -2,24 +2,24 @@
|
|
|
2
2
|
# Optional: the cockpit palette regardless of your everyday Ghostty theme (install.sh --client --palette).
|
|
3
3
|
# Slots 1-4 are the four tones, so 16-colour mode here equals truecolor mode. No font, padding or cursor keys.
|
|
4
4
|
background = #101318
|
|
5
|
-
foreground = #
|
|
6
|
-
cursor-color = #
|
|
5
|
+
foreground = #f2f5f8
|
|
6
|
+
cursor-color = #8fe3a6
|
|
7
7
|
selection-background = #2c333d
|
|
8
|
-
selection-foreground = #
|
|
9
|
-
palette = 0=#
|
|
10
|
-
palette = 1=#
|
|
11
|
-
palette = 2=#
|
|
12
|
-
palette = 3=#
|
|
13
|
-
palette = 4=#
|
|
14
|
-
palette = 5=#
|
|
15
|
-
palette = 6=#
|
|
16
|
-
palette = 7=#
|
|
17
|
-
palette = 8=#
|
|
18
|
-
palette = 9=#
|
|
19
|
-
palette = 10=#
|
|
20
|
-
palette = 11=#
|
|
21
|
-
palette = 12=#
|
|
22
|
-
palette = 13=#
|
|
23
|
-
palette = 14=#
|
|
24
|
-
palette = 15=#
|
|
8
|
+
selection-foreground = #f2f5f8
|
|
9
|
+
palette = 0=#8792a0
|
|
10
|
+
palette = 1=#ff7b6e
|
|
11
|
+
palette = 2=#5fdc8a
|
|
12
|
+
palette = 3=#ffc233
|
|
13
|
+
palette = 4=#6fb6ff
|
|
14
|
+
palette = 5=#c58bff
|
|
15
|
+
palette = 6=#3fd9cf
|
|
16
|
+
palette = 7=#a6b1be
|
|
17
|
+
palette = 8=#97a3b1
|
|
18
|
+
palette = 9=#ff9a8e
|
|
19
|
+
palette = 10=#86f0ad
|
|
20
|
+
palette = 11=#ffd866
|
|
21
|
+
palette = 12=#98cbff
|
|
22
|
+
palette = 13=#dcb2ff
|
|
23
|
+
palette = 14=#7deee6
|
|
24
|
+
palette = 15=#f2f5f8
|
|
25
25
|
# END lanes tokens
|
|
@@ -3,17 +3,24 @@
|
|
|
3
3
|
# lanes-launch [device] attach this terminal to the device's view session (default: see below)
|
|
4
4
|
# lanes-launch <workspace> same, and bring that workspace's tab to the front (e.g. `coder-launch worlds`)
|
|
5
5
|
# lanes-launch <device> --viewports N a wide view session: N (2-3) viewports side by side per workspace
|
|
6
|
-
# lanes-launch <device> [--ascii] [--theme
|
|
6
|
+
# lanes-launch <device> [--ascii] [--theme T] [--glyphs M] [--color C] [--notify 1|0] [--dim 90|2] [--device-only] look (lanes attach)
|
|
7
|
+
# [--editor micro|hx|nvim|vim|auto] and the editor quick edit (Ctrl-Alt-e) starts (auto: none chosen)
|
|
7
8
|
# lanes-launch lane <lane-id> attach straight to ONE lane, no multiplexer in between
|
|
8
9
|
# lanes-launch ls list lanes with live status
|
|
10
|
+
# lanes-launch here [device] show here the lane you opened last on another device of yours (`lanes here`; default:
|
|
11
|
+
# this machine's device); an open window follows, a closed one shows it when it attaches
|
|
9
12
|
# lanes-launch web phone|laptop|<device> [--desktop|--mobile] [--no-qr]
|
|
10
13
|
# a browser view in one step: the server prepares it, prints its private URL and a QR
|
|
11
14
|
# code of it (--no-qr: none), then a login token in THIS terminal. phone = <your
|
|
12
15
|
# device>-phone (phone layout), laptop = <your device>-web (desktop layout); any other
|
|
13
16
|
# name is that device
|
|
14
17
|
# lanes-launch update [install.sh options] the newest client from npm, with your remembered install options
|
|
18
|
+
# lanes-launch --version this client's release (the VERSION beside it; `dev` without one)
|
|
15
19
|
# lanes-launch --helper <device> [--service | --while-pid PID] run only the upload helper
|
|
16
20
|
# (--service: the per-machine background service that install.sh sets up)
|
|
21
|
+
# Self-update: at most every 6 h a window (a device, a workspace or `lane <id>`) first asks the server whether this
|
|
22
|
+
# client is older; then, once npm has that release, it installs exactly it with your remembered install options and
|
|
23
|
+
# starts again as that client, once. LANES_NO_AUTOUPDATE=1: never. A client run from a checkout never does.
|
|
17
24
|
# It never ends by itself: after network loss, sleep, or a server that dropped or refused it, it says so, waits and
|
|
18
25
|
# reattaches (Enter = now). Closing the window (or Ctrl-C) ends it. Log: ~/.local/state/lanes-client/launch-<target>.log
|
|
19
26
|
set -u
|
|
@@ -25,6 +32,15 @@ fallbacks=${LANES_FALLBACK_HOST:-lanes-ip lanes-public}
|
|
|
25
32
|
remote='$HOME/.local/bin/lanes'
|
|
26
33
|
config=${XDG_CONFIG_HOME:-$HOME/.config}/lanes
|
|
27
34
|
state=${XDG_STATE_HOME:-$HOME/.local/state}/lanes-client # logs and helper.alive; `install.sh --check` reads them
|
|
35
|
+
nl='
|
|
36
|
+
'
|
|
37
|
+
# The command line exactly as given, each argument single-quoted for one `eval "set -- $argv"`: the parsing below
|
|
38
|
+
# shifts the arguments away, and a self-update starts this client again with them (self_update).
|
|
39
|
+
argv=
|
|
40
|
+
for arg in "$@"; do
|
|
41
|
+
case "$arg" in *\'*) arg=$(printf '%s' "$arg" | sed "s/'/'\\\\''/g") ;; esac
|
|
42
|
+
argv="$argv '$arg'"
|
|
43
|
+
done
|
|
28
44
|
|
|
29
45
|
default_device() {
|
|
30
46
|
if [ -r "$config/device" ]; then head -n 1 "$config/device"; return; fi
|
|
@@ -97,48 +113,193 @@ web_view() {
|
|
|
97
113
|
exec ssh -t "$host" "sh -c 'wv_release=\$($remote hello 2>/dev/null); wv_release=\${wv_release##* version=}; wv_release=\${wv_release%% *}; case \$wv_release in 0.*|1.0.*) echo \"$wv_old\" >&2; exec $wv_view ;; *) exec $wv_view --default-device $wv_base$wv_qr ;; esac'"
|
|
98
114
|
}
|
|
99
115
|
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
#
|
|
116
|
+
# A client linked to a checkout (install.sh --link, or ./install.sh in one): it is updated there with git pull, and
|
|
117
|
+
# npm must never replace it with a copy. An installed copy has no install.sh beside its folder (install.sh drops it).
|
|
118
|
+
from_checkout() { [ -r "$(self_dir)/../install.sh" ]; }
|
|
119
|
+
|
|
120
|
+
# This machine's remembered choices as install.sh flags, one argument per line (no value holds a newline: the file is
|
|
121
|
+
# read line by line). Every KEY=VALUE line of ~/.config/lanes/install-options becomes its flag, checked as install.sh
|
|
122
|
+
# checks the file (a value it would ignore is not passed; it names that one itself), so an update repeats the last
|
|
123
|
+
# install even where the new package would resolve a default differently. Nothing, and status 0, without a readable
|
|
124
|
+
# file; nothing, and status 2, for a file a newer installer wrote (its SCHEMA is unknown here: not replayed).
|
|
125
|
+
replay_options() {
|
|
126
|
+
ro_options=$config/install-options
|
|
127
|
+
[ -r "$ro_options" ] || return 0
|
|
128
|
+
case "$(sed -n 's/^SCHEMA=//p' "$ro_options" | tail -n 1)" in ''|1) ;; *) return 2 ;; esac
|
|
129
|
+
while IFS= read -r ro_line || [ -n "$ro_line" ]; do
|
|
130
|
+
ro_value=${ro_line#*=}
|
|
131
|
+
ro_flag=$(printf '%s' "${ro_line%%=*}" | tr 'A-Z_' 'a-z-')
|
|
132
|
+
case "$ro_line" in
|
|
133
|
+
DEVICE=*) case "$ro_value" in ''|*[!a-z0-9_.-]*|.*) ;; *) printf '%s\n' --device "$ro_value" ;; esac ;;
|
|
134
|
+
KEY=/*|KEY='~/'*) printf '%s\n' --key "$ro_value" ;;
|
|
135
|
+
SITE=/*) printf '%s\n' --site "$ro_value" ;;
|
|
136
|
+
SITE=*) case "$ro_value" in ''|.*|*/*|*[!A-Za-z0-9_.-]*) ;; *) printf '%s\n' --site "$ro_value" ;; esac ;;
|
|
137
|
+
DESKTOP=1|UPLOADS=1|PALETTE=1|CODER_LAUNCH=1) printf '%s\n' "--$ro_flag" ;;
|
|
138
|
+
DESKTOP=0|UPLOADS=0|PALETTE=0|CODER_LAUNCH=0) printf '%s\n' "--no-$ro_flag" ;;
|
|
139
|
+
esac # (SCHEMA, comments, unknown keys: install.sh does not read them either)
|
|
140
|
+
done < "$ro_options"
|
|
141
|
+
return 0
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
# `lanes-launch update`: the newest client from npm with this machine's remembered choices (replay_options).
|
|
145
|
+
# Arguments given here follow the replayed ones: on install.sh's command line the last flag wins.
|
|
105
146
|
update_client() {
|
|
106
147
|
for uc_arg in "$@"; do
|
|
107
148
|
case "$uc_arg" in --server) echo 'lanes-launch update updates this client; a server updates with: npx @vibelord/lanes@latest --server' >&2; exit 64 ;; esac
|
|
108
149
|
done
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
# (a linked or checkout client: npm would replace it with a copy)
|
|
112
|
-
echo "lanes-launch update: this client runs from a checkout ($(dirname "$uc_dir")): update it there with git pull, then ./install.sh" >&2
|
|
150
|
+
if from_checkout; then
|
|
151
|
+
echo "lanes-launch update: this client runs from a checkout ($(dirname "$(self_dir)")): update it there with git pull, then ./install.sh" >&2
|
|
113
152
|
exit 64
|
|
114
153
|
fi
|
|
115
154
|
command -v npx >/dev/null 2>&1 || { echo 'lanes-launch update: needs Node.js 18 or newer (npx), https://nodejs.org' >&2; exit 69; }
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
case "$(sed -n 's/^SCHEMA=//p' "$uc_options" | tail -n 1)" in
|
|
120
|
-
''|1)
|
|
121
|
-
while IFS= read -r uc_line || [ -n "$uc_line" ]; do
|
|
122
|
-
uc_value=${uc_line#*=}
|
|
123
|
-
uc_flag=$(printf '%s' "${uc_line%%=*}" | tr 'A-Z_' 'a-z-')
|
|
124
|
-
case "$uc_line" in
|
|
125
|
-
DEVICE=*) case "$uc_value" in ''|*[!a-z0-9_.-]*|.*) ;; *) set -- "$@" --device "$uc_value" ;; esac ;;
|
|
126
|
-
KEY=/*|KEY='~/'*) set -- "$@" --key "$uc_value" ;;
|
|
127
|
-
SITE=/*) set -- "$@" --site "$uc_value" ;;
|
|
128
|
-
SITE=*) case "$uc_value" in ''|.*|*/*|*[!A-Za-z0-9_.-]*) ;; *) set -- "$@" --site "$uc_value" ;; esac ;;
|
|
129
|
-
DESKTOP=1|UPLOADS=1|PALETTE=1|CODER_LAUNCH=1) set -- "$@" "--$uc_flag" ;;
|
|
130
|
-
DESKTOP=0|UPLOADS=0|PALETTE=0|CODER_LAUNCH=0) set -- "$@" "--no-$uc_flag" ;;
|
|
131
|
-
esac # (SCHEMA, comments, unknown keys: install.sh does not read them either)
|
|
132
|
-
done < "$uc_options" ;;
|
|
133
|
-
*) echo "lanes-launch update: $uc_options was written by a newer installer: not replayed" >&2 ;;
|
|
134
|
-
esac
|
|
135
|
-
fi
|
|
136
|
-
# The arguments given here go last: move them behind the replayed flags.
|
|
137
|
-
while [ "$uc_given" -gt 0 ]; do set -- "$@" "$1"; shift; uc_given=$((uc_given - 1)); done
|
|
155
|
+
uc_replayed=$(replay_options) || echo "lanes-launch update: $config/install-options was written by a newer installer: not replayed" >&2
|
|
156
|
+
# One argument per line, the replayed ones first (set -f: a value is never a glob pattern; IFS unset: the default).
|
|
157
|
+
set -f; IFS=$nl; set -- $uc_replayed "$@"; unset IFS; set +f
|
|
138
158
|
printf 'lanes-launch update: npx --yes @vibelord/lanes@latest %s\n' "$*" >&2
|
|
139
159
|
exec npx --yes @vibelord/lanes@latest "$@"
|
|
140
160
|
}
|
|
141
161
|
|
|
162
|
+
# ---- self-update (W9 K2) --------------------------------------------------------------------------------------------
|
|
163
|
+
# Before a window connects (attach and `lane <id>`; never the helper, `ls`, `web`, `update` or --version), at most
|
|
164
|
+
# once every 6 h: ask the server whether this client is older (`lanes hello --client-version <this release>`, which
|
|
165
|
+
# then ends its greeting with ` update=<release>`, W9 K1). If it is, and npm has that release, install EXACTLY it,
|
|
166
|
+
# never @latest, with the remembered install options, as `lanes-launch update` would, and start again as the new
|
|
167
|
+
# client with the same command line — once: the restarted client carries LANES_AUTOUPDATED=1 and never updates itself
|
|
168
|
+
# again. LANES_NO_AUTOUPDATE (any value) turns it off; a client run from a checkout, or one without a release, never
|
|
169
|
+
# asks. Nothing here keeps a window from connecting: an update that fails says so in one dim line, and the window goes
|
|
170
|
+
# on with this client. A question that gets no answer (no server, an older one that takes the flag for a device name,
|
|
171
|
+
# a busy one) is only logged: the connection that follows says what is wrong with the path. So is a release npm does
|
|
172
|
+
# not have (yet): a server run from a checkout says its VERSION as soon as it is pulled, which can be before the
|
|
173
|
+
# publish, and no machine can act on that — the next question, 6 h later, asks npm again.
|
|
174
|
+
checked=$state/checked # its time: when a window of this machine last asked (touched on every question)
|
|
175
|
+
claim=$state/checking # the folder of the one window asking right now (mkdir is atomic); the answers land in it
|
|
176
|
+
claimed=
|
|
177
|
+
# Whether a question is due: no stamp, one 6 h old or older, or one from the future (a clock that was wrong).
|
|
178
|
+
update_due() {
|
|
179
|
+
ud_seen=$(mtime "$checked") || return 0
|
|
180
|
+
[ -n "$ud_seen" ] || return 0
|
|
181
|
+
ud_age=$(( $(date +%s) - ud_seen ))
|
|
182
|
+
[ "$ud_age" -ge 21600 ] || [ "$ud_age" -lt 0 ]
|
|
183
|
+
}
|
|
184
|
+
# Windows that open together (a restored session) ask once between them: the first takes the claim, the others go
|
|
185
|
+
# on. A claim older than 10 min was left by a window that closed while it asked; it is taken over.
|
|
186
|
+
take_claim() {
|
|
187
|
+
mkdir -p "$state" 2>/dev/null || return 1
|
|
188
|
+
if ! mkdir "$claim" 2>/dev/null; then
|
|
189
|
+
tc_since=$(mtime "$claim") || return 1
|
|
190
|
+
[ $(( $(date +%s) - tc_since )) -ge 600 ] || return 1
|
|
191
|
+
rm -rf "$claim"; mkdir "$claim" 2>/dev/null || return 1
|
|
192
|
+
fi
|
|
193
|
+
claimed=1
|
|
194
|
+
}
|
|
195
|
+
drop_claim() {
|
|
196
|
+
[ -n "$claimed" ] || return 0
|
|
197
|
+
rm -f "$claim/reply" "$claim/npm.err"; rmdir "$claim" 2>/dev/null || :
|
|
198
|
+
claimed=
|
|
199
|
+
}
|
|
200
|
+
# npx -> $npx_path and the npm beside it -> $npm_path, both absolute: npx on this PATH, else where the login shell
|
|
201
|
+
# finds it — a window a desktop app opened has the system's minimal PATH (macOS: no Homebrew, no Node.js installer
|
|
202
|
+
# folder), as install.sh knows of an ssh session. Node.js puts npm and npx in one folder. Status 1 without both.
|
|
203
|
+
find_npx() {
|
|
204
|
+
npx_path=$(command -v npx 2>/dev/null) || npx_path=
|
|
205
|
+
case "$npx_path" in /*) ;; *)
|
|
206
|
+
limit=10; tree=1
|
|
207
|
+
run "${SHELL:-/bin/sh}" -lc 'command -v npx' </dev/null >"$claim/reply" 2>/dev/null || :
|
|
208
|
+
limit= tree=
|
|
209
|
+
npx_path=$(tail -n 1 "$claim/reply" 2>/dev/null) ;; # (a talkative profile prints before it)
|
|
210
|
+
esac
|
|
211
|
+
npm_path=$(dirname "$npx_path")/npm
|
|
212
|
+
case "$npx_path" in /*) [ -x "$npx_path" ] && [ -x "$npm_path" ] ;; *) return 1 ;; esac
|
|
213
|
+
}
|
|
214
|
+
# on_npm <release>: whether npm has that release of the package, asked as npx would fetch it (the same npm, its config
|
|
215
|
+
# and registry). 0: `npm view` printed it. 1: not published (yet) — npm 10 and later end with code E404 ("No match
|
|
216
|
+
# found for version"), earlier ones print nothing. 2: no answer (offline, a proxy or the registry failing, 15 s), with
|
|
217
|
+
# npm's error code, when it gave one, in $npm_code ([A-Z0-9_] only: it is shown). --fetch-retries=0: the window waits
|
|
218
|
+
# for this, and npm's own retries kept a refused registry going past 60 s (MEASURED, npm 11.6.2: 0.3 s without them);
|
|
219
|
+
# the next question, 6 h later, is the retry.
|
|
220
|
+
on_npm() {
|
|
221
|
+
npm_code=
|
|
222
|
+
limit=15; tree=1
|
|
223
|
+
run env PATH="$(dirname "$npx_path"):$PATH" "$npm_path" view "@vibelord/lanes@$1" version --fetch-retries=0 </dev/null >"$claim/reply" 2>"$claim/npm.err" || :
|
|
224
|
+
limit= tree=
|
|
225
|
+
if grep -qxF -- "$1" "$claim/reply" 2>/dev/null; then return 0; fi
|
|
226
|
+
npm_code=$(sed -n 's/.*code \(E[A-Z0-9_]*\).*/\1/p' "$claim/npm.err" 2>/dev/null | head -n 1)
|
|
227
|
+
if [ "$rc" = 0 ] && [ ! -s "$claim/reply" ]; then return 1; fi
|
|
228
|
+
[ "$npm_code" = E404 ] && return 1
|
|
229
|
+
return 2
|
|
230
|
+
}
|
|
231
|
+
# One dim line on the screen, and the same in the log.
|
|
232
|
+
say_dim() { printf '\033[2mlanes: %s\033[0m\r\n' "$1"; log "self-update: $1"; }
|
|
233
|
+
self_update() {
|
|
234
|
+
[ -z "${LANES_NO_AUTOUPDATE:-}" ] && [ -z "${LANES_AUTOUPDATED:-}" ] && [ -n "$release" ] || return 0
|
|
235
|
+
! from_checkout || return 0
|
|
236
|
+
update_due || return 0
|
|
237
|
+
take_claim || return 0
|
|
238
|
+
# (looked at again under the claim: another window may have asked between the first look and the claim)
|
|
239
|
+
if ! update_due || ! touch "$checked" 2>/dev/null; then drop_claim; return 0; fi
|
|
240
|
+
# Said while it asks (a busy server needs seconds for banner and key exchange); wiped once it has the answer.
|
|
241
|
+
printf '\033[2mlanes %s: asking %s for a newer client…\033[0m' "$release" "$host"
|
|
242
|
+
limit=10
|
|
243
|
+
run ssh -n -o RequestTTY=no -o BatchMode=yes -o ConnectTimeout=5 "$host" "$remote hello$version" >"$claim/reply" 2>/dev/null || :
|
|
244
|
+
limit=
|
|
245
|
+
printf '\r\033[K'
|
|
246
|
+
su_to=$(awk '$1 == "ok" { for (i = 2; i <= NF; i++) if (substr($i, 1, 7) == "update=") print substr($i, 8); exit }' "$claim/reply" 2>/dev/null)
|
|
247
|
+
# An exact release only: digits first, nothing npm could read as a tag, a range, a path or a URL (and such text is
|
|
248
|
+
# never echoed, not even into the log).
|
|
249
|
+
su_ok=
|
|
250
|
+
case "$su_to" in
|
|
251
|
+
''|"$release") log "self-update: asked $host (exit $rc): no update"; drop_claim; return 0 ;;
|
|
252
|
+
*[!A-Za-z0-9.+-]*) ;;
|
|
253
|
+
[0-9]*.[0-9]*.[0-9]*) su_ok=1 ;;
|
|
254
|
+
esac
|
|
255
|
+
if [ -z "$su_ok" ]; then log "self-update: asked $host: ignored an update= that names no release"; drop_claim; return 0; fi
|
|
256
|
+
if ! su_replayed=$(replay_options); then
|
|
257
|
+
drop_claim
|
|
258
|
+
say_dim "the server runs $su_to; not updating by itself: $config/install-options was written by a newer installer — run lanes-launch update"
|
|
259
|
+
return 0
|
|
260
|
+
fi
|
|
261
|
+
if ! find_npx; then
|
|
262
|
+
drop_claim
|
|
263
|
+
say_dim "the server runs $su_to; no npx and npm (Node.js 18 or newer) on this machine to update with — this window goes on with $release"
|
|
264
|
+
return 0
|
|
265
|
+
fi
|
|
266
|
+
# Only a release npm has is installed: npx would fail on any other one only after its own registry round trip, the
|
|
267
|
+
# same way on every machine and every 6 h, with nothing the person at this window can do about it (DEF-1786).
|
|
268
|
+
printf '\033[2mlanes %s: the server runs %s; asking npm for it…\033[0m' "$release" "$su_to"
|
|
269
|
+
on_npm "$su_to"; su_npm=$?
|
|
270
|
+
printf '\r\033[K'
|
|
271
|
+
case "$su_npm" in
|
|
272
|
+
1) log "self-update: the server runs $su_to, which npm does not have (yet): not installed; asked again in 6 h"
|
|
273
|
+
drop_claim; return 0 ;;
|
|
274
|
+
2) drop_claim
|
|
275
|
+
say_dim "the server runs $su_to; asking npm for it failed (exit $rc${npm_code:+, $npm_code}) — this window goes on with $release; windows try again in 6 h"
|
|
276
|
+
return 0 ;;
|
|
277
|
+
esac
|
|
278
|
+
set -f; IFS=$nl; set -- $su_replayed; unset IFS; set +f
|
|
279
|
+
say_dim "the server runs $su_to — updating this client from $release: npx --yes @vibelord/lanes@$su_to $*"
|
|
280
|
+
# The terminal stays this command's (install.sh may ask something; closing the window or Ctrl-C ends both). npx's
|
|
281
|
+
# folder goes first on its PATH: npx is `#!/usr/bin/env node`, and node lives beside it. tree: npx is the first of a
|
|
282
|
+
# chain (npm, its shell, the npm wrapper's node, install.sh, its ssh), each waiting for the next; the watchdog and
|
|
283
|
+
# a closed window end all of it, not only npx, so nothing of the install goes on under the view (DEF-1818).
|
|
284
|
+
limit=600; tree=1
|
|
285
|
+
run env PATH="$(dirname "$npx_path"):$PATH" "$npx_path" --yes "@vibelord/lanes@$su_to" "$@" || :
|
|
286
|
+
limit= tree=
|
|
287
|
+
drop_claim
|
|
288
|
+
# install.sh: 0 ready · 3 and 4 installed, but the key or the server is not ready (it said so) · anything else:
|
|
289
|
+
# npm, the wrapper or the installer failed, or the watchdog ended them, and this client's own files may still be
|
|
290
|
+
# the old ones. The remedy is this release (npm has it): `lanes-launch update` would install @latest instead.
|
|
291
|
+
case "$rc" in
|
|
292
|
+
0|3|4) ;;
|
|
293
|
+
*) say_dim "the update to $su_to did not finish (exit $rc) — this window goes on with $release; windows try again in 6 h, or now: npx @vibelord/lanes@$su_to"; return 0 ;;
|
|
294
|
+
esac
|
|
295
|
+
log "self-update: installed $su_to (exit $rc); starting it"
|
|
296
|
+
eval "set -- $argv"
|
|
297
|
+
LANES_AUTOUPDATED=1; export LANES_AUTOUPDATED
|
|
298
|
+
# (the installed copy is executable; a copy run as `sh <file>` may not be)
|
|
299
|
+
if [ -x "$0" ]; then exec "$0" "$@"; fi
|
|
300
|
+
exec /bin/sh "$0" "$@"
|
|
301
|
+
}
|
|
302
|
+
|
|
142
303
|
while_pid=
|
|
143
304
|
service=
|
|
144
305
|
extra=
|
|
@@ -154,12 +315,20 @@ case "${1:-}" in
|
|
|
154
315
|
esac
|
|
155
316
|
done ;;
|
|
156
317
|
ls) exec ssh -o RequestTTY=no "$host" "$remote ls" ;;
|
|
318
|
+
here)
|
|
319
|
+
# `lanes here <device>` on the server: one connection without a terminal (it opens no view; the device's running
|
|
320
|
+
# window follows). The name is spliced into the remote command line, so it is checked before any connection.
|
|
321
|
+
[ $# -le 2 ] || { echo 'usage: lanes-launch here [device]' >&2; exit 64; }
|
|
322
|
+
here_device=${2:-$(default_device)}
|
|
323
|
+
case "$here_device" in ''|-*|*[!A-Za-z0-9_.-]*) echo "invalid name: $here_device" >&2; exit 64 ;; esac
|
|
324
|
+
exec ssh -o RequestTTY=no "$host" "$remote here $here_device" ;;
|
|
157
325
|
web) shift; web_view "$@" ;;
|
|
158
326
|
update) shift; update_client "$@" ;;
|
|
159
327
|
lane)
|
|
160
328
|
[ -n "${2:-}" ] || { echo 'usage: lanes-launch lane <lane-id>' >&2; exit 64; }
|
|
161
329
|
mode=open target=$2 ;;
|
|
162
|
-
-h|--help) sed -n '
|
|
330
|
+
-h|--help) sed -n '1d;/^#/!q;p' "$0"; exit 0 ;; # the comment header, however long it grows
|
|
331
|
+
--version) cv=$(client_version); printf 'lanes-launch %s\n' "${cv:-dev}"; exit 0 ;;
|
|
163
332
|
*) mode=attach target=${1:-$(default_device)} ;;
|
|
164
333
|
esac
|
|
165
334
|
if [ "$mode" = attach ] && [ $# -gt 1 ]; then
|
|
@@ -172,7 +341,13 @@ if [ "$mode" = attach ] && [ $# -gt 1 ]; then
|
|
|
172
341
|
--ascii) extra="$extra --ascii" ;;
|
|
173
342
|
--theme) case "${2:-}" in dark|light|auto) extra="$extra --theme $2"; shift ;; *) echo '--theme takes dark, light or auto' >&2; exit 64 ;; esac ;;
|
|
174
343
|
--glyphs) case "${2:-}" in unicode|ascii|emoji) extra="$extra --glyphs $2"; shift ;; *) echo '--glyphs takes unicode, ascii or emoji' >&2; exit 64 ;; esac ;;
|
|
175
|
-
--color) case "${2:-}" in truecolor|16|none) extra="$extra --color $2"; shift ;; *) echo '--color takes truecolor, 16 or none' >&2; exit 64 ;; esac ;;
|
|
344
|
+
--color) case "${2:-}" in truecolor|256|16|none) extra="$extra --color $2"; shift ;; *) echo '--color takes truecolor, 256, 16 or none' >&2; exit 64 ;; esac ;;
|
|
345
|
+
--notify) case "${2:-}" in 1|0) extra="$extra --notify $2"; shift ;; *) echo '--notify takes 1 or 0' >&2; exit 64 ;; esac ;;
|
|
346
|
+
--dim) case "${2:-}" in 90|2) extra="$extra --dim $2"; shift ;; *) echo '--dim takes 90 or 2' >&2; exit 64 ;; esac ;;
|
|
347
|
+
--device-only) extra="$extra --device-only" ;;
|
|
348
|
+
# Quick edit's editor (Ctrl-Alt-e, W9 E2): the server's own five values. A server older than `lanes edit`
|
|
349
|
+
# takes it as an unknown flag and records nothing, which costs nothing there: it has no quick edit.
|
|
350
|
+
--editor) case "${2:-}" in micro|hx|nvim|vim|auto) extra="$extra --editor $2"; shift ;; *) echo '--editor takes micro, hx, nvim, vim or auto' >&2; exit 64 ;; esac ;;
|
|
176
351
|
*) echo "lanes-launch: ignoring unknown option $1" >&2 ;;
|
|
177
352
|
esac
|
|
178
353
|
shift
|
|
@@ -182,9 +357,11 @@ fi
|
|
|
182
357
|
case "$target" in ''|-*|*[!A-Za-z0-9_.-]*) echo "invalid name: $target" >&2; exit 64 ;; esac
|
|
183
358
|
client="$(hostname -s 2>/dev/null || echo client)-$$"
|
|
184
359
|
client=$(printf '%s' "$client" | tr -c 'A-Za-z0-9_.-' '-')
|
|
185
|
-
# Told to `lanes attach`, which says in one line when this client is older than the server (`lanes-launch update`)
|
|
186
|
-
|
|
187
|
-
|
|
360
|
+
# Told to `lanes attach`, which says in one line when this client is older than the server (`lanes-launch update`),
|
|
361
|
+
# and shown in the connecting line below.
|
|
362
|
+
release=$(client_version)
|
|
363
|
+
version=
|
|
364
|
+
[ -z "$release" ] || version=" --client-version $release"
|
|
188
365
|
|
|
189
366
|
# ---- log: what a window that showed nothing never left behind ------------------------------------------------------
|
|
190
367
|
# One line per connection event, rotated to .1 at 256 KiB. The service writes to stderr instead: launchd / journald
|
|
@@ -208,14 +385,21 @@ reader=
|
|
|
208
385
|
holding=
|
|
209
386
|
now=
|
|
210
387
|
limit=
|
|
388
|
+
tree= # set: a timeout or a signal ends the child's whole process tree (kill_tree), not only the child
|
|
211
389
|
helper_pid=
|
|
212
390
|
stop() {
|
|
213
391
|
trap - HUP TERM INT
|
|
214
392
|
exec 2>/dev/null # bash reports every child it sees die of a signal: noise in a terminal that stays open
|
|
215
|
-
if [ -n "$child" ]
|
|
393
|
+
if [ -n "$child" ] && [ -n "$tree" ]; then
|
|
394
|
+
# (no second signal may cut the walk short: it stops each process before it ends them all)
|
|
395
|
+
trap '' HUP TERM INT; kill_tree "$child"
|
|
396
|
+
elif [ -n "$child" ]; then
|
|
397
|
+
kill -TERM "$child" 2>/dev/null || :
|
|
398
|
+
fi
|
|
216
399
|
cancel_watchdog
|
|
217
400
|
if [ -n "$reader" ]; then kill -TERM "$reader" 2>/dev/null || :; fi
|
|
218
401
|
end_helper
|
|
402
|
+
drop_claim
|
|
219
403
|
log 'stopped: window closed, or a signal'
|
|
220
404
|
exit 0
|
|
221
405
|
}
|
|
@@ -267,6 +451,14 @@ run() {
|
|
|
267
451
|
wait "$timer" 2>/dev/null || :
|
|
268
452
|
if [ -n "$cancelled" ]; then
|
|
269
453
|
wait "$timer" 2>/dev/null || :
|
|
454
|
+
elif [ -n "$tree" ]; then
|
|
455
|
+
# (the timer has ended and is reaped: a cancellation during the walk must not signal its old pid)
|
|
456
|
+
timer=
|
|
457
|
+
# Nor run the trap: bash that runs a trap between a pipeline's first and last command never starts the
|
|
458
|
+
# last one and waits for the first, whose output then fills a pipe bash itself holds — for ever
|
|
459
|
+
# (DEF-1791). run() cancels as soon as the child ends, mid-walk; there is nothing left to cancel.
|
|
460
|
+
trap '' HUP TERM INT
|
|
461
|
+
kill_tree "$child"
|
|
270
462
|
else
|
|
271
463
|
kill -TERM "$child" 2>/dev/null || :
|
|
272
464
|
fi
|
|
@@ -279,6 +471,61 @@ run() {
|
|
|
279
471
|
cancel_watchdog
|
|
280
472
|
return "$rc"
|
|
281
473
|
}
|
|
474
|
+
# ---- process trees: an installer is more than one process --------------------------------------------------------
|
|
475
|
+
# `npx` runs npm, npm a shell, that shell node (the npm wrapper), node install.sh, install.sh ssh — each waiting in the
|
|
476
|
+
# foreground for the next. Ending the first leaves the rest on this terminal: npm passes TERM to its own child only,
|
|
477
|
+
# node's spawnSync child outlives node, and a command this script starts in the background ignores Ctrl-C (POSIX, for
|
|
478
|
+
# a shell without job control). Its own process group is no way out either: a background group that reads the
|
|
479
|
+
# terminal is stopped (install.sh may ask something). So with $tree set, the watchdog and stop() end the whole tree.
|
|
480
|
+
# children <pid>…: the pids whose parent is one of them, from one `ps` listing (macOS and Linux print the same columns).
|
|
481
|
+
children() { ps -A -o pid= -o ppid= 2>/dev/null | awk -v of=" $* " 'index(of, " " $2 " ") { print $1 }'; }
|
|
482
|
+
# running <pid>…: whether any of them still runs (a zombie has ended; its new parent reaps it).
|
|
483
|
+
running() { ps -A -o pid= -o stat= 2>/dev/null | awk -v of=" $* " 'index(of, " " $1 " ") && $2 !~ /^Z/ { up = 1 } END { exit !up }'; }
|
|
484
|
+
# kill_tree <pid>: TERM to it and to every process under it. Each generation is stopped before its children are
|
|
485
|
+
# listed: a stopped process starts nothing, and no child can be re-parented out of reach (its parent ended) mid-walk.
|
|
486
|
+
# Then TERM and CONT for all, 2 s by the clock for TERM traps (install.sh puts the terminal back), KILL for what is
|
|
487
|
+
# left, and up to 2 s more by the clock until no listing shows it — so a caller goes on only once the whole install has
|
|
488
|
+
# ended. Its callers ignore HUP, TERM and INT first: no trap may run during its pipelines (bash, DEF-1791).
|
|
489
|
+
kill_tree() {
|
|
490
|
+
unset IFS
|
|
491
|
+
# Only from a child of this shell ($$ in the watchdog too): one that ended and was reaped may have left its pid to
|
|
492
|
+
# an unrelated process by now. No answer from ps (gone, or a ps without -p): the child alone, as before.
|
|
493
|
+
case "$(ps -o ppid= -p "$1" 2>/dev/null | tr -d ' ')" in
|
|
494
|
+
"$$") ;;
|
|
495
|
+
'') kill -TERM "$1" 2>/dev/null; return 0 ;;
|
|
496
|
+
*) return 0 ;;
|
|
497
|
+
esac
|
|
498
|
+
kt_all= kt_gen=$1 kt_n=0
|
|
499
|
+
while [ -n "$kt_gen" ] && [ "$kt_n" -lt 32 ]; do
|
|
500
|
+
kill -STOP $kt_gen 2>/dev/null
|
|
501
|
+
kt_all="$kt_all $kt_gen"
|
|
502
|
+
kt_gen=$(children $kt_gen)
|
|
503
|
+
kt_n=$((kt_n + 1))
|
|
504
|
+
done
|
|
505
|
+
kill -TERM $kt_all 2>/dev/null
|
|
506
|
+
kill -CONT $kt_all 2>/dev/null
|
|
507
|
+
# A timer, not a count of polls: each poll lists every process (MEASURED 0.18 s for 3335 processes at load 25),
|
|
508
|
+
# and 20 polls of `sleep .1` had made the 2 s 6 s (DEF-1790). The shell reaps the ended timer during its next
|
|
509
|
+
# command (the listing or the sleep; dash and bash alike), so kill -0 fails from then on.
|
|
510
|
+
sleep 2 >/dev/null 2>&1 &
|
|
511
|
+
kt_timer=$! kt_killed=
|
|
512
|
+
while running $kt_all; do
|
|
513
|
+
if ! kill -0 "$kt_timer" 2>/dev/null; then
|
|
514
|
+
# A KILLed process ends only once it runs again: once it gets a CPU, or once a disk wait it sleeps in
|
|
515
|
+
# uninterruptibly ends. So the walk goes on listing after the KILL, on a second 2 s timer (DEF-1776). What
|
|
516
|
+
# still runs after that is held in the kernel, and the caller goes on without it.
|
|
517
|
+
if [ -n "$kt_killed" ]; then kt_timer=; break; fi
|
|
518
|
+
kill -KILL $kt_all 2>/dev/null
|
|
519
|
+
kt_killed=1
|
|
520
|
+
sleep 2 >/dev/null 2>&1 &
|
|
521
|
+
kt_timer=$!
|
|
522
|
+
fi
|
|
523
|
+
sleep .1
|
|
524
|
+
done
|
|
525
|
+
# (the tree ended within a timer's 2 s: the timer, this shell's own child, still ran one poll ago)
|
|
526
|
+
if [ -n "$kt_timer" ]; then kill -KILL "$kt_timer" 2>/dev/null; wait "$kt_timer" 2>/dev/null; fi
|
|
527
|
+
return 0
|
|
528
|
+
}
|
|
282
529
|
# hold <seconds> [jitter]: wait, but Enter continues at once. No raw mode, so there is no terminal state to restore
|
|
283
530
|
# when the window closes mid-wait: a line reader signals this shell instead. End of input (no terminal) is no Enter.
|
|
284
531
|
trap 'if [ -n "$holding" ]; then now=1; [ -z "$child" ] || { kill -TERM "$child"; wait "$child"; } 2>/dev/null; fi' USR1
|
|
@@ -450,6 +697,8 @@ if [ "$mode" = helper ]; then
|
|
|
450
697
|
trap - HUP TERM INT
|
|
451
698
|
upload_helper "$target"; exit 0
|
|
452
699
|
fi
|
|
700
|
+
# A window (attach, or `lane <id>`) checks for a newer client first: a restart then leaves no helper of this one behind.
|
|
701
|
+
self_update
|
|
453
702
|
if [ "$mode" = attach ]; then
|
|
454
703
|
while_pid=$$ # ends the helper also when this client is killed without a chance to clean up
|
|
455
704
|
upload_helper "$target" </dev/null >/dev/null 2>&1 &
|
|
@@ -458,8 +707,8 @@ fi
|
|
|
458
707
|
while :; do
|
|
459
708
|
printf '\033]0;lanes · %s\007' "$target"
|
|
460
709
|
# Said before ssh draws anything: a server under pressure needs many seconds for banner and key exchange, and a
|
|
461
|
-
# window that stays blank meanwhile reads as dead.
|
|
462
|
-
printf '\033[2mlanes: connecting to %s…\033[0m\r\n' "$host"
|
|
710
|
+
# window that stays blank meanwhile reads as dead. It names this client's release (client_version's charset).
|
|
711
|
+
printf '\033[2mlanes%s: connecting to %s…\033[0m\r\n' "${release:+ $release}" "$host"
|
|
463
712
|
log "connecting: $mode $target via $host"
|
|
464
713
|
started=$(date +%s)
|
|
465
714
|
if [ "$mode" = attach ]; then
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
|
-
# Open the lanes window in its own terminal instance.
|
|
2
|
+
# Open the lanes window in its own terminal instance.
|
|
3
|
+
# usage: lanes-window [--ascii] [--light|--dark] [--device-only] [suffix] (-h / --help: this text)
|
|
3
4
|
# suffix: an independent view session "<device>-<suffix>" (second monitor / other half of an ultrawide).
|
|
4
5
|
# w2 / w3: a WIDE view session with 2 / 3 viewports side by side per workspace (for a full-width window).
|
|
5
|
-
# --ascii / --light / --dark: the cockpit's look
|
|
6
|
-
#
|
|
6
|
+
# --ascii / --light / --dark: the cockpit's look, remembered on the server as `lanes attach … --ascii|--theme` does.
|
|
7
|
+
# On a device with an owner (enrolled with an owner, a window of such a machine, or `lanes device claim`) it goes to
|
|
8
|
+
# that person's profile, shared by all their devices: every running sidebar of theirs paints it within a tick.
|
|
9
|
+
# --device-only keeps it in devices/<device>.json for this device alone, where it wins over the profile and a
|
|
10
|
+
# running sidebar and its status screens pick it up within a tick — as for every look on a device without an owner.
|
|
7
11
|
# macOS: Ghostty. Linux: Ghostty when installed, otherwise Foot with the same window class.
|
|
8
12
|
set -u
|
|
9
13
|
config=${XDG_CONFIG_HOME:-$HOME/.config}/lanes
|
|
@@ -12,13 +16,21 @@ class=org.omarchy.lanes
|
|
|
12
16
|
device=
|
|
13
17
|
file=$config/ghostty.conf
|
|
14
18
|
look=
|
|
19
|
+
only=
|
|
15
20
|
while [ $# -gt 0 ]; do
|
|
16
21
|
case "$1" in
|
|
22
|
+
-h|--help) sed -n '2,/^[^#]/{/^#/p;}' "$0"; exit 0 ;;
|
|
17
23
|
--ascii) look="$look --ascii"; shift ;;
|
|
18
24
|
--light|--dark) look="$look --theme ${1#--}"; shift ;;
|
|
25
|
+
--device-only) only=' --device-only'; shift ;;
|
|
19
26
|
*) break ;;
|
|
20
27
|
esac
|
|
21
28
|
done
|
|
29
|
+
if [ -n "$only" ]; then
|
|
30
|
+
# Alone it would record nothing: say so instead of opening a window that looks like it did.
|
|
31
|
+
[ -n "$look" ] || { echo 'lanes-window: --device-only goes with --ascii, --light or --dark' >&2; exit 64; }
|
|
32
|
+
look="$look$only"
|
|
33
|
+
fi
|
|
22
34
|
if [ -n "${1:-}" ]; then
|
|
23
35
|
case "$1" in *[!A-Za-z0-9_.-]*) echo 'invalid suffix' >&2; exit 64 ;; esac
|
|
24
36
|
device="$(head -n 1 "$config/device")-$1"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{"hooks": {
|
|
2
2
|
"UserPromptSubmit": [{"hooks": [{"type": "command", "command": "~/.lz/bin/lane-signal working --event UserPromptSubmit --harness claude", "timeout": 2}]}],
|
|
3
|
+
"PreToolUse": [{"hooks": [{"type": "command", "command": "~/.lz/bin/lane-signal working --event PreToolUse --harness claude", "timeout": 2}]}],
|
|
3
4
|
"PostToolUse": [{"hooks": [{"type": "command", "command": "~/.lz/bin/lane-signal working --event PostToolUse --harness claude", "timeout": 2}]}],
|
|
4
5
|
"Stop": [{"hooks": [{"type": "command", "command": "~/.lz/bin/lane-signal needs_input --event Stop --harness claude", "timeout": 2}]}],
|
|
5
6
|
"Notification": [{"matcher": "permission_prompt", "hooks": [{"type": "command", "command": "~/.lz/bin/lane-signal needs_approval --event Notification --harness claude", "timeout": 2}]},
|