nexus-agent-cli 0.2.0__tar.gz → 0.5.0__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.
- {nexus_agent_cli-0.2.0 → nexus_agent_cli-0.5.0}/PKG-INFO +22 -2
- {nexus_agent_cli-0.2.0 → nexus_agent_cli-0.5.0}/README.md +21 -1
- nexus_agent_cli-0.5.0/nexus_agent_cli/__init__.py +1 -0
- {nexus_agent_cli-0.2.0 → nexus_agent_cli-0.5.0}/nexus_agent_cli/api_client.py +65 -0
- {nexus_agent_cli-0.2.0 → nexus_agent_cli-0.5.0}/nexus_agent_cli/commands/run/cmd.py +136 -0
- {nexus_agent_cli-0.2.0 → nexus_agent_cli-0.5.0}/nexus_agent_cli/config.py +5 -2
- {nexus_agent_cli-0.2.0 → nexus_agent_cli-0.5.0}/pyproject.toml +1 -1
- nexus_agent_cli-0.2.0/nexus_agent_cli/__init__.py +0 -1
- {nexus_agent_cli-0.2.0 → nexus_agent_cli-0.5.0}/.gitignore +0 -0
- {nexus_agent_cli-0.2.0 → nexus_agent_cli-0.5.0}/nexus_agent_cli/__main__.py +0 -0
- {nexus_agent_cli-0.2.0 → nexus_agent_cli-0.5.0}/nexus_agent_cli/auth_pkce.py +0 -0
- {nexus_agent_cli-0.2.0 → nexus_agent_cli-0.5.0}/nexus_agent_cli/commands/__init__.py +0 -0
- {nexus_agent_cli-0.2.0 → nexus_agent_cli-0.5.0}/nexus_agent_cli/commands/auth/__init__.py +0 -0
- {nexus_agent_cli-0.2.0 → nexus_agent_cli-0.5.0}/nexus_agent_cli/commands/auth/cmd.py +0 -0
- {nexus_agent_cli-0.2.0 → nexus_agent_cli-0.5.0}/nexus_agent_cli/commands/run/__init__.py +0 -0
- {nexus_agent_cli-0.2.0 → nexus_agent_cli-0.5.0}/nexus_agent_cli/main.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nexus-agent-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Nexus internal agent ops CLI — pull run params, push analysis results, send notifications. Admin only.
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Requires-Dist: httpx>=0.27
|
|
@@ -61,6 +61,26 @@ nexus-agent push-backtest --ticker tqqq \
|
|
|
61
61
|
--conservative conservative_aggregated.json \
|
|
62
62
|
--normal normal_aggregated.json
|
|
63
63
|
|
|
64
|
+
# 回读 S3(均为只读,登录即可):最新回测 / 市场分析报告 / 组合推荐
|
|
65
|
+
nexus-agent get-backtest --ticker tqqq --out tqqq_backtest.json
|
|
66
|
+
nexus-agent get-market-analysis --ticker tqqq --out tqqq_market.json
|
|
67
|
+
nexus-agent get-portfolio --out portfolio.json
|
|
68
|
+
|
|
69
|
+
# 拉取 S3 上最新的 signal_position 基线 config(Position-Research 阶段用)
|
|
70
|
+
nexus-agent pull-config --ticker tqqq --kind normal --out tqqq-normal.json
|
|
71
|
+
|
|
72
|
+
# 历史版本(by-date):列出 → 取指定版本
|
|
73
|
+
nexus-agent pull-config --ticker tqqq --kind normal --list --since 2026-06-01
|
|
74
|
+
nexus-agent pull-config --ticker tqqq --kind normal --date 2026-06-01_to_2026-06-14 --out old.json
|
|
75
|
+
nexus-agent get-market-analysis --ticker tqqq --list
|
|
76
|
+
nexus-agent get-portfolio --list --since 2026-06-01
|
|
77
|
+
|
|
78
|
+
# 回传 Position-Research 产出的 3 套仓位 config(≠ push-backtest 的回测聚合)
|
|
79
|
+
nexus-agent push-config --ticker tqqq --time-range 2026-06-01_2026-06-14 \
|
|
80
|
+
--aggressive tqqq-aggressive.json \
|
|
81
|
+
--conservative tqqq-conservative.json \
|
|
82
|
+
--normal tqqq-normal.json
|
|
83
|
+
|
|
64
84
|
# 服务端 SES 发通知邮件
|
|
65
85
|
nexus-agent notify --to a@x.com --to b@y.com --subject "Weekly report" --body-file summary.txt
|
|
66
86
|
```
|
|
@@ -73,7 +93,7 @@ nexus-agent notify --to a@x.com --to b@y.com --subject "Weekly report" --body-fi
|
|
|
73
93
|
| `NEXUS_AGENT_API_BASE_URL` | — | 直接覆盖完整 API base(含 `/api`) |
|
|
74
94
|
| `NEXUS_AGENT_COGNITO_DOMAIN` | `auth.lookatwallstreet.com` | Cognito Hosted UI 域名 |
|
|
75
95
|
| `NEXUS_AGENT_COGNITO_CLIENT_ID` | (内置) | Cognito app client id |
|
|
76
|
-
| `NEXUS_AGENT_REDIRECT_URI` | `http://127.0.0.1:
|
|
96
|
+
| `NEXUS_AGENT_REDIRECT_URI` | `http://127.0.0.1:8251/callback` | 本地回调(须 localhost+端口;端口必须已加入 Cognito app client 的 Allowed callback URLs) |
|
|
77
97
|
|
|
78
98
|
例如指向本地后端调试:
|
|
79
99
|
|
|
@@ -50,6 +50,26 @@ nexus-agent push-backtest --ticker tqqq \
|
|
|
50
50
|
--conservative conservative_aggregated.json \
|
|
51
51
|
--normal normal_aggregated.json
|
|
52
52
|
|
|
53
|
+
# 回读 S3(均为只读,登录即可):最新回测 / 市场分析报告 / 组合推荐
|
|
54
|
+
nexus-agent get-backtest --ticker tqqq --out tqqq_backtest.json
|
|
55
|
+
nexus-agent get-market-analysis --ticker tqqq --out tqqq_market.json
|
|
56
|
+
nexus-agent get-portfolio --out portfolio.json
|
|
57
|
+
|
|
58
|
+
# 拉取 S3 上最新的 signal_position 基线 config(Position-Research 阶段用)
|
|
59
|
+
nexus-agent pull-config --ticker tqqq --kind normal --out tqqq-normal.json
|
|
60
|
+
|
|
61
|
+
# 历史版本(by-date):列出 → 取指定版本
|
|
62
|
+
nexus-agent pull-config --ticker tqqq --kind normal --list --since 2026-06-01
|
|
63
|
+
nexus-agent pull-config --ticker tqqq --kind normal --date 2026-06-01_to_2026-06-14 --out old.json
|
|
64
|
+
nexus-agent get-market-analysis --ticker tqqq --list
|
|
65
|
+
nexus-agent get-portfolio --list --since 2026-06-01
|
|
66
|
+
|
|
67
|
+
# 回传 Position-Research 产出的 3 套仓位 config(≠ push-backtest 的回测聚合)
|
|
68
|
+
nexus-agent push-config --ticker tqqq --time-range 2026-06-01_2026-06-14 \
|
|
69
|
+
--aggressive tqqq-aggressive.json \
|
|
70
|
+
--conservative tqqq-conservative.json \
|
|
71
|
+
--normal tqqq-normal.json
|
|
72
|
+
|
|
53
73
|
# 服务端 SES 发通知邮件
|
|
54
74
|
nexus-agent notify --to a@x.com --to b@y.com --subject "Weekly report" --body-file summary.txt
|
|
55
75
|
```
|
|
@@ -62,7 +82,7 @@ nexus-agent notify --to a@x.com --to b@y.com --subject "Weekly report" --body-fi
|
|
|
62
82
|
| `NEXUS_AGENT_API_BASE_URL` | — | 直接覆盖完整 API base(含 `/api`) |
|
|
63
83
|
| `NEXUS_AGENT_COGNITO_DOMAIN` | `auth.lookatwallstreet.com` | Cognito Hosted UI 域名 |
|
|
64
84
|
| `NEXUS_AGENT_COGNITO_CLIENT_ID` | (内置) | Cognito app client id |
|
|
65
|
-
| `NEXUS_AGENT_REDIRECT_URI` | `http://127.0.0.1:
|
|
85
|
+
| `NEXUS_AGENT_REDIRECT_URI` | `http://127.0.0.1:8251/callback` | 本地回调(须 localhost+端口;端口必须已加入 Cognito app client 的 Allowed callback URLs) |
|
|
66
86
|
|
|
67
87
|
例如指向本地后端调试:
|
|
68
88
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.5.0"
|
|
@@ -60,6 +60,21 @@ def get_strategy_data(
|
|
|
60
60
|
return get_json("/nexus-agent/strategy-data/", params=params)
|
|
61
61
|
|
|
62
62
|
|
|
63
|
+
def get_backtest(ticker: str) -> Any:
|
|
64
|
+
"""GET /nexus-agent/backtest/ — latest aggregated backtest data for one ticker."""
|
|
65
|
+
return get_json("/nexus-agent/backtest/", params={"ticker": ticker})
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_market_analysis(ticker: str) -> Any:
|
|
69
|
+
"""GET /nexus-agent/market-analysis/ — latest market analysis report for one ticker."""
|
|
70
|
+
return get_json("/nexus-agent/market-analysis/", params={"ticker": ticker})
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_portfolio_recommendation() -> Any:
|
|
74
|
+
"""GET /nexus-agent/portfolio-recommendation/ — latest portfolio recommendation."""
|
|
75
|
+
return get_json("/nexus-agent/portfolio-recommendation/")
|
|
76
|
+
|
|
77
|
+
|
|
63
78
|
def upload_market_analysis(ticker: str, content: str, language: str = "zh") -> Any:
|
|
64
79
|
"""POST /nexus-agent/upload/market-analysis/"""
|
|
65
80
|
return post_json(
|
|
@@ -85,6 +100,56 @@ def upload_backtest(ticker: str, levels: dict[str, Any]) -> Any:
|
|
|
85
100
|
return post_json("/nexus-agent/upload/backtest/", {"ticker": ticker, **levels})
|
|
86
101
|
|
|
87
102
|
|
|
103
|
+
def get_position_config(
|
|
104
|
+
ticker: str, kind: str = "normal", date_range: str | None = None
|
|
105
|
+
) -> Any:
|
|
106
|
+
"""GET /nexus-agent/position-config/ — signal_position config from S3.
|
|
107
|
+
|
|
108
|
+
Mirrors the worker's download_latest_normal_config_from_s3: the server reads
|
|
109
|
+
configs/signal_position/{ticker}/<date_range>/{ticker}-{kind}.json and returns it
|
|
110
|
+
as JSON. With date_range, fetch that specific historical version; otherwise the
|
|
111
|
+
latest. This is the Position-Research baseline the local skill cannot otherwise read.
|
|
112
|
+
"""
|
|
113
|
+
params: dict[str, Any] = {"ticker": ticker, "kind": kind}
|
|
114
|
+
if date_range:
|
|
115
|
+
params["date_range"] = date_range
|
|
116
|
+
return get_json("/nexus-agent/position-config/", params=params)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def list_history(
|
|
120
|
+
artifact_type: str, ticker: str | None = None, kind: str | None = None, since: str | None = None
|
|
121
|
+
) -> Any:
|
|
122
|
+
"""GET /nexus-agent/history/ — list historical versions of an artifact.
|
|
123
|
+
|
|
124
|
+
artifact_type: config / market-analysis / portfolio.
|
|
125
|
+
Returns {type, count, items: [{key, created_at, date_range?}, ...]} newest-first.
|
|
126
|
+
"""
|
|
127
|
+
params: dict[str, Any] = {"type": artifact_type}
|
|
128
|
+
if ticker:
|
|
129
|
+
params["ticker"] = ticker
|
|
130
|
+
if kind:
|
|
131
|
+
params["kind"] = kind
|
|
132
|
+
if since:
|
|
133
|
+
params["since"] = since
|
|
134
|
+
return get_json("/nexus-agent/history/", params=params)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def upload_position_config(
|
|
138
|
+
ticker: str, configs: dict[str, Any], time_range: str | None = None
|
|
139
|
+
) -> Any:
|
|
140
|
+
"""POST /nexus-agent/upload/position-config/ — write the 3 position configs to S3.
|
|
141
|
+
|
|
142
|
+
configs: {aggressive/conservative/normal: {<level>: {...}}}. Distinct from
|
|
143
|
+
upload_backtest (which uploads backtest aggregation, not position configs).
|
|
144
|
+
Server writes configs/signal_position/{ticker}/{time_range}/{ticker}-{kind}.json
|
|
145
|
+
and invalidates the dashboard cache.
|
|
146
|
+
"""
|
|
147
|
+
body: dict[str, Any] = {"ticker": ticker, **configs}
|
|
148
|
+
if time_range is not None:
|
|
149
|
+
body["time_range"] = time_range
|
|
150
|
+
return post_json("/nexus-agent/upload/position-config/", body)
|
|
151
|
+
|
|
152
|
+
|
|
88
153
|
def notify(
|
|
89
154
|
to: list[str], subject: str, body_text: str | None = None, body_html: str | None = None
|
|
90
155
|
) -> Any:
|
|
@@ -126,6 +126,142 @@ def register(app: typer.Typer) -> None:
|
|
|
126
126
|
_run_err(e)
|
|
127
127
|
console.print(JSON(json.dumps(data)))
|
|
128
128
|
|
|
129
|
+
@app.command(
|
|
130
|
+
"pull-config",
|
|
131
|
+
help="Pull the latest signal_position config for one ticker from S3 (the "
|
|
132
|
+
"Position-Research baseline; S3 creds stay server-side).",
|
|
133
|
+
)
|
|
134
|
+
def pull_config(
|
|
135
|
+
ticker: str = typer.Option(..., "--ticker", help="tqqq / soxl / ibit."),
|
|
136
|
+
kind: str = typer.Option(
|
|
137
|
+
"normal", "--kind", help="normal (default) / aggressive / conservative."
|
|
138
|
+
),
|
|
139
|
+
list_versions: bool = typer.Option(
|
|
140
|
+
False, "--list", help="List historical config versions instead of fetching one."
|
|
141
|
+
),
|
|
142
|
+
date_range: str = typer.Option(
|
|
143
|
+
None, "--date", help="Fetch a specific version by its date_range (from --list)."
|
|
144
|
+
),
|
|
145
|
+
since: str = typer.Option(
|
|
146
|
+
None, "--since", help="With --list: only versions created on/after this ISO date."
|
|
147
|
+
),
|
|
148
|
+
out: Path = typer.Option(
|
|
149
|
+
None, "--out", help="Write JSON to this file instead of stdout."
|
|
150
|
+
),
|
|
151
|
+
) -> None:
|
|
152
|
+
try:
|
|
153
|
+
if list_versions:
|
|
154
|
+
data = api_client.list_history("config", ticker=ticker, kind=kind, since=since)
|
|
155
|
+
else:
|
|
156
|
+
data = api_client.get_position_config(ticker, kind=kind, date_range=date_range)
|
|
157
|
+
except Exception as e:
|
|
158
|
+
_run_err(e)
|
|
159
|
+
_emit(data, out)
|
|
160
|
+
|
|
161
|
+
def _emit(data: object, out) -> None:
|
|
162
|
+
if out is not None:
|
|
163
|
+
out.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
164
|
+
console.print(f"[green]已保存 →[/green] {out}")
|
|
165
|
+
else:
|
|
166
|
+
console.print(JSON(json.dumps(data)))
|
|
167
|
+
|
|
168
|
+
@app.command(
|
|
169
|
+
"get-backtest", help="Fetch the latest aggregated backtest data for one ticker from S3."
|
|
170
|
+
)
|
|
171
|
+
def get_backtest(
|
|
172
|
+
ticker: str = typer.Option(..., "--ticker", help="tqqq / soxl / ibit."),
|
|
173
|
+
out: Path = typer.Option(None, "--out", help="Write JSON to this file instead of stdout."),
|
|
174
|
+
) -> None:
|
|
175
|
+
try:
|
|
176
|
+
data = api_client.get_backtest(ticker)
|
|
177
|
+
except Exception as e:
|
|
178
|
+
_run_err(e)
|
|
179
|
+
_emit(data, out)
|
|
180
|
+
|
|
181
|
+
@app.command(
|
|
182
|
+
"get-market-analysis",
|
|
183
|
+
help="Fetch the latest market analysis report for one ticker from S3.",
|
|
184
|
+
)
|
|
185
|
+
def get_market_analysis(
|
|
186
|
+
ticker: str = typer.Option(..., "--ticker", help="tqqq / soxl / ibit."),
|
|
187
|
+
list_versions: bool = typer.Option(
|
|
188
|
+
False, "--list", help="List historical report versions instead of fetching the latest."
|
|
189
|
+
),
|
|
190
|
+
since: str = typer.Option(
|
|
191
|
+
None, "--since", help="With --list: only versions created on/after this ISO date."
|
|
192
|
+
),
|
|
193
|
+
out: Path = typer.Option(None, "--out", help="Write JSON to this file instead of stdout."),
|
|
194
|
+
) -> None:
|
|
195
|
+
try:
|
|
196
|
+
if list_versions:
|
|
197
|
+
data = api_client.list_history("market-analysis", ticker=ticker, since=since)
|
|
198
|
+
else:
|
|
199
|
+
data = api_client.get_market_analysis(ticker)
|
|
200
|
+
except Exception as e:
|
|
201
|
+
_run_err(e)
|
|
202
|
+
_emit(data, out)
|
|
203
|
+
|
|
204
|
+
@app.command(
|
|
205
|
+
"get-portfolio", help="Fetch the latest portfolio recommendation from S3."
|
|
206
|
+
)
|
|
207
|
+
def get_portfolio(
|
|
208
|
+
list_versions: bool = typer.Option(
|
|
209
|
+
False, "--list", help="List historical portfolio versions instead of the latest."
|
|
210
|
+
),
|
|
211
|
+
since: str = typer.Option(
|
|
212
|
+
None, "--since", help="With --list: only versions created on/after this ISO date."
|
|
213
|
+
),
|
|
214
|
+
out: Path = typer.Option(None, "--out", help="Write JSON to this file instead of stdout."),
|
|
215
|
+
) -> None:
|
|
216
|
+
try:
|
|
217
|
+
if list_versions:
|
|
218
|
+
data = api_client.list_history("portfolio", since=since)
|
|
219
|
+
else:
|
|
220
|
+
data = api_client.get_portfolio_recommendation()
|
|
221
|
+
except Exception as e:
|
|
222
|
+
_run_err(e)
|
|
223
|
+
_emit(data, out)
|
|
224
|
+
|
|
225
|
+
@app.command(
|
|
226
|
+
"push-config",
|
|
227
|
+
help="Upload the 3 Position-Research configs for one ticker "
|
|
228
|
+
"(distinct from push-backtest, which uploads backtest aggregation).",
|
|
229
|
+
)
|
|
230
|
+
def push_config(
|
|
231
|
+
ticker: str = typer.Option(..., "--ticker", help="tqqq / soxl / ibit."),
|
|
232
|
+
aggressive: Path = typer.Option(
|
|
233
|
+
None, "--aggressive", exists=True, readable=True, help="{ticker}-aggressive.json"
|
|
234
|
+
),
|
|
235
|
+
conservative: Path = typer.Option(
|
|
236
|
+
None, "--conservative", exists=True, readable=True, help="{ticker}-conservative.json"
|
|
237
|
+
),
|
|
238
|
+
normal: Path = typer.Option(
|
|
239
|
+
None, "--normal", exists=True, readable=True, help="{ticker}-normal.json"
|
|
240
|
+
),
|
|
241
|
+
time_range: str = typer.Option(
|
|
242
|
+
None, "--time-range", help="e.g. 2026-06-01_2026-06-14 (for the S3 key)."
|
|
243
|
+
),
|
|
244
|
+
) -> None:
|
|
245
|
+
configs: dict[str, object] = {}
|
|
246
|
+
for name, path in (
|
|
247
|
+
("aggressive", aggressive),
|
|
248
|
+
("conservative", conservative),
|
|
249
|
+
("normal", normal),
|
|
250
|
+
):
|
|
251
|
+
if path is not None:
|
|
252
|
+
try:
|
|
253
|
+
configs[name] = json.loads(path.read_text(encoding="utf-8"))
|
|
254
|
+
except Exception as e:
|
|
255
|
+
_run_err(e)
|
|
256
|
+
if not configs:
|
|
257
|
+
console.print("[red]至少提供一个档位文件: --aggressive / --conservative / --normal[/red]")
|
|
258
|
+
raise typer.Exit(1)
|
|
259
|
+
try:
|
|
260
|
+
data = api_client.upload_position_config(ticker, configs, time_range=time_range)
|
|
261
|
+
except Exception as e:
|
|
262
|
+
_run_err(e)
|
|
263
|
+
console.print(JSON(json.dumps(data)))
|
|
264
|
+
|
|
129
265
|
@app.command("notify", help="Send a notification email via server-side SES.")
|
|
130
266
|
def notify(
|
|
131
267
|
to: list[str] = typer.Option(..., "--to", help="Recipient (repeatable)."),
|
|
@@ -15,8 +15,11 @@ CREDENTIALS_PATH = CONFIG_DIR / "credentials.json"
|
|
|
15
15
|
DEFAULT_API_ENDPOINT = "https://api.nexusquant.co"
|
|
16
16
|
DEFAULT_COGNITO_DOMAIN = "auth.lookatwallstreet.com"
|
|
17
17
|
DEFAULT_COGNITO_CLIENT_ID = "5o7hhd4hn4birr8c29vndehiat"
|
|
18
|
-
# 与 nexusquant-cli
|
|
19
|
-
|
|
18
|
+
# 与 nexusquant-cli 共用同一个 Cognito app client(5o7hhd4hn4birr8c29vndehiat),
|
|
19
|
+
# 该 client 只登记了 8251/callback。沿用 8251 以避免 redirect_mismatch;
|
|
20
|
+
# 两套 CLI 不会同时跑 OAuth 回调,端口冲突可忽略。需要错开时用 NEXUS_AGENT_REDIRECT_URI 覆盖
|
|
21
|
+
# (但所选端口必须已加入 app client 的 Allowed callback URLs)。
|
|
22
|
+
DEFAULT_REDIRECT_URI = "http://127.0.0.1:8251/callback"
|
|
20
23
|
|
|
21
24
|
|
|
22
25
|
def _env(name: str, default: str | None = None) -> str | None:
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "nexus-agent-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.5.0"
|
|
8
8
|
description = "Nexus internal agent ops CLI — pull run params, push analysis results, send notifications. Admin only."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|