@kuandotdev/indicator 0.1.2 → 0.1.4
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 -37
- 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 +9 -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 +21 -24
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { cpSync, 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
|
+
|
|
11
|
+
function printHelp() {
|
|
12
|
+
console.log(`Indicator CLI
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
${TOOL_NAME} <command> [options]
|
|
16
|
+
|
|
17
|
+
Commands:
|
|
18
|
+
install Run Indicator installer
|
|
19
|
+
uninstall Remove agent integrations or the full local install
|
|
20
|
+
doctor Run make doctor
|
|
21
|
+
status Show Indicator status
|
|
22
|
+
version Show Indicator version
|
|
23
|
+
check-skills Verify generated agent skills/prompts
|
|
24
|
+
session-check Verify TradingView browser session
|
|
25
|
+
search QUERY Search TradingView symbols
|
|
26
|
+
model-status Show server-side stock/ETF/index model status
|
|
27
|
+
model-forecast SYMBOL Read/refresh a model forecast row
|
|
28
|
+
ohlcv SYMBOL Fetch TradingView OHLCV data
|
|
29
|
+
rating SYMBOL Fetch raw TradingView technical rating
|
|
30
|
+
agent-import Import MCP/skills for agent platforms
|
|
31
|
+
agent-uninstall Remove MCP registrations for agent platforms
|
|
32
|
+
cli ...args Run the underlying training-view CLI through conda
|
|
33
|
+
|
|
34
|
+
Install options:
|
|
35
|
+
--force Set FORCE_INSTALL=1
|
|
36
|
+
--skip-wizard Set TV_INDICATOR_SETUP=0
|
|
37
|
+
--platform <name> auto | all | pi | claude-code | codex | gemini | cursor
|
|
38
|
+
--language <code> en | zhtw | zhcn | ...
|
|
39
|
+
--prompt-env Enable optional .env prompts during install
|
|
40
|
+
--plain-menu Use plain install menu
|
|
41
|
+
|
|
42
|
+
Uninstall options:
|
|
43
|
+
--dry-run Show what would be removed
|
|
44
|
+
--platform <name> Agent platform(s) for agent uninstall
|
|
45
|
+
--full Run make uninstall-all
|
|
46
|
+
--keep-env Preserve .env with --full
|
|
47
|
+
|
|
48
|
+
Examples:
|
|
49
|
+
${TOOL_NAME} install --platform all --plain-menu
|
|
50
|
+
${TOOL_NAME} uninstall --dry-run --platform all
|
|
51
|
+
${TOOL_NAME} search 6584 --limit 5
|
|
52
|
+
${TOOL_NAME} model-forecast NASDAQ:TSLA --json
|
|
53
|
+
${TOOL_NAME} model-forecast BINANCE:BTCUSDT --refresh --json
|
|
54
|
+
${TOOL_NAME} ohlcv BINANCE:BTCUSDT --interval 1D --bars 3
|
|
55
|
+
${TOOL_NAME} cli data ohlcv TPEX:6584 --interval 1W --bars 40
|
|
56
|
+
|
|
57
|
+
Environment:
|
|
58
|
+
TV_INDICATOR_INSTALL_DIR Install directory for the npm-bundled project copy
|
|
59
|
+
TV_INDICATOR_PROJECT_ROOT Existing repo path to use instead of auto-detecting
|
|
60
|
+
`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function parseOption(args, name) {
|
|
64
|
+
const index = args.indexOf(name);
|
|
65
|
+
if (index === -1) return undefined;
|
|
66
|
+
const value = args[index + 1];
|
|
67
|
+
if (!value || value.startsWith("--")) {
|
|
68
|
+
throw new Error(`${name} requires a value`);
|
|
69
|
+
}
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function hasFlag(args, name) {
|
|
74
|
+
return args.includes(name);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function removeOptions(args, optionsWithValues, flags) {
|
|
78
|
+
const out = [];
|
|
79
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
80
|
+
const arg = args[i];
|
|
81
|
+
if (flags.includes(arg)) continue;
|
|
82
|
+
if (optionsWithValues.includes(arg)) {
|
|
83
|
+
i += 1;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
out.push(arg);
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function findProjectRoot({ bootstrap = false } = {}) {
|
|
92
|
+
const explicit = process.env.TV_INDICATOR_PROJECT_ROOT;
|
|
93
|
+
if (explicit) {
|
|
94
|
+
if (isProjectRoot(explicit)) {
|
|
95
|
+
return resolve(explicit);
|
|
96
|
+
}
|
|
97
|
+
throw new Error(`TV_INDICATOR_PROJECT_ROOT is not an Indicator project root: ${explicit}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const installDir = defaultInstallDir();
|
|
101
|
+
const starts = [process.cwd(), installDir];
|
|
102
|
+
for (const start of starts) {
|
|
103
|
+
let current = resolve(start);
|
|
104
|
+
while (current !== dirname(current)) {
|
|
105
|
+
if (isProjectRoot(current)) {
|
|
106
|
+
return current;
|
|
107
|
+
}
|
|
108
|
+
current = dirname(current);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (bootstrap) {
|
|
113
|
+
return await bootstrapProjectRoot(installDir);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const packaged = packagedProjectRoot();
|
|
117
|
+
if (isProjectRoot(packaged)) {
|
|
118
|
+
return packaged;
|
|
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 packagedProjectRoot() {
|
|
131
|
+
return resolve(dirname(fileURLToPath(import.meta.url)), "..", "project");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function isProjectRoot(path) {
|
|
135
|
+
return (
|
|
136
|
+
existsSync(resolve(path, "Makefile")) &&
|
|
137
|
+
existsSync(resolve(path, "pyproject.toml")) &&
|
|
138
|
+
existsSync(resolve(path, "tools", "interactive_install.sh"))
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function bootstrapProjectRoot(targetDir) {
|
|
143
|
+
if (existsSync(targetDir)) {
|
|
144
|
+
if (!isProjectRoot(targetDir)) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`Install directory exists but is not an Indicator project root: ${targetDir}`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
return targetDir;
|
|
150
|
+
}
|
|
151
|
+
|
|
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
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
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;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function run(spec, cwd) {
|
|
166
|
+
const env = { ...process.env, ...(spec.env ?? {}) };
|
|
167
|
+
console.log(`$ ${spec.command.join(" ")}`);
|
|
168
|
+
return await new Promise((resolveCode) => {
|
|
169
|
+
const proc = spawn(spec.command[0], spec.command.slice(1), {
|
|
170
|
+
cwd,
|
|
171
|
+
env,
|
|
172
|
+
stdio: "inherit",
|
|
173
|
+
});
|
|
174
|
+
proc.on("error", (error) => {
|
|
175
|
+
console.error(error.message);
|
|
176
|
+
resolveCode(1);
|
|
177
|
+
});
|
|
178
|
+
proc.on("close", (code) => {
|
|
179
|
+
resolveCode(code ?? 1);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function makeEnv(args) {
|
|
185
|
+
const env = {};
|
|
186
|
+
const platform = parseOption(args, "--platform");
|
|
187
|
+
const language = parseOption(args, "--language");
|
|
188
|
+
if (platform) env.PLATFORM = platform;
|
|
189
|
+
if (language) env.TV_AGENT_LANGUAGE = language;
|
|
190
|
+
if (hasFlag(args, "--force")) env.FORCE_INSTALL = "1";
|
|
191
|
+
if (hasFlag(args, "--skip-wizard")) env.TV_INDICATOR_SETUP = "0";
|
|
192
|
+
if (hasFlag(args, "--prompt-env")) env.TV_INSTALL_PROMPT_ENV = "1";
|
|
193
|
+
if (hasFlag(args, "--plain-menu")) env.TV_INSTALL_MENU = "plain";
|
|
194
|
+
return env;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function installCommand(args) {
|
|
198
|
+
return {
|
|
199
|
+
command: ["make", "install"],
|
|
200
|
+
env: makeEnv(args),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function uninstallCommand(args) {
|
|
205
|
+
const platform = parseOption(args, "--platform") ?? DEFAULT_PLATFORM;
|
|
206
|
+
if (hasFlag(args, "--full")) {
|
|
207
|
+
const env = {};
|
|
208
|
+
if (hasFlag(args, "--keep-env")) env.KEEP_ENV = "1";
|
|
209
|
+
return {
|
|
210
|
+
command: ["make", "uninstall-all"],
|
|
211
|
+
env,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
command: [
|
|
217
|
+
"./tools/uninstall_agent_tools.sh",
|
|
218
|
+
"--platform",
|
|
219
|
+
platform,
|
|
220
|
+
...(hasFlag(args, "--dry-run") ? ["--dry-run"] : []),
|
|
221
|
+
],
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function makeTarget(target, extraEnv = {}) {
|
|
226
|
+
return {
|
|
227
|
+
command: ["make", target],
|
|
228
|
+
env: extraEnv,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function cliPassthroughCommand(args) {
|
|
233
|
+
if (args.length === 0) throw new Error("cli requires arguments for training-view");
|
|
234
|
+
const condaEnv = parseOption(args, "--conda-env") ?? "tv-indicator";
|
|
235
|
+
const cleaned = removeOptions(args, ["--conda-env"], []);
|
|
236
|
+
return {
|
|
237
|
+
command: [
|
|
238
|
+
"conda",
|
|
239
|
+
"run",
|
|
240
|
+
"-n",
|
|
241
|
+
condaEnv,
|
|
242
|
+
"--live-stream",
|
|
243
|
+
"--no-capture-output",
|
|
244
|
+
"python",
|
|
245
|
+
"-m",
|
|
246
|
+
"tv_indicator.cli.main",
|
|
247
|
+
...cleaned,
|
|
248
|
+
],
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function agentImportCommand(args) {
|
|
253
|
+
return {
|
|
254
|
+
command: ["make", "agent-import"],
|
|
255
|
+
env: { PLATFORM: parseOption(args, "--platform") ?? DEFAULT_PLATFORM },
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function agentUninstallCommand(args) {
|
|
260
|
+
return {
|
|
261
|
+
command: ["make", "agent-uninstall"],
|
|
262
|
+
env: { PLATFORM: parseOption(args, "--platform") ?? DEFAULT_PLATFORM },
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function modelForecastCommand(args) {
|
|
267
|
+
const cleaned = removeOptions(args, ["--period"], ["--refresh", "--json"]);
|
|
268
|
+
const symbol = cleaned[0];
|
|
269
|
+
if (!symbol) throw new Error("model-forecast requires SYMBOL");
|
|
270
|
+
const env = { SYMBOL: symbol };
|
|
271
|
+
if (hasFlag(args, "--refresh")) env.REFRESH = "1";
|
|
272
|
+
if (hasFlag(args, "--json")) env.MODEL_JSON = "1";
|
|
273
|
+
const period = parseOption(args, "--period");
|
|
274
|
+
if (period) env.MODEL_PERIOD = period;
|
|
275
|
+
return makeTarget("model-forecast", env);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function searchCommand(args) {
|
|
279
|
+
const cleaned = removeOptions(args, ["--limit"], []);
|
|
280
|
+
const query = cleaned[0];
|
|
281
|
+
if (!query) throw new Error("search requires QUERY");
|
|
282
|
+
const env = { Q: query };
|
|
283
|
+
const limit = parseOption(args, "--limit");
|
|
284
|
+
if (limit) env.LIMIT = limit;
|
|
285
|
+
return makeTarget("search", env);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function ohlcvCommand(args) {
|
|
289
|
+
const cleaned = removeOptions(args, ["--interval", "--bars"], []);
|
|
290
|
+
const symbol = cleaned[0];
|
|
291
|
+
if (!symbol) throw new Error("ohlcv requires SYMBOL");
|
|
292
|
+
const env = { SYMBOL: symbol };
|
|
293
|
+
const interval = parseOption(args, "--interval");
|
|
294
|
+
const bars = parseOption(args, "--bars");
|
|
295
|
+
if (interval) env.INTERVAL = interval;
|
|
296
|
+
if (bars) env.BARS = bars;
|
|
297
|
+
return makeTarget("ohlcv", env);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function ratingCommand(args) {
|
|
301
|
+
const cleaned = removeOptions(args, ["--interval"], []);
|
|
302
|
+
const symbol = cleaned[0];
|
|
303
|
+
if (!symbol) throw new Error("rating requires SYMBOL");
|
|
304
|
+
const env = { SYMBOL: symbol };
|
|
305
|
+
const interval = parseOption(args, "--interval");
|
|
306
|
+
if (interval) env.INTERVAL = interval;
|
|
307
|
+
return makeTarget("rating", env);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function commandFor(args) {
|
|
311
|
+
const [command, ...rest] = args;
|
|
312
|
+
switch (command) {
|
|
313
|
+
case undefined:
|
|
314
|
+
case "-h":
|
|
315
|
+
case "--help":
|
|
316
|
+
case "help":
|
|
317
|
+
printHelp();
|
|
318
|
+
return null;
|
|
319
|
+
case "install":
|
|
320
|
+
return installCommand(rest);
|
|
321
|
+
case "uninstall":
|
|
322
|
+
return uninstallCommand(rest);
|
|
323
|
+
case "doctor":
|
|
324
|
+
return makeTarget("doctor");
|
|
325
|
+
case "status":
|
|
326
|
+
return makeTarget("status");
|
|
327
|
+
case "version":
|
|
328
|
+
return makeTarget("version");
|
|
329
|
+
case "check-skills":
|
|
330
|
+
return makeTarget("check-skills");
|
|
331
|
+
case "session-check":
|
|
332
|
+
return makeTarget("session-check");
|
|
333
|
+
case "search":
|
|
334
|
+
return searchCommand(rest);
|
|
335
|
+
case "model-status":
|
|
336
|
+
return makeTarget("model-status");
|
|
337
|
+
case "model-forecast":
|
|
338
|
+
return modelForecastCommand(rest);
|
|
339
|
+
case "ohlcv":
|
|
340
|
+
return ohlcvCommand(rest);
|
|
341
|
+
case "rating":
|
|
342
|
+
return ratingCommand(rest);
|
|
343
|
+
case "agent-import":
|
|
344
|
+
return agentImportCommand(rest);
|
|
345
|
+
case "agent-uninstall":
|
|
346
|
+
return agentUninstallCommand(rest);
|
|
347
|
+
case "cli":
|
|
348
|
+
return cliPassthroughCommand(rest);
|
|
349
|
+
default:
|
|
350
|
+
throw new Error(`Unknown command: ${command}`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async function main() {
|
|
355
|
+
try {
|
|
356
|
+
const args = process.argv.slice(2);
|
|
357
|
+
const spec = commandFor(args);
|
|
358
|
+
if (!spec) return;
|
|
359
|
+
const root = await findProjectRoot({ bootstrap: args[0] === "install" });
|
|
360
|
+
const code = await run(spec, root);
|
|
361
|
+
process.exit(code);
|
|
362
|
+
} catch (error) {
|
|
363
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
364
|
+
process.exit(1);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
await main();
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# tradingview-stock-data-skill
|
|
2
|
+
|
|
3
|
+
Generic Agent Skill for fetching current stock prices, OHLCV candles, price history, and CSV exports from TradingView-compatible tooling.
|
|
4
|
+
|
|
5
|
+
For agent-driven installation/setup, see [`docs/agent-install.md`](../../docs/agent-install.md).
|
|
6
|
+
|
|
7
|
+
The package also includes protected Pi startup integration. It does not ship a
|
|
8
|
+
bundled copy of the project prompt.
|
|
9
|
+
|
|
10
|
+
The skill is agent-agnostic and can be used by Claude Code, Pi, Codex-style agents, Gemini CLI, Cursor, and any agent that follows the Agent Skills `SKILL.md` convention or can read repository instruction files.
|
|
11
|
+
|
|
12
|
+
## Install / wire into agents
|
|
13
|
+
|
|
14
|
+
### Pi package install
|
|
15
|
+
|
|
16
|
+
From this repo:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pi install ./packages/tradingview-stock-data-skill
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Project-local Pi install:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pi install -l ./packages/tradingview-stock-data-skill
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
If this repository already wires `tradingview-stock-data` through `.pi/skills`, avoid duplicate skill warnings by loading only the package extension:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"packages": [
|
|
33
|
+
{
|
|
34
|
+
"source": "../packages/tradingview-stock-data-skill",
|
|
35
|
+
"skills": []
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
After publishing to npm:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pi install npm:tradingview-stock-data-skill
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Claude Code
|
|
48
|
+
|
|
49
|
+
Copy or symlink the skill directory into Claude skills:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
mkdir -p .claude/skills
|
|
53
|
+
ln -s ../../skills/tradingview-stock-data .claude/skills/tradingview-stock-data
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Or globally:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
mkdir -p ~/.claude/skills
|
|
60
|
+
ln -s /absolute/path/to/skills/tradingview-stock-data ~/.claude/skills/tradingview-stock-data
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Codex / Gemini / Cursor
|
|
64
|
+
|
|
65
|
+
Use the repository wiring script from this project:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
make update-skills
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
That creates/refreshes:
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
.claude/skills/tradingview-stock-data
|
|
75
|
+
.pi/agent/skills/tradingview-stock-data
|
|
76
|
+
.cursor/rules/tradingview-stock-data.mdc
|
|
77
|
+
AGENTS.md
|
|
78
|
+
GEMINI.md
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Invoke
|
|
82
|
+
|
|
83
|
+
Ask naturally:
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
Get current price for 6584.tw
|
|
87
|
+
Get price history for TPEX:6584
|
|
88
|
+
Export 2330.tw daily history to CSV
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Or explicitly in Pi:
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
/skill:tradingview-stock-data get 6584.tw daily history
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Automatic setup
|
|
98
|
+
|
|
99
|
+
The package performs best-effort agent setup during installation and Pi startup
|
|
100
|
+
when it is installed from this project. The exact startup wiring, prompt marker,
|
|
101
|
+
sentinel files, and protected prompt contents are implementation details and are
|
|
102
|
+
not part of the public package contract.
|
|
103
|
+
|
|
104
|
+
## Requirements
|
|
105
|
+
|
|
106
|
+
One of the following data access paths:
|
|
107
|
+
|
|
108
|
+
- A Indicator MCP server with an OHLCV tool
|
|
109
|
+
- A Makefile target like `make ohlcv SYMBOL=... INTERVAL=... BARS=...`
|
|
110
|
+
- A CLI like `training-view data ohlcv ...`
|
|
111
|
+
|
|
112
|
+
For Taiwan stocks, use TradingView symbols like `TWSE:2330` or `TPEX:6584`.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stock Prompt Injector Extension
|
|
3
|
+
*
|
|
4
|
+
* When this Pi package is loaded, append the active stock-analysis startup prompt
|
|
5
|
+
* to the system prompt only if it is not already present from .pi/APPEND_SYSTEM.md,
|
|
6
|
+
* AGENTS.md/CLAUDE.md/GEMINI.md, or another injector.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
12
|
+
import { dirname, join } from "node:path";
|
|
13
|
+
|
|
14
|
+
const INJECTION_MARKER = "TV_INDICATOR_STOCK_PROMPT_AUTO_INJECTED";
|
|
15
|
+
const AUTO_IMPORT_SENTINEL = ".pi/.tv-indicator-auto-imported";
|
|
16
|
+
const PROMPT_SIGNATURES = [
|
|
17
|
+
"# Market Analysis Agent",
|
|
18
|
+
"# Stock Analysis Agent",
|
|
19
|
+
"## Workflow (run in order, every time)",
|
|
20
|
+
"Default to the **Short Report**",
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
function hasStockPrompt(systemPrompt: string): boolean {
|
|
24
|
+
if (!systemPrompt) return false;
|
|
25
|
+
if (systemPrompt.includes(INJECTION_MARKER)) return true;
|
|
26
|
+
return PROMPT_SIGNATURES.every((signature) => systemPrompt.includes(signature));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readableNonEmpty(file: string): boolean {
|
|
30
|
+
try {
|
|
31
|
+
return existsSync(file) && statSync(file).isFile() && statSync(file).size > 0;
|
|
32
|
+
} catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function findProjectRootWithImportScript(cwd: string): { root: string; script: string } | null {
|
|
38
|
+
let current = cwd;
|
|
39
|
+
for (let i = 0; i < 8; i++) {
|
|
40
|
+
const script = join(current, "tools", "import_agent_tools.sh");
|
|
41
|
+
if (existsSync(script)) return { root: current, script };
|
|
42
|
+
|
|
43
|
+
const parent = dirname(current);
|
|
44
|
+
if (parent === current) break;
|
|
45
|
+
current = parent;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function findProjectPrompt(cwd: string): { path: string; source: string } | null {
|
|
51
|
+
let current = cwd;
|
|
52
|
+
for (let i = 0; i < 8; i++) {
|
|
53
|
+
const promptDir = join(current, "skills", "system-prompt-injection");
|
|
54
|
+
const custom = join(promptDir, "CUSTOM_SYSTEM_PROMPT.md");
|
|
55
|
+
const fallback = join(promptDir, "DEFAULT_SYSTEM_PROMPT.md");
|
|
56
|
+
if (readableNonEmpty(custom)) return { path: custom, source: "project-custom" };
|
|
57
|
+
if (readableNonEmpty(fallback)) return { path: fallback, source: "project-default" };
|
|
58
|
+
|
|
59
|
+
const parent = dirname(current);
|
|
60
|
+
if (parent === current) break;
|
|
61
|
+
current = parent;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function getActivePrompt(cwd: string): { content: string; path: string; source: string } | null {
|
|
67
|
+
const found = findProjectPrompt(cwd);
|
|
68
|
+
if (!found) return null;
|
|
69
|
+
const content = readFileSync(found.path, "utf8").trim();
|
|
70
|
+
if (!content) return null;
|
|
71
|
+
return { ...found, content };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function autoImportProjectTools(cwd: string): void {
|
|
75
|
+
if (process.env.TV_INDICATOR_DISABLE_AUTO_IMPORT === "1") return;
|
|
76
|
+
|
|
77
|
+
const project = findProjectRootWithImportScript(cwd);
|
|
78
|
+
if (!project) return;
|
|
79
|
+
|
|
80
|
+
const sentinel = join(project.root, AUTO_IMPORT_SENTINEL);
|
|
81
|
+
if (existsSync(sentinel)) return;
|
|
82
|
+
|
|
83
|
+
spawnSync("bash", [project.script, "--platform", "pi"], {
|
|
84
|
+
cwd: project.root,
|
|
85
|
+
env: {
|
|
86
|
+
...process.env,
|
|
87
|
+
TV_INDICATOR_AUTO_IMPORT: "1",
|
|
88
|
+
},
|
|
89
|
+
stdio: "ignore",
|
|
90
|
+
timeout: 60_000,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export default function stockPromptInjector(pi: ExtensionAPI) {
|
|
95
|
+
pi.on("session_start", async (event) => {
|
|
96
|
+
if (event.reason !== "startup" && event.reason !== "reload") return;
|
|
97
|
+
autoImportProjectTools(process.cwd());
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
pi.on("before_agent_start", async (event) => {
|
|
101
|
+
const currentSystemPrompt = event.systemPrompt ?? "";
|
|
102
|
+
if (hasStockPrompt(currentSystemPrompt)) {
|
|
103
|
+
return {};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const cwd = String(event.systemPromptOptions?.cwd ?? process.cwd());
|
|
107
|
+
const activePrompt = getActivePrompt(cwd);
|
|
108
|
+
if (!activePrompt) {
|
|
109
|
+
return {};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
systemPrompt: `${currentSystemPrompt}
|
|
114
|
+
|
|
115
|
+
<!-- ${INJECTION_MARKER} source=${activePrompt.source} -->
|
|
116
|
+
|
|
117
|
+
${activePrompt.content}
|
|
118
|
+
`,
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tradingview-stock-data-skill",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Generic Agent Skill for fetching TradingView stock price and OHLCV history via CLI, Makefile, or MCP tools.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agent-skill",
|
|
7
|
+
"pi-package",
|
|
8
|
+
"claude-code",
|
|
9
|
+
"codex",
|
|
10
|
+
"gemini",
|
|
11
|
+
"tradingview",
|
|
12
|
+
"stock-price",
|
|
13
|
+
"ohlcv",
|
|
14
|
+
"taiwan-stocks"
|
|
15
|
+
],
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"type": "module",
|
|
18
|
+
"scripts": {
|
|
19
|
+
"postinstall": "bash ./scripts/postinstall.sh"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"extensions",
|
|
23
|
+
"skills",
|
|
24
|
+
"scripts",
|
|
25
|
+
"README.md",
|
|
26
|
+
"package.json"
|
|
27
|
+
],
|
|
28
|
+
"pi": {
|
|
29
|
+
"skills": ["./skills"],
|
|
30
|
+
"extensions": ["./extensions"]
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Best-effort package install hook.
|
|
3
|
+
# If this package is installed from inside a tv-indicator project, automatically
|
|
4
|
+
# run the project import script so the correct agent context, package filters,
|
|
5
|
+
# and MCP tool config are prepared.
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
if [[ "${TV_INDICATOR_SKIP_POSTINSTALL:-0}" == "1" ]]; then
|
|
10
|
+
echo "tv-indicator: postinstall auto-import skipped by TV_INDICATOR_SKIP_POSTINSTALL=1"
|
|
11
|
+
exit 0
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
PACKAGE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
15
|
+
# Package lifecycle hooks are usually run by Pi/package managers, so default to
|
|
16
|
+
# Pi-only to avoid unexpectedly modifying Claude Desktop/global configs. Set
|
|
17
|
+
# TV_INDICATOR_AGENT_PLATFORM=all (or another platform) to override.
|
|
18
|
+
PLATFORM="${TV_INDICATOR_AGENT_PLATFORM:-pi}"
|
|
19
|
+
|
|
20
|
+
find_import_script_from() {
|
|
21
|
+
local start="$1"
|
|
22
|
+
[[ -n "$start" ]] || return 1
|
|
23
|
+
[[ -d "$start" ]] || start="$(dirname "$start")"
|
|
24
|
+
local current
|
|
25
|
+
current="$(cd "$start" 2>/dev/null && pwd || true)"
|
|
26
|
+
while [[ -n "$current" && "$current" != "/" ]]; do
|
|
27
|
+
if [[ -x "$current/tools/import_agent_tools.sh" ]]; then
|
|
28
|
+
printf '%s\n' "$current/tools/import_agent_tools.sh"
|
|
29
|
+
return 0
|
|
30
|
+
fi
|
|
31
|
+
current="$(dirname "$current")"
|
|
32
|
+
done
|
|
33
|
+
return 1
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
IMPORT_SCRIPT=""
|
|
37
|
+
for candidate in \
|
|
38
|
+
"${TV_INDICATOR_PROJECT_ROOT:-}" \
|
|
39
|
+
"${INIT_CWD:-}" \
|
|
40
|
+
"$PWD" \
|
|
41
|
+
"$PACKAGE_DIR"
|
|
42
|
+
do
|
|
43
|
+
if IMPORT_SCRIPT="$(find_import_script_from "$candidate" 2>/dev/null)"; then
|
|
44
|
+
break
|
|
45
|
+
fi
|
|
46
|
+
IMPORT_SCRIPT=""
|
|
47
|
+
done
|
|
48
|
+
|
|
49
|
+
if [[ -z "$IMPORT_SCRIPT" ]]; then
|
|
50
|
+
echo "tv-indicator: no project tools/import_agent_tools.sh found; skipping install-time import."
|
|
51
|
+
echo "tv-indicator: runtime Pi extension will still inject the stock prompt if loaded."
|
|
52
|
+
exit 0
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
PROJECT_ROOT="$(cd "$(dirname "$IMPORT_SCRIPT")/.." && pwd)"
|
|
56
|
+
SENTINEL="$PROJECT_ROOT/.pi/.tv-indicator-auto-imported"
|
|
57
|
+
|
|
58
|
+
if [[ "$PLATFORM" == "pi" && -f "$SENTINEL" && "${TV_INDICATOR_FORCE_IMPORT:-0}" != "1" ]]; then
|
|
59
|
+
echo "tv-indicator: Pi agent tools already imported; skipping."
|
|
60
|
+
echo "tv-indicator: set TV_INDICATOR_FORCE_IMPORT=1 to force re-import."
|
|
61
|
+
exit 0
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
echo "tv-indicator: running agent tool import for platform '$PLATFORM' in $PROJECT_ROOT"
|
|
65
|
+
|
|
66
|
+
if bash "$IMPORT_SCRIPT" --platform "$PLATFORM"; then
|
|
67
|
+
echo "tv-indicator: agent tool import complete."
|
|
68
|
+
else
|
|
69
|
+
echo "tv-indicator: agent tool import failed; continuing package install." >&2
|
|
70
|
+
echo "tv-indicator: run manually later: $IMPORT_SCRIPT --platform $PLATFORM" >&2
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
exit 0
|