mcpField
S
setup

setup

github.com/davepoonAgent 工作流3.7k68 次安装2 天前更新
代码仓库

关于 setup

setup 是 claude-ops 插件的交互式配置向导,面向希望配置命令行工具、通信渠道、MCP 和项目注册表的用户。它先检测现有状态,再按用户选择引导配置,并要求所有 Bash 调用在后台运行。

主要功能

  1. 01检测 CLI、渠道、MCP 和项目配置状态
  2. 02引导安装命令行工具与配置集成
  3. 03设置渠道凭据及偏好设置
  4. 04配置项目注册表和后台守护进程
  5. 05将独立诊断任务并行执行

适用场景

  1. 01首次配置 claude-ops 插件
  2. 02补齐尚未配置的通信渠道或 MCP
  3. 03只重新运行特定配置部分
  4. 04为多个项目建立管理注册表

MCP 相关

向导检测已配置的 MCP 服务,并提供配置 Linear、Sentry、Vercel 和 Gmail MCP 服务的选项。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/davepoon/buildwithclaude --skill setup

安装后在 Claude Code 中输入/setup确认已加载。

详细介绍

OPS ► 设置向导

你正在为 claude-ops 插件运行交互式配置向导。用户希望你引导他们完成启用插件所需的每个步骤:安装 CLI、设置环境变量、配置频道、填充项目注册表,以及保存偏好设置。


规则零——每次 Bash 调用都必须使用 run_in_background: true

这是不可妥协的。本设置向导中的每一次 Bash 工具调用都必须设置 run_in_background: true。绝无例外。适用于:

  • 凭据扫描、CLI 安装、OAuth 流程、npm/brew 安装
  • 启动守护进程、重新加载守护进程、launchctl 命令
  • 写入钥匙串、查询 Doppler、查询 Chrome 历史记录
  • 自动链接脚本、同步/回填、冒烟测试
  • 文件写入、配置写入、追加环境变量
  • 任何命令,无论你认为它执行得多快

后台命令运行期间,立即继续执行下一个独立步骤,或向用户提出下一个问题。收到 <task-notification> 后再处理结果。设置向导绝不能显示 (ctrl+b to run in background)——如果用户看到此提示,就说明你违反了此规则。

规则一——静默调用 Bash

每次 Bash 工具调用都必须包含简短的 description 参数(5-10 个词,例如「安装缺失的 CLI」「扫描钥匙串中的 Telegram 凭据」「重新加载守护进程」)。用户看到的是这段描述,而不是原始命令。保持设置过程简洁安静——用户应看到进度标题,而非 shell 脚本。


其他硬性规则:

  • 这是一次_对话_,不是脚本输出。每项决定都使用 AskUserQuestion,如果结构化选择器可以完成,就绝不要用普通文字提问。
  • 如果用户尚未明确同意某项操作,请通过 AskUserQuestion 确认(例如,“全部配置”涵盖所有内容,之后无需逐项确认)。
  • 跳过用户拒绝的部分。不要反复催促。
  • 绝不要自动跳过任何频道或集成。 用户选中的每个频道/服务都必须通过 AskUserQuestion 明确询问,并将跳过作为选项之一。如果找不到凭据,请提供 [手动粘贴] / [深度搜寻] / [跳过] 选项。如果冒烟测试失败,请询问用户是重试、重新配置还是跳过。用户选择“跳过”选项是唯一可接受的跳过方式。扫描未找到任何内容时,不要悄悄略过某项服务——此时更应该询问用户。
  • 先展示已配置的内容,让用户只需补齐缺漏。
  • 除非用户在本次会话中明确提供,否则绝不要在输出中显示用户的真实姓名或电子邮件。 不要从记忆、现有配置或环境变量中读取信息来填写显示名称。
  • 每次 AskUserQuestion 调用最多提供 4 个选项。工具架构要求 options 数组中的项目数 <=4。如果某个步骤列出的选项超过 4 个,先过滤掉已配置的项目,再将其余选项分成多个连续调用,每次最多 4 个选项,并按逻辑分组。使用 [更多选项...] 作为最后一个选项,以衔接下一组选项。
  • 尽可能并行运行所有诊断/探测命令。一次消息中使用多次 Bash 工具调用。独立的探测不得顺序运行(例如,gog auth status、wacli doctor 和钥匙串侦测应同时运行)。
  • 所有写入只能写入以下路径,不得写入其他位置:
    • $PREFS_PATH — 每用户偏好设置和密钥。解析为 ${CLAUDE_PLUGIN_DATA_DIR:-$HOME/.claude/plugins/data/ops-ops-marketplace}/preferences.json。位于 Claude Code 的插件数据目录,因此在重新安装插件和升级版本后仍会保留。绝不会提交到 git。
    • ${CLAUDE_PLUGIN_ROOT}/scripts/registry.json — 每用户项目注册表(在源代码仓库中被 git 忽略)。如果父目录不存在,请运行 mkdir -p 创建。
    • ${CLAUDE_PLUGIN_ROOT}/.mcp.json — 仅用于添加 ${user_config.*} 占位符,绝不可写入硬编码令牌。
    • 用户的 shell 配置文件(~/.zshrc 等)— 只能追加,不得重写。
  • 每个向导步骤开始时,确保 $PREFS_PATH 的父目录存在:mkdir -p "$(dirname "$PREFS_PATH")"。Claude Code 安装插件时会创建 ~/.claude/plugins/data/ops-ops-marketplace/,但不要依赖这一点。

Agent Teams 支持

如果已设置 CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1,且需要同时启动多个“深度搜寻”凭据 Agent,请使用 Agent Teams。这可以:

  • 并行安排凭据侦察 Agent 搜寻 Doppler、钥匙串、浏览器配置和密码管理器
  • 让 Agent 共享发现结果(例如,Doppler Agent 找到不完整的配置后,钥匙串 Agent 就知道可跳过该服务)
  • 在搜寻过程中调整指示:“已找到 Telegram 令牌,停止搜寻该项”

团队设置(仅在标志已启用且触发了多个深度搜寻时):

TeamCreate("setup-hunters")
Agent(team_name="setup-hunters", name="hunt-telegram", model="haiku", ...)
Agent(team_name="setup-hunters", name="hunt-sentry", model="haiku", ...)
Agent(team_name="setup-hunters", name="hunt-shopify", model="haiku", ...)

每个 Agent 都会汇报发现结果。汇总结果后,呈现给用户确认。

如果未设置该标志,请使用彼此独立的后台子 Agent,并设置 run_in_background: true。


设置 Agent 委派模式

如果用户在设置期间提出复杂的集成相关问题(例如,“/ops:ecom 如何处理多店铺设置?”),设置 Agent 可以加载相关技能的 SKILL.md,以获取更深入的背景信息:

cat "${CLAUDE_PLUGIN_ROOT}/skills/ops-ecom/SKILL.md"

下面的每个子步骤都包含一个指向相关技能文件的 > **深入了解:** 指引。请遵循这些指引,而不是在本向导中重复操作细节。


步骤 0 — 预检(在你阅读时于后台运行)

${CLAUDE_PLUGIN_ROOT}/bin/ops-setup-preflight &>/dev/null &

预检数据:所有探测结果都缓存在 /tmp/ops-preflight/。运行任何诊断命令之前,请检查结果是否已存在于此处:

  • CLI 状态:cat /tmp/ops-preflight/clis.txt
  • Slack:cat /tmp/ops-preflight/slack.json
  • Telegram:cat /tmp/ops-preflight/telegram.txt
  • gog/Gmail:cat /tmp/ops-preflight/gog-gmail.json
  • gog/Calendar:cat /tmp/ops-preflight/gog-cal.json
  • WhatsApp:cat /tmp/ops-preflight/wacli-doctor.json 和 wacli-chats.json
  • MCP 服务:cat /tmp/ops-preflight/mcp-servers.txt
  • GitHub:cat /tmp/ops-preflight/gh-auth.txt
  • AWS:cat /tmp/ops-preflight/aws-identity.json
  • 项目:cat /tmp/ops-preflight/projects.txt
  • 现有注册表:cat /tmp/ops-preflight/existing-registry.json
  • 现有偏好设置:cat /tmp/ops-preflight/existing-prefs.json
  • Doppler:cat /tmp/ops-preflight/doppler.json

读取之前,请等待 /tmp/ops-preflight/.complete 存在(通常会在 2–3 秒内准备就绪)。绝不要重新运行已有缓存结果的探测——请改为读取缓存文件。


步骤 0b — 检测当前状态

运行检测器并解析其 JSON 输出(或在有预检缓存时读取缓存):

${CLAUDE_PLUGIN_ROOT}/bin/ops-setup-detect 2>/dev/null

如果未设置 CLAUDE_PLUGIN_ROOT,则回退到最新安装的缓存目录 ~/.claude/plugins/cache/ops-marketplace/ops/<latest-version>/。将解析出的路径存储为 PLUGIN_ROOT,以供本次会话后续使用。

同时解析一次 PREFS_PATH,并在所有地方重复使用:

PREFS_PATH="${CLAUDE_PLUGIN_DATA_DIR:-$HOME/.claude/plugins/data/ops-ops-marketplace}/preferences.json"
mkdir -p "$(dirname "$PREFS_PATH")"

向用户打印简洁的状态摘要,每个类别占一行:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 OPS ► SETUP WIZARD
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 Shell:       zsh → ~/.zshrc
 Core CLIs:   ✓ jq  ✓ git  ✓ gh  ✓ aws  ✓ node
 Channels:    ✓ wacli  ✓ gog  ○ telegram (no token)
 Secrets:     ✓ doppler (project: my-app, config: dev)
 MCPs:        ✓ linear  ✓ sentry  ○ slack  ○ vercel
 Registry:    19 projects
 Preferences: not set
──────────────────────────────────────────────────────

已存在/已设置时使用 ✓,缺失/未设置时使用 ○,损坏时使用 ✗。


第 1 步 — 询问要配置哪些部分

首先,提供一个快速的“全部设置”选项:

How would you like to run setup?
  [Set up everything — install CLIs, configure all channels, MCPs, registry, daemon, preferences (Recommended)]
  [Pick sections — choose which parts to configure]
  [Re-run a specific section — I know what I need]

如果用户选择“全部设置”,请选择所有批次中的全部部分,并按顺序运行(第 2 步 → 2b → 2c → 3 → 4 → 5 → 5b → 6 → 7),跳过任何已全部配置的部分。在每个步骤中,尽可能使用“全部配置”快速路径。

如果用户选择“重新运行特定部分”,显示一个 AskUserQuestion,列出各部分名称(cli、daemon、channels、mcp、registry、prefs、env、ecom、mktg、voice、revenue),并直接跳转到对应步骤。

如果用户选择“选择部分”,继续执行下面的分批选择。

使用 AskUserQuestion 并设置 multiSelect: true。仅提供需要处理的部分(跳过已显示为绿色的部分)。由于 AskUserQuestion 最多允许 4 个选项,请按逻辑分组:

批次 1 — 核心设置(尽早运行,以便 daemon 在你完成其余设置时预热缓存):

选项标题说明
安装 CLIcli通过 Homebrew 安装缺失的命令行工具
后台 daemondaemon尽早安装 ops-daemon — 在其余设置运行时预热简报缓存
配置 MCPmcp启用 Linear、Sentry、Vercel、Gmail MCP 服务
构建注册表registry注册 Claude 应管理的项目

批次 2 — 渠道与插件:

选项标题说明
配置渠道channels设置 Telegram、WhatsApp、Email、Slack 的令牌
配套插件plugins安装 GSD,以跟踪项目路线图
保存偏好设置prefs所有者姓名、时区、默认优先级
Shell 环境env在 shell 配置文件中导出 CLAUDE_PLUGIN_ROOT

批次 3 — 附加项(仅在尚未配置时显示):

选项标题说明
配置电商ecom设置 Shopify 商店 URL 和管理员令牌、ShipBob
配置营销mktg设置 Klaviyo、Meta Ads、GA4、Search Console 密钥
配置语音voice设置 Bland AI、ElevenLabs、Groq API 密钥
配置收入revenue设置 Stripe 和 RevenueCat 密钥,以实时跟踪 MRR

将每个批次作为单独的 AskUserQuestion 调用呈现。跳过所有项目均已显示为绿色的批次。收集所有批次中的选择,并按顺序运行每个已选部分。


第 2 步 — 安装 CLI(如果已选择)

如果缺少多个 CLI,先提供批量安装选项:

Missing CLIs detected: jq, gh, wacli. What would you like to do?
  [Install all missing CLIs (Recommended)]
  [Pick which to install]
  [Skip CLI installation]

如果用户选择“全部安装”,则按顺序安装所有缺失的工具,无需进一步询问。如果选择“选择要安装的工具”,则逐个询问:

Install jq?           [Yes, install now] [Skip]
Install gh?           [Yes, install now] [Skip]
Install wacli?        [Yes, install now] [Skip — manual install required]

对于每个 Yes,运行:

${PLUGIN_ROOT}/bin/ops-setup-install <tool>

报告成功或失败。如果 macOS 上缺少 Homebrew,请停止并告知用户先从 https://brew.sh 安装;不要尝试自动安装 brew。

安装后,重新运行 ops-setup-detect,以刷新状态后再继续。


第 2b 步 — 配套插件(如果已选择)

GSD(Get Shit Done)

GSD 是一款第三方 Claude Code 插件,可添加项目路线图跟踪功能。安装后,claude-ops 仪表板(/ops:go、/ops:projects、/ops:next、/ops:yolo)会自动显示每个项目的活动阶段、进度和后续操作。未安装时,这些部分会被省略。

检查 GSD 是否已安装:

find ~/.claude -name "gsd-progress" -path "*/skills/*" 2>/dev/null | head -1 | grep -q . && echo "installed" || echo "not_installed"

如果尚未安装,通过 AskUserQuestion 询问:

GSD adds project roadmap tracking to your ops dashboards.
  /ops:go shows active phases and progress per project
  /ops:projects shows GSD state alongside CI/PR status
  /ops:next factors in GSD work priority

  [Install GSD (latest)] [Skip — I don't need roadmap tracking]

安装时,直接运行命令——不要让用户手动运行:

# Install GSD in one shot — no user intervention needed
claude plugin marketplace add gsd-build/get-shit-done 2>/dev/null && \
claude plugin install gsd@gsd-build-get-shit-done 2>/dev/null

如果 claude CLI 不在路径中,则使用插件缓存机制作为备用方案:

# Direct marketplace clone fallback
GSD_MARKETPLACE_DIR="$HOME/.claude/plugins/marketplaces/gsd-build-get-shit-done"
if [ ! -d "$GSD_MARKETPLACE_DIR" ]; then
  git clone https://github.com/gsd-build/get-shit-done.git "$GSD_MARKETPLACE_DIR" 2>/dev/null
fi

报告成功或失败。在 $PREFS_PATH 中记录 plugins.gsd = "installed"。

如果用户跳过:

Skipped GSD. Install later with: /plugin marketplace add gsd-build/get-shit-done

步骤 2c — 后台守护进程(提前安装,预热缓存)

为什么要这么早安装守护进程? 在其余设置步骤进行的同时运行守护进程,可让它开始预热简报缓存(ops-gather 收集的基础设施、git、PR 和 CI 结果),这样用户到达步骤 7 并运行 /ops:go 时,简报已缓存,加载时间会从 10 秒缩短到 3 秒以内。依赖渠道的服务(wacli-sync、message-listener、inbox-digest、store-health)会在步骤 5b 中,待相应渠道配置完成后再添加。

平台支持

后台守护进程自带 launchd 集成(仅限 macOS)。尝试安装前先检测平台:

case "$(uname -s)" in
  Darwin)                OS=macos ;;
  Linux)                 grep -qi microsoft /proc/version 2>/dev/null && OS=wsl || OS=linux ;;
  MINGW*|MSYS*|CYGWIN*)  OS=windows ;;
  *)                     OS=unknown ;;
esac
  • macOS(OS=macos):继续执行下方完整的 launchctl bootstrap 流程。

  • Linux / WSL(OS=linux|wsl):无法使用 launchctl。守护进程脚本(${CLAUDE_PLUGIN_ROOT}/scripts/ops-daemon.sh)可在 bash 下正常运行,但要将其安装为用户服务,需要使用 systemd --user(Linux)或自定义的 cron/at 包装程序(WSL)。这项工作不在本补丁范围内——列为后续工作。目前请打印:

    ○ Background daemon — skipped (Linux/WSL install via systemd --user is pending; see docs/daemon-guide.md).
      You can still launch it manually with:  nohup ${CLAUDE_PLUGIN_ROOT}/scripts/ops-daemon.sh &
    

    在 $PREFS_PATH 中写入 daemon.enabled = false 和 daemon.skip_reason = "platform:<os>",然后继续执行步骤 3。

  • Windows(原生,OS=windows):不会安装守护进程。打印 ○ Background daemon — not supported on native Windows. Use WSL or run ops-daemon.sh manually.,然后继续。

如果 OS=macos,检查守护进程是否已安装:

launchctl print gui/$(id -u)/com.claude-ops.daemon 2>/dev/null | head -1

如果已经加载,打印 ✓ Background daemon already running — will reconcile services in Step 5b.,然后跳至步骤 3。

否则,通过 AskUserQuestion 询问:

Install the ops background daemon now?
  Starts pre-warming briefing cache while you finish the rest of setup.
  Auto-heals on failure. Single launchd agent (com.claude-ops.daemon).
  Channel services (wacli-sync, message-listener) added after channels are set up.
  [Yes — install now]  [Skip — I'll run it manually later]

如果选择 Yes,运行安装(使用 run_in_background: true——RULE ZERO):

# Always resolve CLAUDE_PLUGIN_ROOT to the CURRENT installed version — never hardcode a version number.
# If CLAUDE_PLUGIN_ROOT is not set, detect it from the plugin cache:
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(ls -d "$HOME/.claude/plugins/cache/ops-marketplace/ops"/*/ 2>/dev/null | sort -V | tail -1)}"
DAEMON_SCRIPT="${PLUGIN_ROOT}/scripts/ops-daemon.sh"
chmod +x "$DAEMON_SCRIPT"
PLIST_TEMPLATE="${PLUGIN_ROOT}/scripts/com.claude-ops.daemon.plist"
PLIST_DEST="$HOME/Library/LaunchAgents/com.claude-ops.daemon.plist"
DATA_DIR="${CLAUDE_PLUGIN_DATA_DIR:-$HOME/.claude/plugins/data/ops-ops-marketplace}"
LOG_DIR="$DATA_DIR/logs"
mkdir -p "$LOG_DIR"

# Resolve bash 4+ (required for associative arrays in ops-daemon.sh).
# macOS ships bash 3; Homebrew installs bash 5 at /opt/homebrew/bin/bash.
BASH_PATH="/bin/bash"
if [[ -x /opt/homebrew/bin/bash ]]; then
  BASH_PATH="/opt/homebrew/bin/bash"
elif [[ -x /usr/local/bin/bash ]]; then
  BASH_PATH="/usr/local/bin/bash"
fi

# Generate plist
sed -e "s|__DAEMON_SCRIPT_PATH__|$DAEMON_SCRIPT|g" \
    -e "s|__BASH_PATH__|$BASH_PATH|g" \
    -e "s|__PLUGIN_ROOT__|$PLUGIN_ROOT|g" \
    -e "s|__LOG_DIR__|$LOG_DIR|g" \
    -e "s|__HOME__|$HOME|g" \
    "$PLIST_TEMPLATE" > "$PLIST_DEST"

# Write a minimal initial services config — only the services that don't depend on
# channels yet. `briefing-pre-warm` runs ops-gather every 2 minutes so the next
# /ops:go is instant. `memory-extractor` runs but stays idle until channels exist.
SERVICES_CONFIG="$DATA_DIR/daemon-services.json"
cat > "$SERVICES_CONFIG" <<JSON
{
  "services": {
    "briefing-pre-warm": {
      "enabled": true,
      "command": "${CLAUDE_PLUGIN_ROOT}/bin/ops-gather",
      "cron": "*/2 * * * *",
      "_note": "Pre-warms /ops:go cache. Runs every 2 minutes."
    },
    "memory-extractor": {
      "enabled": true,
      "command": "${CLAUDE_PLUGIN_ROOT}/scripts/ops-memory-extractor.sh",
      "cron": "*/30 * * * *",
      "health_file": "~/.claude/plugins/data/ops-ops-marketplace/memories/.health",
      "_note": "Idle until channels are configured; will extract profiles once wacli/gog are live."
    }
  }
}
JSON

# Remove the old standalone wacli keepalive if present
launchctl bootout gui/$(id -u)/com.claude-ops.wacli-keepalive 2>/dev/null || true
rm -f "$HOME/Library/LaunchAgents/com.claude-ops.wacli-keepalive.plist"

