@drico2008/fincli 0.1.9 → 0.3.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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +124 -625
  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 +26 -14
  7. package/fincli/app/analysis/analyzer.py +107 -96
  8. package/fincli/app/analysis/assistant_context.py +187 -186
  9. package/fincli/app/analysis/backtest.py +179 -0
  10. package/fincli/app/analysis/gameplay_plan.py +79 -0
  11. package/fincli/app/analysis/multi_timeframe.py +180 -0
  12. package/fincli/app/analysis/trading_methods.py +144 -0
  13. package/fincli/app/cli/commands.py +108 -81
  14. package/fincli/app/cli/router.py +2327 -1237
  15. package/fincli/app/connectors/__init__.py +5 -0
  16. package/fincli/app/connectors/catalog.py +148 -0
  17. package/fincli/app/connectors/news_connectors.py +412 -0
  18. package/fincli/app/modules/alerts.py +80 -0
  19. package/fincli/app/modules/economic_calendar.py +374 -1
  20. package/fincli/app/modules/portfolio_risk.py +305 -0
  21. package/fincli/app/modules/reports.py +151 -0
  22. package/fincli/app/modules/scanner.py +111 -93
  23. package/fincli/app/modules/transactions.py +84 -84
  24. package/fincli/app/modules/user_profile.py +84 -0
  25. package/fincli/app/plugins/loader.py +72 -0
  26. package/fincli/app/providers/ai/manager.py +60 -60
  27. package/fincli/app/providers/market/alphavantage_provider.py +194 -0
  28. package/fincli/app/providers/market/base.py +98 -77
  29. package/fincli/app/providers/market/custom_provider.py +186 -169
  30. package/fincli/app/providers/market/manager.py +84 -1
  31. package/fincli/app/providers/market/symbols.py +143 -0
  32. package/fincli/app/providers/market/twelvedata_provider.py +167 -167
  33. package/fincli/app/providers/reliability.py +86 -0
  34. package/fincli/app/research/__init__.py +8 -0
  35. package/fincli/app/research/engine.py +137 -0
  36. package/fincli/app/research/exporter.py +91 -0
  37. package/fincli/app/research/formatter.py +27 -0
  38. package/fincli/app/research/models.py +24 -0
  39. package/fincli/app/research/prompt_builder.py +54 -0
  40. package/fincli/app/services/macro_data.py +50 -0
  41. package/fincli/app/services/market_data.py +274 -169
  42. package/fincli/app/services/market_overview.py +42 -1
  43. package/fincli/app/services/news_aggregator.py +95 -0
  44. package/fincli/app/services/web_research.py +267 -267
  45. package/fincli/app/storage/config.py +122 -88
  46. package/fincli/app/storage/database.py +209 -99
  47. package/fincli/app/storage/provider_metrics.py +61 -0
  48. package/fincli/app/storage/secrets.py +26 -2
  49. package/fincli/app/tui/components.py +68 -50
  50. package/fincli/app/tui/layout.py +269 -258
  51. package/fincli/app/tui/market_provider_selector.py +3 -1
  52. package/fincli/app/tui/theme.py +134 -74
  53. package/fincli/app/utils/formatting.py +123 -60
  54. package/package.json +22 -20
  55. 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,67 +1,130 @@
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
-
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
21
  def normalize_symbol(symbol: str) -> str:
22
22
  """Normalize user-entered market symbols."""
23
23
  return symbol.strip().upper()
24
24
 
25
25
 
26
- class AIResponseView:
27
- """Renderable AI response that preserves Markdown formatting in Rich/Textual."""
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
+ )
68
+
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"
82
+
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))
28
87
 
29
- def __init__(self, response: AIResponse) -> None:
30
- self.response = response
31
88
 
32
- def __str__(self) -> str:
33
- return (
34
- f"Provider: {self.response.provider}\n"
35
- f"Model: {self.response.model}\n"
36
- f"Response:\n{self.response.content}"
37
- )
38
-
39
- def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
40
- header = Text()
41
- header.append("Provider: ", style="bold cyan")
42
- header.append(self.response.provider, style="white")
43
- header.append(" Model: ", style="bold cyan")
44
- header.append(self.response.model, style="white")
45
- yield header
46
- yield Markdown(self.response.content)
47
-
48
-
49
- class MarkdownBlock:
50
- """Small renderable block for titled Markdown content."""
51
-
52
- def __init__(self, title: str, body: object, footer: str | None = None) -> None:
53
- self.title = title
54
- self.body = body
55
- self.footer = footer
56
-
57
- def __str__(self) -> str:
58
- parts = [self.title, str(self.body)]
59
- if self.footer:
60
- parts.append(self.footer)
61
- return "\n".join(parts)
62
-
63
- def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
64
- yield Text(self.title, style="bold cyan")
65
- yield self.body
66
- if self.footer:
67
- yield Text(self.footer, style="dim")
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/package.json CHANGED
@@ -1,23 +1,25 @@
1
- {
2
- "name": "@drico2008/fincli",
3
- "version": "0.1.9",
4
- "description": "Modern financial CLI/TUI terminal for market monitoring and analysis.",
5
- "license": "MIT",
6
- "bin": {
7
- "fincli": "npm/bin/fincli.js"
8
- },
1
+ {
2
+ "name": "@drico2008/fincli",
3
+ "version": "0.3.0",
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
9
  "scripts": {
10
10
  "postinstall": "node npm/postinstall.js",
11
- "check": "node --check npm/bin/fincli.js && node --check npm/postinstall.js"
11
+ "check": "node --check npm/bin/fincli.js && node --check npm/postinstall.js",
12
+ "prepublish:safety": "python scripts/prepublish_check.py",
13
+ "prepublishOnly": "python scripts/prepublish_check.py"
12
14
  },
13
- "files": [
14
- "fincli/**/*.py",
15
- "npm",
16
- "pyproject.toml",
17
- "README.md",
18
- "requirements.txt"
19
- ],
20
- "engines": {
21
- "node": ">=18"
22
- }
23
- }
15
+ "files": [
16
+ "fincli/**/*.py",
17
+ "npm",
18
+ "pyproject.toml",
19
+ "README.md",
20
+ "requirements.txt"
21
+ ],
22
+ "engines": {
23
+ "node": ">=18"
24
+ }
25
+ }
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.9"
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.3.0"
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 = ["."]