aipa-cli 0.1.30__tar.gz → 0.1.32__tar.gz

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 (46) hide show
  1. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/CHANGELOG.md +12 -0
  2. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/PKG-INFO +2 -2
  3. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/pyproject.toml +1 -1
  4. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/__init__.py +1 -1
  5. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/analyze.py +26 -1
  6. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/cli_commands.py +24 -0
  7. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/deep_research.py +17 -7
  8. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/user_settings.py +1 -1
  9. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/.gitignore +0 -0
  10. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/LICENSE +0 -0
  11. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/README.md +0 -0
  12. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/__main__.py +0 -0
  13. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/actions.py +0 -0
  14. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/agents/__init__.py +0 -0
  15. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/agents/agent.py +0 -0
  16. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/agents/callbacks.py +0 -0
  17. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/agents/config.py +0 -0
  18. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/agents/personas.py +0 -0
  19. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/agents/tools.py +0 -0
  20. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/app.py +0 -0
  21. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/bindings.py +0 -0
  22. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/chart.py +0 -0
  23. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/chat.py +0 -0
  24. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/cli.py +0 -0
  25. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/cli_setup.py +0 -0
  26. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/session.py +0 -0
  27. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/settings_tab.py +0 -0
  28. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/theme.py +0 -0
  29. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/ticker_data.py +0 -0
  30. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/utils.py +0 -0
  31. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/widgets/__init__.py +0 -0
  32. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/widgets/chat_input.py +0 -0
  33. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/widgets/safe_rich_log.py +0 -0
  34. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/widgets/ticker_select.py +0 -0
  35. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/workflows.py +0 -0
  36. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/tests/conftest.py +0 -0
  37. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/tests/openrouter_responses.py +0 -0
  38. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/tests/test_app.py +0 -0
  39. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/tests/test_chat.py +0 -0
  40. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/tests/test_integration.py +0 -0
  41. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/tests/test_settings_api.py +0 -0
  42. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/tests/test_thinking.py +0 -0
  43. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/tests/test_tool_call_streaming.py +0 -0
  44. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/tests/test_tools.py +0 -0
  45. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/tests/test_utils.py +0 -0
  46. {aipa_cli-0.1.30 → aipa_cli-0.1.32}/tests/test_workflows.py +0 -0
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.1.32] - 2026-05-16
9
+
10
+ ### Changed
11
+ - Require `aipriceaction>=0.1.16` for persistent storage in `~/.aipriceaction/` instead of tempfile
12
+ - Align reviewer/aggregator round numbers, increase max rounds to 5, validate supervisor tickers
13
+ - Persist CLI and TUI analyze input/output to `~/.aipriceaction/analyze/<uuid>/`
14
+
15
+ ## [0.1.31] - 2026-05-15
16
+
17
+ ### Changed
18
+ - Default language changed from English (en) to Vietnamese (vn) in user settings
19
+
8
20
  ## [0.1.30] - 2026-05-13
9
21
 
10
22
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aipa-cli
3
- Version: 0.1.30
3
+ Version: 0.1.32
4
4
  Summary: Terminal TUI for AI-powered ticker analysis
5
5
  Project-URL: Homepage, https://github.com/quanhua92/aipriceaction
6
6
  Project-URL: Repository, https://github.com/quanhua92/aipriceaction
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3
15
15
  Classifier: Programming Language :: Python :: 3.13
16
16
  Classifier: Topic :: Office/Business :: Financial
17
17
  Requires-Python: >=3.13
18
- Requires-Dist: aipriceaction>=0.1.15
18
+ Requires-Dist: aipriceaction>=0.1.16
19
19
  Requires-Dist: langchain-core
20
20
  Requires-Dist: langchain-openai
21
21
  Requires-Dist: langgraph
