@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.
Files changed (80) hide show
  1. package/README.md +2 -7
  2. package/package.json +4 -1
  3. package/project/.cursor/rules/agent-docs.mdc +16 -0
  4. package/project/.cursor/rules/api-credential-storage.mdc +16 -0
  5. package/project/.cursor/rules/pinescript-v6.mdc +16 -0
  6. package/project/.cursor/rules/stock-model-forecast.mdc +16 -0
  7. package/project/.cursor/rules/system-prompt-injection.mdc +16 -0
  8. package/project/.cursor/rules/system-prompt-updater.mdc +16 -0
  9. package/project/.cursor/rules/tradingview-stock-data.mdc +16 -0
  10. package/project/.env.example +44 -0
  11. package/project/.npm-packaged-project +1 -0
  12. package/project/.pi/APPEND_SYSTEM.md +338 -0
  13. package/project/.pi/settings.json +8 -0
  14. package/project/AGENTS.md +538 -0
  15. package/project/CLAUDE.md +538 -0
  16. package/project/GEMINI.md +538 -0
  17. package/project/Makefile +488 -0
  18. package/project/README.md +419 -0
  19. package/project/conda-env-active.sh +98 -0
  20. package/project/conda-env-deactive.sh +42 -0
  21. package/project/docs/agent-install.md +446 -0
  22. package/project/docs/agent-skill-directory.md +222 -0
  23. package/project/docs/integration.html +271 -0
  24. package/project/packages/indicator/README.md +39 -0
  25. package/project/packages/indicator/package.json +40 -0
  26. package/project/packages/indicator/scripts/build-project-snapshot.js +57 -0
  27. package/project/packages/indicator/src/cli.js +368 -0
  28. package/project/packages/tradingview-stock-data-skill/README.md +112 -0
  29. package/project/packages/tradingview-stock-data-skill/extensions/stock-prompt-injector.ts +121 -0
  30. package/project/packages/tradingview-stock-data-skill/package.json +35 -0
  31. package/project/packages/tradingview-stock-data-skill/scripts/postinstall.sh +73 -0
  32. package/project/packages/tradingview-stock-data-skill/skills/tradingview-stock-data/SKILL.md +241 -0
  33. package/project/pyproject.toml +68 -0
  34. package/project/screenshots/.gitkeep +0 -0
  35. package/project/scripts/indicators/example_rsi_bands.pine +27 -0
  36. package/project/scripts/indicators/tsla_levels.pine +57 -0
  37. package/project/skills/agent-docs/SKILL.md +56 -0
  38. package/project/skills/api-credential-storage/SKILL.md +83 -0
  39. package/project/skills/api-credential-storage/scripts/upsert_env.py +151 -0
  40. package/project/skills/pinescript-v6/SKILL.md +129 -0
  41. package/project/skills/pinescript-v6/reference/built-ins.md +219 -0
  42. package/project/skills/pinescript-v6/reference/templates/alert-webhook.pine +76 -0
  43. package/project/skills/pinescript-v6/reference/templates/indicator.pine +48 -0
  44. package/project/skills/pinescript-v6/reference/templates/strategy.pine +50 -0
  45. package/project/skills/pinescript-v6/reference/v5-to-v6-migration.md +102 -0
  46. package/project/skills/pinescript-v6/reference/v6-language.md +202 -0
  47. package/project/skills/stock-model-forecast/SKILL.md +192 -0
  48. package/project/skills/system-prompt-injection/CUSTOM_SYSTEM_PROMPT.md +333 -0
  49. package/project/skills/system-prompt-injection/DEFAULT_SYSTEM_PROMPT.md +327 -0
  50. package/project/skills/system-prompt-injection/SKILL.md +90 -0
  51. package/project/skills/system-prompt-injection/SYSTEM_PROMPT.md +23 -0
  52. package/project/skills/system-prompt-updater/SKILL.md +82 -0
  53. package/project/skills/system-prompt-updater/scripts/system_prompt_update.sh +106 -0
  54. package/project/skills/tradingview-stock-data/SKILL.md +272 -0
  55. package/project/src/tv_indicator/__init__.py +0 -0
  56. package/project/src/tv_indicator/browser/__init__.py +0 -0
  57. package/project/src/tv_indicator/browser/automation.py +541 -0
  58. package/project/src/tv_indicator/browser/selectors.py +70 -0
  59. package/project/src/tv_indicator/cli/__init__.py +0 -0
  60. package/project/src/tv_indicator/cli/browser_cmds.py +92 -0
  61. package/project/src/tv_indicator/cli/data_cmds.py +178 -0
  62. package/project/src/tv_indicator/cli/main.py +56 -0
  63. package/project/src/tv_indicator/cli/model_cmds.py +255 -0
  64. package/project/src/tv_indicator/cli/pine_cmds.py +140 -0
  65. package/project/src/tv_indicator/config.py +98 -0
  66. package/project/src/tv_indicator/data/__init__.py +0 -0
  67. package/project/src/tv_indicator/data/client.py +187 -0
  68. package/project/src/tv_indicator/data/screener.py +268 -0
  69. package/project/src/tv_indicator/mcp/__init__.py +0 -0
  70. package/project/src/tv_indicator/mcp/agent_server.py +398 -0
  71. package/project/src/tv_indicator/mcp/browser_server.py +133 -0
  72. package/project/src/tv_indicator/mcp/data_server.py +239 -0
  73. package/project/src/tv_indicator/model/__init__.py +19 -0
  74. package/project/src/tv_indicator/model/forecast.py +693 -0
  75. package/project/tools/import_agent_tools.sh +503 -0
  76. package/project/tools/install_skills.sh +673 -0
  77. package/project/tools/interactive_install.sh +917 -0
  78. package/project/tools/progress.sh +114 -0
  79. package/project/tools/uninstall_agent_tools.sh +373 -0
  80. package/src/cli.js +22 -25
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env bash
2
+ # progress.sh - run a command with a compact installer spinner.
3
+ #
4
+ # Usage:
5
+ # tools/progress.sh [--success MSG] [--failure MSG] LABEL -- COMMAND [ARGS...]
6
+ #
7
+ # Set TV_INSTALL_SPINNER=0 to disable animated output.
8
+
9
+ set -euo pipefail
10
+
11
+ SUCCESS_MSG=""
12
+ FAILURE_MSG=""
13
+ SHOW_OUTPUT_ON_FAIL=1
14
+
15
+ usage() {
16
+ sed -n '1,7p' "$0" | sed 's/^# \{0,1\}//'
17
+ }
18
+
19
+ while [[ $# -gt 0 ]]; do
20
+ case "$1" in
21
+ --success)
22
+ SUCCESS_MSG="${2:-}"
23
+ [[ -n "$SUCCESS_MSG" ]] || { echo "--success requires a value" >&2; exit 2; }
24
+ shift 2
25
+ ;;
26
+ --failure)
27
+ FAILURE_MSG="${2:-}"
28
+ [[ -n "$FAILURE_MSG" ]] || { echo "--failure requires a value" >&2; exit 2; }
29
+ shift 2
30
+ ;;
31
+ --no-output-on-fail)
32
+ SHOW_OUTPUT_ON_FAIL=0
33
+ shift
34
+ ;;
35
+ -h|--help)
36
+ usage
37
+ exit 0
38
+ ;;
39
+ --)
40
+ shift
41
+ break
42
+ ;;
43
+ -*)
44
+ echo "Unknown option: $1" >&2
45
+ usage >&2
46
+ exit 2
47
+ ;;
48
+ *)
49
+ break
50
+ ;;
51
+ esac
52
+ done
53
+
54
+ LABEL="${1:-}"
55
+ [[ -n "$LABEL" ]] || { echo "Missing progress label" >&2; usage >&2; exit 2; }
56
+ shift
57
+
58
+ if [[ "${1:-}" == "--" ]]; then
59
+ shift
60
+ fi
61
+ [[ $# -gt 0 ]] || { echo "Missing command for progress label: $LABEL" >&2; exit 2; }
62
+
63
+ SUCCESS_MSG="${SUCCESS_MSG:-$LABEL complete}"
64
+ FAILURE_MSG="${FAILURE_MSG:-$LABEL failed}"
65
+
66
+ tmp_log="$(mktemp "${TMPDIR:-/tmp}/tv-indicator-progress.XXXXXX")"
67
+ cleanup() {
68
+ rm -f "$tmp_log"
69
+ }
70
+ trap cleanup EXIT
71
+ trap '[[ -n "${pid:-}" ]] && kill "$pid" 2>/dev/null || true; exit 130' INT TERM
72
+
73
+ use_spinner=0
74
+ if [[ "${TV_INSTALL_SPINNER:-1}" != "0" && -t 2 ]]; then
75
+ use_spinner=1
76
+ fi
77
+
78
+ status=0
79
+ if (( use_spinner )); then
80
+ "$@" >"$tmp_log" 2>&1 &
81
+ pid=$!
82
+ frames='|/-\'
83
+ i=0
84
+ while kill -0 "$pid" 2>/dev/null; do
85
+ frame="${frames:$((i % ${#frames})):1}"
86
+ printf '\r %s %s' "$frame" "$LABEL" >&2
87
+ i=$((i + 1))
88
+ sleep 0.12
89
+ done
90
+ if wait "$pid"; then
91
+ status=0
92
+ else
93
+ status=$?
94
+ fi
95
+ printf '\r\033[K' >&2
96
+ else
97
+ printf ' ... %s\n' "$LABEL" >&2
98
+ if "$@" >"$tmp_log" 2>&1; then
99
+ status=0
100
+ else
101
+ status=$?
102
+ fi
103
+ fi
104
+
105
+ if (( status == 0 )); then
106
+ printf ' %s\n' "$SUCCESS_MSG"
107
+ else
108
+ printf ' %s\n' "$FAILURE_MSG" >&2
109
+ if (( SHOW_OUTPUT_ON_FAIL )) && [[ -s "$tmp_log" ]]; then
110
+ sed 's/^/ /' "$tmp_log" >&2
111
+ fi
112
+ fi
113
+
114
+ exit "$status"
@@ -0,0 +1,373 @@
1
+ #!/usr/bin/env bash
2
+ # Remove tv-indicator MCP registrations and Pi package entries from per-agent
3
+ # config files. Symmetric inverse of tools/import_agent_tools.sh.
4
+ #
5
+ # This script ONLY touches the three managed servers — both the new names
6
+ # (training-view-data, training-view-browser, training-view-agent) and the
7
+ # legacy names (tv-data, tv-browser, tv-agent) from before the rename — plus
8
+ # the tradingview-stock-data-skill Pi package entry. Unrelated entries (e.g.
9
+ # the user's own MCP servers) are left intact.
10
+ #
11
+ # Usage:
12
+ # tools/uninstall_agent_tools.sh --platform auto
13
+ # tools/uninstall_agent_tools.sh --platform all
14
+ # tools/uninstall_agent_tools.sh --platform pi,claude-code,codex
15
+ #
16
+ # For removing skill symlinks and generated context files, use:
17
+ # tools/install_skills.sh --uninstall
18
+
19
+ set -euo pipefail
20
+
21
+ PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
22
+ cd "$PROJECT_ROOT"
23
+
24
+ PLATFORM="auto"
25
+ DRY_RUN=0
26
+
27
+ usage() {
28
+ cat <<EOF
29
+ Usage: $(basename "$0") [options]
30
+
31
+ Options:
32
+ --platform <names> auto | all | pi | claude-desktop | claude-code | codex | gemini | cursor
33
+ (comma-separated for multiple, e.g. pi,claude-code)
34
+ --dry-run Show what would change without modifying files
35
+ -h, --help Show this help
36
+
37
+ What gets removed per platform (matches both new training-view-* and legacy tv-* names):
38
+ pi managed training-view-* / tv-* block in .pi/config.toml,
39
+ tradingview-stock-data-skill from .pi/settings.json packages,
40
+ .pi/.tv-indicator-auto-imported sentinel
41
+ claude-desktop training-view-{data,browser,agent} and legacy tv-{data,browser,agent}
42
+ from claude_desktop_config.json mcpServers
43
+ claude-code same servers from project .mcp.json
44
+ codex managed training-view-* / tv-* block from ~/.codex/config.toml
45
+ gemini same servers from ~/.gemini/settings.json
46
+ cursor same servers from project .cursor/mcp.json
47
+
48
+ Note: skill symlinks and generated context files (CLAUDE.md, AGENTS.md, etc.)
49
+ are managed separately. Run: tools/install_skills.sh --uninstall
50
+ EOF
51
+ }
52
+
53
+ while [[ $# -gt 0 ]]; do
54
+ case "$1" in
55
+ --platform) PLATFORM="${2:-auto}"; shift 2 ;;
56
+ --dry-run) DRY_RUN=1; shift ;;
57
+ -h|--help) usage; exit 0 ;;
58
+ *) echo "Unknown option: $1" >&2; usage; exit 1 ;;
59
+ esac
60
+ done
61
+
62
+ log() { printf '%s\n' "$*"; }
63
+ have_cmd() { command -v "$1" >/dev/null 2>&1; }
64
+
65
+ claude_desktop_config_path() {
66
+ case "$(uname -s 2>/dev/null || echo unknown)" in
67
+ Darwin) printf '%s\n' "$HOME/Library/Application Support/Claude/claude_desktop_config.json" ;;
68
+ Linux) printf '%s\n' "$HOME/.config/Claude/claude_desktop_config.json" ;;
69
+ MINGW*|MSYS*|CYGWIN*) printf '%s\n' "${APPDATA:-$HOME/AppData/Roaming}/Claude/claude_desktop_config.json" ;;
70
+ *) printf '%s\n' "$HOME/.config/Claude/claude_desktop_config.json" ;;
71
+ esac
72
+ }
73
+
74
+ # Remove training-view-{data,browser,agent} and legacy tv-{data,browser,agent}
75
+ # from mcpServers in a JSON config file.
76
+ # Leaves other entries intact. If the config becomes effectively empty, removes
77
+ # the file (for project-local configs only; user-global configs are kept).
78
+ remove_mcp_json_servers() {
79
+ local path="$1"
80
+ local label="$2"
81
+ local delete_if_empty="${3:-0}"
82
+
83
+ if [[ ! -f "$path" ]]; then
84
+ log "[$label] no config at: $path (nothing to remove)"
85
+ return
86
+ fi
87
+
88
+ if [[ "$DRY_RUN" == "1" ]]; then
89
+ log "[dry-run] [$label] remove training-view-* and legacy tv-* MCP servers from $path"
90
+ return
91
+ fi
92
+
93
+ python3 - "$path" "$label" "$delete_if_empty" <<'PY'
94
+ import json
95
+ import sys
96
+ from pathlib import Path
97
+
98
+ path = Path(sys.argv[1]).expanduser()
99
+ label = sys.argv[2]
100
+ delete_if_empty = sys.argv[3] == "1"
101
+
102
+ try:
103
+ data = json.loads(path.read_text(encoding="utf-8"))
104
+ except json.JSONDecodeError:
105
+ print(f"[{label}] config is not valid JSON, skipping: {path}")
106
+ sys.exit(0)
107
+
108
+ mcp = data.get("mcpServers")
109
+ if not isinstance(mcp, dict):
110
+ print(f"[{label}] no mcpServers block in {path}, nothing to remove")
111
+ sys.exit(0)
112
+
113
+ removed = []
114
+ # Both legacy (tv-*) and new (training-view-*) names are cleaned so that
115
+ # existing installs migrate cleanly when the user re-imports.
116
+ for name in (
117
+ "training-view-data", "training-view-browser", "training-view-agent",
118
+ "tv-data", "tv-browser", "tv-agent",
119
+ ):
120
+ if name in mcp:
121
+ del mcp[name]
122
+ removed.append(name)
123
+
124
+ if not removed:
125
+ print(f"[{label}] no tv-* entries to remove in {path}")
126
+ sys.exit(0)
127
+
128
+ if mcp:
129
+ data["mcpServers"] = mcp
130
+ else:
131
+ data.pop("mcpServers", None)
132
+
133
+ if delete_if_empty and not data:
134
+ path.unlink()
135
+ print(f"[{label}] removed {', '.join(removed)} and deleted empty config: {path}")
136
+ else:
137
+ path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
138
+ print(f"[{label}] removed {', '.join(removed)} from {path}")
139
+ PY
140
+ }
141
+
142
+ # Strip the managed "# BEGIN tv-indicator agent tools" ... "# END ..." block
143
+ # from a TOML file. Leaves everything outside that block intact.
144
+ remove_toml_managed_block() {
145
+ local path="$1"
146
+ local label="$2"
147
+ local delete_if_empty="${3:-0}"
148
+
149
+ if [[ ! -f "$path" ]]; then
150
+ log "[$label] no config at: $path (nothing to remove)"
151
+ return
152
+ fi
153
+
154
+ if [[ "$DRY_RUN" == "1" ]]; then
155
+ log "[dry-run] [$label] strip managed tv-indicator block from $path"
156
+ return
157
+ fi
158
+
159
+ python3 - "$path" "$label" "$delete_if_empty" <<'PY'
160
+ import sys
161
+ from pathlib import Path
162
+
163
+ path = Path(sys.argv[1]).expanduser()
164
+ label = sys.argv[2]
165
+ delete_if_empty = sys.argv[3] == "1"
166
+
167
+ start = "# BEGIN tv-indicator agent tools"
168
+ end = "# END tv-indicator agent tools"
169
+ text = path.read_text(encoding="utf-8")
170
+
171
+ if start not in text or end not in text:
172
+ print(f"[{label}] no managed tv-indicator block in {path}")
173
+ sys.exit(0)
174
+
175
+ before = text.split(start, 1)[0].rstrip()
176
+ after = text.split(end, 1)[1].lstrip()
177
+ new_text = before
178
+ if before and after:
179
+ new_text = before + "\n\n" + after
180
+ elif after:
181
+ new_text = after
182
+ if new_text and not new_text.endswith("\n"):
183
+ new_text += "\n"
184
+
185
+ if delete_if_empty and not new_text.strip():
186
+ path.unlink()
187
+ print(f"[{label}] stripped managed block and deleted empty config: {path}")
188
+ else:
189
+ path.write_text(new_text, encoding="utf-8")
190
+ print(f"[{label}] stripped managed block from {path}")
191
+ PY
192
+ }
193
+
194
+ # Remove the tradingview-stock-data-skill package entry from .pi/settings.json.
195
+ remove_pi_package_entry() {
196
+ local settings="$PROJECT_ROOT/.pi/settings.json"
197
+ local package_source="../packages/tradingview-stock-data-skill"
198
+
199
+ if [[ ! -f "$settings" ]]; then
200
+ log "[pi] no .pi/settings.json (nothing to remove)"
201
+ return
202
+ fi
203
+
204
+ if [[ "$DRY_RUN" == "1" ]]; then
205
+ log "[dry-run] [pi] remove $package_source from .pi/settings.json packages"
206
+ return
207
+ fi
208
+
209
+ python3 - "$settings" "$package_source" <<'PY'
210
+ import json
211
+ import sys
212
+ from pathlib import Path
213
+
214
+ settings_path = Path(sys.argv[1])
215
+ source = sys.argv[2]
216
+
217
+ try:
218
+ data = json.loads(settings_path.read_text(encoding="utf-8"))
219
+ except json.JSONDecodeError:
220
+ print("[pi] .pi/settings.json is not valid JSON, skipping")
221
+ sys.exit(0)
222
+
223
+ packages = data.get("packages", [])
224
+ if not isinstance(packages, list):
225
+ print("[pi] no packages array in .pi/settings.json, nothing to remove")
226
+ sys.exit(0)
227
+
228
+ original_len = len(packages)
229
+ filtered = []
230
+ for item in packages:
231
+ if item == source:
232
+ continue
233
+ if isinstance(item, dict) and item.get("source") == source:
234
+ continue
235
+ filtered.append(item)
236
+
237
+ if len(filtered) == original_len:
238
+ print("[pi] tradingview-stock-data-skill not present in .pi/settings.json")
239
+ sys.exit(0)
240
+
241
+ if filtered:
242
+ data["packages"] = filtered
243
+ else:
244
+ data.pop("packages", None)
245
+
246
+ if not data:
247
+ settings_path.unlink()
248
+ print("[pi] removed package entry and deleted empty .pi/settings.json")
249
+ else:
250
+ settings_path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
251
+ print("[pi] removed tradingview-stock-data-skill from .pi/settings.json")
252
+ PY
253
+ }
254
+
255
+ remove_pi_sentinel() {
256
+ local sentinel="$PROJECT_ROOT/.pi/.tv-indicator-auto-imported"
257
+ if [[ ! -f "$sentinel" ]]; then
258
+ log "[pi] no auto-import sentinel (nothing to remove)"
259
+ return
260
+ fi
261
+ if [[ "$DRY_RUN" == "1" ]]; then
262
+ log "[dry-run] [pi] remove .pi/.tv-indicator-auto-imported"
263
+ return
264
+ fi
265
+ rm -f "$sentinel"
266
+ log "[pi] removed .pi/.tv-indicator-auto-imported"
267
+ }
268
+
269
+ detect_active_platform() {
270
+ if [[ -n "${PI_CODING_AGENT_SESSION_DIR:-}${PI_SESSION_ID:-}" ]]; then echo "pi"; return; fi
271
+ if [[ -n "${CLAUDE_SESSION_ID:-}${CLAUDECODE:-}" ]]; then echo "claude-code"; return; fi
272
+ if [[ -n "${CODEX_SANDBOX:-}${CODEX_SESSION_ID:-}" ]]; then echo "codex"; return; fi
273
+ if [[ -n "${GEMINI_CLI:-}${GEMINI_SESSION_ID:-}" ]]; then echo "gemini"; return; fi
274
+ echo "all"
275
+ }
276
+
277
+ add_unique_platform() {
278
+ local current="$1" item="$2"
279
+ case " $current " in
280
+ *" $item "*) printf '%s\n' "$current" ;;
281
+ *) printf '%s\n' "${current:+$current }$item" ;;
282
+ esac
283
+ }
284
+
285
+ normalize_platforms() {
286
+ local raw="$1"
287
+ local selected=""
288
+ IFS=',' read -ra parts <<<"$raw"
289
+ for entry in "${parts[@]}"; do
290
+ case "$entry" in
291
+ auto) selected="$(add_unique_platform "$selected" "$(detect_active_platform)")" ;;
292
+ all)
293
+ for mapped in pi claude-desktop claude-code codex gemini cursor; do
294
+ selected="$(add_unique_platform "$selected" "$mapped")"
295
+ done
296
+ ;;
297
+ pi|claude-desktop|claude-code|codex|gemini|cursor)
298
+ selected="$(add_unique_platform "$selected" "$entry")"
299
+ ;;
300
+ codex|opencode|aider) selected="$(add_unique_platform "$selected" "codex")" ;;
301
+ claude) selected="$(add_unique_platform "$selected" "claude-code")" ;;
302
+ "") ;;
303
+ *) echo "Unknown platform: $entry" >&2; exit 1 ;;
304
+ esac
305
+ done
306
+ echo "$selected"
307
+ }
308
+
309
+ unconfigure_pi() {
310
+ log "[pi] Removing Pi MCP registrations and package entry..."
311
+ remove_toml_managed_block "$PROJECT_ROOT/.pi/config.toml" "pi" 1
312
+ remove_pi_package_entry
313
+ remove_pi_sentinel
314
+ }
315
+
316
+ unconfigure_claude_desktop() {
317
+ log "[claude-desktop] Removing Claude Desktop MCP servers..."
318
+ remove_mcp_json_servers "$(claude_desktop_config_path)" "claude-desktop" 0
319
+ }
320
+
321
+ unconfigure_claude_code() {
322
+ log "[claude-code] Removing Claude Code project MCP servers..."
323
+ remove_mcp_json_servers "$PROJECT_ROOT/.mcp.json" "claude-code" 1
324
+ }
325
+
326
+ unconfigure_codex() {
327
+ log "[codex] Removing Codex MCP servers..."
328
+ remove_toml_managed_block "$HOME/.codex/config.toml" "codex" 0
329
+ }
330
+
331
+ unconfigure_gemini() {
332
+ log "[gemini] Removing Gemini MCP servers..."
333
+ remove_mcp_json_servers "$HOME/.gemini/settings.json" "gemini" 0
334
+ }
335
+
336
+ unconfigure_cursor() {
337
+ log "[cursor] Removing Cursor MCP servers..."
338
+ remove_mcp_json_servers "$PROJECT_ROOT/.cursor/mcp.json" "cursor" 1
339
+ }
340
+
341
+ main() {
342
+ local selected
343
+ selected="$(normalize_platforms "$PLATFORM")"
344
+
345
+ if [[ -z "$selected" ]]; then
346
+ log "No platforms selected."
347
+ exit 0
348
+ fi
349
+
350
+ log "Uninstalling tv-indicator agent integrations for: $selected"
351
+ if [[ "$DRY_RUN" == "1" ]]; then
352
+ log "(dry-run — no files will be modified)"
353
+ fi
354
+ log ""
355
+
356
+ for p in $selected; do
357
+ case "$p" in
358
+ pi) unconfigure_pi ;;
359
+ claude-desktop) unconfigure_claude_desktop ;;
360
+ claude-code) unconfigure_claude_code ;;
361
+ codex) unconfigure_codex ;;
362
+ gemini) unconfigure_gemini ;;
363
+ cursor) unconfigure_cursor ;;
364
+ esac
365
+ done
366
+
367
+ log ""
368
+ log "Done. Restart the affected agent(s) so they reload their MCP config."
369
+ log "Skill symlinks and generated context files are not removed by this script."
370
+ log "To remove those, run: tools/install_skills.sh --uninstall"
371
+ }
372
+
373
+ main "$@"
package/src/cli.js CHANGED
@@ -1,13 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn } from "node:child_process";
3
- import { existsSync, mkdirSync } from "node:fs";
3
+ import { cpSync, existsSync, mkdirSync } from "node:fs";
4
4
  import { homedir } from "node:os";