# Load daemon in background — does NOT block the wizard
launchctl bootout gui/$(id -u) "$PLIST_DEST" 2>/dev/null || true
launchctl bootstrap gui/$(id -u) "$PLIST_DEST"

在 $PREFS_PATH 中写入 daemon.enabled = true 和 daemon.installed_at_step = "2c",以便步骤 5b 知道应协调服务,而不是重新安装。

打印:

✓ Background daemon — installed. Pre-warming briefing cache in parallel while you finish setup.
  Channel-dependent services will be added after channels are configured (Step 5b).

立即继续执行步骤 3——不要等待守护进程确认启动。健康文件检查推迟到步骤 5b。

深入了解: 如需了解后台守护进程的完整操作说明、CLI 参考和故障排除方法,请参阅 ${CLAUDE_PLUGIN_ROOT}/docs/daemon-guide.md。如果设置 Agent 需要比此向导更深入的信息,可直接加载该文件。


步骤 3 — 配置频道(如已选择)

首先,在逐项选择前,先提供快速“配置全部”选项。使用 AskUserQuestion:

How would you like to configure channels and integrations?
  [Configure all — set up every available channel and service (Recommended)]
  [Pick individually — choose which channels to configure]
  [Skip all — configure channels later]

如果用户选择“配置全部”,则按顺序运行以下所有频道子流程(Telegram → WhatsApp → Email → Slack → Notion → Calendar → Doppler → Vault),跳过已配置的频道。如果用户选择“全部跳过”,则进入步骤 4。

如果用户选择“逐项选择”,则使用带有 multiSelect: true 的 AskUserQuestion 询问要配置哪些频道。由于 AskUserQuestion 最多允许 4 个选项,请分成两组。跳过已配置的频道(仅显示需要处理的频道)。

第 1 组 — 消息:

选项标题描述
Telegramtelegram用于 /ops-comms telegram 的 Bot token + 所有者 ID
WhatsAppwhatsappwacli doctor + 自动修复 + 回填
Emailemail用于 /ops-inbox email 的 gog CLI → Gmail MCP 备用方案
SlackslackSlack MCP 服务(由 Claude Code 管理)

第 2 组 — 知识与服务:

选项标题描述
NotionnotionNotion MCP — 工作区搜索、评论、任务、知识库
Calendarcalendargog calendar → Google Calendar MCP 备用方案 — 为简报提供日程上下文
Dopplerdoppler密钥管理器 — 为所有 ops 技能设置默认项目 + 配置
Vaultvault密码管理器 — 1Password、Dashlane、Bitwarden 或 macOS Keychain

将每组作为单独的 AskUserQuestion 调用呈现。若某组中的所有项目均已配置,则跳过该组。对于每个选中的频道,运行对应的子流程。


通用:建议安装前检测主机操作系统

claude-ops 向导可在 macOS、Linux(所有主流发行版 + WSL)和 Windows(原生环境 + WSL)上运行。打印任何安装命令前,技能 MUST 检测主机操作系统并选择适用于该操作系统的命令。绝不要向 Windows 用户打印 brew install … 命令,也绝不要向 macOS 用户打印 winget install … 命令。

最简检测代码片段(bash — 适用于 macOS、Linux、WSL、MSYS/Cygwin):

case "$(uname -s)" in
  Darwin*) OS=macos ;;
  Linux*)
    if grep -qi microsoft /proc/version 2>/dev/null; then OS=wsl
    elif [ -f /etc/os-release ]; then
      . /etc/os-release
      case "$ID" in
        arch|manjaro) OS=arch ;;
        fedora|rhel|centos|rocky|almalinux) OS=fedora ;;
        debian|ubuntu|pop|linuxmint) OS=debian ;;
        alpine) OS=alpine ;;
        opensuse*|sles) OS=suse ;;
        *) OS=linux ;;
      esac
    else OS=linux; fi ;;
  MINGW*|MSYS*|CYGWIN*) OS=windows ;;
  *) OS=unknown ;;
esac

按以下顺序选择包管理器(选第一个可用的):

  1. brew(macOS + Linuxbrew)— macOS 上的首选。
  2. 原生操作系统包管理器 — apt-get(debian/ubuntu)、dnf(fedora/rhel)、pacman(arch)、zypper(suse)、apk(alpine)。
  3. Windows 上依次使用 winget(Windows 10 1809+)→ scoop → choco → 最后才从源代码构建。

如果首选包管理器未安装,则改用下一个可用选项,不要中止流程。以下每个 AskUserQuestion 的“[立即安装 — …]”提示都使用适配操作系统的命令表 — 仅打印与检测到的操作系统相匹配的行。

权威的跨操作系统检测逻辑请复用 bin/ops-setup-detect(其 JSON 输出包含 os、pkg_mgr、arch、keyring_backend、shell、browser_profiles_found)。


通用:优先使用 OAuth,而非手动输入令牌

只要某个渠道支持基于浏览器的 OAuth 流程,就应优先提供该选项,并将手动输入令牌作为后备方案。OAuth 更安全(权限范围明确、可撤销,且无需将密钥存入 dotfiles),而且通常能让用户更快完成操作。

渠道OAuth 方式手动后备方案
Email (gog)gog auth add <email> --services gmail,calendar,drive,contacts,docs,sheets(浏览器)不适用 — gog 仅支持 OAuth
Calendar (gog)使用相同的 gog auth add,并在 --services 中包含 calendar不适用
Slackclaude mcp add slack(通过 Claude Code 处理 OAuth)通过自动扫描获取 Bot 令牌 + 手动粘贴
Linearclaude mcp add linearAPI 密钥
Sentryclaude mcp add sentryDSN / 身份验证令牌
Vercelclaude mcp add vercel个人访问令牌
Telegram❌ 不支持 OAuth(Bot API 的设计决定其只能使用令牌)自动扫描 + 手动粘贴(唯一选项)
WhatsApp通过 wacli auth 扫描二维码配对(使用体验类似 OAuth)不适用 — 仅支持配对会话

当渠道支持 OAuth 时,默认的 AskUserQuestion 应优先引导用户使用该方式:

[Connect via OAuth (recommended)]  [Enter a token manually]  [Skip]

只有当用户选择“手动”时,或渠道(Telegram、本地工具)不支持 OAuth 时,才继续执行下方的凭据自动扫描流程。



通用凭据自动扫描

在向用户索取任何凭据之前,先按以下顺序执行扫描。此要求适用于所有步骤——渠道、电商、营销、语音和 MCP。绝不能让用户去查找已经存在于其系统上的密钥。

重要 — 搜索所有来源后再报告。 在一个批次中运行所有扫描来源(下方的 1-10 项),然后再分析汇总结果。只检查环境变量和 Dashlane 后,不要就报告“未找到凭据”——Chrome 历史记录、.env 文件、Doppler 和钥匙串中可能已经有答案。如果缺少 API 令牌,但已找到商店或服务身份信息(例如在 Chrome 历史记录中发现商店 URL,或在 Dashlane 中找到登录信息),请报告发现的内容,并跳转到令牌步骤,同时预填身份信息。用户说“找一下”或“检查所有可用来源”,意味着你的搜索还不够彻底——绝不要让用户查找你能通过程序找到的信息。

