Repository navigation
Expand file tree
/
Copy pathsetup-cli-auth.sh
More file actions
executable file
·298 lines (258 loc) · 8.99 KB
/
Copy pathsetup-cli-auth.sh
File metadata and controls
executable file
·298 lines (258 loc) · 8.99 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
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
#!/bin/bash
# Authenticate AI and internal CLIs.
# Idempotent: re-running is safe and refreshes token-based configs from 1Password.
#
# Two flavors:
#
# 1. Token-based — tailor pulls API tokens from 1Password and writes the CLI's
# config file. Run on every tailor invocation; 1P is source of truth.
# CLIs: cortex, nebula, fizzy. Claude/Codex proxy settings are provisioned
# by setup-ai-proxy.sh and verified here against the proxy's models API.
#
# 2. OAuth-based — interactive login flows. Tailor verifies auth by exercising
# the API (active check, not just file existence) and prints the login
# command if not authed.
# CLIs: hey, basecamp.
#
# 3. Dropbox — not a CLI, but tailor's symlink step in tailor.sh depends on
# ~/Dropbox being a signed-in sync root. Same recheck/skip flow.
#
# After all checks: if anything failed, summarize and prompt (via gum) to either
# re-run the checks (after fixing) or continue anyway. Tailor never blocks on
# auth — worst case, you get warnings and the rest of the run proceeds.
#
# 1Password items expected in $TAILOR_OP_ACCOUNT (default: chamberofsecrets):
# Cortex API fields: token, tenant_id, api_url
# Nebula API fields: token, workspace, workspace_url, domain, scheme, api_url
# Fizzy API fields: token, account, api_url
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/lib/common.sh"
field_value() {
local json="$1" label="$2"
echo "$json" | jq -r --arg L "$label" '
[.fields[] | select(.label | ascii_downcase == ($L | ascii_downcase)) | .value]
| map(select(. != null and . != ""))
| first // empty
'
}
fetch_item() {
op_run item get "$1" --account "$TAILOR_OP_ACCOUNT" --format json 2>/dev/null || true
}
bootstrap_hint() {
local title="$1" fields_desc="$2"
warn "1Password item '$title' not found in $TAILOR_OP_ACCOUNT"
hint "Create it manually with fields: $fields_desc"
hint "Example (replace placeholders):"
hint " op item create --category 'API Credential' --title '$title' \\"
hint " --vault Private --account $TAILOR_OP_ACCOUNT \\"
hint " 'token[concealed]=<your-token>' '<other fields...>'"
}
# --- token-based: cortex, nebula, fizzy --------------------------------
# Each returns 0 on success, 1 if config could not be written.
write_cortex_config() {
hdr "cortex auth"
local item; item=$(fetch_item "Cortex API")
if [ -z "$item" ]; then
bootstrap_hint "Cortex API" \
"token (concealed), tenant_id, api_url (e.g. https://my.cortexhq.app)"
return 1
fi
local token tenant api_url
token=$(field_value "$item" "token")
tenant=$(field_value "$item" "tenant_id")
api_url=$(field_value "$item" "api_url")
: "${api_url:=https://my.cortexhq.app}"
if [ -z "$token" ] || [ -z "$tenant" ]; then
warn "Cortex API missing required fields (token, tenant_id)"
return 1
fi
mkdir -p ~/.config/cortex
umask 077
cat > ~/.config/cortex/config.yaml <<EOF
api_url: $api_url
tenant_id: $tenant
token: $token
EOF
ok "wrote ~/.config/cortex/config.yaml from Cortex API"
}
write_nebula_config() {
hdr "nebula auth"
local item; item=$(fetch_item "Nebula API")
if [ -z "$item" ]; then
bootstrap_hint "Nebula API" \
"token (concealed), workspace, workspace_url, domain, scheme, api_url (any of the location fields, optional)"
return 1
fi
local token workspace workspace_url domain scheme api_url
token=$(field_value "$item" "token")
workspace=$(field_value "$item" "workspace")
workspace_url=$(field_value "$item" "workspace_url")
domain=$(field_value "$item" "domain")
scheme=$(field_value "$item" "scheme")
api_url=$(field_value "$item" "api_url")
if [ -z "$token" ]; then
warn "Nebula API missing required field: token"
return 1
fi
mkdir -p ~/.config/nebula
umask 077
{
[ -n "$workspace_url" ] && echo "workspace_url: \"$workspace_url\""
[ -n "$workspace" ] && echo "workspace: \"$workspace\""
[ -n "$domain" ] && echo "domain: $domain"
[ -n "$scheme" ] && echo "scheme: $scheme"
[ -n "$api_url" ] && echo "api_url: \"$api_url\""
echo "token: $token"
} > ~/.config/nebula/config.yaml
ok "wrote ~/.config/nebula/config.yaml from Nebula API"
}
write_fizzy_config() {
hdr "fizzy auth"
local item; item=$(fetch_item "Fizzy API")
if [ -z "$item" ]; then
bootstrap_hint "Fizzy API" \
"token (concealed), account, api_url"
return 1
fi
local token account api_url
token=$(field_value "$item" "token")
account=$(field_value "$item" "account")
api_url=$(field_value "$item" "api_url")
: "${api_url:=https://app.fizzy.do}"
if [ -z "$token" ] || [ -z "$account" ]; then
warn "Fizzy API missing required fields (token, account)"
return 1
fi
mkdir -p ~/.config/fizzy
umask 077
cat > ~/.config/fizzy/config.yaml <<EOF
token: $token
account: "$account"
api_url: $api_url
board: ""
EOF
ok "wrote ~/.config/fizzy/config.yaml from Fizzy API"
}
# --- Proxy and OAuth verification -------------------------------------
# Each returns 0 on success, 1 on failure.
verify_claude() {
hdr "claude auth"
python3 "$SCRIPT_DIR/lib/ai-proxy.py" verify claude
}
verify_codex() {
hdr "codex auth"
python3 "$SCRIPT_DIR/lib/ai-proxy.py" verify codex
}
verify_hey() {
hdr "hey auth"
if timeout 20 hey auth status </dev/null 2>/dev/null | jq -e '.data.authenticated == true' >/dev/null; then
ok "hey authenticated"
else
warn "hey not authenticated"
hint "Run: hey auth login"
return 1
fi
}
verify_basecamp() {
hdr "basecamp auth"
local status authed expired
status=$(timeout 20 basecamp auth status </dev/null 2>/dev/null || echo '{}')
authed=$(echo "$status" | jq -r '.data.authenticated // false')
expired=$(echo "$status" | jq -r '.data.expired // false')
if [ "$authed" = "true" ] && [ "$expired" = "false" ]; then
ok "basecamp authenticated"
return 0
fi
if [ "$authed" = "true" ] && [ "$expired" = "true" ]; then
info "basecamp token expired — attempting refresh..."
if timeout 30 basecamp auth refresh </dev/null 2>&1 | jq -e '.data.status == "refreshed"' >/dev/null 2>&1; then
ok "basecamp token refreshed"
return 0
fi
warn "basecamp refresh failed (refresh token likely also expired)"
else
warn "basecamp not authenticated"
fi
hint "Run: basecamp auth login"
return 1
}
# --- Dropbox: required because tailor.sh symlinks ~/Pictures, ~/Videos,
# --- ~/Documents into ~/Dropbox/. If Dropbox isn't fully signed in, the
# --- symlink step is skipped (or worse, points into an empty stub), so we
# --- gate it here and let the user fix it before tailor proceeds.
verify_dropbox() {
hdr "dropbox"
if ! command -v dropbox >/dev/null 2>&1; then
warn "dropbox not installed"
hint "Install: omarchy install service dropbox"
return 1
fi
if ! pgrep -x dropbox >/dev/null 2>&1; then
warn "dropbox daemon not running"
hint "Start it: systemctl --user enable --now dropbox (or launch the Dropbox app)"
return 1
fi
# ~/.dropbox/info.json is created on first successful sign-in and lists
# the sync roots. Empty/missing means the user hasn't completed setup.
if [ ! -s "$HOME/.dropbox/info.json" ]; then
warn "dropbox not signed in"
hint "Launch Dropbox and sign in, then wait for initial sync to start"
return 1
fi
if [ ! -d "$HOME/Dropbox" ]; then
warn "~/Dropbox sync folder missing"
hint "Open Dropbox preferences and confirm the sync location is ~/Dropbox"
return 1
fi
ok "dropbox installed, running, and signed in"
}
# --- Run all checks; collect failures ---------------------------------
run_all_checks() {
failures=()
if op_signed_in; then
write_cortex_config || failures+=("cortex")
write_nebula_config || failures+=("nebula")
write_fizzy_config || failures+=("fizzy")
else
hdr "cortex / nebula / fizzy auth"
warn "1Password unavailable — can't refresh tokens from $TAILOR_OP_ACCOUNT"
hint "Unlock 1Password (Settings > Developer > 'Integrate with 1Password CLI'), then Recheck"
failures+=("cortex" "nebula" "fizzy")
fi
verify_claude || failures+=("claude")
verify_codex || failures+=("codex")
verify_hey || failures+=("hey")
verify_basecamp || failures+=("basecamp")
verify_dropbox || failures+=("dropbox")
}
# --- Loop: prompt to recheck after fixing -----------------------------
while true; do
run_all_checks
if [ "${#failures[@]}" -eq 0 ]; then
echo ""
ok "All auth checks passed"
break
fi
echo ""
echo "=== Auth issues ==="
for f in "${failures[@]}"; do
echo " ✗ $f"
done
echo ""
if ! command -v gum >/dev/null 2>&1; then
warn "gum not installed — continuing anyway"
break
fi
if [ ! -t 0 ] || [ ! -t 1 ]; then
warn "Not a TTY — continuing without prompt"
break
fi
if gum confirm --default=true --affirmative="Recheck" --negative="Continue anyway" "Press Recheck after fixing the failed checks."; then
echo ""
info "Re-checking..."
continue
else
break
fi
done