@@ -9,7 +9,7 @@ license = "MIT"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
11
11
  dependencies = [
12
- "aipriceaction>=0.1.15",
12
+ "aipriceaction>=0.1.16",
13
13
  "textual>=3.0.0",
14
14
  "textual-autocomplete>=4.0.6",
15
15
  "textual-plotext>=0.4.1",
@@ -1,4 +1,4 @@
1
1
  """AIPriceAction Terminal - TUI chat interface for ticker analysis."""
2
2
 
3
- __version__ = "0.1.30"
3
+ __version__ = "0.1.32"
4
4
 
@@ -3,12 +3,18 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import asyncio
6
+ import json
7
+ import uuid
6
8
  from collections.abc import Callable
9
+ from datetime import datetime, timezone
10
+ from pathlib import Path
7
11
 
8
12
  from textual.widgets import RichLog
9
13
 
10
14
  from .utils import stream_agent_to_log
11
15
 
16
+ _ANALYZE_DIR = Path.home() / ".aipriceaction" / "analyze"
17
+
12
18
  _DEFAULT_REFERENCE: dict[str, str] = {
13
19
  "crypto": "BTCUSDT",
14
20
  "vn": "VNINDEX",
@@ -99,7 +105,7 @@ async def run_tui_analyze(
99
105
  if prefix:
100
106
  message = prefix + message
101
107
 
102
- await stream_agent_to_log(
108
+ response_text = await stream_agent_to_log(
103
109
  log,
104
110
  agent,
105
111
  message,
@@ -108,3 +114,22 @@ async def run_tui_analyze(
108
114
  on_thinking_done=on_thinking_done,
109
115
  on_message=on_message,
110
116
  )
117
+
118
+ # Persist input + output to ~/.aipriceaction/analyze/<uuid>/
119
+ try:
120
+ session_id = str(uuid.uuid4())
121
+ session_dir = _ANALYZE_DIR / session_id
122
+ session_dir.mkdir(parents=True, exist_ok=True)
123
+
124
+ meta = {
125
+ "session_id": session_id,
126
+ "ticker": ticker,
127
+ "interval": interval,
128
+ "reference_ticker": reference_ticker,
129
+ "created_at": datetime.now(timezone.utc).isoformat(),
130
+ }
131
+ (session_dir / "meta.json").write_text(json.dumps(meta, indent=2))
132
+ (session_dir / "input.md").write_text(message)
133
+ (session_dir / "output.md").write_text(response_text)
134
+ except Exception:
135
+ pass # best-effort, never block the UI
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import asyncio
6
+ import json
6
7
  import sys
7
8
  import time
8
9
 
@@ -156,6 +157,29 @@ async def cmd_analyze(args) -> None:
156
157
  print("\n[result]", file=sys.stderr)
157
158
  print(response)
158
159
 
160
+ # Persist input + output to ~/.aipriceaction/analyze/<uuid>/
161
+ try:
162
+ import uuid
163
+ from pathlib import Path
164
+ from datetime import datetime, timezone
165
+
166
+ session_id = str(uuid.uuid4())
167
+ session_dir = Path.home() / ".aipriceaction" / "analyze" / session_id
168
+ session_dir.mkdir(parents=True, exist_ok=True)
169
+
170
+ meta = {
171
+ "session_id": session_id,
172
+ "tickers": args.tickers,
173
+ "interval": args.interval,
174
+ "reference_ticker": args.reference_ticker,
175
+ "created_at": datetime.now(timezone.utc).isoformat(),
176
+ }
177
+ (session_dir / "meta.json").write_text(json.dumps(meta, indent=2))
178
+ (session_dir / "input.md").write_text(message)
179
+ (session_dir / "output.md").write_text(response)
180
+ except Exception:
181
+ pass
182
+
159
183
 
160
184
  def cmd_get_ohlcv(args) -> None:
161
185
  from aipriceaction import AIPriceAction
@@ -11,7 +11,7 @@ import asyncio
11
11
  import sys
12
12
  import json
13
13
  import operator
14
- import tempfile
14
+
15
15
  import time
16
16
  from collections.abc import Callable
17
17
  from datetime import datetime, timezone
@@ -447,6 +447,10 @@ def _build_graph(checkpointer=None, lang: str = "en", source: str = "vn", output
447
447
  if not subtasks_data:
448
448
  raise ValueError(f"Supervisor did not call create_subtasks tool after {max_tool_retries} attempts. Response: {(response.content or '')[:300]}")
449
449
 
450
+ # Fetch valid tickers for validation
451
+ client, _ = _ensure_clients(lang)
452
+ valid_tickers = {t.ticker.upper() for t in client.get_tickers(source=_source)}
453
+
450
454
  subtasks = []
451
455
  for st in subtasks_data:
452
456
  if isinstance(st, str):
@@ -456,9 +460,15 @@ def _build_graph(checkpointer=None, lang: str = "en", source: str = "vn", output
456
460
  continue
457
461
  sector = st.get("sector", "Unknown")
458
462
  tickers = st.get("tickers", st.get("ticker_list", []))
459
- tickers = tickers[:10]
463
+ validated = [t for t in tickers if t.upper() in valid_tickers][:10]
464
+ if not validated:
465
+ _out(f"[Supervisor] WARNING: {sector} — no valid tickers, skipping")
466
+ continue
467
+ dropped = [t for t in tickers if t.upper() not in valid_tickers]
468
+ if dropped:
469
+ _out(f"[Supervisor] {sector} — dropped invalid tickers: {', '.join(dropped)}")
460
470
  instruction = st.get("instruction", f"Analyze {sector} sector")
461
- subtasks.append(Subtask(sector=sector, tickers=tickers, instruction=instruction))
471
+ subtasks.append(Subtask(sector=sector, tickers=validated, instruction=instruction))
462
472
 
463
473
  _out(f"[Supervisor] Decomposed into {len(subtasks)} subtasks:")
464
474
  for st in subtasks:
@@ -552,11 +562,11 @@ def _build_graph(checkpointer=None, lang: str = "en", source: str = "vn", output
552
562
  _out(f"[Aggregator] Analysis synthesized ({len(content):,} chars)")
553
563
  return {"analysis": content, "review_round": round_num + 1}
554
564
 
555
- MAX_REVIEW_ROUNDS = 3
565
+ MAX_REVIEW_ROUNDS = 5
556
566
 
557
567
  async def reviewer_node(state: OverallState) -> dict:
558
568
  round_num = state.get("review_round", 0)
559
- label = f"Reviewer (round {round_num + 1})"
569
+ label = f"Reviewer (round {round_num})"
560
570
  _out(f"[{label}] Checking data integrity...")
561
571
 
562
572
  worker_reports = ""
@@ -607,7 +617,7 @@ def _build_graph(checkpointer=None, lang: str = "en", source: str = "vn", output
607
617
  def review_router(state: OverallState) -> str:
608
618
  if state.get("review_result") == "approve":
609
619
  return "end"
610
- if state.get("review_round", 0) >= MAX_REVIEW_ROUNDS - 1:
620
+ if state.get("review_round", 0) >= MAX_REVIEW_ROUNDS:
611
621
  _out("[Reviewer] Max rounds reached, accepting current output")
612
622
  return "end"
613
623
  return "aggregator"
@@ -844,7 +854,7 @@ async def run_deep_research(
844
854
 
845
855
  checkpointer = PersistentCheckpointSaver(
846
856
  session_id=resume_id,
847
- base_dir=Path(tempfile.gettempdir()) / "aipriceaction-checkpoints",
857
+ base_dir=Path.home() / ".aipriceaction" / "deep-research",
848
858
  callbacks=[extract_worker_results],
849
859
  )
850
860
  _out(f" Session: {checkpointer.session_id}")
@@ -10,7 +10,7 @@ _SETTINGS_FILE = _CONFIG_DIR / "settings.json"
10
10
  _DEFAULTS = {
11
11
  "ticker": "VNINDEX",
12
12
  "interval": "1D",
13
- "language": "en",
13
+ "language": "vn",
14
14
  "api_key": "",
15
15
  "openai_base_url": "",
16
16
  "openai_model": "",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes