@drico2008/fincli 0.1.3 → 0.2.2

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 (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +909 -684
  3. package/fincli/__init__.py +3 -3
  4. package/fincli/app/agents/__init__.py +5 -0
  5. package/fincli/app/agents/registry.py +76 -0
  6. package/fincli/app/analysis/ai_prompts.py +23 -16
  7. package/fincli/app/analysis/analyzer.py +107 -100
  8. package/fincli/app/analysis/assistant_context.py +187 -160
  9. package/fincli/app/analysis/backtest.py +179 -0
  10. package/fincli/app/analysis/gameplay_plan.py +79 -0
  11. package/fincli/app/analysis/indicators.py +1 -1
  12. package/fincli/app/analysis/market_structure.py +1 -1
  13. package/fincli/app/analysis/multi_timeframe.py +180 -0
  14. package/fincli/app/analysis/trading_methods.py +144 -0
  15. package/fincli/app/cli/commands.py +105 -77
  16. package/fincli/app/cli/router.py +2143 -1121
  17. package/fincli/app/connectors/__init__.py +5 -0
  18. package/fincli/app/connectors/catalog.py +148 -0
  19. package/fincli/app/connectors/news_connectors.py +412 -0
  20. package/fincli/app/modules/alerts.py +80 -0
  21. package/fincli/app/modules/economic_calendar.py +374 -1
  22. package/fincli/app/modules/reports.py +151 -0
  23. package/fincli/app/modules/scanner.py +111 -93
  24. package/fincli/app/modules/session_history.py +113 -0
  25. package/fincli/app/modules/transactions.py +84 -84
  26. package/fincli/app/modules/user_profile.py +84 -0
  27. package/fincli/app/plugins/loader.py +72 -0
  28. package/fincli/app/providers/ai/anthropic_provider.py +8 -7
  29. package/fincli/app/providers/ai/gemini_provider.py +8 -7
  30. package/fincli/app/providers/ai/groq_provider.py +8 -7
  31. package/fincli/app/providers/ai/huggingface_provider.py +8 -7
  32. package/fincli/app/providers/ai/manager.py +60 -60
  33. package/fincli/app/providers/ai/openai_provider.py +8 -7
  34. package/fincli/app/providers/ai/openrouter_provider.py +8 -7
  35. package/fincli/app/providers/ai/together_provider.py +8 -7
  36. package/fincli/app/providers/market/alphavantage_provider.py +194 -0
  37. package/fincli/app/providers/market/base.py +98 -77
  38. package/fincli/app/providers/market/custom_provider.py +186 -169
  39. package/fincli/app/providers/market/manager.py +85 -2
  40. package/fincli/app/providers/market/news_provider.py +4 -4
  41. package/fincli/app/providers/market/symbols.py +143 -0
  42. package/fincli/app/providers/market/twelvedata_provider.py +167 -167
  43. package/fincli/app/providers/market/yfinance_provider.py +1 -1
  44. package/fincli/app/research/__init__.py +7 -0
  45. package/fincli/app/research/engine.py +75 -0
  46. package/fincli/app/research/formatter.py +22 -0
  47. package/fincli/app/research/models.py +18 -0
  48. package/fincli/app/research/prompt_builder.py +47 -0
  49. package/fincli/app/services/macro_data.py +50 -0
  50. package/fincli/app/services/market_data.py +203 -203
  51. package/fincli/app/services/news_aggregator.py +90 -0
  52. package/fincli/app/services/web_research.py +267 -0
  53. package/fincli/app/storage/cache.py +2 -2
  54. package/fincli/app/storage/config.py +122 -88
  55. package/fincli/app/storage/database.py +201 -85
  56. package/fincli/app/storage/secrets.py +12 -3
  57. package/fincli/app/tui/components.py +68 -50
  58. package/fincli/app/tui/layout.py +270 -258
  59. package/fincli/app/tui/market_provider_selector.py +6 -1
  60. package/fincli/app/tui/model_selector.py +11 -3
  61. package/fincli/app/tui/theme.py +134 -74
  62. package/fincli/app/utils/formatting.py +125 -12
  63. package/npm/bin/fincli.js +9 -2
  64. package/package.json +23 -23
  65. package/pyproject.toml +35 -35
@@ -1,157 +1,217 @@
1
- """Theme constants for the Textual UI."""
2
-
3
- APP_CSS = """
1
+ """Theme constants for the Textual UI."""
2
+
3
+ APP_CSS = """
4
4
  Screen {
5
- background: #050505;
6
- color: #e5e7eb;
5
+ background: #00110b;
6
+ color: #d9f99d;
7
7
  }
8
8
 
9
9
  Header {
10
- background: #0b0f14;
11
- color: #22d3ee;
10
+ background: #000805;
11
+ color: #22c55e;
12
12
  text-style: bold;
13
13
  }
14
-
15
- #workspace {
14
+
15
+ #workspace {
16
+ height: 1fr;
17
+ width: 100%;
18
+ }
19
+
20
+ #main {
21
+ width: 1fr;
16
22
  height: 1fr;
