Metadata-Version: 2.5
Name: nexus-agent-cli
Version: 0.11.0
Summary: Nexus internal agent ops CLI — pull run params, push analysis results, drive the Discord KB / blog-draft data plane, send notifications. Admin only.
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: platformdirs>=4.2
Requires-Dist: rich>=13.7
Requires-Dist: typer>=0.12
Description-Content-Type: text/markdown

# nexus-agent-cli

Nexus **内部** agent 运维 CLI。用于把"每周分析任务"从 AWS 跑逐步迁到本地 Claude 跑：
拉取运行参数、回传分析结果、发通知 —— 让 S3 / SES / Google Sheets 凭证只留在服务端。

> 与 strategy provider 用的 `nexusquant-cli` 区分开：这是 **admin 专用** 工具。
> 需要 Cognito `custom:userType=admin`。

## 安装

```bash
pip install nexus-agent-cli
```

本地开发（可编辑安装）：

```bash
git clone https://github.com/lookatwallstreet/nexus-agent-cli.git
cd nexus-agent-cli && pip install -e .
```

## 登录（一次）

```bash
nexus-agent auth            # 打开浏览器走 Cognito 登录（admin 账号）
nexus-agent auth --status   # 查看本机登录状态
nexus-agent auth --refresh  # 刷新 token
nexus-agent auth --logout   # 清除本机 token
```

凭据存放在当前用户的 config 目录（如 macOS `~/Library/Application Support/nexus-agent-cli/credentials.json`），权限 `0600`，与 nexusquant-cli 互不影响。

## 命令

```bash
# 拉取本周运行参数（tickers + 日期区间）
nexus-agent run-config
nexus-agent run-config --tickers TQQQ,SOXL,IBIT

# 回传市场分析报告（markdown）
nexus-agent push-market-analysis --ticker tqqq --file report_cn.md --language zh

# 回传投资组合推荐（英文必填，中文可选）
nexus-agent push-portfolio --file portfolio.md --file-cn portfolio_cn.md \
    --time-range 2026-06-01_2026-06-14

# 回传回测聚合数据（至少一个档位）
nexus-agent push-backtest --ticker tqqq \
    --aggressive aggressive_aggregated.json \
    --conservative conservative_aggregated.json \
    --normal normal_aggregated.json

# 回读 S3（均为只读，登录即可）：最新回测 / 市场分析报告 / 组合推荐
nexus-agent get-backtest --ticker tqqq --out tqqq_backtest.json
nexus-agent get-market-analysis --ticker tqqq --out tqqq_market.json
nexus-agent get-portfolio --out portfolio.json

# 拉取 S3 上最新的 signal_position 基线 config（Position-Research 阶段用）
nexus-agent pull-config --ticker tqqq --kind normal --out tqqq-normal.json

# 历史版本（by-date）：列出 → 取指定版本
nexus-agent pull-config --ticker tqqq --kind normal --list --since 2026-06-01
nexus-agent pull-config --ticker tqqq --kind normal --date 2026-06-01_to_2026-06-14 --out old.json
nexus-agent get-market-analysis --ticker tqqq --list
nexus-agent get-portfolio --list --since 2026-06-01

# 回传 Position-Research 产出的 3 套仓位 config（≠ push-backtest 的回测聚合）
nexus-agent push-config --ticker tqqq --time-range 2026-06-01_2026-06-14 \
    --aggressive tqqq-aggressive.json \
    --conservative tqqq-conservative.json \
    --normal tqqq-normal.json

# 服务端 SES 发通知邮件
nexus-agent notify --to a@x.com --to b@y.com --subject "Weekly report" --body-file summary.txt
```

### `discord` —— Discord KB / blog draft 数据平面

```bash
# 1) 起草前必调：取当前生效的风格 playbook
#    ⚠️ 没有 active playbook 时返回 409 并退出 1 —— 必须中止本次运行，
#       不允许降级为无风格约束照写
nexus-agent discord playbook-active --out playbook.json --body-out playbook.md

# 2) 取材（窗口由服务端水位线定义，**没有 --date**）
nexus-agent discord clusters-pending --out clusters.json

# 3) 回写富化结果（只改 title/summary/tickers/topics，不碰 blog_state）
nexus-agent discord push-enrichment --file enrichment.json

# 4) 回传草稿（markdown 正文 + 结构化 meta）
#    meta.json: {title, draft_date, source_cluster_ids: [...非空], front_matter?, critic_report?}
nexus-agent discord push-blog-draft --file draft.md --meta meta.json

# 5) 反馈折叠：够 10 条可折叠反馈才跑 playbook_reviser
nexus-agent discord feedback-pending --out feedback.json

# 6) 提交 playbook。meta.json: {rules: [...], change_log, derived_from?, folded_feedback_ids?}
#    ⚠️ 不要在 meta 里放 token_est —— 服务端用 count_tokens 实算（客户端会直接拒绝）
nexus-agent discord push-playbook --file playbook.md --meta meta.json --activate   # v1 才用 --activate
nexus-agent discord push-playbook --file playbook.md --meta meta.json             # 修订版：先落库待审批
nexus-agent discord activate-playbook --version 2                                  # 相当于在 #blog-review 点 ✅
```

⚠️ **`clusters-pending` 刻意没有 `--date`。** 取材窗口是「上次成功运行覆盖到的时刻 →
现在」，不是自然日 —— 按自然日切会出现某天的笔记谁也没收或被收两次。响应里的
`window` 字段会告诉你这次实际覆盖了哪一段。

⚠️ **`push-blog-draft` 目前只落库。** 把草稿发到 `#blog-review` 并挂审批 reaction
尚未接线，响应里的 `discord_delivery` 会是 `not_wired`。所以 `activate-playbook`
是目前唯一的"✅"入口。

