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.
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/CHANGELOG.md +12 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/PKG-INFO +2 -2
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/pyproject.toml +1 -1
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/__init__.py +1 -1
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/analyze.py +26 -1
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/cli_commands.py +24 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/deep_research.py +17 -7
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/user_settings.py +1 -1
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/.gitignore +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/LICENSE +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/README.md +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/__main__.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/actions.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/agents/__init__.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/agents/agent.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/agents/callbacks.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/agents/config.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/agents/personas.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/agents/tools.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/app.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/bindings.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/chart.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/chat.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/cli.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/cli_setup.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/session.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/settings_tab.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/theme.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/ticker_data.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/utils.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/widgets/__init__.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/widgets/chat_input.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/widgets/safe_rich_log.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/widgets/ticker_select.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/src/aipriceaction_terminal/workflows.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/tests/conftest.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/tests/openrouter_responses.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/tests/test_app.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/tests/test_chat.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/tests/test_integration.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/tests/test_settings_api.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/tests/test_thinking.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/tests/test_tool_call_streaming.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/tests/test_tools.py +0 -0
- {aipa_cli-0.1.30 → aipa_cli-0.1.32}/tests/test_utils.py +0 -0
- {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.
|
|
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.
|
|
18
|
+
Requires-Dist: aipriceaction>=0.1.16
|
|
19
19
|
Requires-Dist: langchain-core
|
|
20
20
|
Requires-Dist: langchain-openai
|
|
21
21
|
Requires-Dist: langgraph
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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=
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
857
|
+
base_dir=Path.home() / ".aipriceaction" / "deep-research",
|
|
848
858
|
callbacks=[extract_worker_results],
|
|
849
859
|
)
|
|
850
860
|
_out(f" Session: {checkpointer.session_id}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|