Metadata-Version: 2.5
Name: nexus-agent-cli
Version: 0.7.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`
是目前唯一的"✅"入口。

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

| 变量 | 默认 | 说明 |
|------|------|------|
| `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 是这一组的来源）。

## 测试

```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。