对于每个变量名(例如 TELEGRAM_BOT_TOKEN、SHOPIFY_ACCESS_TOKEN、KLAVIYO_API_KEY):

  1. 当前 shell 环境 — printenv <VAR>。运行中的 shell 会继承导出的变量、Doppler 注入的变量以及通过 dotenv 加载的文件。这里最有可能获取正确值。
  2. Shell 配置文件 — 在 ~/.zshrc、~/.bashrc、~/.zprofile、~/.config/fish/config.fish、~/.envrc(direnv)中搜索 <VAR>= 或 export <VAR>=。在值旁边显示文件路径,让用户知道它的来源。
  3. Doppler(所有项目) — 如果 command -v doppler 执行成功:
    for proj in $(doppler projects --json 2>/dev/null | jq -r '.[].slug'); do
      doppler secrets --project "$proj" --config prd --json 2>/dev/null | \
        jq -r --arg var "$VAR" --arg proj "$proj" \
        'to_entries[] | select(.key == $var) | "\(.value.computed) (doppler:\($proj)/prd)"'
    done
    
    如果 prd 失败,也尝试默认项目配置(dev/staging)。显示来源信息 (project: <slug>, config: <config>)。
  4. Dashlane CLI — 如果 command -v dcli 执行成功:
    dcli password "$SERVICE_KEYWORD" --output json 2>/dev/null
    
    映射服务关键词:shopify → SHOPIFY 变量,klaviyo → KLAVIYO 变量,bland / bland-ai → BLAND 变量,等等。
  5. macOS 钥匙串 — 针对特定服务:
    security find-generic-password -s "$SERVICE" -w 2>/dev/null
    
    使用符合常见模式的服务名称(例如 shopify-admin-token、klaviyo-api-key、bland-ai-api-key)。
  6. OpenClaw 配置 — 如果 ~/.openclaw/openclaw.json 存在:
    jq -r --arg var "$VAR" '.agents.defaults.env[$var] // empty' ~/.openclaw/openclaw.json 2>/dev/null
    
  7. 已安装的 MCP 配置 — 读取检测器找到的每个 .mcp.json。对于每个服务条目,检查 .env 和 .args 中是否包含该变量名,或是否有看起来像目标值的字面值。将 MCP 服务名称显示为来源。
  8. 插件偏好设置 — 在现有的 $PREFS_PATH 中检查相关区段下的键(例如 .ecom.shopify.admin_token、.marketing.klaviyo.api_key)。如果找到且不是 doppler: 引用,则将其显示为来源。
  9. Chrome 历史记录 — 对于具有网页管理界面的服务(Shopify、Klaviyo 等),查询 Chrome 的 History SQLite 数据库,查找可用于识别账户/商店的管理页面 URL:
    sqlite3 ~/Library/Application\ Support/Google/Chrome/Default/History \
      "SELECT DISTINCT url FROM urls WHERE url LIKE '%<service_domain>%' ORDER BY last_visit_time DESC LIMIT 10" 2>/dev/null
    
    从 URL 中提取标识符(例如 *.myshopify.com 商店标识和账户 ID)。
  10. 项目 .env 文件 — 扫描 ~/Projects/*/.env*,查找变量名或服务域名模式。这些文件通常包含可复用于其他项目的凭据。

自动扫描时的环境变量 → 服务关键词映射:

变量名称服务关键词(Dashlane/Keychain)
SHOPIFY_ACCESS_TOKEN、SHOPIFY_ADMIN_TOKEN、SHOPIFY_STORE_URLshopify
KLAVIYO_API_KEY、KLAVIYO_PRIVATE_KEYklaviyo
META_ACCESS_TOKEN、FACEBOOK_ACCESS_TOKEN、META_AD_ACCOUNT_IDmeta、facebook
GA4_PROPERTY_ID、GA_MEASUREMENT_IDgoogle-analytics、ga4
BLAND_AI_API_KEY、BLAND_API_KEYbland-ai、bland
ELEVENLABS_API_KEYelevenlabs
GROQ_API_KEYgroq
SHIPBOB_ACCESS_TOKENshipbob
TELEGRAM_BOT_TOKEN、TELEGRAM_API_ID、TELEGRAM_API_HASHtelegram
SLACK_BOT_TOKEN、SLACK_MCP_XOXC_TOKENslack

使用 AskUserQuestion 呈现发现结果(每次调用最多 4 个选项):

Found credential for SHOPIFY_ACCESS_TOKEN:
  [A] shell env + ~/.zshrc + Dashlane — shpat_508b...682e  (matched across 3 sources)
  [B] Doppler (project: mystore, config: prd) — shpat_9f2c...a17b  (different!)
  [C] Enter a different one

提示规则:

  • 显示任何令牌的前 8 个和后 4 个字符,绝不显示完整值。
  • 始终将匹配的来源合并为一个选项,并附上 (matched in env + ~/.zshrc + Dashlane)。这对于将选项数控制在 4 个以内至关重要。
  • 如果来源之间的值不一致,则将每个不同的值显示为单独选项。如果不同的值超过 3 个(很少见),则分多次调用,并使用 [More sources...]。
  • ${user_config.*}、<your-token>、CHANGE_ME 等占位值或空字符串均视为未找到。
  • 始终将 [Enter a different one] 作为最后一个选项。
  • 如果没有任何来源包含值,则通过 AskUserQuestion 向用户提供以下选项:
<SERVICE_NAME> — no credential found after scanning all sources.

  [I have it — let me paste it]
  [Deep hunt — spawn an agent to find it]
  [Skip this service]
  • "I have it" → 说明如何在服务的控制面板中找到凭据,然后接受自由文本输入。

  • "Deep hunt" → 在后台启动一个 Haiku 子 Agent,并向其下达以下任务:

    查找 <CREDENTIAL_NAME>(<SERVICE_NAME>)。请全面搜索:
    1. 所有 Doppler 项目和配置(dev/stg/prd/ci)
    2. ~/Projects/ 下所有 .env* 文件(递归搜索)
    3. macOS 钥匙串(使用 security find-generic-password,并尝试多种服务名称模式)
    4. Dashlane CLI(dcli password <service>,以及相关关键词)
    5. Chrome 浏览器——通过 Kapture/Playwright MCP 前往 <service_admin_url>,如有需要则登录,并从设置页面提取凭据
    6. 所有 shell 配置文件(~/.zshrc、~/.bashrc、~/.zprofile、~/.envrc、~/.config/fish/*)
    7. 1Password CLI(如果可用,使用 op item list --tags <service>)
    8. AWS Secrets Manager / SSM Parameter Store(如果 aws cli 已通过身份验证)
    
    如果找到,请返回凭据值;否则,请详细报告检查过的所有位置及发现的内容(部分匹配、过期令牌、格式错误的值)。
    

    使用 Agent(subagent_type: "general-purpose", model: "haiku"),并设置 run_in_background: true。在搜索进行时,继续处理下一个服务。Agent 返回后,向用户展示搜索结果并请其确认。

  • “跳过” → 在 $PREFS_PATH 中记录为已跳过,然后继续。

用户选择后,以选定的值为准,并在征得用户同意后,可选择将其同步到其他来源(例如:“也要更新 ~/.zshrc 和 Doppler 以保持一致吗?”)。默认不进行同步,除非用户主动选择同步。


共享:凭据自动扫描

本节专门适用于频道令牌(Telegram、Slack)。 其他所有步骤请参阅上方的通用凭据自动扫描部分——所有地方都适用相同的模式。

在提示用户粘贴任何令牌之前,请先使用上方的通用凭据自动扫描流程进行扫描。向用户展示找到的内容,并请其确认或覆盖。未经确认,切勿直接使用令牌。


3a — Telegram(通过 ops-telegram-autolink 使用用户账号进行身份验证)

启动 Telegram 流程前,务必先询问——即使用户选择了“所有频道”也不例外。使用 AskUserQuestion:

Set up Telegram personal account access?
  [Yes — enter my phone number and authenticate]
  [Skip Telegram]

如果用户跳过,请在 $PREFS_PATH 中记录 channels.telegram = "skipped",然后继续。切勿默默将 Telegram 标记为未配置——明确记录为跳过,可防止后续运行时状态标题将 ○ telegram (no token) 显示为待处理事项。

速率限制保护:启动前,检查 $PREFS_PATH 中的 channels.telegram 是否为对象,且 .status == "rate_limited"——使用类型守卫:if (channels.telegram | type) == "object" and .channels.telegram.status == "rate_limited"(jq:if (.channels.telegram | type) == "object" then .channels.telegram.status else "skipped" end)。如果 retry_after 的时间尚未到,请使用 AskUserQuestion 向用户展示:

Telegram is rate-limited until [time]. What would you like to do?
  [Wait and retry after cooldown — re-run /ops:setup telegram after [time]]
  [Skip Telegram for now]

在速率限制期间,切勿尝试 send_password——它会立即失败,并可能延长冷却时间。如果用户选择跳过,请在 $PREFS_PATH 中记录为跳过,然后继续下一个频道。

机器人无法读取用户的私信,因此 /ops-inbox telegram 需要使用个人账号的 MCP。该插件提供了 bin/ops-telegram-autolink.mjs,其功能如下:

  1. 扫描各个探查源(钥匙串 → ~/.claude.json → shell 配置文件 → Doppler),查找之前提取的 TELEGRAM_API_ID / TELEGRAM_API_HASH / TELEGRAM_SESSION。
  2. 如果未找到,则向 my.telegram.org 发送普通 HTTP 请求(不使用浏览器——my.telegram.org 使用服务端 HTML,无需 JS),使用桥接文件中的手机验证码登录,按需创建应用,并提取 api_id + api_hash。
  3. 运行 gram.js client.start() 生成会话字符串,通过同一文件传递第二个验证码。
  4. 在 stdout 上输出最终 JSON:{api_id, api_hash, phone, session}。

子流程(仅在用户在上方选择“是”时运行):

  1. 先探查。 检查钥匙串中是否已有提取过的 Telegram 凭据:

    for svc in telegram-api-id telegram-api-hash telegram-phone telegram-session; do
      security find-generic-password -s "$svc" -w 2>/dev/null && echo "FOUND: $svc"
    done
    

    另请检查 ~/.claude.json mcpServers.telegram.env.TELEGRAM_API_ID。如果这 4 项都已找到,且存储的 TELEGRAM_SESSION 可解码为 StringSession,请告知用户 "✓ Telegram already configured (api_id=XXXXXXX, phone=+XX...)" 并跳至第 8 步。

  2. 通过 AskUserQuestion 询问用户的电话号码,并提供一个自由文本选项。不要提供特定国家/地区的预设选项或示例号码,只提供一个提示用户直接输入的选项:

    Enter your Telegram phone number (include country code, e.g. +31612345678):
      [Enter phone number — type your full number starting with +]
    

    用户会选择此选项,并在“其他”自由文本字段中输入号码。验证号码是否匹配 ^\+\d{7,15}$。说明该手机号仅在首次运行时提取过程中使用一次,并且只会存储在本地。

  3. 提醒用户有 2 个验证码。 通过 AskUserQuestion 告知用户:"Telegram will send TWO codes to your Telegram app — one for my.telegram.org web login, then a second one for gram.js auth. Have your Telegram app ready."。选项:[I'm ready]、[Cancel]。

  4. 在后台以严格的文件权限启动自动关联脚本:

    (umask 077 && node "${CLAUDE_PLUGIN_ROOT}/bin/ops-telegram-autolink.mjs" --phone "$PHONE" 2>/tmp/ops-telegram-autolink.log 1>/tmp/ops-telegram-autolink.out &)
    echo $! > /tmp/ops-telegram-autolink.pid
    

    使用 Bash 工具的 run_in_background: true。umask 077 会将所有桥接文件(日志、输出文件)的权限设为 0600。.out 文件包含完整的凭据 JSON,其中包括 gram.js 会话字符串——如果该文件对所有用户可读,任何本地进程都能窃取长期有效的 Telegram 账户访问权限。

  5. 轮询标准错误日志,查找 need_code 事件。 每 3 秒读取一次 /tmp/ops-telegram-autolink.log,查找尚未处理的最新一行 {"type":"need_code", ...}。看到该行时:

    • 判断验证码类型:channel: "web_login"(第一个)或 channel: "gram_auth"(第二个)。
    • 使用 AskUserQuestion 提供自由文本输入:"Enter the code Telegram just sent to your Telegram app:"。不要说“只能输入数字”——Telegram Web 登录验证码可能包含字母、连字符和下划线(例如 Zv_-ef77YSU)。自动关联脚本的桥接文件接受任意 3-20 位的字母数字、连字符和下划线字符串。
    • 以严格的权限将验证码写入 /tmp/telegram-code.txt:Bash: (umask 077 && printf '%s' "$CODE" > /tmp/telegram-code.txt)。umask 077 至关重要——没有它,在 macOS 上创建的文件将对所有用户可读(/tmp 的权限为 drwxrwxrwt),任何本地进程都能在 2 秒轮询窗口内抢先读取验证码。
    • 在 10 秒内确认验证码已被读取:ls /tmp/telegram-code.txt 2>/dev/null。如果 10 秒后文件仍存在,说明脚本的验证正则表达式拒绝了该验证码。查看日志中的错误。不要重新运行脚本或索取新验证码——这会消耗一次登录尝试。
    • 等待下一个事件。
    • 如果看到 {"type":"need_password"},则处理两步验证(2FA):通过 AskUserQuestion 询问用户,并使用 (umask 077 && printf '%s' "$PW" > /tmp/telegram-password.txt) 写入 /tmp/telegram-password.txt。与验证码文件一样,必须加强权限保护。2FA 密码比一次性验证码敏感得多。
  6. 等待脚本退出。 持续轮询,直到进程不再运行(ps -p "$(cat /tmp/ops-telegram-autolink.pid)")。读取 /tmp/ops-telegram-autolink.out——其中应包含一行 JSON,含有 api_id、api_hash、phone 和 session。安全提示:设置技能应使用 (umask 077 && node "${CLAUDE_PLUGIN_ROOT}/bin/ops-telegram-autolink.mjs" --phone "$PHONE" 2>/tmp/ops-telegram-autolink.log 1>/tmp/ops-telegram-autolink.out &) 启动自动关联脚本,以确保 .log 和 .out 文件的权限为 0600。使用 stat -f '%Lp' /tmp/ops-telegram-autolink.out 验证,结果必须为 600。将凭据读入内存后,立即使用 shred -u(Linux)或 rm -P(macOS)删除 .out 文件。

    错误恢复——至关重要:不要消耗登录尝试。 每次 send_password 调用都会计入 Telegram 的速率限制(每 8 小时约 3-5 次)。如果自动关联失败:

    • "could not extract ... after 6 extraction strategies" / 提取失败:HTML 解析失败,但登录成功。不要重新运行脚本。请检查错误是否包含 html_snippet——该片段显示了剥离后的页面文本。如果其中在 “api_id” 附近包含一个 5–12 位数字,并在 “api_hash” 附近包含一个 32 个字符的十六进制字符串,请直接从片段中用 grep/regex 提取。如果片段显示的是登录页面或重定向,则会话在提取过程中已过期。
    • rate-limited:在 $PREFS_PATH 中记录 channels.telegram.status = "rate_limited" 和 channels.telegram.retry_after(当前时间 + 8 小时)。继续处理下一个频道。在后续运行 /ops:setup 时,检查 retry_after;如果冷却时间尚未结束,则跳过 Telegram。
    • 代码文件未被读取:如果写入 /tmp/telegram-code.txt 后 10 秒以上该文件仍然存在,说明验证正则表达式拒绝了它。读取文件内容和日志。不要向用户索要另一个代码——原代码仍然有效,你只需要修复桥接程序。
    • 一般规则:每个设置会话最多可尝试 send_password 2 次。如果第一次尝试因非限流原因失败,请先诊断根本原因,再重试。如果第二次尝试失败,则保存状态并继续。
  7. 保存到钥匙串 + 偏好设置。 仅限 macOS:

    security add-generic-password -U -s telegram-api-id -a "$USER" -w "$API_ID"
    security add-generic-password -U -s telegram-api-hash -a "$USER" -w "$API_HASH"
    security add-generic-password -U -s telegram-phone -a "$USER" -w "$PHONE"
    security add-generic-password -U -s telegram-session -a "$USER" -w "$SESSION"
    

    然后更新 $PREFS_PATH,写入 channels.telegram = {backend: "gram.js", api_id: "...", phone: "...", status: "configured"}。绝不要将 api_hash 或会话写入 preferences.json——这些信息只能保存在钥匙串中。preferences.json 仅保存非敏感元数据。

  8. 自动配置 MCP 服务。 将凭据直接写入插件的用户配置,这样用户就不必手动粘贴任何内容:

    # Read existing user config or create empty
    USER_CONFIG="${CLAUDE_PLUGIN_DATA_DIR:-$HOME/.claude/plugins/data/ops-ops-marketplace}/user-config.json"
    mkdir -p "$(dirname "$USER_CONFIG")"
    
    # Write Telegram credentials to user config
    jq -n \
      --arg api_id "$API_ID" \
      --arg api_hash "$API_HASH" \
      --arg phone "$PHONE" \
      --arg session "$SESSION" \
      '{telegram_api_id: $api_id, telegram_api_hash: $api_hash, telegram_phone: $phone, telegram_session: $session}' \
      > "${USER_CONFIG}.tmp"
    
    # Merge with existing config if present
    if [ -f "$USER_CONFIG" ]; then
      jq -s '.[0] * .[1]' "$USER_CONFIG" "${USER_CONFIG}.tmp" > "${USER_CONFIG}.new" && mv "${USER_CONFIG}.new" "$USER_CONFIG"
      rm -f "${USER_CONFIG}.tmp"
    else
      mv "${USER_CONFIG}.tmp" "$USER_CONFIG"
    fi
    chmod 600 "$USER_CONFIG"
    

    如果 ~/.claude.json 中存在 telegram 服务条目,也要更新 MCP 服务配置——将凭据注入为环境变量:

    # Update .claude.json mcpServers.telegram.env with actual values
    CLAUDE_JSON="$HOME/.claude.json"
    if [ -f "$CLAUDE_JSON" ] && jq -e '.mcpServers.telegram' "$CLAUDE_JSON" >/dev/null 2>&1; then
      jq --arg id "$API_ID" --arg hash "$API_HASH" --arg phone "$PHONE" --arg session "$SESSION" \
        '.mcpServers.telegram.env.TELEGRAM_API_ID = $id | .mcpServers.telegram.env.TELEGRAM_API_HASH = $hash | .mcpServers.telegram.env.TELEGRAM_PHONE = $phone | .mcpServers.telegram.env.TELEGRAM_SESSION = $session' \
        "$CLAUDE_JSON" > "${CLAUDE_JSON}.tmp" && mv "${CLAUDE_JSON}.tmp" "$CLAUDE_JSON"
    fi
    

    打印:

    ✓ Telegram configured automatically.
      API ID:  [api_id]
      Phone:   [phone]
      Session: stored in keychain + MCP config
      Restart Claude Code to activate the Telegram MCP server.
    
  9. 冒烟测试(可选)。 在环境变量内联设置的情况下,启动 node ${CLAUDE_PLUGIN_ROOT}/telegram-server/index.js 并运行 3 秒。如果没有打印身份验证错误,则会话可用。

给用户的隐私说明(开始时显示一次):

  • 电话号码和所有凭据都会保留在你的设备上。除 HTTP 登录流程期间与 Telegram 自有服务器通信外,向导绝不会将它们传输到任何其他地方。
  • 如果你已有用于其他项目的 gram.js / Telethon 会话,可以跳过此步骤,手动将这些值粘贴到 /plugin settings。
  • 如果 Telegram 回复 “Sorry, too many tries. Please try again later.”,说明你的账号已被限流约 8 小时——向导无法绕过此限制。请等待后重试。

**深入了解:**有关此集成的完整操作说明、CLI 参考和故障排除信息,请参阅 ${CLAUDE_PLUGIN_ROOT}/skills/ops-comms/SKILL.md。如果设置 Agent 需要比本向导更深入的信息,可以直接加载该文件。

3b — WhatsApp(诊断 + 自我修复 + 回填)

WhatsApp 是最常在无提示的情况下出故障的频道。向导必须自动诊断、检查并修复,而不只是报告状态后放弃。请从头到尾运行整个子流程,只有在系统运行正常或用户拒绝修复时才停止。

步骤 3b.1 — 检查是否存在

运行 command -v wacli。如果未找到,请通过 AskUserQuestion 询问:[Show install docs]、[Skip WhatsApp]。如果选择安装文档,请打印:

wacli is not on Homebrew. Install:
  git clone https://github.com/Lifecycle-Innovations-Limited/wacli ~/src/wacli
  cd ~/src/wacli && go build -o /usr/local/bin/wacli ./cmd/wacli

然后停止此子流程。

步骤 3b.2 — 收集状态

并行运行以下命令:

wacli doctor --json 2>&1
wacli auth status --json 2>&1
wacli messages list --after="$(date -v-1d +%Y-%m-%d 2>/dev/null || date -d '1 day ago' +%Y-%m-%d)" --limit=5 --json 2>&1
wacli chats list --json 2>&1 | head -c 4000

解析:

  • doctor.data.authenticated(布尔值)
  • doctor.data.lock_held + doctor.data.lock_info(PID + acquired_at 时间戳)
  • doctor.data.fts_enabled(布尔值——如果为 false,则搜索功能降级,但不属于致命问题)
  • messages.data.messages 长度 → 这是关键的健康信号。已验证身份但过去 24 小时内没有任何消息 = 已损坏。
  • chats 数量及其是否已填充
步骤 3b.3 — 诊断并分类

按顺序应用以下规则。遇到第一个匹配项时停止。

A. 未通过身份验证 如果 doctor.authenticated: false → 打印 "WhatsApp 需要通过 QR 码配对。请在单独的终端中运行 wacli auth,并用手机扫描 QR 码(WhatsApp → 已关联的设备 → 关联设备),然后重新运行 /ops:setup whatsapp。" 子流程结束。不要尝试自动扫描 QR 码——这需要用户将手机摄像头对准终端(规则 2 的例外)。

B. 同步卡住(锁已过期) 如果 lock_held: true,且 lock_info.acquired_at 超过 2 分钟前,且持有锁的 PID 仍在运行(ps -p <pid>):

  1. 通过现有进程的 stderr 尾部输出,在后台运行 wacli sync(timeout 15 wacli sync 2>&1)——或者,如果无法通过 tee 接入,则回退到:
  2. 询问 AskUserQuestion:"wacli sync 进程 (pid=N) 已持有存储锁 Xm。很可能已卡住。要终止它吗?" → [终止 pid N 并重启同步]、[让它继续运行]。
  3. 选择终止时:运行 kill <pid>(不要先用 -9)。等待 3 秒。如果仍在运行,运行 kill -9 <pid>。通过 ps -p <pid> 确认。
  4. 终止后,重新运行 wacli doctor --json,确认锁已释放。继续下一条规则。

C. 应用状态密钥不同步(最常见问题) 运行 timeout 15 wacli sync 2>&1 | tee /tmp/wacli-sync-probe.log(必须在 B 之后执行,以确保锁已释放)。在输出中搜索:

  • didn't find app state key → 会话密钥不同步,需要重新配对
  • failed to decode app state → 同类错误
  • Failed to do initial fetch of app state → 同类错误

如果有任一项匹配:

  1. 原样打印诊断结果:
    ⚠  WhatsApp session is authenticated but the app-state decryption keys
       are out of sync with your primary device. This happens when the
       linked-device session is partially wiped on the phone side.
       Symptom: sync runs but 0 messages come through.
       Fix: logout this session and re-pair via QR.
    
  2. 询问 AskUserQuestion:[退出登录并引导我重新配对]、[跳过 — 我会手动修复]。
  3. 选择退出登录时:运行 wacli auth logout --json 并显示结果。然后打印:
    Now run `wacli auth` in a separate terminal (QR-based auth — requires your phone camera).
    A QR code will appear — scan it from WhatsApp → Settings → Linked Devices → Link a device.
    When it says "Connected", come back and type "done".
    
  4. 等待用户通过 AskUserQuestion 确认:[完成 — 已重新配对]、[取消]。
  5. 选择“完成”后,重新运行步骤 3b.2 以重新收集状态,并继续规则 D。

D. 已通过身份验证、锁已释放、没有近期消息、没有密钥错误 这通常表示缓存为空。转到步骤 3b.4(回填)。

E. 状态正常(消息流动中) 如果 messages.data.messages 中有 ≥1 条来自最近 24 小时的消息,则打印 ✓ 摘要并跳至步骤 3b.5。

步骤 3b.4 — 历史回填(后台静默运行)

每次重新配对后都要运行此步骤;符合规则 D 时也要运行。除非用户明确拒绝,否则绝不跳过。

回填是后台优化操作——不应输出冗长内容或令人担忧的状态消息。请静默运行,并忽略非致命错误。

  1. 按最近活跃时间加载排名前 10 的聊天:
    wacli chats list --json 2>&1 | jq -r '[.data[] | select(.jid) | {jid, name, last_msg: .last_message_ts}] | sort_by(.last_msg) | reverse | .[0:10]'
    
  2. 告知用户:"正在对最近的 10 个聊天运行历史回填。此操作将在后台运行。" 不要打印每个聊天的进度或无消息结果。
  3. 对每个聊天 JID,按顺序运行(回填共享存储锁,无法并行):
    wacli history backfill --chat="<jid>" --count=50 --requests=2 --wait=30s --idle-exit=5s --json 2>&1
    
  4. 禁止输出任何单个聊天的结果。 如果命令以非零状态退出,则静默忽略错误——回填失败不属于面向用户的事件。不要打印“已同步 0 条消息”、错误回溯或关于设备连接的说明。
  5. 循环完成后,只打印最终健康状态摘要(步骤 3b.6)。
步骤 3b.5 — FTS 索引检查(可选)

如果 doctor.fts_enabled: false,则打印:

ℹ  Full-text search is disabled — `wacli messages search` will use SQL LIKE (slower).
   This is a non-fatal known-limitation. See wacli docs to enable FTS5.

不要因此阻塞。

步骤 3b.6 — 记录状态

将 channels.whatsapp = "wacli" 写入 $PREFS_PATH,并打印最终的 ✓ 摘要:

✓ WhatsApp — wacli authenticated, N chats

摘要行中绝不包含消息数量或回填结果。

步骤 3b.7 — 持久连接(保持连接)

完成身份验证和历史记录回填后,设置持久连接,让 wacli 保持连接并自动同步。这能确保 WhatsApp 在不同会话间稳定运行——如果没有持久连接,关联设备会在闲置约 14 天后断开,且 @lid JID 会返回空消息。

如果已配置 ops-daemon(步骤 5b),wacli 将作为守护服务运行——请跳过下方独立的 launchd 路径,并告知用户 wacli 同步由守护服务管理。守护服务会集中处理初始化、自动回填和运行状况报告。

独立 launchd 备用方案(仅当未设置 ops-daemon 时):

1. 安装保持连接脚本:

KEEPALIVE_SCRIPT="${CLAUDE_PLUGIN_ROOT}/scripts/wacli-keepalive.sh"
chmod +x "$KEEPALIVE_SCRIPT"

2. 根据模板生成 launchd plist:

PLIST_TEMPLATE="${CLAUDE_PLUGIN_ROOT}/scripts/com.claude-ops.wacli-keepalive.plist"
PLIST_DEST="$HOME/Library/LaunchAgents/com.claude-ops.wacli-keepalive.plist"
LOG_DIR="$HOME/.claude/plugins/data/ops-ops-marketplace/logs"
mkdir -p "$LOG_DIR" "$HOME/Library/LaunchAgents"

# Resolve bash 4+ (same logic as daemon plist)
BASH_PATH="/bin/bash"
if [[ -x /opt/homebrew/bin/bash ]]; then
  BASH_PATH="/opt/homebrew/bin/bash"
elif [[ -x /usr/local/bin/bash ]]; then
  BASH_PATH="/usr/local/bin/bash"
fi

sed -e "s|__KEEPALIVE_SCRIPT_PATH__|$KEEPALIVE_SCRIPT|g" \
    -e "s|__BASH_PATH__|$BASH_PATH|g" \
    -e "s|__LOG_DIR__|$LOG_DIR|g" \
    -e "s|__HOME__|$HOME|g" \
    "$PLIST_TEMPLATE" > "$PLIST_DEST"

3. 加载 Agent:

# Unload if already loaded (idempotent)
launchctl bootout gui/$(id -u) "$PLIST_DEST" 2>/dev/null || true
launchctl bootstrap gui/$(id -u) "$PLIST_DEST"

4. 验证是否正在运行:

等待 3 秒,然后检查:

launchctl print gui/$(id -u)/com.claude-ops.wacli-keepalive 2>&1 | head -5
cat "$HOME/.wacli/.health" 2>/dev/null

如果运行状况文件显示 status=connected 或 status=needs_reauth,说明守护服务正在运行。输出:

✓ WhatsApp keepalive — launchd agent installed and running
  Persistent sync active. Auto-restarts on disconnect.
  Health: ~/.wacli/.health | Logs: ~/.claude/plugins/data/ops-ops-marketplace/logs/

如果为 status=needs_reauth,请立即按照步骤 3b.3 规则 C 触发重新配对流程。

5. 保持连接脚本如何自动修复问题:

保持连接脚本(wacli-keepalive.sh)会自动处理以下故障:

故障自动修复
孤立的 wacli 进程占用锁终止过期的 PID,清除锁
连接中断(WhatsApp 服务重启)launchd 会在 60 秒内重启
App-state 密钥不同步将 needs_reauth 写入运行状况文件——ops 技能会检测到并提示扫描 QR 码
身份验证过期写入 needs_auth——使用相同的提示流程
脚本崩溃launchd 的 KeepAlive=true 会立即重启(限流为 60 秒)

6. 供其他 ops 技能使用的运行状况文件约定:

所有使用 WhatsApp 的 ops 技能(ops-inbox、ops-comms、ops-go)在尝试执行 wacli 命令前,MUST 检查 ~/.wacli/.health。如果状态为 status=needs_auth 或 status=needs_reauth:

  1. 向用户输出诊断信息:
    ⚠ WhatsApp needs re-authentication.
    Run `wacli auth` in a separate terminal and scan the QR code with your phone
    (QR-based auth — exception to Rule 2). Then type "done" to continue.
    
  2. 使用 AskUserQuestion:[已完成 — 已重新配对]、[跳过 WhatsApp]。
  3. 用户选择“已完成”后:通过 launchctl kickstart -k gui/$(id -u)/com.claude-ops.wacli-keepalive 重启保持连接守护服务,并等待 5 秒,让运行状况文件更新。

这样可确保用户不会在不知情的情况下遇到 WhatsApp 连接故障——每个 ops 技能都会提示问题并引导用户完成修复。

**深入了解:**有关此集成的完整操作说明、CLI 参考和故障排除信息,请参阅 ${CLAUDE_PLUGIN_ROOT}/skills/ops-comms/SKILL.md 和 ${CLAUDE_PLUGIN_ROOT}/skills/ops-inbox/SKILL.md。如果设置 Agent 需要比本向导更深入的信息,可以直接加载这些文件。

3c — 电子邮件

电子邮件有两种可能的后端,按以下顺序尝试:

首选:gog CLI

gog 是 ops-inbox 和 ops-comms 默认调用的电子邮件和日历 CLI。它是一个独立二进制文件,使用自己的 OAuth 令牌(位于 ~/.gog/token.json)——拥有完整的读取和发送权限,无需 Claude Desktop 配置。

  1. 使用 command -v gog 检查 gog 是否位于 PATH 中。
  2. 如果已安装,运行 gog auth status 2>&1 || true 并显示输出。
    • 如果身份验证状态为红色(未通过身份验证/令牌已过期/退出代码 != 0),请通过 Bash 工具运行 gog auth add "$USER_EMAIL" --services gmail,calendar,drive,contacts,docs,sheets,并设置 run_in_background: true(这会打开浏览器进行 OAuth 流程)。告知用户:“正在打开浏览器进行 Gmail OAuth——请在那里完成登录,然后输入‘完成’。”使用 AskUserQuestion:[已完成 — 已通过身份验证]、[跳过电子邮件]。
    • 如果身份验证状态为绿色,请运行以下命令进行探测:
      gog gmail labels list --json 2>&1 | head -5
      
      如果返回的 JSON 包含 labels 数组,则表示 gog 已通过身份验证且 Gmail API 正常工作。报告 ✓。如果输出为错误或为空,则视为不可用,并指导用户重新运行 gog auth add <email> --services gmail,calendar,drive,contacts,docs,sheets。
    • 在 $PREFS_PATH 中记录 channels.email = "gog",然后到此为止。
备用方案:Claude Gmail MCP 连接器

如果 gog 不在 PATH 中,请查看检测器的 mcp_configured 数组,寻找与 gmail、google-mail 或 claude_ai_Gmail 匹配的条目(不区分大小写)——这些是 Anthropic Gmail 连接器或用户安装的 Gmail MCP 服务的常见名称。

  1. 如果已配置 Gmail MCP,请询问 AskUserQuestion:

    • [使用 Gmail MCP(只读备用方案)]
    • [改为安装 gog — 显示文档]
    • [跳过电子邮件]
  2. 如果选择“使用 Gmail MCP”,在 $PREFS_PATH 中记录 channels.email = "mcp:<name>"(其中 <name> 是找到的实际 MCP 服务名称),并原样打印此警告:

    ⚠  Using the Gmail MCP connector as a fallback.
       Read operations (list inbox, search, fetch) will work.
       SEND operations will fail until you explicitly grant send permissions
       in Claude Desktop → Settings → Connectors → Gmail → Permissions.
       The ops plugin cannot grant those permissions for you — it's a Claude
       Desktop-side setting tied to your account.
       If you want unattended sending from ops-comms, install `gog` instead.
    
  3. 如果选择“改为安装 gog”,打印适用于相应操作系统的安装命令:

    操作系统命令
    macOS / Linuxbrewbrew install gogcli
    Windowswinget install -e --id steipete.gogcli
    Arch Linuxyay -S gogcli
    从源代码安装git clone https://github.com/steipete/gogcli.git && cd gogcli && make

    文档:https://gogcli.sh/ · 仓库:https://github.com/steipete/gogcli

    安装后,每个账户授权一次:

    gog auth credentials /path/to/client_secret.json
    gog auth add [email protected] --services gmail,calendar,drive,contacts,docs,sheets
    

    刷新令牌存储在操作系统密钥环中(macOS 上为 Keychain,Linux 上为 Secret Service / libsecret,Windows 上为 Credential Manager)。然后结束此子流程,并等待用户重新运行 /ops:setup email。

两者均不可用
  1. 如果缺少 gog 且未配置 Gmail MCP,请询问 AskUserQuestion:
    • [安装 gog — 显示文档]
    • [添加 Gmail MCP — 显示文档] → 打印 claude mcp add gmail,并告知用户之后重新运行 /ops:setup email
    • [暂时跳过电子邮件]
  2. 无论用户选择哪项,都要在 $PREFS_PATH 中记录最终状态(设置 channels.email = "gog"、channels.email = "mcp:<name>",或完全省略此键)。

**深入了解:**完整的操作说明、CLI 参考和此集成的故障排除,请参阅 ${CLAUDE_PLUGIN_ROOT}/skills/ops-inbox/SKILL.md 和 ${CLAUDE_PLUGIN_ROOT}/skills/ops-comms/SKILL.md。如果配置 Agent 需要比此向导更深入的信息,可以直接加载这些文件。

3d — Slack(scout + ops-slack-autolink)

Slack 官方 API 的大多数实用权限范围都需要工作区管理员批准。slack-mcp-server MCP 使用浏览器会话令牌(xoxc + xoxd),这些令牌按用户区分——无需管理员批准。此插件提供 bin/ops-slack-autolink.mjs,它会:

  1. 阶段 1 — scout — 检查以下位置是否已有提取的令牌:
    • ~/.claude.json mcpServers.slack.env(Claude Code 存储令牌的位置)
    • 进程环境变量(SLACK_MCP_XOXC_TOKEN / SLACK_MCP_XOXD_TOKEN / SLACK_BOT_TOKEN)
    • macOS 钥匙串(slack-xoxc、slack-xoxd)
    • Shell 配置文件(~/.zshrc、~/.bashrc、~/.zprofile、~/.envrc)
    • Doppler(doppler secrets --json)
  2. 阶段 2 — Playwright 提取 — 仅在未找到任何令牌时,启动使用持久配置文件的 Chromium,打开 https://app.slack.com/client/,请用户登录(或在无头运行时使用现有会话),然后从 localStorage.localConfig_v2.teams[teamId].token 中提取 xoxc-...,并从 Cookie 存储中提取 d=... Cookie(xoxd-...)。

从 maorfr/slack-token-extractor 移植而来(Python → Node)。

子流程:

  1. **先进行 scout 检查。**运行:

    node "${CLAUDE_PLUGIN_ROOT}/bin/ops-slack-autolink.mjs" --scout-only 2>/tmp/ops-slack.log
    

    解析标准输出中的 JSON。如果结果非空,且包含 xoxc_token 和 xoxd_token,则报告 "✓ Slack already configured (source=XXX)" 并跳到步骤 5。

  2. 如果没有现有令牌,通过 AskUserQuestion 提问:

    • [通过 Playwright 提取令牌(推荐)] → 以有头模式运行自动关联。
    • [我会手动粘贴令牌] → 通过两个自由文本 AskUserQuestion 分别收集 xoxc-... 和 xoxd-...。
    • [跳过 Slack]
  3. 使用 Playwright 路径:在后台启动 autolink:

    (umask 077 && node "${CLAUDE_PLUGIN_ROOT}/bin/ops-slack-autolink.mjs" \
      --workspace "https://app.slack.com/client/" \
      2>/tmp/ops-slack-autolink.log 1>/tmp/ops-slack-autolink.out &)
    echo $! > /tmp/ops-slack-autolink.pid
    

    轮询日志,查找 {"type":"need_login"}。看到后,使用 AskUserQuestion: "A Chromium window should be open on your desktop. Log in to Slack there, then pick [Done]."。选择 Done 后,执行 touch /tmp/slack-login-done。脚本会完成运行,并将提取的令牌写入 /tmp/ops-slack-autolink.out。

  4. 如果未安装 Playwright(脚本退出并显示 playwright is not installed),请提供以下选项:

    • [Install Playwright now] → 运行 cd ${CLAUDE_PLUGIN_ROOT}/telegram-server && npm install playwright && npx playwright install chromium(后台运行,下载约 150MB,并报告进度)。
    • [Fall back to manual paste] → 转到第 2 步的手动路径。
  5. 验证令牌。 使用准确语法调用 Slack 身份验证端点:

    curl -s -H "Authorization: Bearer XOXC_TOKEN" -b "d=XOXD_TOKEN" "https://slack.com/api/auth.test"
    

    预期结果为 {"ok":true, "team_id":"T...", "user_id":"U...", "url":"https://<workspace>.slack.com/"}。如果 ok:false,显示错误并重新询问。

  6. 持久化。

    • Keychain:security add-generic-password -U -s slack-xoxc -a "$USER" -w "$XOXC"; security add-generic-password -U -s slack-xoxd -a "$USER" -w "$XOXD"。
    • $PREFS_PATH → channels.slack = {backend: "mcp:slack", team_id: "...", source: "...", status: "configured"}。不要将原始令牌存入 preferences.json——仅存储在 Keychain 中。
  7. **接入 Claude Code 插件设置。**打印以下说明:

    Slack tokens saved to keychain. To activate the MCP, Claude Code needs them
    in ~/.claude.json. Since this skill can't write to ~/.claude.json directly,
    either:
      a) Run: claude mcp add slack --transport stdio -- npx -y slack-mcp-server@latest --transport stdio
         and Claude Code will prompt for the env vars.
      b) Manually paste the xoxc + xoxd into /plugin settings for the Slack MCP.
    

    (不自动写入的原因:根据用户反馈,~/.claude.json 是 Claude Code 的内部文件,插件不得修改。MCP 注册由 Claude Code 负责。)

  8. 冒烟测试:使用令牌调用 https://slack.com/api/conversations.list?limit=1。预期返回 ok:true,且响应中至少包含一个频道。

隐私说明:

  • 只要浏览器会话保持有效,令牌就可以使用——如果定期使用 Slack,通常可持续数周到数月。如果 MCP 开始返回 401,请重新运行 /ops:setup slack。
  • 退出 Slack 会使 d Cookie 失效,导致 MCP 无法使用。请使用 /ops:setup slack 重新提取令牌。
  • Slack 的服务条款允许将个人会话令牌用于自己的账户。请勿使用此流程访问不属于自己的账户。

**深入了解:**有关此集成的完整操作说明、CLI 参考和故障排除,请参阅 ${CLAUDE_PLUGIN_ROOT}/skills/ops-comms/SKILL.md 和 ${CLAUDE_PLUGIN_ROOT}/skills/ops-inbox/SKILL.md。当设置 Agent 需要比此向导更详细的信息时,可直接加载这些文件。

3e — Notion(MCP 集成)

开始 Notion 流程前务必先询问——即使用户选择了“所有频道”也不例外。使用 AskUserQuestion:

Set up Notion workspace integration?
  [Yes — configure Notion]
  [Skip Notion]

如果用户跳过,请在 $PREFS_PATH 中记录 channels.notion = "skipped",然后继续。

检测
  1. **检查现有的 claude.ai Notion 集成。**扫描检测器的 mcp_configured 数组,查找匹配 Notion、claude_ai_Notion 或 notion 的条目。如果找到,将 NOTION_MCP_ENABLED=true,然后跳至验证步骤。

  2. **检查自托管的 Notion MCP 服务。**在 ~/.claude/settings.json 中查找 mcpServers.notion,或查找参数/命令中包含 notion 的条目。

设置路径

路径 A — Claude.ai 集成(推荐):

如果未检测到现有集成,请使用 AskUserQuestion:

How would you like to connect Notion?
  [Claude.ai integration (Recommended) — add via claude.ai settings]
  [Self-hosted MCP — use your own Notion API key]
  [Skip Notion]

对于 claude.ai 集成:

  1. 告诉用户:“前往 claude.ai > Settings > Integrations > Notion 添加 Notion 集成。授权访问你的工作区,然后输入 ‘done’。”
  2. 使用 AskUserQuestion:[Done — connected]、[Skip Notion]
  3. 用户选择“Done”后,使用简单查询测试 mcp__claude_ai_Notion__notion-search 进行验证

路径 B — 自托管 MCP:

  1. 在 Keychain 中查找现有的 Notion API 密钥:
    security find-generic-password -s "notion-api-key" -w 2>/dev/null || \
    security find-generic-password -s "NOTION_API_KEY" -w 2>/dev/null || echo ""
    
  2. 如果未找到,询问用户:
    Enter your Notion integration token (starts with ntn_):
      Create one at https://www.notion.so/my-integrations
      [Paste token now]  [Skip Notion]
    
  3. 存储令牌:
    security add-generic-password -s "notion-api-key" -a "claude-ops" -w "$TOKEN" -U
    
  4. 在 ~/.claude/settings.json 的 mcpServers.notion 下添加 MCP 服务配置
验证

测试集成(在后台运行):

# For claude.ai: integration auto-detected — test via MCP tool call
# For self-hosted: verify API key works
if [ -n "$NOTION_API_KEY" ]; then
  curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $NOTION_API_KEY" -H "Notion-Version: 2022-06-28" https://api.notion.com/v1/users/me | grep -q "200" && echo "OK" || echo "FAIL"
else
  echo "OK — claude.ai integration (verify via MCP tool call after restart)"
fi
完成配置
  1. 在 ~/.claude/settings.json 的 env 部分设置 NOTION_MCP_ENABLED=true
  2. 在 $PREFS_PATH 中记录 channels.notion = {"backend": "mcp:notion", "status": "configured", "source": "<claude-ai|self-hosted>"}
  3. 将 "notion" 添加到 $PREFS_PATH 中的 default_channels 数组
  4. 输出:✓ Notion — workspace connected via [source]

深入了解: 完整的 Notion MCP 工具参考和故障排除说明,请参阅 ${CLAUDE_PLUGIN_ROOT}/skills/ops-inbox/CHANNELS.md。

3f — 日历

日历不是消息渠道,但其他每项 ops 技能(简报、/ops-next、/ops-go)都能从了解用户的日程安排中获益良多——例如占用深度工作时间的会议、部署窗口和出行日。向导会以与电子邮件相同的方式进行配置:优先使用 gog calendar,其次使用 Google Calendar MCP 连接器。

首选:gog calendar

gog(即 gogcli 二进制文件——参见第 3c 步)已能处理电子邮件;同一二进制文件还提供 gog calendar,并使用相同的 OAuth 令牌。如果第 3c 步顺利完成,就不需要额外授权。注意:gog cal 不是有效别名——始终使用 gog calendar。

  1. 使用 command -v gog 检查 gog 是否在 PATH 中。
  2. 如果已安装,且已在第 3c 步完成授权,则进行探测:
    gog calendar calendars --json 2>&1 | head -20
    
    如果返回包含日历元数据的 JSON,则在 $PREFS_PATH 中记录 channels.calendar = "gog",并输出 ✓ Calendar — gog calendar。到此为止。
  3. 如果已安装 gog,但缺少日历权限范围(常见错误:insufficient scope 或 403 insufficient_permissions),则输出:
    Your gog OAuth token doesn't include the calendar scope.
    Re-add the account with the calendar service via Bash tool with `run_in_background: true`:
      gog auth add "$USER_EMAIL" --services gmail,calendar,drive,contacts,docs,sheets
    Tell the user: "Opening browser for Calendar OAuth — complete the sign-in there, then type 'done'." Use `AskUserQuestion`: `[Done — re-authorized]`, `[Skip calendar]`.
    
    不要尝试通过技能重新授权——这是一个浏览器流程。
后备方案:Claude Google Calendar MCP 连接器
  1. 如果 gog 不在 PATH 中,则扫描检测器的 mcp_configured 数组,查找匹配(不区分大小写)calendar、google-calendar 或 claude_ai_Calendar 的条目。
  2. 如果找到,则询问 AskUserQuestion:
    • [使用 Google Calendar MCP(只读后备方案)]
    • [改为安装 gog — 查看文档]
    • [跳过日历]
  3. 选择“使用 Google Calendar MCP”后,在 $PREFS_PATH 中记录 channels.calendar = "mcp:<name>",并逐字输出以下警告:
    ⚠  Using the Google Calendar MCP connector as a fallback.
       Read operations (list calendars, fetch events, check free/busy) will work.
       WRITE operations (create events, decline meetings, reschedule) will fail
       until you explicitly grant write permissions in
       Claude Desktop → Settings → Connectors → Google Calendar → Permissions.
       The ops plugin cannot grant those permissions for you.
       If you want ops-next to auto-block focus time or ops-comms to confirm
       meetings, install `gog` instead.
    
  4. 选择“改为安装 gog”后,通过 Bash 执行安装(规则 2),使用与第 3c 步相同的 npm → bun → source-clone 链。可以直接嵌入代码片段,也可以调用 ${CLAUDE_PLUGIN_ROOT}/bin/ops-setup-install gog(根据规则 4 在后台运行)。只有四次尝试全部失败后,才改为输出手动操作说明。
两者都不可用
  1. 如果缺少 gog,且未配置 Calendar MCP,则询问:
    • [安装 gog — 查看文档]
    • [添加 Google Calendar MCP — 查看文档] → 输出 claude mcp add google-calendar,并告知用户之后重新运行 /ops:setup calendar
    • [跳过日历]
为什么这很重要(技能背景)

下游技能(/ops-go、/ops-next、/ops-fires)会从 $PREFS_PATH 读取 channels.calendar,以决定是否将今天的日程与其输出进行交叉关联:

  • 简报会提示“你下午 2 点有个站会,所以现在不要开始重构”
  • /ops-next 会在距离会议不到 30 分钟时降低深度工作的优先级
  • 如果生产事故发生在预定通话期间,/ops-fires 会发出警告 因此,对于希望获得具备上下文感知能力的简报的用户,本节不可跳过。

深入了解: 有关此集成的完整操作说明、CLI 参考和故障排除,请参阅 ${CLAUDE_PLUGIN_ROOT}/skills/ops-go/SKILL.md(日历上下文会提供给 /ops:go 简报)。如果设置 Agent 需要比本向导更详细的信息,可以直接加载该文件。

3g — Doppler(密钥管理)

Doppler 是一款密钥管理器,可在运行时注入环境变量。配置后,所有 ops 技能都可以通过 doppler secrets get 查询密钥,而不必从点文件或钥匙串读取。向导会检查其是否存在、授权状态以及默认项目上下文。

步骤 3g.1 — 检查是否已安装
command -v doppler

如果未安装,通过 uname -s / $OSTYPE / $OS 检测主机操作系统,并选择相应的安装命令。通过 AskUserQuestion 询问:

Doppler CLI is not installed.
  [Install now — <os-specific command>]
  [Skip Doppler]

操作系统对应的命令如下:

操作系统安装命令
macOS / Linuxbrewbrew install dopplerhq/cli/doppler
Debian / Ubuntucurl -Ls https://cli.doppler.com/install.sh | sudo sh
Fedora / RHELsudo rpm --import https://packages.doppler.com/public.key && sudo dnf install -y doppler
Arch Linuxyay -S doppler-cli
Alpineapk add --no-cache doppler-cli
Windows (winget)winget install Doppler.doppler
Windows (scoop)scoop bucket add doppler https://github.com/DopplerHQ/scoop-doppler.git; scoop install doppler

在后台运行所选命令,捕获 stdout/stderr,并报告成功或失败。如果用户跳过,将 secrets_manager: "none" 写入 $PREFS_PATH,然后结束此子流程。

步骤 3g.2 — 身份验证状态

运行:

doppler me --json 2>&1

解析 JSON。如果输出包含 "error",或退出代码非零,则表示用户尚未通过身份验证。打印:

Doppler is not authenticated. Running `doppler login` now...

通过 Bash 工具运行 doppler login,并设置 run_in_background: true(此命令会打开浏览器以完成 OAuth 流程)。告诉用户:“正在打开浏览器进行 Doppler OAuth 身份验证——请在那里完成登录,然后输入 ‘done’。”使用 AskUserQuestion:[完成 — 已通过身份验证]、[跳过 Doppler]。用户选择“完成”后,重新运行 doppler me --json 进行验证。如果已通过身份验证,doppler me 将返回包含 name 和 email 的 JSON — 请确认:

✓ Doppler authenticated as <name> (<email>)

除非姓名或电子邮件来自本会话中 doppler me 的输出,否则绝不显示它们。

步骤 3g.3 — 项目上下文

如果已通过身份验证,列出可用项目:

doppler projects --json 2>&1

解析项目对象数组。通过 AskUserQuestion 并设置 singleSelect 展示这些项目。每次调用最多提供 4 个选项 — 如果项目超过 3 个,则分页显示:每页展示 3 个项目和 [更多项目...];最后一页始终将 [跳过 — 不设置默认项目] 作为最后一个选项。

Select your default Doppler project (page 1):
  [ ] my-app
  [ ] my-api
  [ ] my-service
  [ ] More projects...

如果用户选择了项目,获取该项目的配置:

doppler configs --project <selected_project> --json 2>&1

通过 AskUserQuestion 并设置 singleSelect 展示可用配置(最多 4 个选项 — 必要时分页):

Select the default config for <project>:
  [ ] dev
  [ ] staging
  [ ] production

将所选内容写入 $PREFS_PATH(合并,不要覆盖):

{
  "secrets_manager": "doppler",
  "doppler": {
    "project": "<selected>",
    "config": "<selected>"
  }
}

打印确认信息:

✓ Doppler default context set: <project>/<config>
步骤 3g.4 — 向 Agent 提供文档

打印以下说明,使其在会话中可见:

All ops skills can now query secrets via:

  doppler secrets get <KEY> --plain --project <project> --config <config>

For example:
  doppler secrets get TELEGRAM_BOT_TOKEN --plain --project my-app --config dev

The project and config above are the defaults saved to preferences.
Individual skills can override with --project / --config flags.

**深入了解:**Doppler 未提供专用技能 — 有关下游技能如何使用 $PREFS_PATH 中的 secrets_manager / doppler.* 值,以及如何在运行时解析 doppler:KEY_NAME 引用,请参阅 ${CLAUDE_PLUGIN_ROOT}/docs/memories-system.md(Runtime Context 部分)。如果设置 Agent 需要比此向导更深入的信息,可以直接加载该文件。

步骤 3g.5 — Doppler MCP 服务

配置并完成 CLI 身份验证后,询问用户是否要设置官方 @dopplerhq/mcp-server MCP 集成。这样 Claude 无需调用 shell 命令,即可直接使用 Doppler 密钥工具。

  1. 检查是否可用:在后台运行 npx -y @dopplerhq/mcp-server --help 2>&1。如果退出码为 0,则表示该软件包可用。

  2. 生成服务令牌:如果用户在步骤 3g.3 中选择了项目/配置,则生成一个限定范围的令牌:

    doppler configs tokens create mcp-server-token --project <project> --config <config> --plain 2>/dev/null
    

    如果命令失败,或用户未选择项目/配置,则询问:

    Doppler MCP Server needs a token. Options:
      [Generate from CLI (requires project/config)]
      [Paste a token manually]
      [Skip MCP server]
    
  3. 将令牌保存到 userConfig:将令牌写入插件的 userConfig 中的 doppler_token(运行时会通过 ${user_config.doppler_token} 将其传入 .mcp.json)。如果用户选择了项目/配置,也要保存 doppler_project 和 doppler_config。

  4. 冒烟测试:验证 MCP 服务能否启动:

    DOPPLER_TOKEN="<token>" timeout 10 npx -y @dopplerhq/mcp-server --help 2>&1
    

    如果退出码为 0,则表示服务正常运行。

  5. 确认:

    ✓ Doppler MCP Server configured — secrets accessible via MCP tools (mcp__doppler__*)
    
  6. 给 Agent 的说明:

    With the MCP server configured, skills can now query secrets directly via
    MCP tool calls (mcp__doppler__*) instead of shelling out to `doppler secrets get`.
    The Doppler CLI remains available as a fallback when the MCP server is unavailable.
    
3h — 密码管理器(凭证库)

Ops Agent 经常需要代用户查找凭证(API 密钥、数据库密码、服务令牌)。此步骤会配置密码管理器,以便通过存储在 $PREFS_PATH 中的标准命令模板自动执行这些查询。

步骤 3h.1 — 自动检测已安装的管理器

并行运行以下命令:

command -v op 2>/dev/null && op account list --format=json 2>&1    # 1Password CLI
command -v dcli 2>/dev/null && dcli sync 2>&1                       # Dashlane CLI
command -v bw 2>/dev/null && bw status --raw 2>&1                   # Bitwarden CLI
security find-generic-password -s "test" 2>&1 | head -1             # macOS Keychain (always available)

解析每项结果,并将其归类为 authenticated、needs_unlock、not_installed 或 available(Keychain 始终为 available)。

步骤 3h.2 — 展示检测结果

通过 AskUserQuestion 仅展示检测到的内容。**每次调用最多提供 4 个选项。**由于始终会显示 macOS Keychain 和 Skip,每次调用最多只能再显示 2 个已检测到的管理器。如果 3 个 CLI(1Password、Dashlane、Bitwarden)均已安装,则分两次调用:

如果检测到的 CLI 管理器不超过 2 个(常见情况,可在一次调用中展示):

Password managers found:
  [1Password — authenticated as <account>]
  [Dashlane — needs unlock]
  [macOS Keychain — always available]
  [Skip — don't connect a password manager]

如果检测到 3 个 CLI 管理器(罕见情况,分两次调用): 调用 1:

  [1Password — authenticated as <account>]
  [Dashlane — needs unlock]
  [Bitwarden — <status>]
  [More options...]

调用 2:

  [macOS Keychain — always available]
  [Skip — don't connect a password manager]

不要显示未安装的管理器。始终显示 macOS Keychain 和 Skip。如果没有安装任何 CLI,则跳过后续步骤,仅显示 [macOS Keychain — 始终可用] 和 [Skip]。

步骤 3h.3 — 配置所选管理器

1Password(op):

  1. 检查身份验证:op account list --format=json
  2. 如果输出为空或退出码非零(未登录),则打印:
    1Password CLI is installed but not signed in.
    Run `op signin` via Bash tool with `run_in_background: true`, then re-run /ops:setup vault.
    
    结束此子流程。
  3. 如果已完成身份验证,则列出保险库,供用户选择默认保险库:
    op vault list --format=json
    
    使用 AskUserQuestion(单选)展示保险库名称。用户选择的保险库将成为 password_manager_config.vault。
  4. 记录查询语法:
    op item get "{{name}}" --fields label=password --format=json
    

Dashlane(dcli):

  1. 检查身份验证:dcli sync
  2. 如果 dcli sync 失败或返回未配置错误,则打印:
    Dashlane CLI is installed but not configured.
    Run `dcli configure` via Bash tool, then re-run /ops:setup vault.
    
    结束此子流程。
  3. 记录查询语法:
    dcli password --filter "{{name}}" --output json
    
  4. 无需选择保险库——Dashlane 使用扁平命名空间。

Bitwarden(bw):

  1. 检查身份验证:运行 bw status --raw 并解析 JSON 的 status 字段。
    • "unauthenticated" → 打印:
      Bitwarden CLI 已安装,但尚未登录。
      使用 Bash 工具并设置 `run_in_background: true`,运行 `bw login`,然后重新运行 /ops:setup vault。
      
      结束此子流程。
    • "locked" → 打印:
      Bitwarden 保险库已锁定。
      使用 Bash 工具运行 `bw unlock --raw`,捕获会话令牌,并将其导出为 `BW_SESSION`,供后续命令使用。然后继续运行 /ops:setup vault。
      
      结束此子流程。
    • "unlocked" → 继续。
  2. 记录查询语法:
    bw get item "{{name}}" --pretty
    
  3. 无需选择保险库——Bitwarden 在每个会话中使用一个已解锁的保险库。

macOS Keychain:

  1. 无需检查身份验证——始终可用。
  2. 给用户的说明:
    macOS Keychain is always available but is limited to items stored locally.
    No cross-device sync. Best for machine-specific secrets (API keys added via
    `security add-generic-password`).
    
  3. 记录查询语法:
    security find-generic-password -s "{{name}}" -w
    
第 3h.4 步 — 写入偏好设置

用户选择并配置管理器后,写入 $PREFS_PATH:

{
  "password_manager": "<1password|dashlane|bitwarden|keychain>",
  "password_manager_config": {
    "vault": "<vault name, or omit if not applicable>",
    "query_cmd": "<template with {{name}} placeholder>"
  }
}

与现有文件合并(jq '. + { ... }')——绝不覆盖。以下是 1Password 的示例:

{
  "password_manager": "1password",
  "password_manager_config": {
    "vault": "Private",
    "query_cmd": "op item get \"{{name}}\" --fields label=password --format=json"
  }
}

如果用户选择跳过,则写入 "password_manager": "none",这样后续运行时就不会再次询问,除非用户明确运行 /ops:setup vault。

第 3h.5 步 — 为 Agent 编写说明

保存后,打印以下说明一次:

All ops skills can now query credentials via your configured password manager.
The query command template is in preferences.json under password_manager_config.query_cmd.
Replace {{name}} with the item name — e.g. "GitHub PAT", "AWS root key", "my-project-db".

To query manually:
  op item get "GitHub PAT" --fields label=password --format=json   (1Password example)
  security find-generic-password -s "my-project-db" -w               (Keychain example)
仪表板显示

更新第 0b 步的状态标题,加入保险库状态:

 Vault:       ✓ 1password (vault: Private)

如果跳过,则使用 ○ none;如果管理器已安装但无法访问,则使用 ✗ locked。

完成摘要(第 8 步)

在最终摘要块中加入:

 ✓ Vault:      1password → Private vault

如果 password_manager 为 "none" 或未设置,则完全省略此行。

调用快捷方式

在快捷方式表中添加:vault、password-manager、pm → 第 3h 步

深入了解: 密码管理器集成不附带专用技能——如何由下游技能从 $PREFS_PATH 解析 password_manager 和相关保险库引用,请参阅 ${CLAUDE_PLUGIN_ROOT}/docs/memories-system.md(Runtime Context 部分)。隐私与安全指南位于此 SKILL.md 中(仅在钥匙串中存储 API 哈希/会话字符串,桥接文件使用 umask 077)。如果设置 Agent 需要比此向导提供的更深入的信息,可以直接加载该文件。


3i — 电商(Shopify + 动态合作方)
第 3i.1 步 — 自动扫描现有 Shopify 凭据

在询问任何信息之前,同时对所有 Shopify 相关变量运行通用凭据自动扫描:

# --- Token scan (API credentials) ---

# Scan shell env
printenv SHOPIFY_ACCESS_TOKEN SHOPIFY_ADMIN_TOKEN SHOPIFY_STORE_URL SHOPIFY_ADMIN_API_ACCESS_TOKEN 2>/dev/null

# Scan shell profiles
grep -h 'SHOPIFY\|myshopify' ~/.zshrc ~/.bashrc ~/.zprofile ~/.envrc 2>/dev/null | grep -v '^#'

# Scan Doppler across all projects
for proj in $(doppler projects --json 2>/dev/null | jq -r '.[].slug'); do
  doppler secrets --project "$proj" --config prd --json 2>/dev/null | \
    jq -r --arg proj "$proj" 'to_entries[] | select(.key | test("SHOPIFY|STORE")) | "\(.key)=\(.value.computed) (doppler:\($proj)/prd)"'
done

# Scan Dashlane for API tokens
dcli password shopify --output json 2>/dev/null | jq -r '.[] | select(.password != null and .password != "") | "\(.title): \(.url) → token found"'

# Scan macOS Keychain
security find-generic-password -s "shopify-admin-token" -w 2>/dev/null
security find-generic-password -s "shopify-access-token" -w 2>/dev/null

# Scan OpenClaw
jq -r '.agents.defaults.env | to_entries[] | select(.key | test("SHOPIFY")) | "\(.key)=\(.value)"' ~/.openclaw/openclaw.json 2>/dev/null

# Check existing prefs
jq -r '.ecom.shopify // empty' "$PREFS_PATH" 2>/dev/null

# --- Store URL discovery (even if no tokens found) ---
# Scan Chrome history for myshopify.com admin URLs
sqlite3 ~/Library/Application\ Support/Google/Chrome/Default/History \
  "SELECT DISTINCT replace(replace(url, 'https://', ''), 'http://', '') FROM urls WHERE url LIKE '%myshopify.com/admin%' OR url LIKE '%admin.shopify.com/store/%' ORDER BY last_visit_time DESC LIMIT 10" 2>/dev/null | \
  grep -oE '[a-z0-9-]+\.myshopify\.com|admin\.shopify\.com/store/[a-z0-9-]+' | sort -u

# Scan Dashlane URLs for myshopify.com store references
dcli password shopify --output json 2>/dev/null | jq -r '.[].url // empty' | grep -oE '[a-z0-9-]+\.myshopify\.com' | sort -u

# Scan project .env files for store URLs
grep -rhE 'myshopify\.com|SHOPIFY_STORE' ~/Projects/*/.env* 2>/dev/null | grep -v '^#' | head -5

