-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbyteask
More file actions
executable file
·250 lines (235 loc) · 11.4 KB
/
Copy pathbyteask
File metadata and controls
executable file
·250 lines (235 loc) · 11.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
#!/bin/sh
# byteask — ByteAsk AI coding agent CLI.
# Thin wrapper over the engine so the client surface is fully ByteAsk-branded.
set -eu
VERSION="0.1.3"
DEFAULT_GATEWAY="https://code.byteask.ai"
export CODEX_HOME="${BYTEASK_HOME:-$HOME/.byteask}" # engine's config/home dir
export CODEX_BRAND="${BYTEASK_BRAND:-ByteAsk}" # in-app banner brand
export BYTEASK_CLIENT_VERSION="$VERSION" # engine displays THIS (not its crate ver)
SELF_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
ENGINE="$SELF_DIR/byteask-engine"
# minimal JSON string-field extractor (no jq dependency on clients)
_json() { grep -o "\"$1\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | head -1 | sed -E "s/.*:[[:space:]]*\"([^\"]*)\"/\1/"; }
resolve_gateway() {
if [ -n "${BYTEASK_GATEWAY:-}" ]; then echo "$BYTEASK_GATEWAY"
elif [ -f "$CODEX_HOME/gateway" ]; then cat "$CODEX_HOME/gateway"
else echo "$DEFAULT_GATEWAY"; fi
}
# ---- auto-update check: cached ~hourly, fail-open, never blocks launch ----
# State file holds: last_check=<epoch> latest=<ver>
# No "dismissed" memory: a declined update is re-offered on every launch, so a
# freshly published version keeps nudging until the user takes it.
UPDATE_STATE="$CODEX_HOME/update-check"
# version_gt A B -> returns 0 if A > B (numeric, dot-separated), else 1.
# POSIX component compare (macOS `sort` has no -V, so we cannot use it).
version_gt() {
_a="$1"; _b="$2"
if [ "$_a" = "$_b" ]; then return 1; fi
while [ -n "$_a" ] || [ -n "$_b" ]; do
_ia=${_a%%.*}; _ib=${_b%%.*}
case "$_a" in *.*) _a=${_a#*.};; *) _a="";; esac
case "$_b" in *.*) _b=${_b#*.};; *) _b="";; esac
case "$_ia" in ''|*[!0-9]*) _ia=0;; esac
case "$_ib" in ''|*[!0-9]*) _ib=0;; esac
if [ "$_ia" -gt "$_ib" ]; then return 0; fi
if [ "$_ia" -lt "$_ib" ]; then return 1; fi
done
return 1
}
# Check the server for a newer version and (on a TTY) offer to update. Entirely
# best-effort: any failure (offline, no /version, bad data) is swallowed so the
# engine always launches. Opt out with BYTEASK_NO_UPDATE_CHECK=1.
check_for_update() {
if [ -n "${BYTEASK_NO_UPDATE_CHECK:-}" ]; then return 0; fi
_uc_latest=""; _uc_last=0
if [ -f "$UPDATE_STATE" ]; then
while IFS='=' read -r _k _v; do
case "$_k" in
last_check) _uc_last=$_v;;
latest) _uc_latest=$_v;;
esac
done < "$UPDATE_STATE"
fi
case "$_uc_last" in ''|*[!0-9]*) _uc_last=0;; esac
_uc_now=$(date +%s 2>/dev/null || echo 0)
# Hit the network at most once per hour; cache the result. Fail-open. The
# short TTL means a new release is noticed within the hour on any launch.
if [ -z "$_uc_latest" ] || [ $(( _uc_now - _uc_last )) -ge 3600 ]; then
_uc_gw="$(resolve_gateway)"; _uc_gw="${_uc_gw%/}"
_uc_fetched=$(curl -fsS --max-time 2 "$_uc_gw/version" 2>/dev/null | head -n1 | tr -d '[:space:]') || _uc_fetched=""
if printf '%s' "$_uc_fetched" | grep -qE '^[0-9]+(\.[0-9]+)+$'; then
_uc_latest="$_uc_fetched"; _uc_last="$_uc_now"
mkdir -p "$CODEX_HOME" 2>/dev/null || true
printf 'last_check=%s\nlatest=%s\n' "$_uc_last" "$_uc_latest" > "$UPDATE_STATE" 2>/dev/null || true
fi
fi
if [ -z "$_uc_latest" ]; then return 0; fi
if ! version_gt "$_uc_latest" "$VERSION"; then return 0; fi # not newer than installed
if [ -t 0 ] && [ -t 1 ]; then
printf 'ByteAsk %s is available (you have %s). Update now? [y/N] ' "$_uc_latest" "$VERSION" >&2
read -r _uc_ans </dev/tty 2>/dev/null || _uc_ans=""
case "$_uc_ans" in
[yY]*)
_uc_gw="$(resolve_gateway)"; _uc_gw="${_uc_gw%/}"
printf 'Updating ByteAsk to %s ...\n' "$_uc_latest" >&2
# Install in place (over the running wrapper), then relaunch the fresh
# wrapper with the original args. The relaunch carries a no-recheck guard
# so a same-session update can never loop, even if the install no-ops.
if curl -fsSL "$_uc_gw/install.sh" | PREFIX="$SELF_DIR" sh; then
exec env BYTEASK_NO_UPDATE_CHECK=1 "$SELF_DIR/byteask" "$@"
fi
printf 'Update failed; continuing on %s.\n' "$VERSION" >&2
;;
*) : ;; # declined: continue now, re-offer next launch (no dismiss memory)
esac
else
printf 'ByteAsk %s is available (you have %s). Run: byteask --update\n' "$_uc_latest" "$VERSION" >&2
fi
return 0
}
do_login() {
GATEWAY="$(resolve_gateway)"; EMAIL=""; MODEL="${BYTEASK_MODEL:-gpt-5.4}"; AUTOCLICK=0; REF=""
while [ $# -gt 0 ]; do case "$1" in
--gateway) GATEWAY="$2"; shift 2;;
--email) EMAIL="$2"; shift 2;;
--model) MODEL="$2"; shift 2;;
--ref=*) REF="${1#--ref=}"; shift;;
--auto-click) AUTOCLICK=1; shift;;
--interactive) shift;; # onboarding entry: a no-op arg so callers invoke
# do_login WITH args, replacing $@ (else a POSIX
# function inherits the caller's args, e.g. a prompt)
*) echo "byteask login: unknown option $1" >&2; exit 2;;
esac; done
# Referral code (one-shot, first signup only): an explicit --ref= flag wins, then
# BYTEASK_REF, then the file install.sh wrote. Validated; cleared after sign-in.
[ -n "$REF" ] || REF="${BYTEASK_REF:-}"
if [ -z "$REF" ] && [ -f "$CODEX_HOME/referral" ]; then
REF="$(cat "$CODEX_HOME/referral" 2>/dev/null || echo '')"
fi
case "$REF" in *[!A-Za-z0-9_-]*) REF="" ;; esac
[ "${#REF}" -le 64 ] || REF=""
[ -n "$EMAIL" ] || { printf "Email: "; read -r EMAIL; }
GATEWAY="${GATEWAY%/}"
echo "Signing in to ByteAsk as $EMAIL ..."
body="{\"email\":\"$EMAIL\"}"
[ -n "$REF" ] && body="{\"email\":\"$EMAIL\",\"ref\":\"$REF\"}"
start=$(curl -fsS -X POST "$GATEWAY/auth/start" -H 'content-type: application/json' -d "$body")
poll=$(printf '%s' "$start" | _json poll_token)
link=$(printf '%s' "$start" | _json dev_magic_link)
[ -n "$poll" ] || { echo "sign-in failed: $start" >&2; exit 1; }
echo " -> Check $EMAIL for a sign-in link and click it."
[ -n "$link" ] && echo " -> (dev) link: $link"
# Dev mode (no email configured): the gateway returns the link, so complete sign-in automatically.
[ -n "$link" ] && curl -fsS "$link" >/dev/null 2>&1 && echo " -> sign-in confirmed"
echo " -> Waiting for confirmation ..."
token=""; i=0
while [ "$i" -lt 120 ]; do
r=$(curl -fsS -X POST "$GATEWAY/auth/poll" -H 'content-type: application/json' -d "{\"poll_token\":\"$poll\"}" || echo '{}')
[ "$(printf '%s' "$r" | _json status)" = approved ] && { token=$(printf '%s' "$r" | _json access_token); break; }
i=$((i+1)); sleep 1
done
[ -n "$token" ] || { echo "sign-in timed out" >&2; exit 1; }
mkdir -p "$CODEX_HOME"; printf '%s' "$GATEWAY" > "$CODEX_HOME/gateway"
cat > "$CODEX_HOME/config.toml" <<EOF
model = "$MODEL"
model_provider = "byteask"
web_search = "live"
[model_providers.byteask]
name = "ByteAsk"
base_url = "$GATEWAY/byteask/v1"
wire_api = "responses"
requires_openai_auth = false
experimental_bearer_token = "$token"
[model_providers.byteask.http_headers]
x-openai-actor-authorization = "byteask"
EOF
rm -f "$CODEX_HOME/referral" 2>/dev/null || true # one-shot: only the first signup is credited
echo "Signed in as $EMAIL. You're ready: byteask \"...\""
}
# ByteAsk mode-C auth is the experimental_bearer_token in config.toml — the
# engine's own `logout` only clears its OAuth store (auth.json), which mode C
# never writes, so it left the user still signed in. Strip the token line (back
# to unsigned; the next `byteask` onboards) and drop any auth.json too. Reports
# on the ACTUAL post-state so a read-only config can't fake a logout. Returns
# non-zero only when a token was present but couldn't be removed.
do_logout() {
_lo_cfg="$CODEX_HOME/config.toml"; _lo_had=0
if [ -f "$_lo_cfg" ] && grep -q 'experimental_bearer_token' "$_lo_cfg" 2>/dev/null; then
_lo_had=1
_lo_tmp="$_lo_cfg.logout.$$"
if grep -v 'experimental_bearer_token' "$_lo_cfg" > "$_lo_tmp" 2>/dev/null; then
mv "$_lo_tmp" "$_lo_cfg" 2>/dev/null || true
fi
rm -f "$_lo_tmp" 2>/dev/null || true
fi
rm -f "$CODEX_HOME/auth.json" 2>/dev/null || true # also clear any engine OAuth store
if [ "$_lo_had" = 0 ]; then
echo "You're not signed in to ByteAsk."; return 0
elif grep -q 'experimental_bearer_token' "$_lo_cfg" 2>/dev/null; then
echo "Couldn't remove the saved token — check permissions on $_lo_cfg" >&2; return 1
fi
echo "Logged out of ByteAsk. Run 'byteask' to sign back in."; return 0
}
case "${1:-}" in
--version|-V|version) echo "byteask $VERSION"; exit 0;;
--update|update|upgrade)
echo "Updating ByteAsk CLI..."
_gw="$(resolve_gateway)"; _gw="${_gw%/}"
exec sh -c "curl -fsSL '$_gw/install.sh' | PREFIX='$SELF_DIR' sh";;
login)
shift
case " $* " in
*" --with-api-key "*|*" --api-key "*) exec "$ENGINE" login "$@";; # bring-your-own-key path
esac
do_login "$@"; exit 0;;
logout)
_lc=0; do_logout || _lc=$?; exit "$_lc";;
--uninstall-gdb-bridge)
_gi="$HOME/.gdbinit"
_gm="# ===== ByteAsk GDB bridge (added by the byteask installer) ====="
if [ -f "$_gi" ] && grep -qF "$_gm" "$_gi" 2>/dev/null; then
sed -i.bak '/# ===== ByteAsk GDB bridge (added by the byteask installer) =====/,/# ===== end ByteAsk GDB bridge =====/d' "$_gi" \
&& echo "Removed the ByteAsk gdb bridge block from $_gi (backup: $_gi.bak)."
else
echo "No ByteAsk gdb bridge block in $_gi; nothing to remove."
fi
exit 0;;
esac
# Non-blocking, cached, fail-open update check before launching the engine
# (skipped for --version/--update/login, which exit in the case above). Args
# are forwarded so an accepted in-line update can relaunch with them intact.
check_for_update "$@" || true
# Launch loop. Normally we just run the engine once. But the TUI's `/login` and
# `/logout` can't hot-swap the startup-loaded token, so they drop a one-word marker
# and exit; we read it here and (re)authenticate with the shell flow, then relaunch.
# We run (not exec) the engine so we regain control after it exits to act on that.
AUTH_REQ="$CODEX_HOME/.byteask-auth-request"
rm -f "$AUTH_REQ" 2>/dev/null || true
while : ; do
# Not signed in yet (mode-C default, no token). On an interactive terminal, onboard
# right here so a bare `byteask` just works like `claude`/`codex`: do_login prompts
# for the email and writes the token; then we launch. A non-interactive run can't
# prompt, so it keeps the one-line nudge instead of hanging on a read.
CFG="$CODEX_HOME/config.toml"
if grep -q '^model_provider = "byteask"' "$CFG" 2>/dev/null && ! grep -q 'experimental_bearer_token' "$CFG" 2>/dev/null; then
if [ -t 0 ] && [ -t 1 ]; then
echo "Welcome to ByteAsk — let's get you signed in (one time)."
# `--interactive` (a no-op flag) forces a WITH-args call so do_login does NOT
# inherit this script's "$@" (e.g. a `byteask "prompt"`); "$@" is preserved.
do_login --interactive
else
echo "You're not signed in to ByteAsk. Run: byteask login --email you@company.com" >&2
exit 1
fi
fi
_rc=0; "$ENGINE" "$@" || _rc=$?
# No auth action requested -> propagate the engine's exit code and stop.
[ -f "$AUTH_REQ" ] || exit "$_rc"
_act=$(cat "$AUTH_REQ" 2>/dev/null || echo ""); rm -f "$AUTH_REQ" 2>/dev/null || true
case "$_act" in
logout*) _lc=0; do_logout || _lc=$?; exit "$_lc" ;; # /logout: clear token, back to shell
login*) do_login --interactive; set -- ; continue ;; # /login: (re)auth, relaunch a fresh TUI
*) exit "$_rc" ;;
esac
done