@kuandotdev/indicator 0.1.0
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 +44 -0
- package/package.json +37 -0
- package/src/cli.js +371 -0
package/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# @kuandotdev/indicator
|
|
2
|
+
|
|
3
|
+
Node wrapper CLI for Indicator.
|
|
4
|
+
|
|
5
|
+
This package is a production-shaped bootstrapper: it does not replace the Python
|
|
6
|
+
runtime. It wraps the existing repo commands for install, uninstall, diagnostics,
|
|
7
|
+
MCP/agent import, market data, and model forecast checks.
|
|
8
|
+
|
|
9
|
+
When no local Indicator repo is found, `install` clones
|
|
10
|
+
`https://github.com/cr2s367067/indicator.git` into `~/.indicator` before running
|
|
11
|
+
the installer. Override with `TV_INDICATOR_INSTALL_DIR` or
|
|
12
|
+
`TV_INDICATOR_REPO_URL`.
|
|
13
|
+
|
|
14
|
+
## Local Development
|
|
15
|
+
|
|
16
|
+
Use the repo Makefile for local development installs and reinstalls:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
make install
|
|
20
|
+
make uninstall
|
|
21
|
+
make install FORCE_INSTALL=1
|
|
22
|
+
make agent-import PLATFORM=auto
|
|
23
|
+
make check-skills
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Use the wrapper directly only when testing the package itself:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
node src/cli.js --help
|
|
30
|
+
node src/cli.js doctor
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Production Install
|
|
34
|
+
|
|
35
|
+
Publish the package and expose:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx @kuandotdev/indicator install
|
|
39
|
+
npx @kuandotdev/indicator install --force
|
|
40
|
+
npx @kuandotdev/indicator uninstall --platform auto
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
For IP protection, keep proprietary prompts/model orchestration server-side and
|
|
44
|
+
ship this package as a thin local installer/client.
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kuandotdev/indicator",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Indicator CLI wrapper for install, uninstall, diagnostics, market data, and model forecast commands.",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"private": false,
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"indicator": "src/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"start": "node src/cli.js",
|
|
13
|
+
"check": "node --check src/cli.js",
|
|
14
|
+
"test-install-wrapper": "node src/cli.js install --skip-wizard",
|
|
15
|
+
"test-uninstall-wrapper": "node src/cli.js uninstall --dry-run",
|
|
16
|
+
"doctor": "node src/cli.js doctor"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"tradingview",
|
|
20
|
+
"indicator",
|
|
21
|
+
"mcp",
|
|
22
|
+
"agent",
|
|
23
|
+
"cli",
|
|
24
|
+
"installer"
|
|
25
|
+
],
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"src",
|
|
34
|
+
"README.md",
|
|
35
|
+
"package.json"
|
|
36
|
+
]
|
|
37
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { dirname, resolve } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
const TOOL_NAME = "indicator";
|
|
9
|
+
const DEFAULT_PLATFORM = "auto";
|
|
10
|
+
const DEFAULT_REPO_URL = "https://github.com/cr2s367067/indicator.git";
|
|
11
|
+
|
|
12
|
+
function printHelp() {
|
|
13
|
+
console.log(`Indicator CLI
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
${TOOL_NAME} <command> [options]
|
|
17
|
+
|
|
18
|
+
Commands:
|
|
19
|
+
install Run Indicator installer
|
|
20
|
+
uninstall Remove agent integrations or the full local install
|
|
21
|
+
doctor Run make doctor
|
|
22
|
+
status Show Indicator status
|
|
23
|
+
version Show Indicator version
|
|
24
|
+
check-skills Verify generated agent skills/prompts
|
|
25
|
+
session-check Verify TradingView browser session
|
|
26
|
+
search QUERY Search TradingView symbols
|
|
27
|
+
model-status Show embedded stock/ETF/index model status
|
|
28
|
+
model-forecast SYMBOL Read/refresh a model forecast row
|
|
29
|
+
ohlcv SYMBOL Fetch TradingView OHLCV data
|
|
30
|
+
rating SYMBOL Fetch raw TradingView technical rating
|
|
31
|
+
agent-import Import MCP/skills for agent platforms
|
|
32
|
+
agent-uninstall Remove MCP registrations for agent platforms
|
|
33
|
+
cli ...args Run the underlying training-view CLI through conda
|
|
34
|
+
|
|
35
|
+
Install options:
|
|
36
|
+
--force Set FORCE_INSTALL=1
|
|
37
|
+
--skip-wizard Set TV_INDICATOR_SETUP=0
|
|
38
|
+
--platform <name> auto | all | pi | claude-code | codex | gemini | cursor
|
|
39
|
+
--language <code> en | zhtw | zhcn | ...
|
|
40
|
+
--prompt-env Enable optional .env prompts during install
|
|
41
|
+
--plain-menu Use plain install menu
|
|
42
|
+
|
|
43
|
+
Uninstall options:
|
|
44
|
+
--dry-run Show what would be removed
|
|
45
|
+
--platform <name> Agent platform(s) for agent uninstall
|
|
46
|
+
--full Run make uninstall-all
|
|
47
|
+
--keep-env Preserve .env with --full
|
|
48
|
+
|
|
49
|
+
Examples:
|
|
50
|
+
${TOOL_NAME} install --platform all --plain-menu
|
|
51
|
+
${TOOL_NAME} uninstall --dry-run --platform all
|
|
52
|
+
${TOOL_NAME} search 6584 --limit 5
|
|
53
|
+
${TOOL_NAME} model-forecast NASDAQ:TSLA --json
|
|
54
|
+
${TOOL_NAME} model-forecast BINANCE:BTCUSDT --refresh --json
|
|
55
|
+
${TOOL_NAME} ohlcv BINANCE:BTCUSDT --interval 1D --bars 3
|
|
56
|
+
${TOOL_NAME} cli data ohlcv TPEX:6584 --interval 1W --bars 40
|
|
57
|
+
|
|
58
|
+
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
|
|
61
|
+
TV_INDICATOR_PROJECT_ROOT Existing repo path to use instead of auto-detecting
|
|
62
|
+
`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function parseOption(args, name) {
|
|
66
|
+
const index = args.indexOf(name);
|
|
67
|
+
if (index === -1) return undefined;
|
|
68
|
+
const value = args[index + 1];
|
|
69
|
+
if (!value || value.startsWith("--")) {
|
|
70
|
+
throw new Error(`${name} requires a value`);
|
|
71
|
+
}
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function hasFlag(args, name) {
|
|
76
|
+
return args.includes(name);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function removeOptions(args, optionsWithValues, flags) {
|
|
80
|
+
const out = [];
|
|
81
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
82
|
+
const arg = args[i];
|
|
83
|
+
if (flags.includes(arg)) continue;
|
|
84
|
+
if (optionsWithValues.includes(arg)) {
|
|
85
|
+
i += 1;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
out.push(arg);
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function findProjectRoot({ bootstrap = false } = {}) {
|
|
94
|
+
const explicit = process.env.TV_INDICATOR_PROJECT_ROOT;
|
|
95
|
+
if (explicit) {
|
|
96
|
+
if (isProjectRoot(explicit)) {
|
|
97
|
+
return resolve(explicit);
|
|
98
|
+
}
|
|
99
|
+
throw new Error(`TV_INDICATOR_PROJECT_ROOT is not an Indicator project root: ${explicit}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const installDir = defaultInstallDir();
|
|
103
|
+
const starts = [process.cwd(), dirname(fileURLToPath(import.meta.url)), installDir];
|
|
104
|
+
for (const start of starts) {
|
|
105
|
+
let current = resolve(start);
|
|
106
|
+
while (current !== dirname(current)) {
|
|
107
|
+
if (isProjectRoot(current)) {
|
|
108
|
+
if (bootstrap && current === installDir) {
|
|
109
|
+
await updateProjectRoot(current);
|
|
110
|
+
}
|
|
111
|
+
return current;
|
|
112
|
+
}
|
|
113
|
+
current = dirname(current);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (bootstrap) {
|
|
118
|
+
return await bootstrapProjectRoot(installDir);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
throw new Error(
|
|
122
|
+
`Could not find Indicator project root. Run '${TOOL_NAME} install' first or set TV_INDICATOR_PROJECT_ROOT.`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function defaultInstallDir() {
|
|
127
|
+
return resolve(process.env.TV_INDICATOR_INSTALL_DIR || resolve(homedir(), ".indicator"));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function isProjectRoot(path) {
|
|
131
|
+
return (
|
|
132
|
+
existsSync(resolve(path, "Makefile")) &&
|
|
133
|
+
existsSync(resolve(path, "pyproject.toml")) &&
|
|
134
|
+
existsSync(resolve(path, "tools", "interactive_install.sh"))
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function bootstrapProjectRoot(targetDir) {
|
|
139
|
+
if (existsSync(targetDir)) {
|
|
140
|
+
if (!isProjectRoot(targetDir)) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
`Install directory exists but is not an Indicator project root: ${targetDir}`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
await updateProjectRoot(targetDir);
|
|
146
|
+
return targetDir;
|
|
147
|
+
}
|
|
148
|
+
|
|
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}`);
|
|
155
|
+
}
|
|
156
|
+
return targetDir;
|
|
157
|
+
}
|
|
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
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function run(spec, cwd) {
|
|
169
|
+
const env = { ...process.env, ...(spec.env ?? {}) };
|
|
170
|
+
console.log(`$ ${spec.command.join(" ")}`);
|
|
171
|
+
return await new Promise((resolveCode) => {
|
|
172
|
+
const proc = spawn(spec.command[0], spec.command.slice(1), {
|
|
173
|
+
cwd,
|
|
174
|
+
env,
|
|
175
|
+
stdio: "inherit",
|
|
176
|
+
});
|
|
177
|
+
proc.on("error", (error) => {
|
|
178
|
+
console.error(error.message);
|
|
179
|
+
resolveCode(1);
|
|
180
|
+
});
|
|
181
|
+
proc.on("close", (code) => {
|
|
182
|
+
resolveCode(code ?? 1);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function makeEnv(args) {
|
|
188
|
+
const env = {};
|
|
189
|
+
const platform = parseOption(args, "--platform");
|
|
190
|
+
const language = parseOption(args, "--language");
|
|
191
|
+
if (platform) env.PLATFORM = platform;
|
|
192
|
+
if (language) env.TV_AGENT_LANGUAGE = language;
|
|
193
|
+
if (hasFlag(args, "--force")) env.FORCE_INSTALL = "1";
|
|
194
|
+
if (hasFlag(args, "--skip-wizard")) env.TV_INDICATOR_SETUP = "0";
|
|
195
|
+
if (hasFlag(args, "--prompt-env")) env.TV_INSTALL_PROMPT_ENV = "1";
|
|
196
|
+
if (hasFlag(args, "--plain-menu")) env.TV_INSTALL_MENU = "plain";
|
|
197
|
+
return env;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function installCommand(args) {
|
|
201
|
+
return {
|
|
202
|
+
command: ["make", "install"],
|
|
203
|
+
env: makeEnv(args),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function uninstallCommand(args) {
|
|
208
|
+
const platform = parseOption(args, "--platform") ?? DEFAULT_PLATFORM;
|
|
209
|
+
if (hasFlag(args, "--full")) {
|
|
210
|
+
const env = {};
|
|
211
|
+
if (hasFlag(args, "--keep-env")) env.KEEP_ENV = "1";
|
|
212
|
+
return {
|
|
213
|
+
command: ["make", "uninstall-all"],
|
|
214
|
+
env,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
command: [
|
|
220
|
+
"./tools/uninstall_agent_tools.sh",
|
|
221
|
+
"--platform",
|
|
222
|
+
platform,
|
|
223
|
+
...(hasFlag(args, "--dry-run") ? ["--dry-run"] : []),
|
|
224
|
+
],
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function makeTarget(target, extraEnv = {}) {
|
|
229
|
+
return {
|
|
230
|
+
command: ["make", target],
|
|
231
|
+
env: extraEnv,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function cliPassthroughCommand(args) {
|
|
236
|
+
if (args.length === 0) throw new Error("cli requires arguments for training-view");
|
|
237
|
+
const condaEnv = parseOption(args, "--conda-env") ?? "tv-indicator";
|
|
238
|
+
const cleaned = removeOptions(args, ["--conda-env"], []);
|
|
239
|
+
return {
|
|
240
|
+
command: [
|
|
241
|
+
"conda",
|
|
242
|
+
"run",
|
|
243
|
+
"-n",
|
|
244
|
+
condaEnv,
|
|
245
|
+
"--live-stream",
|
|
246
|
+
"--no-capture-output",
|
|
247
|
+
"python",
|
|
248
|
+
"-m",
|
|
249
|
+
"tv_indicator.cli.main",
|
|
250
|
+
...cleaned,
|
|
251
|
+
],
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function agentImportCommand(args) {
|
|
256
|
+
return {
|
|
257
|
+
command: ["make", "agent-import"],
|
|
258
|
+
env: { PLATFORM: parseOption(args, "--platform") ?? DEFAULT_PLATFORM },
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function agentUninstallCommand(args) {
|
|
263
|
+
return {
|
|
264
|
+
command: ["make", "agent-uninstall"],
|
|
265
|
+
env: { PLATFORM: parseOption(args, "--platform") ?? DEFAULT_PLATFORM },
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function modelForecastCommand(args) {
|
|
270
|
+
const cleaned = removeOptions(args, ["--period"], ["--refresh", "--json"]);
|
|
271
|
+
const symbol = cleaned[0];
|
|
272
|
+
if (!symbol) throw new Error("model-forecast requires SYMBOL");
|
|
273
|
+
const env = { SYMBOL: symbol };
|
|
274
|
+
if (hasFlag(args, "--refresh")) env.REFRESH = "1";
|
|
275
|
+
if (hasFlag(args, "--json")) env.MODEL_JSON = "1";
|
|
276
|
+
const period = parseOption(args, "--period");
|
|
277
|
+
if (period) env.MODEL_PERIOD = period;
|
|
278
|
+
return makeTarget("model-forecast", env);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function searchCommand(args) {
|
|
282
|
+
const cleaned = removeOptions(args, ["--limit"], []);
|
|
283
|
+
const query = cleaned[0];
|
|
284
|
+
if (!query) throw new Error("search requires QUERY");
|
|
285
|
+
const env = { Q: query };
|
|
286
|
+
const limit = parseOption(args, "--limit");
|
|
287
|
+
if (limit) env.LIMIT = limit;
|
|
288
|
+
return makeTarget("search", env);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function ohlcvCommand(args) {
|
|
292
|
+
const cleaned = removeOptions(args, ["--interval", "--bars"], []);
|
|
293
|
+
const symbol = cleaned[0];
|
|
294
|
+
if (!symbol) throw new Error("ohlcv requires SYMBOL");
|
|
295
|
+
const env = { SYMBOL: symbol };
|
|
296
|
+
const interval = parseOption(args, "--interval");
|
|
297
|
+
const bars = parseOption(args, "--bars");
|
|
298
|
+
if (interval) env.INTERVAL = interval;
|
|
299
|
+
if (bars) env.BARS = bars;
|
|
300
|
+
return makeTarget("ohlcv", env);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function ratingCommand(args) {
|
|
304
|
+
const cleaned = removeOptions(args, ["--interval"], []);
|
|
305
|
+
const symbol = cleaned[0];
|
|
306
|
+
if (!symbol) throw new Error("rating requires SYMBOL");
|
|
307
|
+
const env = { SYMBOL: symbol };
|
|
308
|
+
const interval = parseOption(args, "--interval");
|
|
309
|
+
if (interval) env.INTERVAL = interval;
|
|
310
|
+
return makeTarget("rating", env);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function commandFor(args) {
|
|
314
|
+
const [command, ...rest] = args;
|
|
315
|
+
switch (command) {
|
|
316
|
+
case undefined:
|
|
317
|
+
case "-h":
|
|
318
|
+
case "--help":
|
|
319
|
+
case "help":
|
|
320
|
+
printHelp();
|
|
321
|
+
return null;
|
|
322
|
+
case "install":
|
|
323
|
+
return installCommand(rest);
|
|
324
|
+
case "uninstall":
|
|
325
|
+
return uninstallCommand(rest);
|
|
326
|
+
case "doctor":
|
|
327
|
+
return makeTarget("doctor");
|
|
328
|
+
case "status":
|
|
329
|
+
return makeTarget("status");
|
|
330
|
+
case "version":
|
|
331
|
+
return makeTarget("version");
|
|
332
|
+
case "check-skills":
|
|
333
|
+
return makeTarget("check-skills");
|
|
334
|
+
case "session-check":
|
|
335
|
+
return makeTarget("session-check");
|
|
336
|
+
case "search":
|
|
337
|
+
return searchCommand(rest);
|
|
338
|
+
case "model-status":
|
|
339
|
+
return makeTarget("model-status");
|
|
340
|
+
case "model-forecast":
|
|
341
|
+
return modelForecastCommand(rest);
|
|
342
|
+
case "ohlcv":
|
|
343
|
+
return ohlcvCommand(rest);
|
|
344
|
+
case "rating":
|
|
345
|
+
return ratingCommand(rest);
|
|
346
|
+
case "agent-import":
|
|
347
|
+
return agentImportCommand(rest);
|
|
348
|
+
case "agent-uninstall":
|
|
349
|
+
return agentUninstallCommand(rest);
|
|
350
|
+
case "cli":
|
|
351
|
+
return cliPassthroughCommand(rest);
|
|
352
|
+
default:
|
|
353
|
+
throw new Error(`Unknown command: ${command}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async function main() {
|
|
358
|
+
try {
|
|
359
|
+
const args = process.argv.slice(2);
|
|
360
|
+
const spec = commandFor(args);
|
|
361
|
+
if (!spec) return;
|
|
362
|
+
const root = await findProjectRoot({ bootstrap: args[0] === "install" });
|
|
363
|
+
const code = await run(spec, root);
|
|
364
|
+
process.exit(code);
|
|
365
|
+
} catch (error) {
|
|
366
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
367
|
+
process.exit(1);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
await main();
|