重要:只有在检查完所有扫描来源后,才能报告“未找到 Shopify 凭据”。如果缺少令牌,但找到了商店 URL(例如,在 Chrome 历史记录或 Dashlane 中),则报告:"Found Shopify store(s): <stores>. No API token found — you'll need to create one.",并直接跳到第 3i.3 步(令牌),同时预填商店 URL。

如果已找到 store_url 和 admin_token,则显示:

✓ Shopify — already configured (<store_url>)
  [Keep existing]  [Reconfigure]

如果用户选择保留现有配置,则跳到第 3i.4 步。如果用户要重新配置或未找到任何值,则继续。

第 3i.2 步 — Shopify 商店 URL

如果在自动扫描中找到 SHOPIFY_STORE_URL,则使用通用凭据自动扫描提示格式显示该值。只有在未找到值时,才通过自由文本询问:

Enter your Shopify store URL:
  Format: yourstore.myshopify.com
  (Do not include https://)

验证输入:移除 https://,移除末尾斜杠,并检查结果是否以 .myshopify.com 结尾。如果无效,则附上更正说明并重新询问。

第 3i.3 步 — Shopify Admin API 令牌

如果在自动扫描中找到 SHOPIFY_ACCESS_TOKEN、SHOPIFY_ADMIN_TOKEN 或 SHOPIFY_ADMIN_API_ACCESS_TOKEN,请使用通用凭据自动扫描提示格式展示,并截断显示(shpat_508b...682e)。只有在未找到值时,才通过自由文本询问。

多店铺处理:发现多个店铺时,请分别处理每个店铺。对于没有令牌的店铺,请先尝试自动化方式:

  1. 检查所有项目中的 Doppler,查找特定店铺的 Shopify 令牌:
    for proj in $(doppler projects --json 2>/dev/null | jq -r '.[].slug'); do
      doppler secrets --project "$proj" --config prd --json 2>/dev/null | \
        jq -r --arg proj "$proj" 'to_entries[] | select(.key | test("SHOPIFY.*TOKEN|SHOPIFY.*ACCESS"; "i")) | "\(.key)=\(.value.computed | .[0:12])... (doppler:\($proj)/prd)"'
    done
    
  2. 如果已安装 Shopify CLI(command -v shopify),请尝试使用它:
    shopify auth logout 2>/dev/null  # Clear stale session
    shopify auth login --store <store>.myshopify.com 2>&1  # Opens browser OAuth
    
    成功完成身份验证后,通过 CLI 生成自定义应用令牌。这样可以避免手动浏览管理后台。
  3. 浏览器自动化 — 如果 Kapture/Playwright MCP 可用,请访问 https://admin.shopify.com/store/<slug>/settings/apps/development,并自动执行“创建应用”→“配置范围”→“安装”→“显示令牌”流程。使用以下范围:read_orders,read_products,read_customers,read_inventory,read_fulfillments,read_analytics。
  4. 手动备用方案 — 仅在所有自动化方式均失败时使用:
    No automated path available for <store>.myshopify.com.
    To generate a token manually:
      1. Go to https://admin.shopify.com/store/<slug>/settings/apps/development
      2. Create an app → Configure → grant scopes → Install → copy token
      Token starts with "shpat_"
    

不要仅仅因为没有找到令牌就跳过某个店铺——务必先尝试自动化。用户希望向导负责生成凭据,而不只是查找凭据。

保存到 $PREFS_PATH 下的 ecom.shopify。应用 Doppler 引用模式——如果已配置 Doppler,请运行:

doppler secrets set SHOPIFY_ADMIN_TOKEN="<token>" --project <project> --config <config>

并在偏好设置中存储 "admin_token": "doppler:SHOPIFY_ADMIN_TOKEN",而不是原始令牌。

冒烟测试:

STORE=$(jq -r '.ecom.shopify.store_url' "$PREFS_PATH")
TOKEN=$(jq -r '.ecom.shopify.admin_token' "$PREFS_PATH")
if [[ "$TOKEN" == doppler:* ]]; then
  KEY="${TOKEN#doppler:}"
  TOKEN=$(doppler secrets get "$KEY" --plain 2>/dev/null)
fi
curl -s -H "X-Shopify-Access-Token: $TOKEN" \
  "https://$STORE/admin/api/2024-10/shop.json" | jq '.shop.name'

预期结果为店铺名称字符串。如果响应中包含 errors 或 {"shop":null},请显示错误并让用户检查令牌范围。打印 ✓ Shopify — 已连接(<shop name>)。

步骤 3i.4 — 动态电商合作伙伴

配置好 Shopify 后,通过 AskUserQuestion 提问(自由文本):

Do you use any other ecommerce tools you'd like to connect?
  Examples: ShipBob (fulfillment), Recharge (subscriptions), Yotpo (reviews),
            Shippo (shipping rates), Gorgias (support), Attentive (SMS), Loop (returns)
  Type the names separated by commas, or leave blank to skip.

如果用户提供了合作伙伴名称,请循环处理每个名称:

对于每个合作伙伴:

  1. 查询凭据 — 网页搜索:"<partner name> API authentication developer docs 2025"。确定:

    • 需要哪些凭据(API 密钥、OAuth 令牌、Webhook 密钥、基础 URL、账户 ID 等)
    • API 基础 URL
    • 适合用于冒烟测试的健康检查/身份验证端点(例如 /me、/account、/v1/ping,或限制为 1 条的列表端点)
    • 身份验证请求头格式(Authorization: Bearer、X-Api-Key、自定义请求头等)
  2. 通过 AskUserQuestion 询问每项凭据(每个凭据字段提一个问题),根据文档说明凭据的获取位置。以 ShipBob 为例: 在提问前,对 SHIPBOB_ACCESS_TOKEN、SHIPBOB_API_TOKEN 运行 Universal Credential Auto-Scan。如果找到凭据,请注明来源并显示。只有在未找到时才手动询问:

    Enter your ShipBob Personal Access Token:
      To generate: ShipBob dashboard → Integrations → API → Personal Access Tokens → Create Token
    
  3. 冒烟测试,使用步骤 1 中找到的身份验证端点:

    curl -s -H "<auth_header>: $TOKEN" "<base_url>/<health_endpoint>" | jq '.<identity_field>'
    

    显示结果。如果失败,显示原始响应,并提供 [重新输入凭据]/[跳过此合作伙伴] 选项。

  4. 将信息保存到偏好设置,路径为 ecom.partners.<partner_slug>,其中 partner_slug 是合作伙伴名称转换为小写并以连字符连接后的形式:

    {
      "ecom": {
        "partners": {
          "shipbob": {
            "api_base_url": "https://api.shipbob.com/v1",
            "auth_pattern": "Authorization: Bearer <token>",
            "credentials": { "api_token": "doppler:SHIPBOB_API_TOKEN" },
            "health_endpoint": "/user",
            "configured_at": "<ISO timestamp>"
          }
        }
      }
    }
    

    配置了 Doppler 时,将实际密钥存储在 Doppler 中(密钥:<PARTNER_SLUG_UPPER>_API_TOKEN),否则内联存储。auth_pattern 和 api_base_url 字段是未来 /ops:ecom 调用用于访问合作伙伴的信息,务必根据查询到的文档填写。

  5. 打印确认信息:

    ✓ <Partner Name> — connected
    
  6. 循环:处理完每个合作伙伴后,通过 AskUserQuestion 提问:“还要连接其他电商工具吗?” → [是 — 添加另一个]/[完成]。这样用户可以逐个添加合作伙伴,而不必在最初用逗号分隔的列表一次性提供。

已知凭据格式的合作伙伴(直接使用以下信息,无需搜索,但仍需进行冒烟测试):

合作伙伴身份验证请求头基础 URL健康检查端点
ShipBobAuthorization: Bearer <token>https://api.shipbob.com/v1/user
RechargeX-Recharge-Access-Token: <token>https://api.rechargeapps.com/v1/shop
YotpoX-Api-Key: <app_key>https://api.yotpo.com/core/v3/stores/<id>
ShippoAuthorization: ShippoToken <token>https://api.goshippo.com/carrier_accounts
GorgiasAuthorization: Basic <base64>https://<domain>.gorgias.com/api/account
LoopX-Authorization: <secret>https://api.loopreturns.com/api/v1/warehouse
AttentiveAuthorization: Bearer <token>https://api.attentivemobile.com/v1/me

对于此表中未列出的任何合作伙伴,在询问凭据前,务必先通过网页搜索查询最新的身份验证文档。

深入了解: 请参阅 ${CLAUDE_PLUGIN_ROOT}/skills/ops-ecom/SKILL.md,了解电商集成的完整操作说明、CLI 参考和故障排查信息(多店铺 Shopify、合作伙伴分发、店铺健康守护进程)。当设置 Agent 需要比本向导更深入的信息时,可以直接加载该文件。


3j — 营销(Klaviyo、Meta Ads、GA4、Search Console)

显示服务选择器之前,同时对所有营销变量运行 Universal Credential Auto-Scan:

# Shell env
printenv KLAVIYO_API_KEY KLAVIYO_PRIVATE_KEY META_ACCESS_TOKEN FACEBOOK_ACCESS_TOKEN META_AD_ACCOUNT_ID GA4_PROPERTY_ID GA_MEASUREMENT_ID 2>/dev/null
printenv GOOGLE_ADS_DEVELOPER_TOKEN GOOGLE_ADS_CLIENT_ID GOOGLE_ADS_CLIENT_SECRET GOOGLE_ADS_REFRESH_TOKEN GOOGLE_ADS_CUSTOMER_ID 2>/dev/null

# Shell profiles
grep -h 'KLAVIYO\|META_\|FACEBOOK\|GA4\|GA_MEASUREMENT\|GOOGLE_ADS' ~/.zshrc ~/.bashrc ~/.zprofile ~/.envrc 2>/dev/null | grep -v '^#'

# Doppler across all projects
for proj in $(doppler projects --json 2>/dev/null | jq -r '.[].slug'); do
  doppler secrets --project "$proj" --config prd --json 2>/dev/null | \
    jq -r --arg proj "$proj" 'to_entries[] | select(.key | test("KLAVIYO|META|FACEBOOK|GA4|GOOGLE|GOOGLE_ADS")) | "\(.key)=\(.value.computed) (doppler:\($proj)/prd)"'
done

# Dashlane
dcli password klaviyo --output json 2>/dev/null
dcli password facebook --output json 2>/dev/null
dcli password meta --output json 2>/dev/null

# OpenClaw
jq -r '.agents.defaults.env | to_entries[] | select(.key | test("KLAVIYO|META_|FACEBOOK|GA4")) | "\(.key)=\(.value)"' ~/.openclaw/openclaw.json 2>/dev/null

缓存这些结果,用于预填以下各个子步骤的答案。对于以下每项服务,在提示用户之前,检查是否已配置(先检查 $PREFS_PATH 中的 marketing.*,再检查上面的自动扫描结果)。如果已配置,显示 ✓ <service> — already configured,并提供 [Keep] / [Reconfigure] 选项。

通过两次连续的 AskUserQuestion 调用询问要配置哪些营销集成,使用 multiSelect: true(根据 Rule 1,最多 4 项):

第一次调用(主要集成):

选项标题描述
Klaviyoklaviyo邮件/SMS 营销 — 私有 API 密钥
Meta AdsmetaFacebook/Instagram 广告 — 访问令牌 + 广告账户 ID
Google Adsgoogle-ads付费搜索广告 — OAuth2 + 开发者令牌
更多...moreGoogle Analytics 4、Search Console

如果用户选择 [More...],则显示第二次调用:

选项标题描述
Google Analytics 4ga4网站分析 — GA4 属性 ID
Google Search ConsolegscSEO 数据 — 网站 URL(使用 gcloud 身份验证)
WhatsApp Business APIwaba大规模模板消息 — Business 令牌 + ID
跳过skip营销设置已完成

按所选顺序运行下面选中的子步骤。

Klaviyo

如果自动扫描中找到了 KLAVIYO_API_KEY 或 KLAVIYO_PRIVATE_KEY,则按 Universal Credential Auto-Scan 提示格式显示。只有未找到时才通过自由文本询问:

Enter your Klaviyo Private API Key:
  To generate one: Klaviyo dashboard → Settings → API Keys → Create Private Key
  Key starts with "pk_"

冒烟测试:

curl -s -H "Authorization: Klaviyo-API-Key $KEY" \
  -H "revision: 2024-10-15" \
  "https://a.klaviyo.com/api/lists" | jq '.data | length'

预期结果为大于或等于 0 的数字。如果响应中包含带有身份验证错误的 "detail",则显示该错误并重新询问。

Meta Ads

如果自动扫描中找到了 META_ACCESS_TOKEN 或 FACEBOOK_ACCESS_TOKEN,则显示该令牌。如果找到了 META_AD_ACCOUNT_ID,也一并显示。只有未找到的值才通过自由文本询问。

询问以下内容:

  1. 访问令牌(说明:前往 Meta Business Suite → Settings → System Users,或你的个人账户 → Generate token,并授予 ads_read 权限)
  2. 广告账户 ID(格式:act_XXXXXXXXXX — 可在 Business Manager → Ad Accounts 中找到)

冒烟测试:

curl -s "https://graph.facebook.com/v20.0/$AD_ACCOUNT_ID/campaigns?access_token=$TOKEN&limit=1" | jq '.data | length'
Google Ads

Google Ads 需要三组凭据。按顺序逐步引导用户完成每个步骤。

步骤 A — 开发者令牌: 如果自动扫描中找到了 GOOGLE_ADS_DEVELOPER_TOKEN,请展示该令牌,并提供 [Keep] / [Reconfigure] 选项。 否则,通过自由文本询问:

您的 Google Ads 开发者令牌(可在 Google Ads → Tools & Settings → API Center 中找到)。 注意:新开发者令牌初始处于“测试”模式,只能用于测试账号。要获取生产数据,请通过 https://ads.google.com/home/tools/manager-accounts/ 申请 Basic Access。

步骤 B — OAuth2 客户端凭据: 如果自动扫描中找到了 GOOGLE_ADS_CLIENT_ID 和 GOOGLE_ADS_CLIENT_SECRET,请展示并提供 [Keep] / [Reconfigure] 选项。 否则,通过自由文本询问(分两次提问):

  1. OAuth2 Client ID(从 Google Cloud Console → APIs & Services → Credentials → OAuth 2.0 Client ID 中获取,类型:Desktop app;必须启用 Google Ads API)
  2. OAuth2 Client Secret(与客户端 ID 一同显示)

步骤 C — 刷新令牌(浏览器 OAuth 流程): 如果自动扫描中找到了 GOOGLE_ADS_REFRESH_TOKEN,请展示并提供 [Keep] / [Reconfigure] 选项。 否则,生成授权 URL 并运行 OAuth 流程:

AUTH_URL="https://accounts.google.com/o/oauth2/auth?client_id=${GADS_CLIENT_ID}&redirect_uri=http://localhost:8080&response_type=code&scope=https://www.googleapis.com/auth/adwords&access_type=offline&prompt=consent"
open "$AUTH_URL"  # macOS

启动临时 localhost 服务以接收重定向:

# One-liner node HTTP server to capture the auth code
node -e "require('http').createServer((req,res)=>{const code=new URL(req.url,'http://localhost').searchParams.get('code');if(code){res.end('Authorization code received. You can close this tab.');process.stdout.write(code);process.exit(0)}else{res.end('Waiting for auth...')}}).listen(8080)"

通过 Bash 运行服务,并设置 run_in_background: true。等待授权码,最长 120 秒。

如果 localhost 方式失败,则请用户从浏览器地址栏中复制代码(code= 参数)。

使用代码交换刷新令牌:

TOKEN_RESP=$(curl -s -X POST https://oauth2.googleapis.com/token \
  --data "code=${AUTH_CODE}" \
  --data "client_id=${GADS_CLIENT_ID}" \
  --data "client_secret=${GADS_CLIENT_SECRET}" \
  --data "redirect_uri=http://localhost:8080" \
  --data "grant_type=authorization_code")
GADS_REFRESH_TOKEN=$(echo "$TOKEN_RESP" | jq -r '.refresh_token')

如果 GADS_REFRESH_TOKEN 为 null 或为空,则打印错误并提供重试选项。

警告:如果用户的 Google Cloud 项目发布状态为“测试”,刷新令牌会在 7 天后过期。请警告用户:“您的 OAuth 应用处于测试模式,令牌将在 7 天后过期。要获取长期有效的令牌,请在 Google Cloud Console → OAuth consent screen → Publish App 中发布该应用。”

步骤 D — 客户 ID: 使用刷新令牌获取访问令牌,然后列出可访问的客户:

ACCESS_TOKEN=$(curl -s -X POST https://oauth2.googleapis.com/token \
  --data "client_id=${GADS_CLIENT_ID}&client_secret=${GADS_CLIENT_SECRET}&refresh_token=${GADS_REFRESH_TOKEN}&grant_type=refresh_token" | jq -r '.access_token')

CUSTOMERS=$(curl -s -X GET \
  "https://googleads.googleapis.com/v23/customers:listAccessibleCustomers" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "developer-token: ${GADS_DEV_TOKEN}")

如果返回多个账号,请以 AskUserQuestion 选项的形式展示(根据规则 1,最多 4 个;如果更多则分页)。如果只有一个账号,则自动选择。保存为 customer_id(去掉 customers/ 前缀和连字符)。

如果有任何账号是经理(MCC)账号,也要保存 login_customer_id。通过检查 listAccessibleCustomers 是否同时返回经理账号和客户账号来自动检测。

步骤 E — 冒烟测试:

curl -s -X GET "https://googleads.googleapis.com/v23/customers:listAccessibleCustomers" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "developer-token: ${GADS_DEV_TOKEN}"

预期返回包含 resourceNames 数组的 JSON。如果发生错误,则打印错误并提供 [Retry] / [Skip] 选项。

步骤 F — 保存到偏好设置:

{
  "marketing": {
    "google_ads": {
      "developer_token": "<YOUR_DEVELOPER_TOKEN>",
      "client_id": "<YOUR_CLIENT_ID>.apps.googleusercontent.com",
      "client_secret": "GOCSPX-<YOUR_SECRET>",
      "refresh_token": "1//<YOUR_REFRESH_TOKEN>",
      "customer_id": "1234567890",
      "login_customer_id": "9876543210"
    }
  }
}

使用与步骤 3i 相同的 Doppler 引用模式——如果已配置 Doppler,优先使用 doppler:KEY_NAME,而不是原始令牌。

打印:[Google Ads] ✓ connected — customer ID: XXXXXXXXXX

Google Analytics 4

如果自动扫描中找到了 GA4_PROPERTY_ID 或 GA_MEASUREMENT_ID,请展示该值。只有未找到时才通过自由文本询问。询问 GA4 Property ID(说明:GA4 信息中心 → Admin → Property Settings → Property ID,格式为数字,例如 123456789)。

如果已通过 gcloud 进行身份验证,则无需 API 密钥——GA4 Data API 使用 Application Default Credentials。检查:

gcloud auth application-default print-access-token 2>/dev/null | head -c 10

如果尚未设置 gcloud ADC,请说明 GA4 查询需要手动进行身份验证:gcloud auth application-default login。

Google Search Console

询问网站 URL(格式:https://example.com/ 或 sc-domain:example.com)。如果已通过 gcloud 进行身份验证,则无需 API 密钥。

冒烟测试:

ACCESS_TOKEN=$(gcloud auth application-default print-access-token 2>/dev/null)
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://searchconsole.googleapis.com/webmasters/v3/sites" | jq '.siteEntry | length'
WhatsApp Business API

**与个人 WhatsApp 的 wacli 服务分开。**用于大规模发送面向客户的 Business 模板消息。

自动扫描:

printenv WHATSAPP_BUSINESS_TOKEN WHATSAPP_PHONE_NUMBER_ID WHATSAPP_BUSINESS_ACCOUNT_ID 2>/dev/null
claude plugin config get whatsapp_business_token 2>/dev/null && echo "waba_token: already configured"
claude plugin config get whatsapp_phone_number_id 2>/dev/null && echo "waba_phone_id: already configured"
claude plugin config get whatsapp_business_account_id 2>/dev/null && echo "waba_account_id: already configured"

凭证位置:

  • WHATSAPP_BUSINESS_TOKEN:Meta Developer Portal → Your App → WhatsApp → API Setup → 临时令牌或 System User 令牌
  • WHATSAPP_PHONE_NUMBER_ID:同一页面 →“From”电话号码 → Phone Number ID
  • WHATSAPP_BUSINESS_ACCOUNT_ID:Meta Business Manager → Business Settings → WhatsApp Accounts → ID

如果未找到,使用 AskUserQuestion 逐项收集(自由文本)。保存到:

claude plugin config set whatsapp_business_token "$WABA_TOKEN"
claude plugin config set whatsapp_phone_number_id "$WABA_PHONE_ID"
claude plugin config set whatsapp_business_account_id "$WABA_ACCOUNT_ID"

冒烟测试:

curl -s "https://graph.facebook.com/v20.0/${WABA_PHONE_ID}" \
  -H "Authorization: Bearer ${WABA_TOKEN}" | jq '.display_phone_number // empty'

如果返回电话号码:WhatsApp Business ✓ connected — Phone: <number>。否则显示错误。

保存到偏好设置

写入 $PREFS_PATH(合并):

{
  "marketing": {
    "klaviyo": { "api_key": "<pk_...>" },
    "meta": { "access_token": "<token>", "ad_account_id": "act_XXXXXXXXXX" },
    "ga4": { "property_id": "123456789" },
    "gsc": { "site_url": "https://example.com/" },
    "whatsapp_business": { "phone_number_id": "<ID>", "business_account_id": "<WABA_ID>" }
  }
}

采用与步骤 3i 相同的 Doppler 引用模式 — 配置了 Doppler 时,优先使用 doppler:KEY_NAME,而不是原始令牌。

动态营销合作伙伴

处理完已知服务后,通过 AskUserQuestion 询问(自由文本):

Any other marketing tools you'd like to connect?
  Examples: Postscript (SMS), Privy (popups), Triple Whale (attribution), Northbeam,
            Hotjar, Heap, Segment, Mixpanel, Mailchimp, ActiveCampaign, HubSpot
  Type names separated by commas, or leave blank to skip.

如果用户提供合作伙伴名称,采用与步骤 3i.4 相同的动态合作伙伴循环 — 对每个合作伙伴:

  1. 研究凭证,通过网页搜索:"<partner name> API authentication developer docs 2025"
  2. 询问凭证,通过 AskUserQuestion 提供从文档中获取的说明
  3. 冒烟测试,针对身份验证/健康检查端点
  4. 保存到偏好设置,路径为 marketing.partners.<partner_slug>:
    {
      "marketing": {
        "partners": {
          "triple-whale": {
            "api_base_url": "https://api.triplewhale.com/api/v2",
            "auth_pattern": "Authorization: Bearer <token>",
            "credentials": { "api_key": "doppler:TRIPLE_WHALE_API_KEY" },
            "health_endpoint": "/attribution/get-attribution-data",
            "configured_at": "<ISO timestamp>"
          }
        }
      }
    }
    
  5. 循环 — 处理完每个合作伙伴后,提供 [Add another] / [Done] 选项。

已知凭证模式的合作伙伴(直接使用,但仍需冒烟测试):

PartnerAuth headerBase URLHealth endpoint
HubSpotAuthorization: Bearer <token>https://api.hubapi.com/crm/v3/objects/contacts?limit=1
MailchimpAuthorization: Bearer <api_key>https://<dc>.api.mailchimp.com/3.0/ping
SegmentAuthorization: Basic <base64_key:>https://api.segment.io/v1n/a — use write key
MixpanelAuthorization: Basic <base64_secret:>https://data.mixpanel.com/api/2.0/engage?limit=1
PostscriptAuthorization: ApiKey <key>https://api.postscript.io/api/v2/shops
Triple WhaleAuthorization: Bearer <token>https://api.triplewhale.com/api/v2/attribution/get-attribution-data

对于此表中未列出的合作伙伴,在询问凭证前,务必通过网页搜索查找最新的身份验证文档。

**深入了解:**有关营销集成(Klaviyo flows、Meta Ads、GA4、Search Console)的完整操作说明、CLI 参考和故障排除,请参阅 ${CLAUDE_PLUGIN_ROOT}/skills/ops-marketing/SKILL.md。如果设置 Agent 需要比本向导更深入的信息,可以直接加载该文件。


3k — 语音(Bland AI、ElevenLabs、Groq)

显示服务选择器前,运行通用凭证自动扫描,同时扫描所有语音变量:

# Shell env
printenv BLAND_AI_API_KEY BLAND_API_KEY ELEVENLABS_API_KEY GROQ_API_KEY 2>/dev/null

# Shell profiles
grep -h 'BLAND\|ELEVENLABS\|GROQ' ~/.zshrc ~/.bashrc ~/.zprofile ~/.envrc 2>/dev/null | grep -v '^#'

# Doppler across all projects
for proj in $(doppler projects --json 2>/dev/null | jq -r '.[].slug'); do
  doppler secrets --project "$proj" --config prd --json 2>/dev/null | \
    jq -r --arg proj "$proj" 'to_entries[] | select(.key | test("BLAND|ELEVENLABS|GROQ")) | "\(.key)=\(.value.computed) (doppler:\($proj)/prd)"'
done

# Dashlane
dcli password "bland-ai" --output json 2>/dev/null
dcli password elevenlabs --output json 2>/dev/null
dcli password groq --output json 2>/dev/null

# OpenClaw (common location for AI service keys)
jq -r '.agents.defaults.env | to_entries[] | select(.key | test("BLAND|ELEVENLABS|GROQ")) | "\(.key)=\(.value)"' ~/.openclaw/openclaw.json 2>/dev/null

缓存这些结果 — 使用它们为以下各子步骤预填答案。同时检查 $PREFS_PATH 中 voice.* 下的现有配置。如果某个密钥已设置,显示 ✓ <service> — already configured,并提供 [Keep] / [Reconfigure] 选项。

通过 AskUserQuestion 询问要配置哪些语音服务,并设置 multiSelect: true:

OptionHeaderDescription
Bland AIbland外呼 AI 电话 — API 密钥
ElevenLabselevenlabs文本转语音和语音克隆 — API 密钥
Groqgroq快速 LLM 推理(Whisper、LLaMA)— API 密钥
Bland AI

如果自动扫描中发现了 BLAND_AI_API_KEY 或 BLAND_API_KEY,请使用通用凭据自动扫描提示格式展示。只有在未发现时,才通过自由文本询问:

Enter your Bland AI API Key:
  To find it: https://app.bland.ai → Settings → API Key

冒烟测试:

curl -s -H "Authorization: $KEY" "https://api.bland.ai/v1/me" | jq '.user.id'

预期得到非空的用户 ID。

ElevenLabs

如果自动扫描中发现了 ELEVENLABS_API_KEY,请使用通用凭据自动扫描提示格式展示。只有在未发现时,才通过自由文本询问:

Enter your ElevenLabs API Key:
  To find it: https://elevenlabs.io → Profile (top-right) → API Key

冒烟测试:

curl -s -H "xi-api-key: $KEY" "https://api.elevenlabs.io/v1/user" | jq '.subscription.tier'

预期得到订阅等级字符串(例如 "free"、"starter")。

Groq

如果自动扫描中发现了 GROQ_API_KEY,请使用通用凭据自动扫描提示格式展示。只有在未发现时,才通过自由文本询问:

Enter your Groq API Key:
  To generate one: https://console.groq.com → API Keys → Create API Key
  Key starts with "gsk_"

冒烟测试:

curl -s -H "Authorization: Bearer $KEY" \
  "https://api.groq.com/openai/v1/models" | jq '.data | length'

预期得到一个正整数(可用模型的数量)。

保存到偏好设置

写入 $PREFS_PATH(合并):

{
  "voice": {
    "bland": { "api_key": "<key>" },
    "elevenlabs": { "api_key": "<key>" },
    "groq": { "api_key": "gsk_..." }
  }
}

采用相同的 Doppler 引用模式——配置了 Doppler 时,优先使用 doppler:KEY_NAME,而不是原始令牌。

深入了解: 有关语音集成(Bland AI 呼叫流程、ElevenLabs TTS、Groq 转录)的完整操作说明、CLI 参考和故障排除,请参阅 ${CLAUDE_PLUGIN_ROOT}/skills/ops-voice/SKILL.md。如果设置 Agent 需要比本向导更深入的信息,可直接加载该文件。


3l — 收入(Stripe + RevenueCat)

显示服务选择器之前,同时运行所有收入变量的通用凭据自动扫描(规则 4 — 在后台运行):

# Shell env
printenv STRIPE_SECRET_KEY STRIPE_API_KEY REVENUECAT_API_KEY REVENUECAT_SECRET_KEY REVENUECAT_PROJECT_ID 2>/dev/null

# Shell profiles + .envrc files
grep -h 'STRIPE\|REVENUECAT\|RC_API' ~/.zshrc ~/.bashrc ~/.zprofile ~/.envrc 2>/dev/null | grep -v '^#'

# Doppler across all projects
for proj in $(doppler projects --json 2>/dev/null | jq -r '.[].slug'); do
  doppler secrets --project "$proj" --config prd --json 2>/dev/null | \
    jq -r --arg proj "$proj" 'to_entries[] | select(.key | test("STRIPE|REVENUECAT|RC_API")) | "\(.key)=\(.value.computed) (doppler:\($proj)/prd)"'
done

# 1Password
op item list --categories "API Credential" --format json 2>/dev/null | \
  jq -r '.[] | select(.title | test("stripe|revenuecat"; "i")) | .id' | \
  while read id; do op item get "$id" --format json 2>/dev/null; done

# Dashlane
dcli password stripe --output json 2>/dev/null
dcli password revenuecat --output json 2>/dev/null

# Bitwarden
bw list items --search stripe 2>/dev/null | jq -r '.[] | select(.login.password) | .login.password' | head -1
bw list items --search revenuecat 2>/dev/null | jq -r '.[] | select(.login.password) | .login.password' | head -1

# macOS Keychain
security find-generic-password -s "stripe" -w 2>/dev/null
security find-generic-password -s "revenuecat" -w 2>/dev/null

# OpenClaw
jq -r '.agents.defaults.env | to_entries[] | select(.key | test("STRIPE|REVENUECAT")) | "\(.key)=\(.value)"' ~/.openclaw/openclaw.json 2>/dev/null

缓存这些结果。同时检查 $PREFS_PATH 中的 revenue.stripe.* 和 revenue.revenuecat.*——如果已设置,则显示 ✓ <service> — 已配置,并提供 [保留] / [重新配置] 选项。

使用 AskUserQuestion 并设置 multiSelect: true,询问要配置哪些收入集成:

选项标题描述
StripestripeSaaS 收入 — 用于 MRR、收费和争议的密钥
RevenueCatrevenuecat移动端订阅 — 用于移动端 MRR 的 API 密钥 + 项目 ID
Stripe

如果自动扫描中发现了 STRIPE_SECRET_KEY,请使用通用凭据自动扫描提示格式展示,并提供 [使用此值] / [粘贴其他值] / [跳过] 选项。

根据规则 3——如果未发现任何结果,请提供(≤4 个选项):

No Stripe secret key found. How do you want to provide one?
  [Paste Stripe secret key manually]
  [Deep hunt — spawn agent]
  [Skip — use registry.json values]

选择 [深入搜索 — 启动 Agent] 时,根据规则 3 启动后台研究 Agent:

Agent(
  subagent_type: "general-purpose",
  model: "haiku",
  run_in_background: true,
  prompt: "Grep the filesystem under $HOME (excluding node_modules, .git, Library/Caches) for Stripe secret key patterns: sk_live_[A-Za-z0-9]+ and sk_test_[A-Za-z0-9]+. Also scan ~/.config, ~/.aws, ~/.docker, any .env files, and known secrets directories. Return every hit with file path + line number + 6 chars of redacted prefix (e.g. sk_live_abc***). Do not print full keys."
)

Agent 运行期间,继续执行 RevenueCat 部分。Agent 报告结果后返回 Stripe;通过 AskUserQuestion 展示发现的结果(按规则 1 分页,每页 ≤4 项)。

选择 [手动粘贴 Stripe 密钥] 时:

Enter your Stripe Secret Key:
  Format: sk_live_XXX  (production)  or  sk_test_XXX  (test mode)
  Find it: Stripe Dashboard → Developers → API keys → Secret key → Reveal
  Prefer a Doppler reference (e.g. doppler:STRIPE_SECRET_KEY) over the raw value.

冒烟测试:

curl -s -u "$STRIPE_SECRET_KEY:" "https://api.stripe.com/v1/balance" | jq '.available | length'

预期得到非零整数。如果返回 {"error": ...},请显示错误消息并重新询问。

RevenueCat

如果自动扫描中同时发现了 REVENUECAT_API_KEY 和 REVENUECAT_PROJECT_ID,请一并展示,并提供 [使用这些值] / [粘贴其他值] / [跳过] 选项。

根据规则 3——如果未发现,请提供:

No RevenueCat credentials found. How do you want to provide them?
  [Paste RevenueCat API key manually]
  [Deep hunt — spawn agent]
  [Skip — mobile MRR will be omitted]

选择 [深入搜索 — 启动 Agent] 时,启动后台 Agent(规则 4):

Agent(
  subagent_type: "general-purpose",
  model: "haiku",
  run_in_background: true,
  prompt: "Grep the filesystem under $HOME (excluding node_modules, .git, Library/Caches) for RevenueCat credential patterns: rcb_[A-Za-z0-9]+ (V2 secret), sk_[A-Za-z0-9]+ near the literal string 'revenuecat', and any env vars matching REVENUECAT_* or RC_API_*. Also look for project IDs (32-char alphanum strings) adjacent to any revenuecat match. Return each hit with file path + line number + 6-char redacted prefix. Do not print full keys."
)

选择 [手动粘贴 RevenueCat API 密钥] 时:

Enter your RevenueCat API Key:
  Find it: app.revenuecat.com → Project settings → API keys → Secret key
  Format: rcb_XXX (V2 secret)  or  sk_XXX (legacy secret key)

Enter your RevenueCat Project ID:
  Find it in the URL: app.revenuecat.com/projects/<project_id>/...

冒烟测试:

curl -s -H "Authorization: Bearer $REVENUECAT_API_KEY" \
  "https://api.revenuecat.com/v2/projects/$REVENUECAT_PROJECT_ID/metrics/overview" | jq '.metrics // .object'

预期得到数值型 mrr 或对象描述符。如果响应为 {"code": 7243, ...}(身份验证错误),请重新询问。

保存到偏好设置

写入 $PREFS_PATH(合并):

{
  "revenue": {
    "stripe": {
      "secret_key": "doppler:STRIPE_SECRET_KEY",
      "configured_at": "<ISO timestamp>"
    },
    "revenuecat": {
      "api_key": "doppler:REVENUECAT_API_KEY",
      "project_id": "<project_id>",
      "configured_at": "<ISO timestamp>"
    }
  }
}

配置了 Doppler 时,优先使用 Doppler 引用(doppler:STRIPE_SECRET_KEY、doppler:REVENUECAT_API_KEY),而不是原始令牌。对于任一服务,如果用户选择了 [Skip],则保存 {"revenue": {"<service>": "skipped"}},这样向导下次运行时就不会再次询问;不过,/ops:revenue 会回退到 scripts/registry.json 中的 revenue.mrr 值,具体说明见 revenue-tracker Agent。

深入了解: 完整的运营说明、CLI 参考,以及收入集成(Stripe MRR/ARR 查询、RevenueCat 订阅指标、注册表回退)的故障排除,请参阅 ${CLAUDE_PLUGIN_ROOT}/skills/ops-revenue/SKILL.md。需要比本向导更深入的信息时,设置 Agent 可以直接加载该文件。


3n — 通知(fires-watcher 接收端)

为 CRITICAL/HIGH 级别的 fires 设置推送通知,让用户不必再手动轮询 /ops:fires。此功能依赖 fires-watcher 守护进程服务(默认禁用)。

询问用户之前,在后台运行接收端自动扫描(规则 4——全部并行):

# Already-configured sinks (env + $PREFS_PATH)
printenv TELEGRAM_BOT_TOKEN TELEGRAM_NOTIFY_CHAT_ID TELEGRAM_OWNER_ID \
         DISCORD_WEBHOOK_URL NTFY_TOPIC PUSHOVER_USER PUSHOVER_TOKEN 2>/dev/null

jq -r 'to_entries
       | map(select(.key | test("telegram_bot_token|telegram_notify_chat_id|discord_webhook_url|ntfy_topic|pushover_user_key|pushover_app_token")))
       | .[] | "\(.key)=\(.value | if type == "string" and length > 6 then .[0:6] + "***" else . end)"' \
       "$PREFS_PATH" 2>/dev/null

# Macro: is the Telegram bot already configured via /ops:setup telegram?
jq -r '.telegram.bot_token // empty, .telegram.owner_id // empty' "$PREFS_PATH" 2>/dev/null

缓存结果。然后将选项列表筛选为尚未配置的项目(规则 1——最多 4 项)。

通过 AskUserQuestion 呈现(≤4 个选项)。典型的首批选项:

选项标题描述
使用 Telegram(推荐)telegram复用你在 3a 中配置的机器人。无需额外设置。
配置 ntfy.shntfy免费,无需账户。选择一个随机主题,并在手机上安装应用。
配置 Pushoverpushover一次性约 $5。移动端投递可靠性最高,并支持 P0 优先级绕过。
跳过——改为轮询 /ops:firesskip保持 fires-watcher 禁用。你仍需手动查询 fires。

如果 $PREFS_PATH 中尚未保存 Telegram 机器人令牌和所有者 ID,将 [Use Telegram] 替换为 [Configure Telegram bot — jump to 3a],返回此处前重新执行 3a。根据规则 3,不要默默跳过——始终明确提供 [Skip]。

对于希望额外使用 Discord 的用户(许多用户希望同时接收团队和个人通知),再运行一次 AskUserQuestion:

选项标题描述
另外添加 Discord webhookdiscord除 X 外,也发送到 #incidents 频道。
不需要——仅使用我选择的接收端done仅配置一个接收端。
各接收端配置

Telegram——如果 $TELEGRAM_NOTIFY_CHAT_ID 为空,将其默认设为 $TELEGRAM_OWNER_ID(机器人会给所有者发送私信),并将 {"telegram_notify_chat_id": "<owner_id>"} 保存到 $PREFS_PATH。在后台进行冒烟测试(规则 4):

scripts/ops-notify.sh LOW "setup test" "fires-watcher Telegram sink is live" &

ntfy.sh——如果用户没有主题,则生成一个随机主题:

TOPIC="claude-ops-fires-$(openssl rand -hex 6 2>/dev/null || head -c 12 /dev/urandom | base64 | tr -dc 'a-z0-9' | head -c 12)"
echo "$TOPIC"

显示主题和安装说明(在浏览器中打开 https://ntfy.sh/<topic>,或在 ntfy 移动应用中扫描二维码)。将其以 ntfy_topic 为键保存到 $PREFS_PATH。

Pushover——根据规则 3 提供选项:[Paste user key + app token] / [Deep hunt — spawn agent] / [Skip]。用户粘贴后,在后台进行冒烟测试以验证(规则 4):

curl -s -X POST https://api.pushover.net/1/messages.json \
  --data-urlencode "token=$PUSHOVER_TOKEN" \
  --data-urlencode "user=$PUSHOVER_USER" \
  --data-urlencode "title=claude-ops setup test" \
  --data-urlencode "message=fires-watcher Pushover sink is live" &

应在 5 秒内返回 {"status":1,...}。

Discord——如果用户之前的运行(或 issue #20 的相关工作)已设置 discord_default_webhook_url,则复用;否则根据规则 3 询问 webhook URL(https://discord.com/api/webhooks/...)。

保存并启用守护进程服务

写入 $PREFS_PATH(合并,绝不覆盖无关键):

{
  "notifications": {
    "configured_sinks": ["telegram", "ntfy"],
    "configured_at": "<ISO timestamp>"
  },
  "telegram_notify_chat_id": "<chat_id>",
  "ntfy_topic": "<topic>"
}

然后在后台启用守护进程服务(规则 4):

jq '.services["fires-watcher"].enabled = true' \
   ~/.claude/plugins/data/ops-ops-marketplace/daemon-services.json \
  > /tmp/daemon-services.json.new && \
  mv /tmp/daemon-services.json.new ~/.claude/plugins/data/ops-ops-marketplace/daemon-services.json && \
  scripts/ops-daemon.sh restart &

重启后确认监视器仍在运行:

cat ~/.claude/plugins/data/ops-ops-marketplace/fires-watcher.health 2>/dev/null

预期在 60 秒内获得 {"status": "ok", ...}。

**深入了解:**请参阅 ${CLAUDE_PLUGIN_ROOT}/docs/notifications.md、${CLAUDE_PLUGIN_ROOT}/scripts/ops-fires-watcher.sh 和 ${CLAUDE_PLUGIN_ROOT}/scripts/ops-notify.sh,了解接收端优先级的原因、去抖规则以及故障排查指南。

3m — Discord(webhook + 可选 bot)

Discord 是 v1 集成——基于 webhook 发送消息,并通过 REST 读取频道。私信和 gateway 支持推迟到 v2 问题中实现。发送端 webhook 也提供 scripts/ops-notify.sh 使用的 Discord 通知接收端,因此在此处配置即可同时支持 /ops:comms discord send 和 ops-fires 告警。

显示凭据提示之前,对 Discord 运行 Universal Credential Auto-Scan(规则 4——除非下一步依赖结果,否则每次 bash 调用都在后台运行):

# Shell env
printenv DISCORD_BOT_TOKEN DISCORD_WEBHOOK_URL DISCORD_GUILD_ID 2>/dev/null

# Shell profiles + .envrc
grep -hE 'DISCORD_(BOT_TOKEN|WEBHOOK|GUILD_ID)' ~/.zshrc ~/.bashrc ~/.zprofile ~/.envrc 2>/dev/null | grep -v '^#'

# Doppler across all projects
for proj in $(doppler projects --json 2>/dev/null | jq -r '.[].slug'); do
  doppler secrets --project "$proj" --config prd --json 2>/dev/null | \
    jq -r --arg proj "$proj" 'to_entries[] | select(.key | test("DISCORD")) | "\(.key)=\(.value.computed) (doppler:\($proj)/prd)"'
done

# 1Password
op item list --categories "API Credential" --format json 2>/dev/null | \
  jq -r '.[] | select(.title | test("discord"; "i")) | .id' | \
  while read id; do op item get "$id" --format json 2>/dev/null; done

# Dashlane / Bitwarden
dcli password discord --output json 2>/dev/null
bw list items --search discord 2>/dev/null | jq -r '.[] | select(.login.password) | .login.password' | head -1

# macOS Keychain (Darwin only — the setup wizard already guards this at the OS level)
security find-generic-password -s "discord" -w 2>/dev/null

# Claude-ops credential store (cross-OS)
"${CLAUDE_PLUGIN_ROOT}/lib/credential-store.sh" get discord bot-token 2>/dev/null

缓存这些结果。还要检查 $PREFS_PATH 中的 discord.* 和 discord_webhook_url(与 ops-notify.sh 共用的扁平键)——如果已存在 discord.bot_token、discord.default_webhook_url 或 discord_webhook_url 中的任何一项,则显示 ✓ Discord — already configured,并提供 [Keep] / [Reconfigure] 选项。

如果找到了任何内容

按照 Universal Credential Auto-Scan 提示格式展示,并提供 [Use this value] / [Paste a different one] / [Skip] 选项。如果找到了 webhook URL,但没有 bot token,请说明读取和频道列表功能将不可用(仅支持发送)。

根据规则 3——如果没有找到任何内容,明确询问(根据规则 1,≤4 个选项)
No Discord credentials found. How do you want to configure Discord?
  [Paste bot token]
  [Paste webhook URL only]
  [Deep hunt — spawn agent]
  [Skip — I'll configure later]

选择 [Deep hunt — spawn agent] 时,启动一个后台研究 Agent(规则 4——run_in_background: true):

Agent(
  subagent_type: "general-purpose",
  model: "haiku",
  run_in_background: true,
  prompt: "Grep the filesystem under $HOME (excluding node_modules, .git, Library/Caches) for Discord credentials. Patterns: bot tokens look like base64 segments separated by dots (e.g. MTAxXXXX.YYYYYY.ZZZZZZZZZZZZ), webhook URLs start with https://discord.com/api/webhooks/ or https://discordapp.com/api/webhooks/, guild IDs are 17-20 digit snowflakes adjacent to the word 'guild' or 'server'. Also scan ~/.config, ~/.env, any .envrc files, and Doppler/1Password exports. Return every hit with file path + line number + 6-char redacted prefix. Do not print full tokens."
)

等待搜索期间,继续进行下一项设置;Agent 返回结果后,再回到 Discord,并通过 AskUserQuestion 展示结果(分页,每页 ≤4 项,遵循规则 1)。

选择 [Paste bot token] 时:

Enter your Discord Bot Token:
  Find it: https://discord.com/developers/applications → your app → Bot → Reset Token
  Format: MTAxXXXX.YYYYYY.ZZZZZZZZZZZZZZ  (base64 dot-separated)
  Prefer a Doppler reference (doppler:DISCORD_BOT_TOKEN) over the raw value.

Enter your Discord Guild ID (optional — needed for `channels` listing):
  Enable Developer Mode in Discord → right-click server → Copy ID.

冒烟测试(根据规则 4,通过 Bash 在后台运行):

curl -sS "https://discord.com/api/v10/users/@me" \
  -H "Authorization: Bot $DISCORD_BOT_TOKEN" \
  -H "User-Agent: ops-discord (claude-ops, v1)" | jq '.id // .message'

预期得到一个数字形式的 snowflake id。如果响应为 {"message":"401: Unauthorized", ...},请重新询问。

选择 [Paste webhook URL only] 时:

Enter a default Discord Webhook URL:
  Find it: Discord → Server Settings → Integrations → Webhooks → New Webhook → Copy URL
  Format: https://discord.com/api/webhooks/<ID>/<TOKEN>

冒烟测试——设置期间不要向 webhook 发送内容。改为发送 GET 请求,确认该 URL 能返回 webhook 元数据:

curl -sS -o /dev/null -w '%{http_code}\n' -X GET "$DISCORD_WEBHOOK_URL"

预期返回 200(已返回 webhook 元数据)或 401(URL 有效,但 token 无效——用户需要重新复制)。

保存到偏好设置

写入 $PREFS_PATH(合并):

{
  "discord": {
    "bot_token": "doppler:DISCORD_BOT_TOKEN",
    "guild_id": "<GUILD_ID>",
    "default_webhook_url": "doppler:DISCORD_WEBHOOK_URL",
    "configured_at": "<ISO timestamp>"
  },
  "discord_webhook_url": "doppler:DISCORD_WEBHOOK_URL"
}

将 webhook 同步到扁平的 discord_webhook_url 键,以便 scripts/ops-notify.sh(现有 fires 接收端)继续找到它。配置了 Doppler 时,优先使用 Doppler 引用,而不是原始 token。在配有原生密钥环的系统上,优先使用凭据存储(ops_cred_set discord bot-token <value>),而不是以明文写入偏好设置。如果用户选择了 [Skip],则保存 {"discord": "skipped"},避免向导下次运行时再次询问。

**深入了解:**请参阅 ${CLAUDE_PLUGIN_ROOT}/skills/ops-comms/SKILL.md(Discord 发送/读取部分)和 ${CLAUDE_PLUGIN_ROOT}/bin/ops-discord,了解完整的操作说明和子命令参考。


步骤 4 — 配置 MCP(如果已选择)

对于每个尚未添加到 mcp_configured 的 MCP,先提供批量设置选项:

Unconfigured MCPs: Linear, Sentry, Vercel. What would you like to do?
  [Configure all MCPs — run claude mcp add for each (Recommended)]
  [Pick which MCPs to add]
  [Skip MCP configuration]

如果用户选择“全部配置”,依次为每个未配置的 MCP 运行 claude mcp add <name>。如果选择“选择要配置的项目”,则逐项列出:

Linear:  claude mcp add linear
Sentry:  claude mcp add sentry
Vercel:  claude mcp add vercel
Slack:   claude mcp add slack
Gmail:   claude mcp add gmail   (fallback only — prefer `gog` CLI, see Step 3c)

对每项提供 [立即添加]、[跳过]。不要尝试从技能中注册 MCP——插件无法安全地完成此操作。

**电子邮件说明:**ops 插件的主要电子邮件处理方式是使用 gog CLI(完整读取和发送功能,使用自有 OAuth)。Gmail MCP 连接器可用作备用方案,但若不在 Claude Desktop → Settings → Connectors 中进行额外权限配置,就无法发送邮件。向导会在步骤 3c 中检测这一情况;此步骤仅列出该连接器,供有意使用 MCP 的用户在此安装。

**深入了解:**有关 MCP 支持的集成(Linear 问题流、分流路由、Sentry/Vercel 故障)的完整操作说明、CLI 参考和故障排除,请参阅 ${CLAUDE_PLUGIN_ROOT}/skills/ops-linear/SKILL.md、${CLAUDE_PLUGIN_ROOT}/skills/ops-triage/SKILL.md 和 ${CLAUDE_PLUGIN_ROOT}/skills/ops-fires/SKILL.md。当设置 Agent 需要比此向导提供的更多信息时,可直接加载这些文件。


步骤 5 — 构建项目注册表(如果已选择)

模板:${CLAUDE_PLUGIN_ROOT}/scripts/registry.templates/ 中提供了适用于常见技术栈的预设模板。若要从模板开始,运行 cp "${CLAUDE_PLUGIN_ROOT}/scripts/registry.templates/<stack>.json" "${CLAUDE_PLUGIN_ROOT}/scripts/registry.json",然后进行编辑;也可以继续使用下方的向导进行交互式发现。

从文件系统自动发现

在要求用户手动输入项目之前,先扫描现有的 Git 仓库:

find ~ ~/Projects -maxdepth 2 -name ".git" -type d 2>/dev/null | sed 's|/.git||' | sort

通过 AskUserQuestion 向用户展示发现的路径,并设置 multiSelect: true。每次最多提供 4 个选项——每页显示 3 个项目,并将 [更多...] 或 [都没有 — 我将手动输入项目] 作为最后一个选项:

Found git repositories (page 1 of N):
  [ ] ~/Projects/my-app
  [ ] ~/Projects/my-api
  [ ] ~/Projects/my-ai
  [ ] More repositories...

在最后一页,将“更多仓库...”替换为 [以上都不是 / 已完成选择]。

对于每个选中的项目,每次通过一个 AskUserQuestion 收集以下字段:

  • alias(短名称,必填——建议使用目录名称作为默认值)
  • org(GitHub 组织或所有者,例如 your-org 或 your-username)
  • infra.platform → 选择 [aws]、[vercel]、[cloudflare]、[other]
  • revenue.model → 分批选择,每批 4 项:[saas]、[subscription]、[marketplace]、[More...],然后是 [internal]、[portfolio]、[other]
自动发现外部项目

真实用户的项目组合很少只有 Git 仓库。Shopify 商店、Linear 团队、Slack 工作区和 Notion 数据库都是一等项目,ops-external + ops-projects 知道如何展示它们——但前提是它们已添加到 registry.json。此子步骤会探测向导已配置的集成,并提供发现的项目供用户一键注册。

${CLAUDE_PLUGIN_ROOT}/bin/ops-discover-external 2>/dev/null || echo '[]'

该脚本从 $PREFS_PATH .ecom.shopify.* 和 SHOPIFY_* 环境变量读取 Shopify 凭据,从 LINEAR_API_KEY 读取 Linear 凭据,从 keychain 的 slack-xoxc/slack-xoxd 读取 Slack 凭据,并从 NOTION_API_KEY / keychain 的 notion-api-key 读取 Notion 凭据。脚本会返回候选项目数组,每个候选项目都包含可直接合并的 config 块。

解析这些候选项目,并针对每个尚未出现在 registry.json 中的项目(按 config.alias 或 source + source-specific ID 匹配),通过 AskUserQuestion 提供给用户。每次调用最多列出 3 个候选项目,并添加 [More...],以符合规则 1(最多 4 个选项)。批次示例:

Found external projects not yet in your registry (page 1 of N):
  [Register "mystore" (shopify — basic plan, 142 products)]
  [Register "linear-eng" (linear — Engineering, 42 open issues)]
  [Register "notion-roadmap" (notion — Product Roadmap database)]
  [More candidates...]

最后一页的最后一个选项改为 [None of the remaining — skip]。可以使用多选;如果提供多选,multiSelect: true 列表的大小不得超过 4。

对于每个接受的候选项目,使用 jq 将其 config 块直接合并到 registry.json .projects[] 中:

PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT}"
REG="$PLUGIN_ROOT/scripts/registry.json"
[ -f "$REG" ] || echo '{"version":"1.0","owner":"","projects":[]}' > "$REG"
jq --argjson new "$CONFIG_JSON" '.projects += [$new]' "$REG" > "$REG.tmp" && mv "$REG.tmp" "$REG"

状态处理:

  • discovered → 按原样注册。
  • auth_expired → 显示警告,并引导用户前往 /ops:setup,为受影响的渠道重新配置后再试。
  • unreachable → 提供 [Register anyway (will show as unreachable in dashboards)] / [Skip]。

如果发现脚本返回 [],则打印一条信息提示(ℹ No external projects auto-discovered. You can add Shopify / Linear / Slack / Notion manually below.)并继续。切勿静默跳过——用户必须知道已执行发现操作。

如果候选项目的凭据值来自环境变量,而用户之后想改用 Doppler,则可以稍后通过 /ops:settings,将候选项目中存储的凭据键(如 SHOPIFY_ADMIN_TOKEN)安全地替换为 doppler: 引用。

现有注册表

如果 registry.json 中已有项目,先询问用户(4 个选项,可在一次调用中提供):[Keep existing N projects]、[Add more projects]、[Auto-detect from existing registry]、[Start from scratch]。

  • “Keep existing” → 跳过此步骤。
  • “Auto-detect from existing registry” → 重新读取注册表,显示现有项目摘要,并提供选项以添加缺失字段或新发现的仓库。
  • “Start from scratch” → 先写入空骨架({"version":"1.0","owner":"","projects":[]})——覆盖前提示用户确认。
  • “Add more” → 运行上述自动发现扫描,然后提供手动输入作为备选。
手动添加循环

自动发现后(或用户选择“我将手动输入项目”后):

  • 询问 AskUserQuestion:“添加另一个项目?” → [Yes]、[Done]。
  • 如果选择 Yes,每次只通过一个 AskUserQuestion 收集以下字段:
    • alias(短名称,必填)
    • paths(逗号分隔的绝对路径,必填)
    • repos(逗号分隔的 org/repo,必填)
    • type → 选择 [monorepo]、[multi-repo]
    • infra.platform → 选择 [aws]、[vercel]、[cloudflare]、[other]
    • revenue.model → 每批选择 4 个:[saas]、[subscription]、[marketplace]、[More...],然后选择 [internal]、[portfolio]、[other]
    • revenue.stage → 选择 [pre-launch]、[development]、[growth]、[active]
    • gsd → 选择 [Yes]、[No]
    • priority(1-99,默认为 max+1)
  • 确保注册表目录存在:mkdir -p "${CLAUDE_PLUGIN_ROOT}/scripts"。如果因权限问题导致写入失败(插件缓存目录可能为只读),则改为写入 $DATA_DIR/registry.json 并创建符号链接。
  • 使用 jq 读取当前注册表,追加新项目,并以原子方式写回(jq ... > tmp && mv tmp registry.json)。
  • 每次添加后,打印当前总数,并提供 [Add another] / [Done] 选项。
  • 注册表 Agent(如果已启动)必须有权写入目标路径。如果无法写入,设置向导应根据 Agent 返回的 JSON 自行写入文件——不要要求用户介入。

**深入了解:**完整操作说明、CLI 参考,以及项目注册表(自动发现、注册表架构、GSD 筛选器、优先级排序)的故障排除,请参阅 ${CLAUDE_PLUGIN_ROOT}/skills/ops-projects/SKILL.md。当设置 Agent 需要比此向导更详细的信息时,可以直接加载该文件。


步骤 5b — 守护进程服务协调

此时,守护进程已在步骤 2c 中安装,并在用户配置频道期间于后台预热简报缓存。此步骤会在确认已配置的频道和集成后,添加依赖频道的服务(wacli-sync、message-listener、inbox-digest、store-health、competitor-intel)。

跳过条件:

  • 如果用户在步骤 2c 中拒绝安装守护进程,则完全跳过此步骤。
  • 如果 $PREFS_PATH 中的 daemon.enabled != true,则跳过。

否则继续——协调服务列表:

1. 确认守护进程正在运行:

DATA_DIR="${CLAUDE_PLUGIN_DATA_DIR:-$HOME/.claude/plugins/data/ops-ops-marketplace}"
PLIST_DEST="$HOME/Library/LaunchAgents/com.claude-ops.daemon.plist"
launchctl print gui/$(id -u)/com.claude-ops.daemon >/dev/null 2>&1 || {
  # Daemon not running — install it now as a fallback
  launchctl bootstrap gui/$(id -u) "$PLIST_DEST" 2>/dev/null
}

2. 5 秒后确认运行状况:

cat "$DATA_DIR/daemon-health.json" 2>/dev/null

解析 JSON。如果 action_needed 不为 null,则向用户显示所需操作。如果守护进程已写入运行状况文件,则打印:

✓ Background daemon — running (wacli-sync: connected, memory-extractor: scheduled)

如果运行状况文件缺失(守护进程可能仍在初始化),再等待 5 秒并重试一次。如果仍然缺失,则打印:

⚠ Daemon started but health file not yet written. Check:
  launchctl print gui/$(id -u)/com.claude-ops.daemon
  tail -20 ~/.claude/plugins/data/ops-ops-marketplace/logs/ops-daemon.log

3. 构建完整的服务列表,并与步骤 2c 中写入的配置协调:

根据前面步骤中的配置确定要启用的服务。briefing-pre-warm 和 memory-extractor 服务已在步骤 2c 中启用——保留它们。根据当前配置添加依赖频道的服务:

  • wacli-sync — 如果已配置 WhatsApp(channels.whatsapp 已设置),则始终包含
  • memory-extractor — 始终包含
  • inbox-digest — 始终包含(每 4 小时运行一次,汇总所有已配置的频道)
  • store-health — 仅当已配置电子商务($PREFS_PATH 中设置了 ecom.shopify.store_url)时包含
  • competitor-intel — 始终包含(每周一上午 10 点运行)
  • message-listener — 如果已配置 WhatsApp 或 Telegram,则包含(持久轮询器)

以编程方式构建服务数组(从步骤 2c 的基线开始):

SERVICES='["briefing-pre-warm","memory-extractor","inbox-digest","competitor-intel"]'
PREFS=$(cat "$PREFS_PATH" 2>/dev/null || echo '{}')
# Add wacli-sync + message-listener if WhatsApp is configured
if echo "$PREFS" | jq -e '.channels.whatsapp' > /dev/null 2>&1; then
  SERVICES=$(echo "$SERVICES" | jq '. + ["wacli-sync","message-listener"]')
fi
# Add message-listener for Telegram too (deduplicate)
if echo "$PREFS" | jq -e '.channels.telegram' > /dev/null 2>&1; then
  SERVICES=$(echo "$SERVICES" | jq '. + ["message-listener"] | unique')
fi
# Add store-health only if Shopify is configured
if echo "$PREFS" | jq -e '.ecom.shopify.store_url' > /dev/null 2>&1; then
  SERVICES=$(echo "$SERVICES" | jq '. + ["store-health"]')
fi
echo "Services to enable: $SERVICES"

将守护进程服务配置写入 $DATA_DIR/daemon-services.json——与第 2c 步的现有配置合并,保留 briefing-pre-warm 和 memory-extractor,并启用依赖新渠道的服务。每项服务都必须包含 command 字段——守护进程的 start_service() 会跳过没有该字段的服务。脚本路径使用 ${CLAUDE_PLUGIN_ROOT}(在运行时解析)。每项服务应包含:

  • briefing-pre-warm:{ "enabled": true, "command": "${CLAUDE_PLUGIN_ROOT}/bin/ops-gather", "cron": "*/2 * * * *" }——预热 /ops:go 缓存(在第 2c 步安装)
  • wacli-sync:{ "enabled": true, "command": "${CLAUDE_PLUGIN_ROOT}/scripts/wacli-keepalive.sh", "health_file": "~/.wacli/.health", "restart_delay": 60, "max_restarts": 10 }——仅在已配置 WhatsApp 时启用
  • memory-extractor:{ "enabled": true, "command": "${CLAUDE_PLUGIN_ROOT}/scripts/ops-memory-extractor.sh", "health_file": "~/.claude/plugins/data/ops-ops-marketplace/memories/.health", "cron": "*/30 * * * *" }——每 30 分钟运行一次(在第 2c 步安装)
  • inbox-digest:{ "enabled": true, "command": "${CLAUDE_PLUGIN_ROOT}/scripts/ops-cron-inbox-digest.sh", "cron": "0 */4 * * *" }——每 4 小时运行一次
  • store-health:{ "enabled": true, "command": "${CLAUDE_PLUGIN_ROOT}/scripts/ops-cron-store-health.sh", "cron": "0 9 * * *" }——每天上午 9 点运行,仅在已配置电商时启用
  • competitor-intel:{ "enabled": true, "command": "${CLAUDE_PLUGIN_ROOT}/scripts/ops-cron-competitor-intel.sh", "cron": "0 10 * * 1" }——每周一上午 10 点运行
  • message-listener:{ "enabled": true, "command": "${CLAUDE_PLUGIN_ROOT}/scripts/ops-message-listener.sh" }——仅在已配置 WhatsApp 或 Telegram 时启用

重写服务配置后,快速进行健康检查(前台运行,少于 2 秒),然后在后台重新加载:

# Quick health check — foreground (fast)
test -f "$DATA_DIR/daemon-services.json" && echo "✓ Daemon services config written — N services enabled"

然后在后台执行实际的守护进程重新加载——此操作可能较慢:

# Background — daemon reload is slow, don't block setup
launchctl kickstart -k gui/$(id -u)/com.claude-ops.daemon 2>&1 && echo "✓ Daemon reloaded" || echo "⚠ Daemon kick failed (may still be running)"

在重新加载命令中使用 run_in_background: true。不要等待其完成——立即继续下一步。即使 kickstart 运行较慢,守护进程也会在自己的周期中读取新配置。

将 daemon.enabled = true 和 daemon.services(协调后的数组)写入 $PREFS_PATH。打印:

✓ Daemon services reconciled — N services enabled (briefing-pre-warm, memory-extractor, wacli-sync, ...)
  Daemon reloading in background.

**深入了解:**完整的守护进程操作说明、CLI 参考以及后台守护进程故障排除(服务生命周期、launchctl/systemd 集成、健康报告、协调语义),请参阅 ${CLAUDE_PLUGIN_ROOT}/docs/daemon-guide.md。如果设置 Agent 需要比本向导更深入的信息,可以直接加载该文件。


步骤 6 — 保存偏好设置(如果已选择)

通过 AskUserQuestion 收集以下信息——每项提问一次。绝不要根据记忆、现有配置或之前的会话自动填充。务必明确询问。

  1. 所有者姓名(自由文本):"Claude 在简报中应该怎么称呼你?"——不设默认值,也不根据记忆提供建议。

  2. 时区(单选——每次最多 4 个选项,按地区分批):

    首先,通过 date +%Z 或 readlink /etc/localtime 检测系统时区。如果检测到,则将其作为第一个选项提供:

    Select your timezone:
      [<detected timezone>]
      [Americas...]
      [Europe/Asia/Oceania...]
      [Other — type it]
    

    如果用户选择“美洲…”:[America/New_York]、[America/Los_Angeles]、[America/Chicago]、[Back] 如果用户选择“欧洲/亚洲/大洋洲…”:[Europe/London]、[Asia/Bangkok]、[Asia/Tokyo]、[Australia/Sydney]

  3. 简报详细程度(单选):

    How much detail do you want in briefings?
      [full]     — complete rundown of all channels, projects, and incidents
      [compact]  — key signals only, one line per item
      [minimal]  — just the fires and urgent items
    
  4. 主要项目 → **“过去 7 天内活跃的所有项目”应作为第一个/默认选项。**大多数同时参与多个项目的用户希望获得统一的简报,而不是只关注单个项目:

    Primary project for briefings?
      [All projects active in last 7 days]  ← default
      [Pick a specific project...]
    

    如果选择“特定项目”,则每页显示 3 个注册表别名,并提供 [More...]。默认选项存储为 "primary_project": "all_active_7d",或存储所选的特定别名。

  5. YOLO 模式 → 选择 [Yes — auto-approve low-risk actions]、[No — always confirm]。

  6. 默认频道(单选——这两个选项互斥)。“所有已配置频道”应作为第一个选项并默认预选——大多数用户希望启用所有频道:

    Which channels should ops skills use by default?
      [All configured channels]
      [Pick specific channels...]
    

    如果用户选择“特定频道”,则显示一个后续多选项,其中包含各个频道的复选框。如果用户接受“所有已配置频道”,则将完整的已配置频道名称列表设为 default_channels。

写入 $PREFS_PATH:

{
  "version": "1.0",
  "owner": "...",
  "primary_project": "...",
  "timezone": "...",
  "briefing_verbosity": "...",
  "yolo_enabled": false,
  "default_channels": ["whatsapp", "email"],
  "secrets_manager": "doppler",
  "doppler": {
    "project": "...",
    "config": "..."
  },
  "channels": {
    "telegram": { "bot_token": "...", "owner_id": "..." }
  }
}

如果文件已存在,则合并——不要覆盖。使用 jq 读取,通过 jq '. + { ... }' 应用更新,然后写回。


步骤 7 — Shell 环境变量(如果已选择)

  1. 检查配置文件中是否已导出 CLAUDE_PLUGIN_ROOT(搜索 CLAUDE_PLUGIN_ROOT)。
  2. 如果缺失,自动追加——这是必需步骤,不是可选项。使用 >>(追加,绝不覆盖)。输出:"✓ Added export CLAUDE_PLUGIN_ROOT=... to ~/.zshrc"。不要征求用户许可——规则 2(绝不让用户代为执行命令)适用于此处。
  3. 告知用户:"Run 'source ~/.zshrc' or open a new terminal for it to take effect."——这将在 Claude 的下一次工具调用中显示为确认提示,用户照常接受即可。

步骤 8 — 最终摘要和验证

重新运行检测器并展示最终状态面板:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 OPS ► SETUP COMPLETE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 ✓ Core CLIs:  jq, git, gh, aws, node
 ✓ Channels:   telegram, whatsapp, email
 ✓ Ecommerce:  shopify (<store_url>)             ← omit line if not configured
 ✓ Marketing:  klaviyo, meta, google-ads, ga4, gsc           ← omit line if not configured
 ✓ Voice:      bland, elevenlabs, groq           ← omit line if not configured
 ✓ Secrets:    doppler → my-app/dev
 ✓ MCPs:       linear, sentry, vercel
 ✓ Registry:   20 projects
 ✓ Prefs:      saved to ~/.claude/plugins/data/ops-ops-marketplace/preferences.json
 ✓ Daemon:     ops-daemon → wacli-sync, memory-extractor, inbox-digest

 Next: /ops-go for your first briefing
──────────────────────────────────────────────────────

对于 ecommerce、marketing 和 voice,只有在相应类别中至少配置了一项服务时才显示状态行。已配置时使用 ✓,已跳过时使用 ○。如果从未访问过该部分,则完全省略该行。

对于守护进程行,只列出实际已启用的服务(来自步骤 5b 中计算出的 services 数组)。

如果仍缺少任何必需工具,请列出该工具以及安装它的准确命令,并停止,不要声称已成功。

显示摘要后,运行完成横幅,庆祝设置成功。传入本次设置会话中的实际数量:

bash ${CLAUDE_PLUGIN_ROOT}/bin/ops-setup-complete --channels <N> --projects <N> --agents 9 --skills 15

其中 <N> 替换为本次会话中配置的频道数和注册的项目数。


守护进程健康状态约定

所有运维技能在依赖后台服务之前,都应检查守护进程的健康状态:

cat ~/.claude/plugins/data/ops-ops-marketplace/daemon-health.json

如果 action_needed 不为 null,则在继续之前向用户说明所需操作。 如果守护进程未运行,则提供启动选项:launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.claude-ops.daemon.plist


快捷跳转

如果 $ARGUMENTS 中包含特定章节名称,直接跳转到该章节:

参数跳转至
cli, install步骤 2
channels步骤 3
telegram步骤 3a
whatsapp, wacli, whatsapp-doctor步骤 3b
email步骤 3c
slack步骤 3d
notion步骤 3e
calendar, cal步骤 3f
doppler, secrets步骤 3g
vault, password-manager, pm步骤 3h
ecom, shopify, store步骤 3i
marketing, klaviyo, ads, meta, ga4步骤 3j
google-ads, gads步骤 3j(Google Ads)
voice, bland, elevenlabs, tts步骤 3k
mcp步骤 4
registry, projects步骤 5
daemon, background步骤 5b
prefs, preferences步骤 6
env, shell步骤 7

参数为空 → 从步骤 0 开始完整向导。


安全

  • 绝不在未经明确 AskUserQuestion 确认的情况下运行 brew install 或写入文件。
  • 绝不在未展示差异并征求确认的情况下覆盖现有文件。
  • 绝不将密钥放入 registry.json 或提交密钥。密钥只能放在 $PREFS_PATH(完全位于插件源代码树之外,即 Claude Code 的每插件数据目录)或用户的 shell 配置文件中。
  • 绝不触碰 ~/.claude.json 或 ~/.claude/settings.json——MCP 注册是 Claude Code 的职责,不是你的。
  • 除非用户在当前会话中明确提供,否则绝不在输出中显示用户的真实姓名或电子邮件地址。不要从记忆文件、现有偏好设置或环境变量中读取信息来填充显示名称。

附录:CLI 参考(确切语法——绝不猜测)

gog(v0.12.0+)
顶层命令

auth, gmail, calendar, contacts, drive, docs, slides, sheets, forms, tasks, keep, chat, people, appscript, config, agent

Gmail — 搜索与读取
gog gmail search "<query>" --max N -j --results-only --no-input    # Search threads (Gmail query syntax)
gog gmail thread get <threadId> -j                                  # Get full thread with all messages
gog gmail get <messageId> -j                                        # Get single message
Gmail — 操作
gog gmail archive <messageId> ... --no-input --force               # Archive messages (remove from inbox)
gog gmail archive --query "<gmail-query>" --max N --force           # Archive by query
gog gmail mark-read <messageId> ... --no-input                     # Mark as read
gog gmail unread <messageId> ... --no-input                        # Mark as unread
gog gmail trash <messageId> ... --no-input --force                 # Move to trash
Gmail — 标签
gog gmail labels list -j                                            # List all labels
gog gmail labels modify <threadId> --add LABEL --remove LABEL       # Modify thread labels
gog gmail messages modify <messageId> --add LABEL --remove LABEL    # Modify message labels
Gmail — 发送与回复
gog gmail send --to "[email protected]" --subject "subj" --body "text"                    # Send new email
gog gmail send --to "[email protected]" --subject "Re: ..." --body "reply" --reply-to-message-id <msgId>  # Reply
gog gmail send --reply-to-message-id <msgId> --reply-all --body "reply text"             # Reply all
gog gmail send --to "[email protected]" --subject "subj" --body "text" --attach /path/to/file      # With attachment
Gmail — 草稿
gog gmail drafts list -j                                            # List drafts
gog gmail drafts create --to "[email protected]" --subject "subj" --body "text"
日历
gog calendar calendars -j                                           # List calendars
gog calendar events primary --today -j                              # Today's events
gog calendar events primary --from "2026-04-14" --to "2026-04-15" -j  # Date range
gog calendar create primary --summary "Meeting" --from "2026-04-15T10:00:00" --to "2026-04-15T11:00:00"
gog calendar freebusy --from "2026-04-14T00:00:00Z" --to "2026-04-14T23:59:59Z" -j
联系人
gog contacts search "name" -j                                       # Search contacts
gog contacts list -j                                                # List all contacts
云端硬盘
gog drive ls -j                                                     # List files
gog drive search "query" -j                                         # Search files
gog drive download <fileId>                                         # Download file
任务
gog tasks lists                                                     # List task lists
gog tasks list <tasklistId> -j                                      # List tasks
身份验证
gog auth status                                                     # Check auth status
gog auth add [email protected] --services gmail,calendar,drive,contacts,docs,sheets
wacli
# Health check
wacli doctor --json

# Auth status
wacli auth status --json

# List chats (MUST use subcommand `list`)
wacli chats list --json

# List messages (--after flag uses YYYY-MM-DD)
# macOS
wacli messages list --after="$(date -v-1d +%Y-%m-%d)" --limit=5 --json
# Linux
wacli messages list --after="$(date -d '1 day ago' +%Y-%m-%d)" --limit=5 --json

# Send message
wacli send --to "JID" --message "text"

# Sync (connect and pull)
wacli sync

# Backfill history
wacli history backfill --chat="JID" --count=50 --requests=2 --wait=30s --idle-exit=5s --json

# Contact lookup
wacli contacts --search "name" --json

设置完成后,memory-extractor 守护进程服务会根据这些联系人数据填充 memories/contact_*.md。

Slack 令牌验证
curl -s -H "Authorization: Bearer XOXC_TOKEN" -b "d=XOXD_TOKEN" "https://slack.com/api/auth.test"
macOS 钥匙串
security find-generic-password -s "KEY_NAME" -w 2>/dev/null
security add-generic-password -U -s "KEY_NAME" -a "$USER" -w "VALUE"
security delete-generic-password -s "KEY_NAME" 2>/dev/null

评论

支持 Markdown