5
5
  import { dirname, resolve } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
 
8
8
  const TOOL_NAME = "indicator";
9
9
  const DEFAULT_PLATFORM = "auto";
10
- const DEFAULT_REPO_URL = "https://github.com/cr2s367067/indicator.git";
11
10
 
12
11
  function printHelp() {
13
12
  console.log(`Indicator CLI
@@ -24,7 +23,7 @@ Commands:
24
23
  check-skills Verify generated agent skills/prompts
25
24
  session-check Verify TradingView browser session
26
25
  search QUERY Search TradingView symbols
27
- model-status Show embedded stock/ETF/index model status
26
+ model-status Show server-side stock/ETF/index model status
28
27
  model-forecast SYMBOL Read/refresh a model forecast row
29
28
  ohlcv SYMBOL Fetch TradingView OHLCV data
30
29
  rating SYMBOL Fetch raw TradingView technical rating
@@ -56,8 +55,7 @@ Examples:
56
55
  ${TOOL_NAME} cli data ohlcv TPEX:6584 --interval 1W --bars 40
57
56
 
58
57
  Environment:
59
- TV_INDICATOR_INSTALL_DIR Install/clone directory when no local repo is found
60
- TV_INDICATOR_REPO_URL Git repo URL used for first-time bootstrap
58
+ TV_INDICATOR_INSTALL_DIR Install directory for the npm-bundled project copy
61
59
  TV_INDICATOR_PROJECT_ROOT Existing repo path to use instead of auto-detecting
62
60
  `);
63
61
  }