### SizingPolicy 与策略注册**不在这里**

发布公式（`policy publish` / `features` / `send-signal`）和注册策略（`strategy register`）
都是**策略提供者**的动作，它们在 **`nexusquant-cli`**（命令入口 `nexusquant`）：

```bash
pip install -U nexusquant-cli
nexusquant policy features
nexusquant policy publish --strategy-id wall001 --ticker TQQQ --file tqqq.json
nexusquant strategy create --strategy-id my_alpha --name "My Alpha" ...
```

校验逻辑在 `nexusquant-sdk` 里，`nexusquant-cli` 依赖它 —— 一份实现，不会与服务端分叉。

> ⚠️ 2026-09-07：这几条命令**曾经错放在本仓库**。它们在这里也能跑，因为服务端的
> `StrategyProviderPermission` 放行 provider **或** admin —— 功能上通，受众是错的。
> 本仓库只保留 admin 侧的 `strategy review-list` / `strategy review`（审核别人提交的策略）。

## 配置（环境变量，均可选）

| 变量 | 默认 | 说明 |
|------|------|------|
| `NEXUS_AGENT_API_ENDPOINT` | `https://api.nexusquant.co` | 后端 host（会自动加 `/api`） |
| `NEXUS_AGENT_API_BASE_URL` | — | 直接覆盖完整 API base（含 `/api`） |
| `NEXUS_AGENT_COGNITO_DOMAIN` | `auth.lookatwallstreet.com` | Cognito Hosted UI 域名 |
| `NEXUS_AGENT_COGNITO_CLIENT_ID` | （内置） | Cognito app client id |
| `NEXUS_AGENT_REDIRECT_URI` | `http://127.0.0.1:8251/callback` | 本地回调（须 localhost+端口；端口必须已加入 Cognito app client 的 Allowed callback URLs） |

例如指向本地后端调试：

```bash
NEXUS_AGENT_API_BASE_URL=http://127.0.0.1:8000/api nexus-agent run-config
```

## 对应后端

这些命令调用 nexus-service `apps/nexus_agent` 的 admin 端点：
`run-config/`、`upload/market-analysis/`、`upload/portfolio-recommendation/`、
`upload/backtest/`、`notify/`。

`discord` 子命令组调用 `apps/discord_kb` 的 7 个 admin 端点
（`clusters/pending/`、`clusters/enrich/`、`blog-drafts/`、`style-playbook/active/`、
`style-playbook/`、`style-playbook/<version>/activate/`、`feedback/pending/`）。
设计与不变量见 nexus-service `docs/DISCORD_KB_AND_BOT_DESIGN.md`（§A.6 是这一组的来源）。

## 作为 SDK 用（不经 CLI）

`nexus_agent_cli.policy_sdk` 是纯标准库的，可以直接 import 进自己的 bot：

```python
from nexus_agent_cli import api_client
from nexus_agent_cli.policy_sdk import PolicySpec, build_signal_payload

row = api_client.list_sizing_policies("wall001", ticker="TQQQ", mode="active")["data"]["policies"][0]
spec = PolicySpec.from_artifact(row, policy_id=row["policy_id"])

body = build_signal_payload(
    strategy_id="wall001", strategy_name="Wall Momentum",
    ticker="TQQQ", price=51.2, direction="buy", quantity=7,
    features={"depth": 3, "atr_pct_14": 0.05, "regime": "bull"},
    spec=spec,                      # 传了它才会真的校验
)
api_client.send_strategy_signal(body)
```

`build_signal_payload` 在拼之前就会抛 `SignalValidationError` —— 拿到的是「哪个 feature、
哪里不对」，而不是发出去之后一条 veto。

## 测试

```bash
python -m unittest discover -s tests
```

只用标准库（`unittest` + `http.server`），不需要额外依赖、不出网、不读凭据目录 ——
`tests/test_api_client_shapes.py` 把请求打到 127.0.0.1 的捕获服务器上，验路径与
JSON body 的形状（这类错误在真实调用里只会表现为服务端 400/422，那时已分不清是
本地拼错还是服务端变了）。

## 发布新版本

```bash
# 1. 版本号有**两个**事实源，必须一起改：
#      pyproject.toml 的 version   → 决定包版本
#      nexus_agent_cli/__init__.py 的 __version__ → 决定 `nexus-agent version` 打印什么
#    只改前者的话，装上新版后 `nexus-agent version` 仍打印旧号 —— 不报错、只是骗人。
#    （2026-08-22 发 0.7.0 时就差点这样发出去。）
# 2. 构建并发布到 PyPI（用 .zshrc 里的 UV_PUBLISH_PASSWORD all-projects token）
rm -rf dist && python -m build
python -m twine check dist/*          # 先查，再传
TWINE_USERNAME=__token__ TWINE_PASSWORD="$UV_PUBLISH_PASSWORD" twine upload dist/*
```

发布前建议核一遍（同一版本号 PyPI 不允许重传，错了只能跳版本）：

```bash
grep '^version' pyproject.toml; grep __version__ nexus_agent_cli/__init__.py   # 两处一致
python -c "import zipfile,glob; print(*sorted(zipfile.ZipFile(glob.glob('dist/*.whl')[0]).namelist()),sep=chr(10))"
```

## 迁移说明

当前 AWS（EventBridge → ECS Fargate）的每周定时运行**保持不变**。本 CLI 提供并行的
"本地驱动"路径，逐步把运行从云上挪到本地，详见 nexus-claude-plugin 的
`nexus-agent / weekly-analysis-run` skill。