17
- width: 100%;
23
+ padding: 1 4 0 4;
24
+ background: #00110b;
18
25
  }
19
26
 
20
- #main {
21
- width: 1fr;
27
+ #top_strip {
28
+ height: 3;
29
+ content-align: center middle;
30
+ background: #031b10;
31
+ color: #86efac;
32
+ border: heavy #15803d;
33
+ text-style: bold;
34
+ margin: 0 0 1 0;
35
+ }
36
+
37
+ #market_ribbon {
38
+ height: 1;
39
+ content-align: center middle;
40
+ background: #020d08;
41
+ color: #22c55e;
42
+ text-style: bold;
43
+ margin: 0 0 1 0;
44
+ }
45
+
46
+ #output_header {
47
+ height: 1;
48
+ background: #052e16;
49
+ color: #bbf7d0;
50
+ text-style: bold;
51
+ padding: 0 1;
52
+ }
53
+
54
+ #output_frame {
22
55
  height: 1fr;
23
- padding: 1 6;
24
- background: #050505;
56
+ background: #000805;
57
+ border: heavy #166534;
58
+ padding: 1 2;
25
59
  }
26
60
 
27
61
  #output {
28
- background: #050505;
62
+ background: #000805;
29
63
  color: #e5e7eb;
30
64
  border: none;
65
+ scrollbar-size: 1 1;
66
+ scrollbar-background: #000805;
67
+ scrollbar-color: #22c55e;
31
68
  }
32
69
 
33
70
  #command_area {
34
71
  dock: bottom;
35
72
  height: auto;
36
- background: #050505;
37
- padding: 0 6 1 6;
73
+ background: #00110b;
74
+ padding: 0 4 1 4;
75
+ }
76
+
77
+ #command_hint {
78
+ height: 1;
79
+ background: #020d08;
80
+ color: #84cc16;
81
+ padding: 0 1;
38
82
  }
39
83
 
40
84
  #command_line {
41
85
  height: 3;
42
86
  margin: 0 0 1 0;
43
- border: none;
44
- background: #262a27;
87
+ border: heavy #15803d;
88
+ background: #031b10;
45
89
  color: #f8fafc;
46
90
  }
47
91
 
48
92
  #command_prompt {
49
- width: 3;
50
- height: 3;
51
- background: #262a27;
52
- color: #22d3ee;
93
+ width: 4;
94
+ height: 1;
95
+ background: #031b10;
96
+ color: #22c55e;
53
97
  text-style: bold;
54
- padding: 0 0 0 2;
98
+ padding: 0 0 0 1;
55
99
  }
56
100
 
57
101
  #command_input {
58
102
  width: 1fr;
59
- height: 3;
103
+ height: 1;
60
104
  border: none;
61
- background: #262a27;
105
+ background: #031b10;
62
106
  color: #f8fafc;
63
- padding: 0 2 0 0;
64
- }
65
-
66
- #command_input:focus {
67
- border: none;
107
+ padding: 0 1 0 0;
68
108
  }
69
-
109
+
110
+ #command_input:focus {
111
+ border: none;
112
+ }
113
+
70
114
  #command_palette_scroll {
71
115
  height: 9;
72
116
  margin: 0 0 0 0;
73
- background: #050505;
117
+ background: #00110b;
74
118
  color: #f8fafc;
75
119
  scrollbar-size: 1 1;
76
- scrollbar-background: #050505;
77
- scrollbar-color: #22d3ee;
120
+ scrollbar-background: #00110b;
121
+ scrollbar-color: #22c55e;
78
122
  }
79
-
80
- #command_palette {
123
+
124
+ #command_palette {
81
125
  height: auto;
82
126
  margin: 0 0 0 0;
83
- background: #050505;
127
+ background: #00110b;
84
128
  color: #f8fafc;
85
129
  }
86
130
 
87
131
  #status_bar {
88
132
  dock: bottom;