@@ -100,14 +98,11 @@ async function findProjectRoot({ bootstrap = false } = {}) {
100
98
  }
101
99
 
102
100
  const installDir = defaultInstallDir();
103
- const starts = [process.cwd(), dirname(fileURLToPath(import.meta.url)), installDir];
101
+ const starts = [process.cwd(), installDir];
104
102
  for (const start of starts) {
105
103
  let current = resolve(start);
106
104
  while (current !== dirname(current)) {
107
105
  if (isProjectRoot(current)) {
108
- if (bootstrap && current === installDir) {
109
- await updateProjectRoot(current);
110
- }
111
106
  return current;
112
107
  }
113
108
  current = dirname(current);
@@ -118,6 +113,11 @@ async function findProjectRoot({ bootstrap = false } = {}) {
118
113
  return await bootstrapProjectRoot(installDir);
119
114
  }
120
115
 
116
+ const packaged = packagedProjectRoot();
117
+ if (isProjectRoot(packaged)) {
118
+ return packaged;
119
+ }
120
+
121
121
  throw new Error(
122
122
  `Could not find Indicator project root. Run '${TOOL_NAME} install' first or set TV_INDICATOR_PROJECT_ROOT.`,
123
123
  );
@@ -127,6 +127,10 @@ function defaultInstallDir() {
127
127
  return resolve(process.env.TV_INDICATOR_INSTALL_DIR || resolve(homedir(), ".indicator"));
128
128
  }
129
129
 
130
+ function packagedProjectRoot() {
131
+ return resolve(dirname(fileURLToPath(import.meta.url)), "..", "project");
132
+ }
133
+
130
134
  function isProjectRoot(path) {
131
135
  return (
132
136
  existsSync(resolve(path, "Makefile")) &&
@@ -142,27 +146,20 @@ async function bootstrapProjectRoot(targetDir) {
142
146
  `Install directory exists but is not an Indicator project root: ${targetDir}`,
143
147
  );
144
148
  }
145
- await updateProjectRoot(targetDir);
146
149
  return targetDir;
147
150
  }
148
151
 
149
- const repoUrl = process.env.TV_INDICATOR_REPO_URL || DEFAULT_REPO_URL;
150
- mkdirSync(dirname(targetDir), { recursive: true });
151
- console.log(`Indicator repo not found; cloning ${repoUrl} into ${targetDir}`);
152
- const cloneCode = await run({ command: ["git", "clone", repoUrl, targetDir] }, process.cwd());
153
- if (cloneCode !== 0) {
154
- throw new Error(`Failed to clone Indicator repo into ${targetDir}`);
152
+ const sourceDir = packagedProjectRoot();
153
+ if (!isProjectRoot(sourceDir)) {
154
+ throw new Error(
155
+ "Packaged Indicator project files are missing. Reinstall @kuandotdev/indicator or set TV_INDICATOR_PROJECT_ROOT.",
156
+ );
155
157
  }
156
- return targetDir;
157
- }
158
158
 
159
- async function updateProjectRoot(root) {
160
- if (!existsSync(resolve(root, ".git"))) return;
161
- console.log(`Updating Indicator repo in ${root}`);
162
- const code = await run({ command: ["git", "-C", root, "pull", "--ff-only"] }, process.cwd());
163
- if (code !== 0) {
164
- console.warn("Could not fast-forward update the Indicator repo; continuing with the existing checkout.");
165
- }
159
+ mkdirSync(dirname(targetDir), { recursive: true });
160
+ console.log(`Indicator project not found; copying npm-bundled project into ${targetDir}`);
161
+ cpSync(sourceDir, targetDir, { recursive: true });
162
+ return targetDir;
166
163
  }
167
164
 
168
165
  async function run(spec, cwd) {