-
Notifications
You must be signed in to change notification settings - Fork 156
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·496 lines (457 loc) · 19.7 KB
/
Copy pathinstall.sh
File metadata and controls
executable file
·496 lines (457 loc) · 19.7 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
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
#!/usr/bin/env bash
#
# ResearchStudio — unified installer for ResearchStudio-Idea + ResearchStudio-Reel.
#
# Lets you pick:
# • which skill bundles to install — Idea, Reel, or both
# • which agent runtimes to link into — Claude Code, Codex, or both
#
# Auto-detects OS + Python, installs each bundle's native and Python deps,
# and symlinks the selected skills into <repo-root>/.claude/skills/ and/or
# <repo-root>/.codex/skills/ (both git-ignored). Idempotent — safe to re-run.
#
# Usage:
# bash install.sh # interactive prompts
# bash install.sh --yes # non-interactive (idea+reel, claude only)
# bash install.sh --idea --claude # explicit selection
# bash install.sh --reel --codex --with-pdf # explicit selection + LaTeX
# bash install.sh --idea --reel --claude --codex
#
# Flags (anything you don't pass falls back to a prompt, or the --yes defaults):
# --idea / --no-idea include / skip Idea skills
# --reel / --no-reel include / skip Reel skills
# --claude / --no-claude link into <repo-root>/.claude/skills/
# --codex / --no-codex link into <repo-root>/.codex/skills/
# --with-pdf also install a LaTeX engine (Idea PDF idea cards)
# --yes, -y non-interactive; default selection = idea+reel for Claude
# --help, -h this help
#
# Env overrides:
# PYTHON=./.venv/bin/python use a specific interpreter
# EDITOR=code use a specific editor (defaults to vim)
# CLAUDE_SKILLS_DIR=/custom/path use a non-default Claude skills dir
# CODEX_SKILLS_DIR=/custom/path use a non-default Codex skills dir
#
set -euo pipefail
# Some install steps and their child processes expect EDITOR to be set. Keep a
# caller-provided value; otherwise use the widely available terminal editor.
export EDITOR="${EDITOR:-vim}"
REPO_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
IDEA_REPO="${REPO_ROOT}/ResearchStudio-Idea"
REEL_REPO="${REPO_ROOT}/ResearchStudio-Reel"
CLAUDE_SKILLS_DIR="${CLAUDE_SKILLS_DIR:-$REPO_ROOT/.claude/skills}"
CODEX_SKILLS_DIR="${CODEX_SKILLS_DIR:-$REPO_ROOT/.codex/skills}"
# ---------------------------------------------------------------------------
# pretty-print helpers
# ---------------------------------------------------------------------------
bold() { printf '\033[1m%s\033[0m\n' "$*"; }
log() { printf '\033[1;36m==>\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m[warn]\033[0m %s\n' "$*" >&2; }
die() { printf '\033[1;31m[error]\033[0m %s\n' "$*" >&2; exit 1; }
# ---------------------------------------------------------------------------
# CLI parsing
# ---------------------------------------------------------------------------
USE_IDEA=""; USE_REEL=""
USE_CLAUDE=""; USE_CODEX=""
WITH_PDF=0
NONINTERACTIVE=0
print_help() {
sed -n '2,33p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
}
while [ $# -gt 0 ]; do
case "$1" in
--idea) USE_IDEA=1 ;;
--no-idea) USE_IDEA=0 ;;
--reel) USE_REEL=1 ;;
--no-reel) USE_REEL=0 ;;
--claude) USE_CLAUDE=1 ;;
--no-claude) USE_CLAUDE=0 ;;
--codex) USE_CODEX=1 ;;
--no-codex) USE_CODEX=0 ;;
--with-pdf) WITH_PDF=1 ;;
--yes|-y) NONINTERACTIVE=1 ;;
--help|-h) print_help; exit 0 ;;
*) die "Unknown flag: $1 (try --help)" ;;
esac
shift
done
# ---------------------------------------------------------------------------
# interactive prompts (only ask when a flag wasn't set)
# ---------------------------------------------------------------------------
ask_yn() {
# ask_yn "question" "default(Y|N)" -> returns 0 for yes, 1 for no
local q="$1" def="$2" ans
if [ "$NONINTERACTIVE" = 1 ] || [ ! -t 0 ]; then
[ "$def" = "Y" ] && return 0 || return 1
fi
local hint="[Y/n]"
[ "$def" = "N" ] && hint="[y/N]"
printf '\033[1m%s %s \033[0m' "$q" "$hint"
read -r ans || true
ans="${ans:-$def}"
case "$ans" in
[Yy]*) return 0 ;;
*) return 1 ;;
esac
}
bold " installer"
echo " repo root: $REPO_ROOT"
# Bundle selection
if [ -z "$USE_IDEA" ]; then ask_yn "Install ResearchStudio-Idea skills (idea-spark, paper-search, scoop-check)?" Y && USE_IDEA=1 || USE_IDEA=0; fi
if [ -z "$USE_REEL" ]; then ask_yn "Install ResearchStudio-Reel skills (paper2assets, paper2poster, paper2video, paper2blog, paper2reel)?" Y && USE_REEL=1 || USE_REEL=0; fi
if [ "$USE_IDEA" = 0 ] && [ "$USE_REEL" = 0 ]; then
die "Nothing selected — pick --idea and/or --reel."
fi
# Runtime selection
if [ -z "$USE_CLAUDE" ]; then ask_yn "Link skills for Claude Code (-> $CLAUDE_SKILLS_DIR)?" Y && USE_CLAUDE=1 || USE_CLAUDE=0; fi
if [ -z "$USE_CODEX" ]; then ask_yn "Link skills for Codex (-> $CODEX_SKILLS_DIR)?" N && USE_CODEX=1 || USE_CODEX=0; fi
if [ "$USE_CLAUDE" = 0 ] && [ "$USE_CODEX" = 0 ]; then
die "No runtime selected — pick --claude and/or --codex."
fi
echo
echo " bundles: $([ "$USE_IDEA" = 1 ] && echo -n "Idea ")$([ "$USE_REEL" = 1 ] && echo -n "Reel")"
echo " runtimes: $([ "$USE_CLAUDE" = 1 ] && echo -n "Claude($CLAUDE_SKILLS_DIR) ")$([ "$USE_CODEX" = 1 ] && echo -n "Codex($CODEX_SKILLS_DIR)")"
echo " optional pdf: $([ "$WITH_PDF" = 1 ] && echo yes || echo no)"
# Sanity: required bundle repos must exist
[ "$USE_IDEA" = 1 ] && [ ! -d "$IDEA_REPO/skills" ] && die "Missing ${IDEA_REPO}/skills — did you clone the submodule?"
[ "$USE_REEL" = 1 ] && [ ! -d "$REEL_REPO/skills" ] && die "Missing ${REEL_REPO}/skills — did you clone the submodule?"
# ---------------------------------------------------------------------------
# 0) OS detect (for package-manager hints)
# ---------------------------------------------------------------------------
case "$(uname -s)" in
Darwin) OS=macos; PKG=brew ;;
Linux)
if grep -qiE "microsoft|wsl" /proc/version 2>/dev/null; then OS=wsl; else OS=linux; fi
if command -v apt-get >/dev/null 2>&1; then PKG=apt
elif command -v dnf >/dev/null 2>&1; then PKG=dnf
elif command -v pacman >/dev/null 2>&1; then PKG=pacman
else PKG=none; fi ;;
MINGW*|MSYS*|CYGWIN*|Windows*)
echo "Windows shell detected — install.sh is a bash script. Run it inside WSL (Ubuntu):"
echo " wsl --install # one-time, then reopen Ubuntu and re-run: bash install.sh"
exit 1 ;;
*) OS=unknown; PKG=none ;;
esac
echo " os: $OS (package manager: $PKG)"
# ---------------------------------------------------------------------------
# 1) Python detection — Reel needs ≥3.10, Idea-only is happy with ≥3.9
# ---------------------------------------------------------------------------
MIN_PY="3.9"
[ "$USE_REEL" = 1 ] && MIN_PY="3.10"
PY="${PYTHON:-}"
if [ -z "$PY" ]; then
for c in python3 python; do
if command -v "$c" >/dev/null 2>&1 && "$c" -c "import sys; raise SystemExit(0 if sys.version_info[:2] >= tuple(int(x) for x in '$MIN_PY'.split('.')) else 1)" 2>/dev/null; then
PY="$c"; break
fi
done
fi
if [ -z "$PY" ]; then
echo "Python ${MIN_PY}+ not found. Install it, then re-run."
case "$OS" in
macos) echo " brew install python" ;;
wsl|linux) [ "$PKG" = apt ] && echo " sudo apt-get install -y python3 python3-pip" || echo " install python3 (>=${MIN_PY}) with your package manager" ;;
*) echo " install python3 (>=${MIN_PY})" ;;
esac
exit 1
fi
PY_EXE="$("$PY" -c 'import sys; print(sys.executable)')"
echo " python: $("$PY" --version 2>&1) — $PY_EXE"
# A default `uv venv` intentionally has no pip module. Prefer pip when the
# selected interpreter provides it (including conda), otherwise let uv install
# directly into that interpreter's environment.
if "$PY" -m pip --version >/dev/null 2>&1; then
PY_PACKAGE_MANAGER=pip
elif command -v uv >/dev/null 2>&1; then
PY_PACKAGE_MANAGER=uv
else
die "Neither pip for $PY_EXE nor uv was found. Install pip or uv, then re-run."
fi
echo " python deps: $PY_PACKAGE_MANAGER"
echo
# ---------------------------------------------------------------------------
# helpers used by both bundles
# ---------------------------------------------------------------------------
# pip_install <pkg> [<pkg> …] — install into the selected conda/venv/uv environment.
pip_install() {
if [ "$PY_PACKAGE_MANAGER" = uv ]; then
uv pip install --python "$PY_EXE" --upgrade "$@"
return
fi
if "$PY" -m pip install --upgrade "$@"; then return 0; fi
if "$PY" -m pip install --user --break-system-packages --upgrade "$@"; then
echo " (used --user --break-system-packages for an externally-managed environment)"
return 0
fi
"$PY" -m pip install --user --upgrade "$@"
}
# link_skill <abs_src_dir> <link_name> — into every selected runtime's skills dir.
link_skill() {
local src="$1" name="$2"
[ -d "$src" ] || { warn "missing $src — skipped"; return; }
if [ "$USE_CLAUDE" = 1 ]; then
mkdir -p "$CLAUDE_SKILLS_DIR"
local dst="$CLAUDE_SKILLS_DIR/$name"
rm -rf "$dst"; ln -s "$src" "$dst"
printf ' • claude %s → %s\n' "$name" "$src"
fi
if [ "$USE_CODEX" = 1 ]; then
mkdir -p "$CODEX_SKILLS_DIR"
local dst="$CODEX_SKILLS_DIR/$name"
rm -rf "$dst"; ln -s "$src" "$dst"
printf ' • codex %s → %s\n' "$name" "$src"
fi
}
# seed_config <runtime> — write the embedded settings.json into
# <repo-root>/.<runtime>/ unless one is already present. The settings are
# inlined below (no .<runtime>_template/ directory required) so the installer
# is self-contained. Re-runs leave user edits intact.
seed_config() {
local runtime="$1"
local dst_dir="$REPO_ROOT/.${runtime}"
local dst="$dst_dir/settings.json"
mkdir -p "$dst_dir"
if [ -f "$dst" ]; then
echo " $dst already exists — left untouched"
return 0
fi
case "$runtime" in
claude)
cat >"$dst" <<'CLAUDE_SETTINGS_JSON'
{
"permissions": {
"allow": [
"Bash(*)",
"Read",
"Write",
"Edit",
"WebFetch(*)",
"NotebookEdit(*)"
],
"defaultMode": "dontAsk"
},
"skipDangerousModePermissionPrompt": true,
"autoCompactEnabled": true,
"debug": true,
"effortLevel": "high"
}
CLAUDE_SETTINGS_JSON
;;
codex)
cat >"$dst" <<'CODEX_SETTINGS_JSON'
{
"approval_policy": "never",
"sandbox_mode": "danger-full-access",
"model_reasoning_effort": "high",
"model_reasoning_summary": "auto",
"disable_response_storage": false,
"hide_agent_reasoning": false,
"tools": {
"web_search": true
},
"shell_environment_policy": {
"inherit": "all"
},
"history": {
"persistence": "save-all"
}
}
CODEX_SETTINGS_JSON
;;
*) warn "seed_config: unknown runtime '$runtime'"; return 1 ;;
esac
echo " seeded $dst"
}
# ---------------------------------------------------------------------------
# 2) ResearchStudio-Idea
# ---------------------------------------------------------------------------
if [ "$USE_IDEA" = 1 ]; then
bold "── Installing ResearchStudio-Idea ──"
log "Python dependencies (idea-spark, paper-search, scoop-check)"
IDEA_PKGS=(
"feedparser>=6.0.12" # idea-spark: arXiv connector
"openreview-py>=2.2.3" # idea-spark + paper-search: OpenReview connector
"beautifulsoup4>=4.13.0" # idea-spark: full-text HTML parsing
"pymupdf>=1.26.0" # idea-spark: full-text PDF parsing
"scholarly>=1.7.11" # paper-search: Google Scholar connector
"requests>=2.31.0" # paper-search / idea-spark: HTTP
)
pip_install "${IDEA_PKGS[@]}"
log "Linking Idea skills"
# Legacy slot cleanup (underscore → dash naming).
for rt_dir in "$CLAUDE_SKILLS_DIR" "$CODEX_SKILLS_DIR"; do
rm -rf "$rt_dir/idea_spark" 2>/dev/null || true
done
link_skill "$IDEA_REPO/skills/idea_spark" idea-spark
link_skill "$IDEA_REPO/skills/paper_search" paper-search
link_skill "$IDEA_REPO/skills/scoop_check" scoop-check
log ".env scaffold"
if [ -f "$REPO_ROOT/.env" ]; then
echo " .env already exists — left untouched"
elif [ -f "$IDEA_REPO/.env.template" ]; then
cp "$IDEA_REPO/.env.template" "$REPO_ROOT/.env"
echo " created .env from .env.template — fill in the Phase 0 connector keys"
else
echo " (no .env.template found — skipping)"
fi
log "Verifying IdeaSpark connectors (best-effort)"
( cd "$IDEA_REPO/skills/idea_spark" && "$PY" -m scripts.run check_connectors ) \
|| echo " (connector check skipped/failed — fill .env and re-run: cd ResearchStudio-Idea/skills/idea_spark && $PY -m scripts.run check_connectors)"
log "PDF idea cards (optional LaTeX engine)"
if command -v tectonic >/dev/null 2>&1 || command -v xelatex >/dev/null 2>&1; then
echo " a LaTeX engine is already on PATH ✓"
else
case "$OS" in
macos) PDF_CMD="brew install tectonic" ;;
wsl|linux)
case "$PKG" in
apt) PDF_CMD="sudo apt-get install -y texlive-xetex" ;;
dnf) PDF_CMD="sudo dnf install -y texlive-xetex" ;;
pacman) PDF_CMD="sudo pacman -S --noconfirm texlive-bin texlive-latexextra" ;;
*) PDF_CMD="cargo install tectonic" ;;
esac ;;
*) PDF_CMD="install 'tectonic' or 'xelatex'" ;;
esac
if [ "$WITH_PDF" = 1 ]; then
echo " installing a LaTeX engine: $PDF_CMD"
eval "$PDF_CMD" || echo " (install failed — cards still render as .md/.tex; only the PDF is skipped)"
else
echo " none found — cards still render as .md/.tex (only the PDF is skipped)."
echo " for PDF cards: $PDF_CMD (or re-run: bash install.sh --idea --with-pdf)"
fi
fi
echo
fi
# ---------------------------------------------------------------------------
# 3) ResearchStudio-Reel
# ---------------------------------------------------------------------------
if [ "$USE_REEL" = 1 ]; then
bold "── Installing ResearchStudio-Reel ──"
log "Native tools (poppler-utils, libreoffice, ffmpeg)"
REEL_APT=(poppler-utils libreoffice ffmpeg)
case "$PKG" in
apt)
SUDO=""; [ "${EUID:-$(id -u)}" -ne 0 ] && SUDO="sudo"
${SUDO} apt-get update -y || warn "apt-get update failed — continuing"
${SUDO} apt-get install -y --no-install-recommends "${REEL_APT[@]}" || warn "native tools (poppler-utils/libreoffice/ffmpeg) failed to install — Reel features degrade; install manually if needed"
;;
brew)
brew install poppler ffmpeg || warn "poppler/ffmpeg via brew failed — Reel features may degrade"
brew install --cask libreoffice || warn "libreoffice cask failed — install manually if you need it"
;;
dnf) sudo dnf install -y poppler-utils libreoffice ffmpeg || warn "native tools failed — Reel features degrade; install manually if needed" ;;
pacman) sudo pacman -S --noconfirm poppler libreoffice-fresh ffmpeg || warn "native tools failed — Reel features degrade; install manually if needed" ;;
*) warn "no known package manager — install these manually: ${REEL_APT[*]}" ;;
esac
log "Python dependencies (PyMuPDF, Pillow, numpy, python-docx, qrcode, playwright, imageio-ffmpeg, edge-tts)"
REEL_PIP=(
"pymupdf"
"pillow"
"numpy"
"python-docx>=1.1.2"
"qrcode"
"playwright"
"imageio-ffmpeg"
"edge-tts>=7.2.8"
)
if [ "$PY_PACKAGE_MANAGER" = pip ]; then
"$PY" -m pip install --upgrade pip || true
fi
pip_install "${REEL_PIP[@]}"
log "Playwright Chromium (used by Paper2Poster HTML → PDF/PNG)"
"$PY" -m playwright install chromium
log "Linking Reel skills"
for skill_dir in "$REEL_REPO/skills"/*/; do
skill_name="$(basename "${skill_dir%/}")"
link_skill "${skill_dir%/}" "$skill_name"
done
# Paper2Video delegates deck authoring to ppt-master and rendering/QA to
# pptx2video. Fetch both skills from their upstream repos via `npx skills add`.
if command -v npx >/dev/null 2>&1; then
log "Fetching Paper2Video dependency skills via npx skills add (ppt-master, pptx2video)"
for target_dir in \
"$([ "$USE_CLAUDE" = 1 ] && echo "$CLAUDE_SKILLS_DIR")" \
"$([ "$USE_CODEX" = 1 ] && echo "$CODEX_SKILLS_DIR")"; do
[ -n "$target_dir" ] || continue
mkdir -p "$target_dir"
( cd "$target_dir" \
&& npx -y skills add hugohe3/ppt-master --skill ppt-master \
&& npx -y skills add ai-nuts/pptx2video --skill pptx2video ) \
|| warn "npx skills add failed for ppt-master/pptx2video — install them manually; paper2video will not work until then"
# `skills add` drops the skill content under <target>/.agents/skills/<name>
# (its own project layout) rather than the skills-dir top level where the
# Reel/Idea skills live. Move each up to the top level so the agent's
# top-level scan finds them next to the other skills.
for dep_name in ppt-master pptx2video; do
dep_src="$target_dir/.agents/skills/$dep_name"
if [ -d "$dep_src" ]; then
rm -rf "$target_dir/$dep_name"
mv "$dep_src" "$target_dir/$dep_name"
fi
done
rmdir "$target_dir/.agents/skills" "$target_dir/.agents" 2>/dev/null || true
done
else
warn "npx not found — install ppt-master + pptx2video manually:"
warn " npx skills add hugohe3/ppt-master --skill ppt-master"
warn " npx skills add ai-nuts/pptx2video --skill pptx2video"
fi
echo
fi
# ---------------------------------------------------------------------------
# done
# ---------------------------------------------------------------------------
if [ "$USE_CLAUDE" = 1 ]; then
bold "Seeding Claude config (.claude/)"
seed_config claude
fi
if [ "$USE_CODEX" = 1 ]; then
bold "Seeding Codex config (.codex/)"
seed_config codex
fi
bold "Done."
if [ "$USE_CLAUDE" = 1 ]; then
echo " Claude Code:"
echo " skills linked into $CLAUDE_SKILLS_DIR"
echo " launch with: CLAUDE_CONFIG_DIR=\"$REPO_ROOT/.claude\" claude"
fi
if [ "$USE_CODEX" = 1 ]; then
echo " Codex:"
echo " skills linked into $CODEX_SKILLS_DIR"
echo " settings: $REPO_ROOT/.codex/settings.json"
fi
if [ "$USE_REEL" = 1 ]; then
echo
echo " Reel notes:"
echo " • Export ANTHROPIC_API_KEY (or your backend's key) in your shell."
echo " • Paper2Video's 'ppt-master' + 'pptx2video' skills were fetched via npx skills add."
echo " • pptx2video also needs its pip runtime (see ResearchStudio-Reel/skills/paper2video/README.md):"
echo " pip install 'pptx2video[svg] @ git+https://github.com/ai-nuts/pptx2video.git'"
fi
if [ "$USE_IDEA" = 1 ]; then
echo
echo " Idea notes:"
echo " • Fill in connector keys in ResearchStudio-Idea/.env, then re-run the connector check:"
echo " cd ResearchStudio-Idea/skills/idea_spark && $PY -m scripts.run check_connectors"
fi
# ---------------------------------------------------------------------------
# Offer to open the connector .env now, otherwise remind the user to fill it in.
# ---------------------------------------------------------------------------
ENV_FILE="$REPO_ROOT/.env"
if [ "$USE_IDEA" = 1 ] && [ -f "$ENV_FILE" ]; then
echo
if [ "$NONINTERACTIVE" = 1 ] || [ ! -t 0 ]; then
echo " >> Remember to edit $ENV_FILE and fill in your connector keys before the skills can use them."
elif ask_yn "Open $ENV_FILE in your editor now to fill in the connector keys?" Y; then
_ed="${VISUAL:-$EDITOR}"
if [ -n "$_ed" ]; then $_ed "$ENV_FILE"
elif command -v sensible-editor >/dev/null 2>&1; then sensible-editor "$ENV_FILE"
elif command -v xdg-open >/dev/null 2>&1; then xdg-open "$ENV_FILE" >/dev/null 2>&1 || true
elif command -v open >/dev/null 2>&1; then open "$ENV_FILE"
elif command -v nano >/dev/null 2>&1; then nano "$ENV_FILE"
else vi "$ENV_FILE"
fi
else
echo " >> Remember to edit $ENV_FILE and fill in your connector keys before the skills can use them."
fi
fi