@kuandotdev/indicator 0.1.1 → 0.1.3
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 +2 -7
- package/package.json +4 -1
- package/project/.cursor/rules/agent-docs.mdc +16 -0
- package/project/.cursor/rules/api-credential-storage.mdc +16 -0
- package/project/.cursor/rules/pinescript-v6.mdc +16 -0
- package/project/.cursor/rules/stock-model-forecast.mdc +16 -0
- package/project/.cursor/rules/system-prompt-injection.mdc +16 -0
- package/project/.cursor/rules/system-prompt-updater.mdc +16 -0
- package/project/.cursor/rules/tradingview-stock-data.mdc +16 -0
- package/project/.env.example +44 -0
- package/project/.npm-packaged-project +1 -0
- package/project/.pi/APPEND_SYSTEM.md +338 -0
- package/project/.pi/settings.json +8 -0
- package/project/AGENTS.md +538 -0
- package/project/CLAUDE.md +538 -0
- package/project/GEMINI.md +538 -0
- package/project/Makefile +488 -0
- package/project/README.md +419 -0
- package/project/conda-env-active.sh +98 -0
- package/project/conda-env-deactive.sh +42 -0
- package/project/docs/agent-install.md +446 -0
- package/project/docs/agent-skill-directory.md +222 -0
- package/project/docs/integration.html +271 -0
- package/project/packages/indicator/README.md +39 -0
- package/project/packages/indicator/package.json +40 -0
- package/project/packages/indicator/scripts/build-project-snapshot.js +57 -0
- package/project/packages/indicator/src/cli.js +368 -0
- package/project/packages/tradingview-stock-data-skill/README.md +112 -0
- package/project/packages/tradingview-stock-data-skill/extensions/stock-prompt-injector.ts +121 -0
- package/project/packages/tradingview-stock-data-skill/package.json +35 -0
- package/project/packages/tradingview-stock-data-skill/scripts/postinstall.sh +73 -0
- package/project/packages/tradingview-stock-data-skill/skills/tradingview-stock-data/SKILL.md +241 -0
- package/project/pyproject.toml +68 -0
- package/project/screenshots/.gitkeep +0 -0
- package/project/scripts/indicators/example_rsi_bands.pine +27 -0
- package/project/scripts/indicators/tsla_levels.pine +57 -0
- package/project/skills/agent-docs/SKILL.md +56 -0
- package/project/skills/api-credential-storage/SKILL.md +83 -0
- package/project/skills/api-credential-storage/scripts/upsert_env.py +151 -0
- package/project/skills/pinescript-v6/SKILL.md +129 -0
- package/project/skills/pinescript-v6/reference/built-ins.md +219 -0
- package/project/skills/pinescript-v6/reference/templates/alert-webhook.pine +76 -0
- package/project/skills/pinescript-v6/reference/templates/indicator.pine +48 -0
- package/project/skills/pinescript-v6/reference/templates/strategy.pine +50 -0
- package/project/skills/pinescript-v6/reference/v5-to-v6-migration.md +102 -0
- package/project/skills/pinescript-v6/reference/v6-language.md +202 -0
- package/project/skills/stock-model-forecast/SKILL.md +192 -0
- package/project/skills/system-prompt-injection/CUSTOM_SYSTEM_PROMPT.md +333 -0
- package/project/skills/system-prompt-injection/DEFAULT_SYSTEM_PROMPT.md +327 -0
- package/project/skills/system-prompt-injection/SKILL.md +90 -0
- package/project/skills/system-prompt-injection/SYSTEM_PROMPT.md +23 -0
- package/project/skills/system-prompt-updater/SKILL.md +82 -0
- package/project/skills/system-prompt-updater/scripts/system_prompt_update.sh +106 -0
- package/project/skills/tradingview-stock-data/SKILL.md +272 -0
- package/project/src/tv_indicator/__init__.py +0 -0
- package/project/src/tv_indicator/browser/__init__.py +0 -0
- package/project/src/tv_indicator/browser/automation.py +541 -0
- package/project/src/tv_indicator/browser/selectors.py +70 -0
- package/project/src/tv_indicator/cli/__init__.py +0 -0
- package/project/src/tv_indicator/cli/browser_cmds.py +92 -0
- package/project/src/tv_indicator/cli/data_cmds.py +178 -0
- package/project/src/tv_indicator/cli/main.py +56 -0
- package/project/src/tv_indicator/cli/model_cmds.py +255 -0
- package/project/src/tv_indicator/cli/pine_cmds.py +140 -0
- package/project/src/tv_indicator/config.py +98 -0
- package/project/src/tv_indicator/data/__init__.py +0 -0
- package/project/src/tv_indicator/data/client.py +187 -0
- package/project/src/tv_indicator/data/screener.py +268 -0
- package/project/src/tv_indicator/mcp/__init__.py +0 -0
- package/project/src/tv_indicator/mcp/agent_server.py +398 -0
- package/project/src/tv_indicator/mcp/browser_server.py +133 -0
- package/project/src/tv_indicator/mcp/data_server.py +239 -0
- package/project/src/tv_indicator/model/__init__.py +19 -0
- package/project/src/tv_indicator/model/forecast.py +693 -0
- package/project/tools/import_agent_tools.sh +503 -0
- package/project/tools/install_skills.sh +673 -0
- package/project/tools/interactive_install.sh +917 -0
- package/project/tools/progress.sh +114 -0
- package/project/tools/uninstall_agent_tools.sh +373 -0
- package/src/cli.js +22 -25
|
@@ -0,0 +1,917 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# interactive_install.sh — post-core-install wizard for new users.
|
|
3
|
+
# It optionally writes .env values, runs TradingView browser login, waits for
|
|
4
|
+
# session-check, then imports skills/context/MCP config for selected agents.
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
10
|
+
CONDA_ENV="${CONDA_ENV:-tv-indicator}"
|
|
11
|
+
DEFAULT_PLATFORM="${TV_INDICATOR_AGENT_PLATFORM:-${PLATFORM:-auto}}"
|
|
12
|
+
INPUT_TIMEOUT_SECONDS="${TV_INSTALL_INPUT_TIMEOUT:-60}"
|
|
13
|
+
MAX_NO_INPUT_ATTEMPTS="${TV_INSTALL_MAX_NO_INPUT_ATTEMPTS:-3}"
|
|
14
|
+
|
|
15
|
+
cd "$PROJECT_ROOT"
|
|
16
|
+
|
|
17
|
+
log() { printf '%s\n' "$*"; }
|
|
18
|
+
warn() { printf 'WARN: %s\n' "$*" >&2; }
|
|
19
|
+
|
|
20
|
+
# Interactive only requires stdin to be a TTY. Stdout may be a pipe (e.g. when
|
|
21
|
+
# select_platforms is called via command substitution: $(select_platforms));
|
|
22
|
+
# the menu draws to stderr which still reaches the terminal in that case.
|
|
23
|
+
is_tty() { [[ -t 0 ]]; }
|
|
24
|
+
|
|
25
|
+
# Keep timeout knobs sane if users pass malformed env values.
|
|
26
|
+
case "$INPUT_TIMEOUT_SECONDS" in ''|*[!0-9]*) INPUT_TIMEOUT_SECONDS=60 ;; esac
|
|
27
|
+
case "$MAX_NO_INPUT_ATTEMPTS" in ''|*[!0-9]*) MAX_NO_INPUT_ATTEMPTS=3 ;; esac
|
|
28
|
+
(( INPUT_TIMEOUT_SECONDS < 1 )) && INPUT_TIMEOUT_SECONDS=60
|
|
29
|
+
(( MAX_NO_INPUT_ATTEMPTS < 1 )) && MAX_NO_INPUT_ATTEMPTS=3
|
|
30
|
+
|
|
31
|
+
abort_install_no_input() {
|
|
32
|
+
local context="${1:-installer prompt}"
|
|
33
|
+
printf '\n' > /dev/tty 2>/dev/null || true
|
|
34
|
+
warn "No input received for $context after $MAX_NO_INPUT_ATTEMPTS attempts. Aborting install."
|
|
35
|
+
|
|
36
|
+
if [[ "${TV_INSTALL_CLEANUP_ON_NO_INPUT:-1}" == "1" ]]; then
|
|
37
|
+
warn "Cleaning up data installed by this installer (managed agent config, browser sessions, conda env, .env)."
|
|
38
|
+
if command -v make >/dev/null 2>&1; then
|
|
39
|
+
MAKEFLAGS= make --no-print-directory uninstall-all KEEP_ENV=0 || \
|
|
40
|
+
warn "Automatic cleanup failed. Run 'make uninstall-all' manually."
|
|
41
|
+
else
|
|
42
|
+
warn "make not found; run './tools/uninstall_agent_tools.sh --platform all' and remove the conda env manually."
|
|
43
|
+
fi
|
|
44
|
+
else
|
|
45
|
+
warn "Cleanup skipped because TV_INSTALL_CLEANUP_ON_NO_INPUT=0."
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
exit 130
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
warn_no_input_retry() {
|
|
52
|
+
local context="$1"
|
|
53
|
+
local attempt="$2"
|
|
54
|
+
warn "No input detected for $context ($attempt/$MAX_NO_INPUT_ATTEMPTS). Waiting again..."
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
ask_yes_no() {
|
|
58
|
+
local prompt="$1"
|
|
59
|
+
local default="${2:-n}"
|
|
60
|
+
local suffix answer oldstty junk drain_i attempts timed_out
|
|
61
|
+
if [[ "$default" == "y" ]]; then suffix="[Y/n]"; else suffix="[y/N]"; fi
|
|
62
|
+
attempts=0
|
|
63
|
+
|
|
64
|
+
while true; do
|
|
65
|
+
answer=""
|
|
66
|
+
timed_out=0
|
|
67
|
+
|
|
68
|
+
# Single-key prompt in interactive terminals: y/Y/n/N proceeds immediately;
|
|
69
|
+
# Enter still accepts the default. This avoids requiring an extra Enter after
|
|
70
|
+
# typing `n` or `N` during install.
|
|
71
|
+
if is_tty && [ -r /dev/tty ] && [ -w /dev/tty ] \
|
|
72
|
+
&& oldstty="$(stty -g < /dev/tty 2>/dev/null)" \
|
|
73
|
+
&& stty -echo -icanon time 0 min 1 < /dev/tty 2>/dev/null; then
|
|
74
|
+
printf '%s %s ' "$prompt" "$suffix" > /dev/tty
|
|
75
|
+
if ! IFS= read -r -s -n 1 -t "$INPUT_TIMEOUT_SECONDS" answer < /dev/tty; then
|
|
76
|
+
timed_out=1
|
|
77
|
+
answer=""
|
|
78
|
+
fi
|
|
79
|
+
if [[ "$answer" == $'\004' ]]; then
|
|
80
|
+
timed_out=1
|
|
81
|
+
answer=""
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# Drain an immediately buffered Enter (e.g. if the user typed n+Enter out
|
|
85
|
+
# of habit) so the next prompt does not consume the leftover newline.
|
|
86
|
+
if (( ! timed_out )); then
|
|
87
|
+
stty time 0 min 0 < /dev/tty 2>/dev/null || true
|
|
88
|
+
for drain_i in 1 2; do
|
|
89
|
+
if IFS= read -r -s -n 1 -t 0 junk < /dev/tty 2>/dev/null; then
|
|
90
|
+
case "$junk" in $'\r'|$'\n') : ;; *) break ;; esac
|
|
91
|
+
else
|
|
92
|
+
break
|
|
93
|
+
fi
|
|
94
|
+
done
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
stty "$oldstty" < /dev/tty 2>/dev/null || true
|
|
98
|
+
case "$answer" in
|
|
99
|
+
$'\r'|$'\n'|"") printf '\n' > /dev/tty ;;
|
|
100
|
+
*) printf '%s\n' "$answer" > /dev/tty ;;
|
|
101
|
+
esac
|
|
102
|
+
else
|
|
103
|
+
if ! read -r -t "$INPUT_TIMEOUT_SECONDS" -p "$prompt $suffix " answer; then
|
|
104
|
+
timed_out=1
|
|
105
|
+
answer=""
|
|
106
|
+
printf '\n'
|
|
107
|
+
fi
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
if (( timed_out )); then
|
|
111
|
+
((attempts+=1))
|
|
112
|
+
if (( attempts >= MAX_NO_INPUT_ATTEMPTS )); then
|
|
113
|
+
abort_install_no_input "$prompt"
|
|
114
|
+
fi
|
|
115
|
+
warn_no_input_retry "$prompt" "$attempts"
|
|
116
|
+
continue
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
answer="${answer:-$default}"
|
|
120
|
+
answer="$(printf '%s' "$answer" | tr '[:upper:]' '[:lower:]')"
|
|
121
|
+
case "$answer" in
|
|
122
|
+
y|yes) return 0 ;;
|
|
123
|
+
*) return 1 ;;
|
|
124
|
+
esac
|
|
125
|
+
done
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
ensure_env_file() {
|
|
129
|
+
if [[ ! -f .env ]]; then
|
|
130
|
+
if [[ -f .env.example ]]; then
|
|
131
|
+
cp .env.example .env
|
|
132
|
+
log "Created .env from .env.example"
|
|
133
|
+
else
|
|
134
|
+
touch .env
|
|
135
|
+
log "Created empty .env"
|
|
136
|
+
fi
|
|
137
|
+
fi
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
upsert_env() {
|
|
141
|
+
local key="$1"
|
|
142
|
+
local value="$2"
|
|
143
|
+
ensure_env_file
|
|
144
|
+
python3 - "$key" "$value" <<'PY'
|
|
145
|
+
import sys
|
|
146
|
+
from pathlib import Path
|
|
147
|
+
key, value = sys.argv[1], sys.argv[2]
|
|
148
|
+
path = Path(".env")
|
|
149
|
+
lines = path.read_text(encoding="utf-8").splitlines() if path.exists() else []
|
|
150
|
+
out = []
|
|
151
|
+
written = False
|
|
152
|
+
for line in lines:
|
|
153
|
+
if line.startswith(key + "="):
|
|
154
|
+
out.append(f"{key}={value}")
|
|
155
|
+
written = True
|
|
156
|
+
else:
|
|
157
|
+
out.append(line)
|
|
158
|
+
if not written:
|
|
159
|
+
if out and out[-1].strip():
|
|
160
|
+
out.append("")
|
|
161
|
+
out.append(f"{key}={value}")
|
|
162
|
+
path.write_text("\n".join(out) + "\n", encoding="utf-8")
|
|
163
|
+
PY
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
prompt_env_value() {
|
|
167
|
+
local key="$1"
|
|
168
|
+
local label="$2"
|
|
169
|
+
local secret="${3:-1}"
|
|
170
|
+
local value=""
|
|
171
|
+
local attempts=0
|
|
172
|
+
local timed_out=0
|
|
173
|
+
|
|
174
|
+
while true; do
|
|
175
|
+
value=""
|
|
176
|
+
timed_out=0
|
|
177
|
+
if [[ "$secret" == "1" ]]; then
|
|
178
|
+
if ! read -r -s -t "$INPUT_TIMEOUT_SECONDS" -p "$label ($key, Enter to skip): " value; then
|
|
179
|
+
timed_out=1
|
|
180
|
+
fi
|
|
181
|
+
printf '\n'
|
|
182
|
+
else
|
|
183
|
+
if ! read -r -t "$INPUT_TIMEOUT_SECONDS" -p "$label ($key, Enter to skip): " value; then
|
|
184
|
+
timed_out=1
|
|
185
|
+
printf '\n'
|
|
186
|
+
fi
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
if (( timed_out )); then
|
|
190
|
+
((attempts+=1))
|
|
191
|
+
if (( attempts >= MAX_NO_INPUT_ATTEMPTS )); then
|
|
192
|
+
abort_install_no_input "$key input"
|
|
193
|
+
fi
|
|
194
|
+
warn_no_input_retry "$key input" "$attempts"
|
|
195
|
+
continue
|
|
196
|
+
fi
|
|
197
|
+
break
|
|
198
|
+
done
|
|
199
|
+
|
|
200
|
+
if [[ -n "$value" ]]; then
|
|
201
|
+
upsert_env "$key" "$value"
|
|
202
|
+
log "Saved $key to .env"
|
|
203
|
+
fi
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
get_env_value() {
|
|
207
|
+
local key="$1"
|
|
208
|
+
[[ -f .env ]] || return 1
|
|
209
|
+
awk -F= -v key="$key" '$1 == key { print substr($0, length(key) + 2) }' .env | tail -n 1
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
normalize_language_code() {
|
|
213
|
+
local raw="$1"
|
|
214
|
+
raw="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]' | tr '-' '_' | tr -d '[:space:]')"
|
|
215
|
+
raw="${raw%%.*}"
|
|
216
|
+
raw="${raw%%@*}"
|
|
217
|
+
case "$raw" in
|
|
218
|
+
""|c|posix) printf '\n' ;;
|
|
219
|
+
en|en_*|eng|english) printf 'en\n' ;;
|
|
220
|
+
zhtw|zh_tw|zh_hk|zh_mo|zh_hant|zh_hant_*|traditional|traditionalchinese|tc)
|
|
221
|
+
printf 'zhtw\n' ;;
|
|
222
|
+
zhcn|zh_cn|zh_sg|zh_hans|zh_hans_*|simplified|simplifiedchinese|sc|cn)
|
|
223
|
+
printf 'zhcn\n' ;;
|
|
224
|
+
zh) printf 'zhtw\n' ;;
|
|
225
|
+
ja|ja_*|jp|jp_*|japanese) printf 'jp\n' ;;
|
|
226
|
+
ko|ko_*|kr|kr_*|korean) printf 'ko\n' ;;
|
|
227
|
+
*_*) printf '%s\n' "${raw%%_*}" | sed 's/[^a-z0-9_-]//g' ;;
|
|
228
|
+
*)
|
|
229
|
+
printf '%s\n' "$raw" | sed 's/[^a-z0-9_-]//g'
|
|
230
|
+
;;
|
|
231
|
+
esac
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
language_label() {
|
|
235
|
+
case "$1" in
|
|
236
|
+
en) printf 'English\n' ;;
|
|
237
|
+
zhtw) printf 'Traditional Chinese\n' ;;
|
|
238
|
+
zhcn) printf 'Simplified Chinese\n' ;;
|
|
239
|
+
jp) printf 'Japanese\n' ;;
|
|
240
|
+
ko) printf 'Korean\n' ;;
|
|
241
|
+
*) printf 'custom\n' ;;
|
|
242
|
+
esac
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
detect_terminal_language() {
|
|
246
|
+
local locale_value
|
|
247
|
+
locale_value="${LC_ALL:-${LC_MESSAGES:-${LANG:-}}}"
|
|
248
|
+
normalize_language_code "$locale_value"
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
default_language_code() {
|
|
252
|
+
local selected=""
|
|
253
|
+
if [[ -n "${TV_AGENT_LANGUAGE:-}" ]]; then
|
|
254
|
+
normalize_language_code "$TV_AGENT_LANGUAGE"
|
|
255
|
+
return
|
|
256
|
+
fi
|
|
257
|
+
|
|
258
|
+
selected="$(detect_terminal_language)"
|
|
259
|
+
if [[ -n "$selected" ]]; then
|
|
260
|
+
printf '%s\n' "$selected"
|
|
261
|
+
return
|
|
262
|
+
fi
|
|
263
|
+
|
|
264
|
+
selected="$(get_env_value TV_AGENT_LANGUAGE || true)"
|
|
265
|
+
selected="$(normalize_language_code "$selected")"
|
|
266
|
+
printf '%s\n' "${selected:-en}"
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
ensure_language_default() {
|
|
270
|
+
local selected
|
|
271
|
+
selected="$(default_language_code)"
|
|
272
|
+
[[ -z "$selected" ]] && selected="en"
|
|
273
|
+
upsert_env "TV_AGENT_LANGUAGE" "$selected"
|
|
274
|
+
export TV_AGENT_LANGUAGE="$selected"
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
prompt_language() {
|
|
278
|
+
local default answer selected attempts timed_out
|
|
279
|
+
default="$(default_language_code)"
|
|
280
|
+
[[ -z "$default" ]] && default="en"
|
|
281
|
+
|
|
282
|
+
attempts=0
|
|
283
|
+
while true; do
|
|
284
|
+
answer=""
|
|
285
|
+
timed_out=0
|
|
286
|
+
if [ -r /dev/tty ] && [ -w /dev/tty ]; then
|
|
287
|
+
printf 'Preferred agent output language (en English, zhtw Traditional Chinese, zhcn Simplified Chinese, jp Japanese, ko Korean) [%s]: ' "$default" > /dev/tty
|
|
288
|
+
if ! IFS= read -r -t "$INPUT_TIMEOUT_SECONDS" answer < /dev/tty; then
|
|
289
|
+
timed_out=1
|
|
290
|
+
printf '\n' > /dev/tty
|
|
291
|
+
fi
|
|
292
|
+
else
|
|
293
|
+
if ! read -r -t "$INPUT_TIMEOUT_SECONDS" -p "Preferred agent output language (en/zhtw/zhcn/jp/ko) [$default]: " answer; then
|
|
294
|
+
timed_out=1
|
|
295
|
+
printf '\n'
|
|
296
|
+
fi
|
|
297
|
+
fi
|
|
298
|
+
|
|
299
|
+
if (( timed_out )); then
|
|
300
|
+
((attempts+=1))
|
|
301
|
+
if (( attempts >= MAX_NO_INPUT_ATTEMPTS )); then
|
|
302
|
+
abort_install_no_input "language selection"
|
|
303
|
+
fi
|
|
304
|
+
warn_no_input_retry "language selection" "$attempts"
|
|
305
|
+
continue
|
|
306
|
+
fi
|
|
307
|
+
break
|
|
308
|
+
done
|
|
309
|
+
|
|
310
|
+
selected="$(normalize_language_code "${answer:-$default}")"
|
|
311
|
+
[[ -z "$selected" ]] && selected="en"
|
|
312
|
+
upsert_env "TV_AGENT_LANGUAGE" "$selected"
|
|
313
|
+
export TV_AGENT_LANGUAGE="$selected"
|
|
314
|
+
log " → Language: $selected ($(language_label "$selected"))"
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
_PLATFORM_KEYS=(pi claude-desktop claude-code codex gemini cursor)
|
|
318
|
+
_PLATFORM_LABELS=(
|
|
319
|
+
"Pi"
|
|
320
|
+
"Claude Desktop"
|
|
321
|
+
"Claude Code"
|
|
322
|
+
"Codex / OpenCode / Aider"
|
|
323
|
+
"Gemini"
|
|
324
|
+
"Cursor"
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
# Arrow-key multi-select menu. Pure bash, no external deps. Works in bash 3.2+.
|
|
328
|
+
# Controls: ↑/↓ or k/j to move, SPACE to toggle, a = all, n = none, ENTER to confirm, q = quit.
|
|
329
|
+
# Menu drawing goes to stderr (>&2) so it survives $(...) command substitution
|
|
330
|
+
# which only captures stdout. Stdin (`read` builtin) is the terminal.
|
|
331
|
+
# Falls back to "all" if stdin is not a TTY.
|
|
332
|
+
# Sets the global _MENU_RESULT to a space-separated list of selected keys, or "skip".
|
|
333
|
+
arrow_multi_select() {
|
|
334
|
+
local header="$1"; shift
|
|
335
|
+
# IMPORTANT: bash 3.2 doesn't reliably treat `local -a a=() b=() c=()` as
|
|
336
|
+
# three array declarations on one line, so declare each on its own line.
|
|
337
|
+
local -a keys
|
|
338
|
+
local -a labels
|
|
339
|
+
local -a checked
|
|
340
|
+
keys=()
|
|
341
|
+
labels=()
|
|
342
|
+
checked=()
|
|
343
|
+
|
|
344
|
+
while (( $# > 0 )); do
|
|
345
|
+
keys+=("${1%%|*}")
|
|
346
|
+
labels+=("${1#*|}")
|
|
347
|
+
checked+=(0)
|
|
348
|
+
shift
|
|
349
|
+
done
|
|
350
|
+
local n=${#keys[@]}
|
|
351
|
+
local cursor=0
|
|
352
|
+
|
|
353
|
+
# Start with nothing checked. SPACE toggles one or more items; pressing
|
|
354
|
+
# ENTER always selects the highlighted item when nothing is checked.
|
|
355
|
+
local i
|
|
356
|
+
for ((i=0; i<n; i++)); do checked[$i]=0; done
|
|
357
|
+
|
|
358
|
+
# Non-TTY → signal fallback by leaving _MENU_RESULT empty.
|
|
359
|
+
if ! is_tty || [ ! -r /dev/tty ] || [ ! -w /dev/tty ]; then
|
|
360
|
+
_MENU_RESULT=""
|
|
361
|
+
return 1
|
|
362
|
+
fi
|
|
363
|
+
|
|
364
|
+
# Bind the menu directly to the controlling terminal. In `make` recipes and
|
|
365
|
+
# command substitutions, stdin/stderr can be captured or partially detached;
|
|
366
|
+
# /dev/tty keeps the arrow UI visible and makes reads reliable.
|
|
367
|
+
if ! exec 3</dev/tty 4>/dev/tty; then
|
|
368
|
+
_MENU_RESULT=""
|
|
369
|
+
return 1
|
|
370
|
+
fi
|
|
371
|
+
|
|
372
|
+
# Probe: can we actually save/restore the TTY state? Some terminal/shell
|
|
373
|
+
# combinations report -t 0 as true but then error on stty operations
|
|
374
|
+
# (e.g. read returning "Input/output error" on FD 0). Detect that here
|
|
375
|
+
# and bail out before drawing anything.
|
|
376
|
+
local _stty_saved
|
|
377
|
+
if ! _stty_saved="$(stty -g <&3 2>/dev/null)"; then
|
|
378
|
+
exec 3<&- 4>&- 2>/dev/null || true
|
|
379
|
+
_MENU_RESULT=""
|
|
380
|
+
return 1
|
|
381
|
+
fi
|
|
382
|
+
if ! stty -echo -icanon time 0 min 1 <&3 2>/dev/null; then
|
|
383
|
+
exec 3<&- 4>&- 2>/dev/null || true
|
|
384
|
+
_MENU_RESULT=""
|
|
385
|
+
return 1
|
|
386
|
+
fi
|
|
387
|
+
|
|
388
|
+
# Disable set -e for the menu's body so a single read failure doesn't kill
|
|
389
|
+
# the whole wizard — we want to fall back to the numeric prompt instead.
|
|
390
|
+
set +e
|
|
391
|
+
|
|
392
|
+
# Keep menu input/output bound to the controlling terminal so the UI paints
|
|
393
|
+
# immediately even when launched from `make`.
|
|
394
|
+
_arrow_menu_cleanup() {
|
|
395
|
+
# RETURN traps persist in bash until explicitly cleared. Clear them first
|
|
396
|
+
# so cleanup only runs for this menu, not for later function returns.
|
|
397
|
+
trap - RETURN
|
|
398
|
+
trap - INT TERM
|
|
399
|
+
if [[ -n "${_stty_saved:-}" ]]; then
|
|
400
|
+
stty "$_stty_saved" < /dev/tty 2>/dev/null || true
|
|
401
|
+
fi
|
|
402
|
+
printf '\033[?25h' > /dev/tty 2>/dev/null || true
|
|
403
|
+
# Close only our menu fds. Do not use `exec ... 2>/dev/null` here; with
|
|
404
|
+
# exec and no command, that would permanently redirect the script stderr.
|
|
405
|
+
exec 3<&- || true
|
|
406
|
+
exec 4>&- || true
|
|
407
|
+
set -e
|
|
408
|
+
}
|
|
409
|
+
trap '_arrow_menu_cleanup' RETURN
|
|
410
|
+
trap '_arrow_menu_cleanup; exit 130' INT TERM
|
|
411
|
+
|
|
412
|
+
printf '\033[?25l' >&4
|
|
413
|
+
|
|
414
|
+
# Do not try to drain pre-buffered keystrokes here. On macOS/bash 3.2 under
|
|
415
|
+
# `make`, `read -n1` can block even after `stty min 0`, which makes the
|
|
416
|
+
# installer appear to hang before the menu is drawn.
|
|
417
|
+
stty time 0 min 1 <&3 2>/dev/null
|
|
418
|
+
|
|
419
|
+
printf '\n%s\n' "$header" >&4
|
|
420
|
+
printf ' ↑/↓ or j/k to move · SPACE to toggle · a = all · n = none · q = skip · ENTER = confirm/current\n\n' >&4
|
|
421
|
+
|
|
422
|
+
local marker prefix key rest first_draw idle_attempts
|
|
423
|
+
first_draw=1
|
|
424
|
+
idle_attempts=0
|
|
425
|
+
while true; do
|
|
426
|
+
if (( first_draw )); then
|
|
427
|
+
first_draw=0
|
|
428
|
+
else
|
|
429
|
+
# Redraw in-place. CSI save/restore cursor is not reliable in every
|
|
430
|
+
# terminal/tmux/make combination, so move back over the fixed-height
|
|
431
|
+
# list and clear from there before repainting.
|
|
432
|
+
printf '\033[%dA' "$n" >&4
|
|
433
|
+
fi
|
|
434
|
+
printf '\033[J' >&4
|
|
435
|
+
for ((i=0; i<n; i++)); do
|
|
436
|
+
marker="[ ]"
|
|
437
|
+
[[ "${checked[$i]}" == "1" ]] && marker="[x]"
|
|
438
|
+
prefix=" "
|
|
439
|
+
[[ $i -eq $cursor ]] && prefix=" ❯ "
|
|
440
|
+
printf '\r\033[2K%s%s %s\n' "$prefix" "$marker" "${labels[$i]}" >&4
|
|
441
|
+
done
|
|
442
|
+
|
|
443
|
+
key=""
|
|
444
|
+
rest=""
|
|
445
|
+
# If the user provides no input for the timeout window, retry up to the
|
|
446
|
+
# global limit, then abort and clean up installed artifacts. Other read
|
|
447
|
+
# failures still fall back to the numeric prompt.
|
|
448
|
+
if ! IFS= read -rsn1 -t "$INPUT_TIMEOUT_SECONDS" key <&3 2>/dev/null; then
|
|
449
|
+
((idle_attempts+=1))
|
|
450
|
+
if (( idle_attempts >= MAX_NO_INPUT_ATTEMPTS )); then
|
|
451
|
+
_arrow_menu_cleanup
|
|
452
|
+
abort_install_no_input "platform selection"
|
|
453
|
+
fi
|
|
454
|
+
printf '\nWARN: No input detected for platform selection (%d/%d). Waiting again...\n' \
|
|
455
|
+
"$idle_attempts" "$MAX_NO_INPUT_ATTEMPTS" >&4
|
|
456
|
+
first_draw=1
|
|
457
|
+
continue
|
|
458
|
+
fi
|
|
459
|
+
if [[ "$key" == $'\004' ]]; then
|
|
460
|
+
((idle_attempts+=1))
|
|
461
|
+
if (( idle_attempts >= MAX_NO_INPUT_ATTEMPTS )); then
|
|
462
|
+
_arrow_menu_cleanup
|
|
463
|
+
abort_install_no_input "platform selection"
|
|
464
|
+
fi
|
|
465
|
+
printf '\nWARN: No input detected for platform selection (%d/%d). Waiting again...\n' \
|
|
466
|
+
"$idle_attempts" "$MAX_NO_INPUT_ATTEMPTS" >&4
|
|
467
|
+
first_draw=1
|
|
468
|
+
continue
|
|
469
|
+
fi
|
|
470
|
+
idle_attempts=0
|
|
471
|
+
case "$key" in
|
|
472
|
+
$'\x1b')
|
|
473
|
+
stty time 1 min 0 <&3 2>/dev/null
|
|
474
|
+
IFS= read -rsn2 rest <&3 2>/dev/null || rest=""
|
|
475
|
+
stty time 0 min 1 <&3 2>/dev/null
|
|
476
|
+
case "$rest" in
|
|
477
|
+
"[A") (( cursor > 0 )) && ((cursor--)) ;;
|
|
478
|
+
"[B") (( cursor < n-1 )) && ((cursor++)) ;;
|
|
479
|
+
esac
|
|
480
|
+
;;
|
|
481
|
+
k|K) (( cursor > 0 )) && ((cursor--)) ;;
|
|
482
|
+
j|J) (( cursor < n-1 )) && ((cursor++)) ;;
|
|
483
|
+
' ') checked[$cursor]=$(( 1 - checked[$cursor] )) ;;
|
|
484
|
+
a|A) for ((i=0; i<n; i++)); do checked[$i]=1; done ;;
|
|
485
|
+
n|N) for ((i=0; i<n; i++)); do checked[$i]=0; done ;;
|
|
486
|
+
q|Q) _MENU_RESULT="skip"; printf '\n' >&4; return 0 ;;
|
|
487
|
+
''|$'\n'|$'\r') break ;;
|
|
488
|
+
esac
|
|
489
|
+
done
|
|
490
|
+
|
|
491
|
+
local out=""
|
|
492
|
+
local any=0
|
|
493
|
+
for ((i=0; i<n; i++)); do
|
|
494
|
+
if [[ "${checked[$i]}" == "1" ]]; then
|
|
495
|
+
out+="${keys[$i]} "
|
|
496
|
+
any=1
|
|
497
|
+
fi
|
|
498
|
+
done
|
|
499
|
+
printf '\n' >&4
|
|
500
|
+
if (( ! any )); then
|
|
501
|
+
_MENU_RESULT="${keys[$cursor]}"
|
|
502
|
+
else
|
|
503
|
+
_MENU_RESULT="${out% }"
|
|
504
|
+
fi
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
select_platforms_numeric_fallback() {
|
|
508
|
+
# Plain prompt that reads/writes directly from the controlling terminal.
|
|
509
|
+
# This avoids bash `select` + command-substitution visibility issues under
|
|
510
|
+
# `make`, and works when the arrow-key UI is not supported.
|
|
511
|
+
local tty_in="/dev/tty"
|
|
512
|
+
local tty_out="/dev/tty"
|
|
513
|
+
if [ ! -r "$tty_in" ] || [ ! -w "$tty_out" ]; then
|
|
514
|
+
_PLATFORM_RESULT="all"
|
|
515
|
+
return
|
|
516
|
+
fi
|
|
517
|
+
|
|
518
|
+
local idle_attempts=0
|
|
519
|
+
while true; do
|
|
520
|
+
cat > "$tty_out" <<'MENU'
|
|
521
|
+
|
|
522
|
+
Select agent platforms to configure:
|
|
523
|
+
1) All — Pi, Claude Desktop, Claude Code, Codex, Gemini, Cursor (recommended)
|
|
524
|
+
2) Claude Code only
|
|
525
|
+
3) Claude Desktop only
|
|
526
|
+
4) Pi only
|
|
527
|
+
5) Codex / OpenCode / Aider only
|
|
528
|
+
6) Gemini only
|
|
529
|
+
7) Cursor only
|
|
530
|
+
8) Custom — type a comma-separated list
|
|
531
|
+
9) Skip — don't configure any agents now
|
|
532
|
+
|
|
533
|
+
MENU
|
|
534
|
+
printf 'Your choice (1-9, default 1): ' > "$tty_out"
|
|
535
|
+
local reply=""
|
|
536
|
+
if ! IFS= read -r -t "$INPUT_TIMEOUT_SECONDS" reply < "$tty_in"; then
|
|
537
|
+
((idle_attempts+=1))
|
|
538
|
+
if (( idle_attempts >= MAX_NO_INPUT_ATTEMPTS )); then
|
|
539
|
+
abort_install_no_input "platform selection"
|
|
540
|
+
fi
|
|
541
|
+
warn_no_input_retry "platform selection" "$idle_attempts"
|
|
542
|
+
continue
|
|
543
|
+
fi
|
|
544
|
+
idle_attempts=0
|
|
545
|
+
reply="${reply:-1}"
|
|
546
|
+
|
|
547
|
+
case "$reply" in
|
|
548
|
+
1) _PLATFORM_RESULT="all"; return ;;
|
|
549
|
+
2) _PLATFORM_RESULT="claude-code"; return ;;
|
|
550
|
+
3) _PLATFORM_RESULT="claude-desktop"; return ;;
|
|
551
|
+
4) _PLATFORM_RESULT="pi"; return ;;
|
|
552
|
+
5) _PLATFORM_RESULT="codex"; return ;;
|
|
553
|
+
6) _PLATFORM_RESULT="gemini"; return ;;
|
|
554
|
+
7) _PLATFORM_RESULT="cursor"; return ;;
|
|
555
|
+
8)
|
|
556
|
+
local custom=""
|
|
557
|
+
local custom_attempts=0
|
|
558
|
+
while true; do
|
|
559
|
+
printf '\nEnter platforms separated by commas.\n' > "$tty_out"
|
|
560
|
+
printf 'Available: pi, claude-desktop, claude-code, codex, gemini, cursor\n' > "$tty_out"
|
|
561
|
+
printf 'Platforms: ' > "$tty_out"
|
|
562
|
+
if IFS= read -r -t "$INPUT_TIMEOUT_SECONDS" custom < "$tty_in"; then
|
|
563
|
+
break
|
|
564
|
+
fi
|
|
565
|
+
((custom_attempts+=1))
|
|
566
|
+
if (( custom_attempts >= MAX_NO_INPUT_ATTEMPTS )); then
|
|
567
|
+
abort_install_no_input "custom platform entry"
|
|
568
|
+
fi
|
|
569
|
+
warn_no_input_retry "custom platform entry" "$custom_attempts"
|
|
570
|
+
done
|
|
571
|
+
# Normalize: trim whitespace, allow space-separated input too.
|
|
572
|
+
custom="${custom//,/ }"
|
|
573
|
+
local token token_lc out=""
|
|
574
|
+
for token in $custom; do
|
|
575
|
+
token_lc="$(printf '%s' "$token" | tr '[:upper:]' '[:lower:]')"
|
|
576
|
+
case "$token_lc" in
|
|
577
|
+
pi|claude-desktop|claude-code|codex|gemini|cursor|all|skip)
|
|
578
|
+
out+="${out:+,}$token_lc" ;;
|
|
579
|
+
claude|code) out+="${out:+,}claude-code" ;;
|
|
580
|
+
desktop) out+="${out:+,}claude-desktop" ;;
|
|
581
|
+
opencode|aider) out+="${out:+,}codex" ;;
|
|
582
|
+
*) warn "Ignoring unknown platform: $token" ;;
|
|
583
|
+
esac
|
|
584
|
+
done
|
|
585
|
+
_PLATFORM_RESULT="${out:-all}"
|
|
586
|
+
return ;;
|
|
587
|
+
9) _PLATFORM_RESULT="skip"; return ;;
|
|
588
|
+
*) printf 'Invalid choice: %s — pick a number 1 through 9.\n' "$reply" > "$tty_out" ;;
|
|
589
|
+
esac
|
|
590
|
+
done
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
# Top-level platform selector.
|
|
594
|
+
#
|
|
595
|
+
# Default: arrow-key multi-select menu (↑/↓ or j/k, SPACE to toggle, ENTER).
|
|
596
|
+
# Fallback: TV_INSTALL_MENU=plain make install → numeric prompt
|
|
597
|
+
# (always works; use if the arrow menu misbehaves in your terminal).
|
|
598
|
+
# Sets _PLATFORM_RESULT instead of echoing, so the menu is not hidden inside
|
|
599
|
+
# command substitution.
|
|
600
|
+
select_platforms() {
|
|
601
|
+
_PLATFORM_RESULT=""
|
|
602
|
+
if [[ "$DEFAULT_PLATFORM" != "auto" && -n "$DEFAULT_PLATFORM" ]]; then
|
|
603
|
+
_PLATFORM_RESULT="$DEFAULT_PLATFORM"
|
|
604
|
+
return
|
|
605
|
+
fi
|
|
606
|
+
|
|
607
|
+
# No controlling terminal at all (CI, piped install) → default to all.
|
|
608
|
+
if [ ! -r /dev/tty ] || [ ! -w /dev/tty ]; then
|
|
609
|
+
_PLATFORM_RESULT="all"
|
|
610
|
+
return
|
|
611
|
+
fi
|
|
612
|
+
|
|
613
|
+
if [[ "${TV_INSTALL_MENU:-arrow}" == "plain" ]]; then
|
|
614
|
+
select_platforms_numeric_fallback
|
|
615
|
+
return
|
|
616
|
+
fi
|
|
617
|
+
|
|
618
|
+
local _MENU_RESULT=""
|
|
619
|
+
# `|| true` so `set -e` doesn't kill the wizard if the arrow menu bails out
|
|
620
|
+
# (e.g. terminal doesn't support raw mode). The fallback handles it below.
|
|
621
|
+
arrow_multi_select \
|
|
622
|
+
"Select agent platforms to configure:" \
|
|
623
|
+
"pi|Pi" \
|
|
624
|
+
"claude-desktop|Claude Desktop" \
|
|
625
|
+
"claude-code|Claude Code" \
|
|
626
|
+
"codex|Codex / OpenCode / Aider" \
|
|
627
|
+
"gemini|Gemini" \
|
|
628
|
+
"cursor|Cursor" || true
|
|
629
|
+
|
|
630
|
+
if [[ -z "$_MENU_RESULT" ]]; then
|
|
631
|
+
warn "Arrow-key menu unavailable in this terminal — switching to numeric prompt."
|
|
632
|
+
warn "(Tip: TV_INSTALL_MENU=plain make install skips the arrow menu directly.)"
|
|
633
|
+
select_platforms_numeric_fallback
|
|
634
|
+
return
|
|
635
|
+
fi
|
|
636
|
+
|
|
637
|
+
if [[ "$_MENU_RESULT" == "skip" ]]; then
|
|
638
|
+
_PLATFORM_RESULT="skip"
|
|
639
|
+
return
|
|
640
|
+
fi
|
|
641
|
+
_PLATFORM_RESULT="${_MENU_RESULT// /,}"
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
tv_session_ok() {
|
|
645
|
+
command -v conda >/dev/null 2>&1 || return 1
|
|
646
|
+
local out
|
|
647
|
+
out="$(conda run -n "$CONDA_ENV" --no-capture-output python -m tv_indicator.cli.main browser session-check 2>/dev/null || true)"
|
|
648
|
+
grep -Eiq 'signed_in:[[:space:]]*true|signed_in[[:space:]]+true' <<<"$out"
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
print_tv_login_status() {
|
|
652
|
+
if ! command -v conda >/dev/null 2>&1; then
|
|
653
|
+
warn "conda not found; cannot check TradingView login status."
|
|
654
|
+
return 0
|
|
655
|
+
fi
|
|
656
|
+
|
|
657
|
+
if ! conda run -n "$CONDA_ENV" --live-stream --no-capture-output \
|
|
658
|
+
python -m tv_indicator.cli.main browser session-check; then
|
|
659
|
+
warn "TradingView login status check failed. Retry with 'make session-check'."
|
|
660
|
+
fi
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
ensure_playwright_chromium() {
|
|
664
|
+
# Returns 0 if Chromium is installed for Playwright, 1 otherwise.
|
|
665
|
+
conda run -n "$CONDA_ENV" --no-capture-output python -c \
|
|
666
|
+
"import sys; from pathlib import Path; from playwright._impl._driver import compute_driver_executable; \
|
|
667
|
+
sys.exit(0 if any(Path(p).exists() for p in (Path.home()/'Library/Caches/ms-playwright', Path.home()/'.cache/ms-playwright')) else 1)" \
|
|
668
|
+
>/dev/null 2>&1
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
install_playwright_chromium_if_missing() {
|
|
672
|
+
if ensure_playwright_chromium; then
|
|
673
|
+
return 0
|
|
674
|
+
fi
|
|
675
|
+
warn "Playwright Chromium not found. Installing it now (one-time, ~150 MB download)..."
|
|
676
|
+
if conda run -n "$CONDA_ENV" --live-stream --no-capture-output python -m playwright install chromium; then
|
|
677
|
+
log "Chromium installed ✓"
|
|
678
|
+
return 0
|
|
679
|
+
else
|
|
680
|
+
warn "Could not install Chromium automatically. Run 'make install-playwright' and try login again."
|
|
681
|
+
return 1
|
|
682
|
+
fi
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
run_tv_login() {
|
|
686
|
+
if ! command -v conda >/dev/null 2>&1; then
|
|
687
|
+
warn "conda not found; skipping TradingView browser login."
|
|
688
|
+
return 0
|
|
689
|
+
fi
|
|
690
|
+
|
|
691
|
+
if tv_session_ok; then
|
|
692
|
+
log " → TradingView session already signed in ✓; skipping browser login."
|
|
693
|
+
return 0
|
|
694
|
+
fi
|
|
695
|
+
|
|
696
|
+
if ! install_playwright_chromium_if_missing; then
|
|
697
|
+
return 0
|
|
698
|
+
fi
|
|
699
|
+
|
|
700
|
+
log ""
|
|
701
|
+
log " Opening TradingView login in Playwright."
|
|
702
|
+
log " → A Chromium window will open. Complete the login (or 2FA) in that window."
|
|
703
|
+
log " → The window closes automatically once login is detected."
|
|
704
|
+
log " → If the window doesn't appear, check behind other apps (macOS Mission Control)."
|
|
705
|
+
log ""
|
|
706
|
+
|
|
707
|
+
# TV_BROWSER_HEADLESS may be set to "true" in .env or from a parent shell —
|
|
708
|
+
# the login flow forces a visible window regardless.
|
|
709
|
+
if env -u TV_BROWSER_HEADLESS \
|
|
710
|
+
conda run -n "$CONDA_ENV" --live-stream --no-capture-output \
|
|
711
|
+
env TV_BROWSER_HEADLESS=false python -m tv_indicator.cli.main browser login; then
|
|
712
|
+
log " → Login captured. Verifying session..."
|
|
713
|
+
else
|
|
714
|
+
warn " → Login did not complete. You can run 'make login' later."
|
|
715
|
+
return 0
|
|
716
|
+
fi
|
|
717
|
+
|
|
718
|
+
conda run -n "$CONDA_ENV" --live-stream --no-capture-output python -m tv_indicator.cli.main browser session-check || \
|
|
719
|
+
warn " → Session check failed. Retry with 'make login' && 'make session-check'."
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
run_agent_import() {
|
|
723
|
+
local platforms="$1"
|
|
724
|
+
[[ "$platforms" == "skip" ]] && { log "Skipping agent import."; return 0; }
|
|
725
|
+
if [[ -x tools/progress.sh ]]; then
|
|
726
|
+
tools/progress.sh \
|
|
727
|
+
--success "Agent tools imported for: $platforms ✓" \
|
|
728
|
+
--failure "Agent tool import failed ✗" \
|
|
729
|
+
"Importing agent tools for: $platforms" \
|
|
730
|
+
-- bash tools/import_agent_tools.sh --platform "$platforms"
|
|
731
|
+
else
|
|
732
|
+
log "Importing agent tools for: $platforms"
|
|
733
|
+
bash tools/import_agent_tools.sh --platform "$platforms"
|
|
734
|
+
fi
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
_add_unique_platform() {
|
|
738
|
+
local current="$1"
|
|
739
|
+
local item="$2"
|
|
740
|
+
case " $current " in
|
|
741
|
+
*" $item "*) printf '%s\n' "$current" ;;
|
|
742
|
+
*) printf '%s\n' "${current:+$current }$item" ;;
|
|
743
|
+
esac
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
_expand_platforms_for_examples() {
|
|
747
|
+
local raw="$1"
|
|
748
|
+
raw="${raw//,/ }"
|
|
749
|
+
local selected=""
|
|
750
|
+
local token mapped
|
|
751
|
+
for token in $raw; do
|
|
752
|
+
case "$token" in
|
|
753
|
+
all|auto)
|
|
754
|
+
for mapped in pi claude-desktop claude-code codex gemini cursor; do
|
|
755
|
+
selected="$(_add_unique_platform "$selected" "$mapped")"
|
|
756
|
+
done
|
|
757
|
+
;;
|
|
758
|
+
pi|claude-desktop|claude-code|codex|gemini|cursor)
|
|
759
|
+
selected="$(_add_unique_platform "$selected" "$token")" ;;
|
|
760
|
+
claude) selected="$(_add_unique_platform "$selected" "claude-code")" ;;
|
|
761
|
+
desktop) selected="$(_add_unique_platform "$selected" "claude-desktop")" ;;
|
|
762
|
+
opencode|aider) selected="$(_add_unique_platform "$selected" "codex")" ;;
|
|
763
|
+
skip|"") ;;
|
|
764
|
+
esac
|
|
765
|
+
done
|
|
766
|
+
printf '%s\n' "$selected"
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
print_usage_examples() {
|
|
770
|
+
local platforms="$1"
|
|
771
|
+
log ""
|
|
772
|
+
log "Usage examples for selected platform(s):"
|
|
773
|
+
|
|
774
|
+
if [[ "$platforms" == "skip" ]]; then
|
|
775
|
+
log " No agent platform was configured. Import later with:"
|
|
776
|
+
log " make agent-import PLATFORM=pi # or: all, claude-code, cursor, ..."
|
|
777
|
+
return
|
|
778
|
+
fi
|
|
779
|
+
|
|
780
|
+
local selected
|
|
781
|
+
selected="$(_expand_platforms_for_examples "$platforms")"
|
|
782
|
+
if [[ -z "$selected" ]]; then
|
|
783
|
+
log " Run: make agent-import PLATFORM=pi # or: all, claude-code, cursor, ..."
|
|
784
|
+
return
|
|
785
|
+
fi
|
|
786
|
+
|
|
787
|
+
local p
|
|
788
|
+
for p in $selected; do
|
|
789
|
+
case "$p" in
|
|
790
|
+
pi)
|
|
791
|
+
log " Pi:"
|
|
792
|
+
log " /reload # inside Pi, or restart Pi"
|
|
793
|
+
log " Ask: analyze NASDAQ:AAPL"
|
|
794
|
+
;;
|
|
795
|
+
claude-desktop)
|
|
796
|
+
log " Claude Desktop:"
|
|
797
|
+
log " Quit and reopen Claude Desktop"
|
|
798
|
+
log " Ask: analyze NASDAQ:AAPL"
|
|
799
|
+
;;
|
|
800
|
+
claude-code)
|
|
801
|
+
log " Claude Code:"
|
|
802
|
+
log " claude # run from this repo root"
|
|
803
|
+
log " Ask: analyze NASDAQ:AAPL"
|
|
804
|
+
;;
|
|
805
|
+
codex)
|
|
806
|
+
log " Codex / OpenCode / Aider:"
|
|
807
|
+
log " Restart the agent/client, then open this repo"
|
|
808
|
+
log " Ask: analyze NASDAQ:AAPL"
|
|
809
|
+
;;
|
|
810
|
+
gemini)
|
|
811
|
+
log " Gemini CLI:"
|
|
812
|
+
log " gemini # run from this repo root"
|
|
813
|
+
log " Ask: analyze NASDAQ:AAPL"
|
|
814
|
+
;;
|
|
815
|
+
cursor)
|
|
816
|
+
log " Cursor:"
|
|
817
|
+
log " Reload Window, then check Cursor MCP settings"
|
|
818
|
+
log " Ask: analyze NASDAQ:AAPL"
|
|
819
|
+
;;
|
|
820
|
+
esac
|
|
821
|
+
done
|
|
822
|
+
|
|
823
|
+
log " CLI checks:"
|
|
824
|
+
log " make status"
|
|
825
|
+
log " make session-check"
|
|
826
|
+
log " make model-status"
|
|
827
|
+
log " make model-setup # verify model API env"
|
|
828
|
+
log " make model-forecast SYMBOL=NASDAQ:TSLA MODEL_JSON=1"
|
|
829
|
+
log " make ohlcv SYMBOL=NASDAQ:AAPL BARS=50"
|
|
830
|
+
log " Analysis artifacts:"
|
|
831
|
+
log " temp/{YYYYMMDD_HHMMSS}/{TARGET_ID}/analyzed_result.md"
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
noninteractive_setup() {
|
|
835
|
+
ensure_env_file
|
|
836
|
+
ensure_language_default
|
|
837
|
+
local platform="${TV_INDICATOR_AGENT_PLATFORM:-$DEFAULT_PLATFORM}"
|
|
838
|
+
# In non-interactive package/CI installs, avoid unexpectedly touching global
|
|
839
|
+
# Claude Desktop config. Users can override with TV_INDICATOR_AGENT_PLATFORM=all.
|
|
840
|
+
[[ "$platform" == "auto" ]] && platform="pi"
|
|
841
|
+
run_agent_import "$platform"
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
main() {
|
|
845
|
+
if [[ "${TV_INDICATOR_SETUP:-1}" == "0" ]]; then
|
|
846
|
+
ensure_env_file
|
|
847
|
+
ensure_language_default
|
|
848
|
+
log "TV_INDICATOR_SETUP=0; skipping interactive agent setup."
|
|
849
|
+
return 0
|
|
850
|
+
fi
|
|
851
|
+
|
|
852
|
+
if ! is_tty; then
|
|
853
|
+
log "Non-interactive shell detected; running default agent import only."
|
|
854
|
+
noninteractive_setup
|
|
855
|
+
return 0
|
|
856
|
+
fi
|
|
857
|
+
|
|
858
|
+
log ""
|
|
859
|
+
log "=== Indicator — interactive setup ==="
|
|
860
|
+
ensure_env_file
|
|
861
|
+
|
|
862
|
+
log ""
|
|
863
|
+
log "Step 1/4 — preferred agent output language:"
|
|
864
|
+
prompt_language
|
|
865
|
+
|
|
866
|
+
log ""
|
|
867
|
+
log "Step 2/4 — choose which agent platforms to configure:"
|
|
868
|
+
local platforms
|
|
869
|
+
select_platforms
|
|
870
|
+
platforms="$_PLATFORM_RESULT"
|
|
871
|
+
if [[ "$platforms" == "skip" ]]; then
|
|
872
|
+
log " → Skipping agent import."
|
|
873
|
+
else
|
|
874
|
+
log " → Selected: $platforms"
|
|
875
|
+
fi
|
|
876
|
+
|
|
877
|
+
log ""
|
|
878
|
+
log "Step 3/4 — optional TradingView cookies and AI API keys:"
|
|
879
|
+
if [[ "${TV_INSTALL_PROMPT_ENV:-0}" =~ ^(1|true|TRUE|yes|YES)$ ]]; then
|
|
880
|
+
if ask_yes_no "Store optional API/session values in .env now?" n; then
|
|
881
|
+
log "Press Enter to skip any value. Secret values are hidden while typing."
|
|
882
|
+
prompt_env_value "TV_SESSIONID" "TradingView sessionid cookie" 1
|
|
883
|
+
prompt_env_value "TV_SESSIONID_SIGN" "TradingView sessionid_sign cookie" 1
|
|
884
|
+
prompt_env_value "TV_USERNAME" "TradingView username/label" 0
|
|
885
|
+
prompt_env_value "ANTHROPIC_API_KEY" "Anthropic API key" 1
|
|
886
|
+
prompt_env_value "OPENAI_API_KEY" "OpenAI API key" 1
|
|
887
|
+
prompt_env_value "GEMINI_API_KEY" "Gemini API key" 1
|
|
888
|
+
prompt_env_value "GOOGLE_API_KEY" "Google API key" 1
|
|
889
|
+
else
|
|
890
|
+
log "Skipped .env API/session input."
|
|
891
|
+
fi
|
|
892
|
+
else
|
|
893
|
+
log " → Skipped by default; no API/session answer is required during install."
|
|
894
|
+
log " → Store values later with: python3 skills/api-credential-storage/scripts/upsert_env.py OPENAI_API_KEY --stdin"
|
|
895
|
+
log " → To enable install-time prompts, rerun with: TV_INSTALL_PROMPT_ENV=1 make install"
|
|
896
|
+
fi
|
|
897
|
+
|
|
898
|
+
log ""
|
|
899
|
+
log "Step 4/4 — TradingView browser login:"
|
|
900
|
+
if tv_session_ok; then
|
|
901
|
+
log " → Already signed in ✓; no browser login needed."
|
|
902
|
+
elif ask_yes_no "Run TradingView browser login now?" y; then
|
|
903
|
+
run_tv_login
|
|
904
|
+
else
|
|
905
|
+
log " → Skipped browser login. You can run 'make login' later."
|
|
906
|
+
fi
|
|
907
|
+
|
|
908
|
+
log ""
|
|
909
|
+
log "Importing agent tools..."
|
|
910
|
+
run_agent_import "$platforms"
|
|
911
|
+
|
|
912
|
+
log ""
|
|
913
|
+
log "Setup complete. Restart/reload selected agents to use the generated tools and prompts."
|
|
914
|
+
print_usage_examples "$platforms"
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
main
|