@kuandotdev/indicator 0.1.2 → 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 +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