89
133
  height: 1;
90
- background: #0b0f14;
91
- color: #64748b;
92
- padding: 0 6;
134
+ background: #000805;
135
+ color: #86efac;
136
+ padding: 0 4;
93
137
  }
94
138
 
95
139
  .section-title {
96
- color: #7dd3fc;
140
+ color: #86efac;
97
141
  text-style: bold;
98
142
  }
99
143
 
100
144
  .muted {
101
- color: #94a3b8;
145
+ color: #64748b;
102
146
  }
103
147
 
104
- #ai_selector_card {
105
- width: 78;
106
- height: 30;
107
- background: #252525;
108
- color: #f8fafc;
109
- padding: 1;
148
+ .semantic-positive {
149
+ color: #22c55e;
150
+ text-style: bold;
110
151
  }
111
152
 
112
- #ai_selector_title {
113
- height: 2;
114
- color: #f8fafc;
153
+ .semantic-negative {
154
+ color: #ef4444;
115
155
  text-style: bold;
116
156
  }
117
157
 
118
- #ai_selector_provider {
119
- height: 2;
120
- color: #f8fafc;
121
- padding: 0 2;
158
+ .semantic-caution {
159
+ color: #facc15;
160
+ text-style: bold;
122
161
  }
123
-
162
+
163
+ #ai_selector_card {
164
+ width: 78;
165
+ height: 30;
166
+ background: #031b10;
167
+ color: #f8fafc;
168
+ padding: 1;
169
+ border: heavy #15803d;
170
+ }
171
+
172
+ #ai_selector_title {
173
+ height: 2;
174
+ color: #f8fafc;
175
+ text-style: bold;
176
+ }
177
+
178
+ #ai_selector_provider {
179
+ height: 2;
180
+ color: #f8fafc;
181
+ padding: 0 2;
182
+ }
183
+
124
184
  #ai_selector_search {
125
185
  height: 3;
126
186
  margin: 0 1 1 1;
127
- border: solid #8a8a8a;
128
- background: #252525;
187
+ border: solid #22c55e;
188
+ background: #020d08;
129
189
  color: #f8fafc;
130
190
  padding: 0 1;
131
191
  }
132
-
192
+
133
193
  #ai_selector_search:focus {
134
- border: solid #a3a3a3;
194
+ border: solid #86efac;
135
195
  }
136
-
137
- #ai_selector_scroll {
196
+
197
+ #ai_selector_scroll {
138
198
  height: 1fr;
139
199
  margin: 0 1;
140
- background: #252525;
200
+ background: #031b10;
141
201
  scrollbar-size: 1 1;
142
- scrollbar-background: #252525;
143
- scrollbar-color: #22d3ee;
202
+ scrollbar-background: #031b10;
203
+ scrollbar-color: #22c55e;
144
204
  }
145
205
 
146
206
  #ai_selector_list {
147
207
  height: auto;
148
- background: #252525;
208
+ background: #031b10;
149
209
  color: #f8fafc;
150
210
  }
151
-
152
- #ai_selector_help {
153
- height: 3;
154
- color: #9ca3af;
155
- padding: 1 0 0 0;
156
- }
157
- """
211
+
212
+ #ai_selector_help {
213
+ height: 3;
214
+ color: #9ca3af;
215
+ padding: 1 0 0 0;
216
+ }
217
+ """
@@ -1,17 +1,130 @@
1
- """Small formatting helpers."""
1
+ """Small formatting helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from rich.console import Console, ConsoleOptions, RenderResult
6
+ from rich.markdown import Markdown
7
+ from rich.text import Text
8
+
9
+ from fincli.app.providers.ai.base import AIResponse
10
+
11
+
12
+ def mask_secret(value: str | None) -> str:
13
+ """Mask API keys and tokens before displaying them."""
14
+ if not value:
15
+ return "not set"
16
+ if len(value) <= 8:
17
+ return "set"
18
+ return f"{value[:4]}...{value[-4:]}"
19
+
20
+
21
+ def normalize_symbol(symbol: str) -> str:
22
+ """Normalize user-entered market symbols."""
23
+ return symbol.strip().upper()
2
24
 
3
- from __future__ import annotations
4
25
 
26
+ POSITIVE_TERMS = (
27
+ "bullish",
28
+ "best to buy",
29
+ "buy",
30
+ "breakout",
31
+ "positive",
32
+ "gain",
33
+ "upside",
34
+ "higher",
35
+ "profit",
36
+ "win",
37
+ "triggered above",
38
+ "confirmed",
39
+ )
40
+ NEGATIVE_TERMS = (
41
+ "bearish",
42
+ "best to sell",
43
+ "sell",
44
+ "breakdown",
45
+ "negative",
46
+ "loss",
47
+ "drawdown",
48
+ "downside",
49
+ "lower",
50
+ "decline",
51
+ "drop",
52
+ "failed",
53
+ "unavailable",
54
+ )
55
+ CAUTION_TERMS = (
56
+ "caution",
57
+ "hold",
58
+ "wait",
59
+ "neutral",
60
+ "sideways",
61
+ "mixed",
62
+ "risk",
63
+ "warning",
64
+ "delayed",
65
+ "fallback",
66
+ "not confirmed",
67
+ )
5
68
 
6
- def mask_secret(value: str | None) -> str:
7
- """Mask API keys and tokens before displaying them."""
8
- if not value:
9
- return "not set"
10
- if len(value) <= 8:
11
- return "set"
12
- return f"{value[:4]}...{value[-4:]}"
13
69
 
70
+ def semantic_style(value: object) -> str:
71
+ """Map financial meaning to a consistent terminal style."""
72
+ text = str(value).strip().lower()
73
+ if not text:
74
+ return "white"
75
+ if any(term in text for term in CAUTION_TERMS):
76
+ return "bold yellow"
77
+ if any(term in text for term in NEGATIVE_TERMS):
78
+ return "bold red"
79
+ if any(term in text for term in POSITIVE_TERMS):
80
+ return "bold green"
81
+ return "white"
14
82
 
15
- def normalize_symbol(symbol: str) -> str:
16
- """Normalize user-entered market symbols."""
17
- return symbol.strip().upper()
83
+
84
+ def semantic_text(value: object) -> Text:
85
+ """Return Rich Text styled by financial semantics."""
86
+ return Text(str(value), style=semantic_style(value))
87
+
88
+
89
+ class AIResponseView:
90
+ """Renderable AI response that preserves Markdown formatting in Rich/Textual."""
91
+
92
+ def __init__(self, response: AIResponse) -> None:
93
+ self.response = response
94
+
95
+ def __str__(self) -> str:
96
+ return (
97
+ f"Provider: {self.response.provider}\n"
98
+ f"Model: {self.response.model}\n"
99
+ f"Response:\n{self.response.content}"
100
+ )
101
+
102
+ def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
103
+ header = Text()
104
+ header.append("Provider: ", style="bold cyan")
105
+ header.append(self.response.provider, style="white")
106
+ header.append(" Model: ", style="bold cyan")
107
+ header.append(self.response.model, style="white")
108
+ yield header
109
+ yield Markdown(self.response.content)
110
+
111
+
112
+ class MarkdownBlock:
113
+ """Small renderable block for titled Markdown content."""
114
+
115
+ def __init__(self, title: str, body: object, footer: str | None = None) -> None:
116
+ self.title = title
117
+ self.body = body
118
+ self.footer = footer
119
+
120
+ def __str__(self) -> str:
121
+ parts = [self.title, str(self.body)]
122
+ if self.footer:
123
+ parts.append(self.footer)
124
+ return "\n".join(parts)
125
+
126
+ def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
127
+ yield Text(self.title, style="bold cyan")
128
+ yield self.body
129
+ if self.footer:
130
+ yield Text(self.footer, style="dim")
package/npm/bin/fincli.js CHANGED
@@ -5,20 +5,27 @@ const path = require("path");
5
5
  const { spawn } = require("child_process");
6
6
 
7
7
  const packageRoot = path.resolve(__dirname, "..", "..");
8
+ const packageJson = require(path.join(packageRoot, "package.json"));
8
9
  const venvDir = path.join(packageRoot, ".npm-python");
9
10
  const pythonBin = process.platform === "win32"
10
11
  ? path.join(venvDir, "Scripts", "python.exe")
11
12
  : path.join(venvDir, "bin", "python");
12
13
 
13
14
  function run() {
15
+ const args = process.argv.slice(2);
16
+ if (args.includes("--version") || args.includes("-v")) {
17
+ console.log(packageJson.version);
18
+ return;
19
+ }
20
+
14
21
  if (!fs.existsSync(pythonBin)) {
15
22
  console.error("FinCLI Python runtime is missing.");
16
- console.error("Try reinstalling with: npm install -g fincli");
23
+ console.error("Try reinstalling with: npm install -g @drico2008/fincli");
17
24
  console.error("Python 3.11+ must be available during npm install.");
18
25
  process.exit(1);
19
26
  }
20
27
 
21
- const child = spawn(pythonBin, ["-m", "fincli.app.main", ...process.argv.slice(2)], {
28
+ const child = spawn(pythonBin, ["-m", "fincli.app.main", ...args], {
22
29
  cwd: packageRoot,
23
30
  stdio: "inherit"
24
31
  });
package/package.json CHANGED
@@ -1,23 +1,23 @@
1
- {
2
- "name": "@drico2008/fincli",
3
- "version": "0.1.3",
4
- "description": "Modern financial CLI/TUI terminal for market monitoring and analysis.",
5
- "license": "MIT",
6
- "bin": {
7
- "fincli": "npm/bin/fincli.js"
8
- },
9
- "scripts": {
10
- "postinstall": "node npm/postinstall.js",
11
- "check": "node --check npm/bin/fincli.js && node --check npm/postinstall.js"
12
- },
13
- "files": [
14
- "fincli/**/*.py",
15
- "npm",
16
- "pyproject.toml",
17
- "README.md",
18
- "requirements.txt"
19
- ],
20
- "engines": {
21
- "node": ">=18"
22
- }
23
- }
1
+ {
2
+ "name": "@drico2008/fincli",
3
+ "version": "0.2.2",
4
+ "description": "Modern financial CLI/TUI terminal for market monitoring and analysis.",
5
+ "license": "MIT",
6
+ "bin": {
7
+ "fincli": "npm/bin/fincli.js"
8
+ },
9
+ "scripts": {
10
+ "postinstall": "node npm/postinstall.js",
11
+ "check": "node --check npm/bin/fincli.js && node --check npm/postinstall.js"
12
+ },
13
+ "files": [
14
+ "fincli/**/*.py",
15
+ "npm",
16
+ "pyproject.toml",
17
+ "README.md",
18
+ "requirements.txt"
19
+ ],
20
+ "engines": {
21
+ "node": ">=18"
22
+ }
23
+ }
package/pyproject.toml CHANGED
@@ -1,35 +1,35 @@
1
- [build-system]
2
- requires = ["setuptools>=68", "wheel"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [project]
6
- name = "fincli"
7
- version = "0.1.3"
8
- description = "Modern financial CLI/TUI terminal for market monitoring and analysis."
9
- readme = "README.md"
10
- requires-python = ">=3.11"
11
- dependencies = [
12
- "textual>=0.86.0",
13
- "rich>=13.9.0",
14
- "python-dotenv>=1.0.1",
15
- "httpx>=0.27.2",
16
- "pydantic>=2.9.2",
17
- "yfinance>=0.2.50",
18
- "pandas>=2.2.3",
19
- "numpy>=2.1.0",
20
- ]
21
-
22
- [project.optional-dependencies]
23
- dev = ["pytest>=8.3.3"]
24
- market = ["yfinance>=0.2.50", "pandas>=2.2.3", "numpy>=2.1.0"]
25
-
26
- [project.scripts]
27
- fincli = "fincli.app.main:main"
28
-
29
- [tool.setuptools.packages.find]
30
- include = ["fincli*"]
31
- exclude = ["npm*", "tests*"]
32
-
33
- [tool.pytest.ini_options]
34
- testpaths = ["tests"]
35
- pythonpath = ["."]
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "fincli"
7
+ version = "0.2.2"
8
+ description = "Modern financial CLI/TUI terminal for market monitoring and analysis."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ dependencies = [
12
+ "textual>=0.86.0",
13
+ "rich>=13.9.0",
14
+ "python-dotenv>=1.0.1",
15
+ "httpx>=0.27.2",
16
+ "pydantic>=2.9.2",
17
+ "yfinance>=0.2.50",
18
+ "pandas>=2.2.3",
19
+ "numpy>=2.1.0",
20
+ ]
21
+
22
+ [project.optional-dependencies]
23
+ dev = ["pytest>=8.3.3"]
24
+ market = ["yfinance>=0.2.50", "pandas>=2.2.3", "numpy>=2.1.0"]
25
+
26
+ [project.scripts]
27
+ fincli = "fincli.app.main:main"
28
+
29
+ [tool.setuptools.packages.find]
30
+ include = ["fincli*"]
31
+ exclude = ["npm*", "tests*"]
32
+
33
+ [tool.pytest.ini_options]
34
+ testpaths = ["tests"]
35
+ pythonpath = ["."]