mcpField
C
conversation-history-brief

conversation-history-brief

github.com/hubspot营销与增长28194 次安装今天更新
代码仓库

关于 conversation-history-brief

conversation-history-brief 用于从 HubSpot Conversations 收件箱会话整理客户简报,适合 CRM 和销售工作流。它说明如何完整分页获取数据、解析渠道类型并汇总会话记录。

主要功能

  1. 01枚举收件箱和渠道
  2. 02查找关联联系人的会话
  3. 03完整分页遍历结果
  4. 04获取并汇总消息
  5. 05生成结构化客户简报数据

适用场景

  1. 01通话前准备联系人简报
  2. 02查看联系人的收件箱会话历史
  3. 03将收件箱会话与 CRM 活动记录合并

安装

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

$npx skills add https://github.com/hubspot/agent-cli-skills --skill conversation-history-brief

安装后在 Claude Code 中输入/conversation-history-brief确认已加载。

详细介绍

需要使用 hub-cli PR #251 中的 conversations 命令组。请以 hubspot conversations --help 为准。此外,CRM 活动历史记录(通话、邮件、备注)请参阅 communication-history/SKILL.md——本技能仅涵盖 Conversations 收件箱界面。

线程如何关联到联系人

conversations threads list 接受 --associated-contact-id。你还必须提供 --inbox-id——如果不知道收件箱,就无法按联系人查询整个门户中的线程。要覆盖所有收件箱,请先枚举收件箱(步骤 1),然后逐个查询。

分页:切勿使用 --format jsonl 收集完整结果集

所有 conversations list 命令默认使用 --limit 100,并返回用于获取下一页的游标。--format jsonl 只会打印数据行——游标仅通过 --format json 的 .meta.next 提供。 因此,将 --format jsonl 的输出重定向到文件会在前 100 行后静默截断;提高 --limit 也无济于事,因为端点会限制每页大小。

需要查看所有行时,请使用此辅助工具:

# conv_page <output-file> <conversations list args...>
# Pages a conversations list endpoint to exhaustion, writing one JSON object
# per line to <output-file>.
conv_page() {
  out=$1; shift
  after=""
  : > "$out"
  while :; do
    if [ -n "$after" ]; then
      page=$(hubspot "$@" --format json --after "$after") || return 1
    else
      page=$(hubspot "$@" --format json) || return 1
    fi
    printf '%s' "$page" | jq -c '.data[]' >> "$out"
    after=$(printf '%s' "$page" | jq -r '.meta.next // empty')
    [ -n "$after" ] || break
  done
}

在交互式查看第一页时,直接使用 --format jsonl 仍然没问题。

1. 枚举收件箱

conv_page /tmp/inboxes.jsonl conversations inboxes list
# {"id":"123","name":"Support Inbox","createdAt":"...","updatedAt":"..."}
# {"id":"124","name":"Sales Chat","createdAt":"...","updatedAt":"..."}

2. 查找某个联系人的所有线程

遍历收件箱 ID,收集与该联系人关联的线程:

CONTACT_ID=73235
: > /tmp/contact_threads.jsonl   # truncate output file

while IFS= read -r inbox; do
  inbox_id=$(echo "$inbox" | jq -r '.id')
  conv_page /tmp/inbox_threads.jsonl conversations threads list \
    --inbox-id "$inbox_id" \
    --associated-contact-id "$CONTACT_ID"
  cat /tmp/inbox_threads.jsonl >> /tmp/contact_threads.jsonl
done < /tmp/inboxes.jsonl

echo "Total threads found: $(wc -l < /tmp/contact_threads.jsonl)"

线程输出结构:

{"id":"111","status":"OPEN","createdAt":"2026-07-28T10:02:00Z","latestMessageTimestamp":"2026-07-30T14:22:00Z","assignedTo":"A-78901","inboxId":"123","originalChannelId":"1000","originalChannelAccountId":"148662","associatedContactId":"73235","spam":false,"archived":false}

线程上有两个容易想当然、但实际上不存在的字段:

  • 没有 channelType。 线程仅通过 ID 标明其来源渠道,字段为 originalChannelId。将其与 conversations channels list 的结果关联,即可解析为便于阅读的类型;该命令返回 {"id":"1000","name":"Support Email","type":"EMAIL","inboxId":"123"}(见下方步骤 3)。
  • 不保证有 subject。 status(OPEN/CLOSED)、时间戳、assignedTo 和渠道 ID 才是可靠字段。读取主题时,务必使用 // "(no subject)" 作为回退值。

3. 将渠道 ID 解析为渠道类型

: > /tmp/channels.jsonl
while IFS= read -r inbox; do
  inbox_id=$(echo "$inbox" | jq -r '.id')
  conv_page /tmp/inbox_channels.jsonl conversations channels list --inbox-id "$inbox_id"
  cat /tmp/inbox_channels.jsonl >> /tmp/channels.jsonl
done < /tmp/inboxes.jsonl

# {"1000":"EMAIL","1001":"LIVE_CHAT"}
jq -s 'map({key: (.id|tostring), value: .type}) | from_entries' \
  /tmp/channels.jsonl > /tmp/channel_types.json

以下步骤中用于标记线程的辅助工具:

CHANNEL_TYPES=$(cat /tmp/channel_types.json)

4. 获取每个线程的消息

messages list 与其他 list 端点一样会分页,因此应逐页获取,而不是只取前 100 条:

: > /tmp/all_messages.jsonl

while IFS= read -r thread; do
  tid=$(echo "$thread" | jq -r '.id')
  conv_page /tmp/thread_messages.jsonl conversations messages list --thread-id "$tid"
  jq -c --argjson thread "$thread" --argjson types "$CHANNEL_TYPES" \
    '. + {
       _threadId: $thread.id,
       _subject: ($thread.subject // "(no subject)"),
       _channelType: ($types[($thread.originalChannelId | tostring)] // "UNKNOWN")
     }' /tmp/thread_messages.jsonl \
  >> /tmp/all_messages.jsonl
done < /tmp/contact_threads.jsonl

消息输出结构:

{"id":"222","type":"MESSAGE","text":"Hello, I have a billing question.","sender":{"actorId":"A-456","name":"Jane Doe"},"createdAt":"2026-07-30T14:20:00Z"}

要获取单条消息的完整详情:

hubspot conversations messages get --thread-id 111 --message-id 222 --format jsonl

5. 账户简介的精简时间线

按最近活动时间排序,生成易读的线程摘要。请注意 -s:该文件为 JSONL 格式;如果不先 slurp,sort_by 会一次只收到一个对象,导致 jq 以状态码 5 退出并显示 Cannot index string with string,而不是打印时间线。

echo "=== Conversation History for Contact $CONTACT_ID ==="
jq -sr --argjson types "$CHANNEL_TYPES" \
  'sort_by(.latestMessageTimestamp) | reverse[] |
   "\(.latestMessageTimestamp[0:10])  \($types[(.originalChannelId | tostring)] // "?")  [\(.status)]  \(.subject // "(no subject)")"' \
  /tmp/contact_threads.jsonl

关键消息摘录(每个线程的第一条消息):

echo ""
echo "=== First message per thread ==="
while IFS= read -r thread; do
  tid=$(echo "$thread" | jq -r '.id')
  subject=$(echo "$thread" | jq -r '.subject // "(no subject)"')
  first_msg=$(hubspot conversations messages list --thread-id "$tid" --limit 1 --format jsonl \
    | jq -r '.text // .richText // "(no text)" | .[0:200]')
  echo "[$tid] $subject: $first_msg"
done < /tmp/contact_threads.jsonl

6. 账户简介的结构化部分

输出适合嵌入较长账户简介中的 JSON 块:

jq -n \
  --argjson threads "$(jq -s '.' /tmp/contact_threads.jsonl)" \
  --argjson types "$CHANNEL_TYPES" \
  --arg contact_id "$CONTACT_ID" \
  '{
    conversations: {
      contact_id: $contact_id,
      total_threads: ($threads | length),
      open_threads: ($threads | map(select(.status=="OPEN")) | length),
      channels: ($threads | map($types[(.originalChannelId | tostring)] // "UNKNOWN") | unique),
      latest_activity: ($threads | sort_by(.latestMessageTimestamp) | last | .latestMessageTimestamp),
      threads: ($threads | map({
        id: .id,
        subject: (.subject // "(no subject)"),
        status: .status,
        channel: ($types[(.originalChannelId | tostring)] // "UNKNOWN"),
        last_activity: .latestMessageTimestamp,
        assigned_to: (.assignedTo // null)
      }))
    }
  }'

与 CRM 活动历史记录结合

要生成完整的通话前简介,请将此技能与 communication-history/SKILL.md 结合使用:

CONTACT_ID=73235

echo "=== CRM Activity (calls, emails, notes) ==="
hubspot activities list --contact $CONTACT_ID --limit 10 \
| jq -r '"\(.timestamp[0:10])  \(.type)  \(.title)"'

echo ""
echo "=== Conversations Inbox Threads ==="
# ... run the thread loop from step 2 above ...

已知限制

  • threads list 需要 --inbox-id——必须遍历所有收件箱才能获得完整信息。
  • 不支持服务端日期范围筛选——请使用 jq 在客户端按 latestMessageTimestamp 筛选。
  • 每条 list 命令每页返回 100 行,且只有 --format json 会提供游标(.meta.next)。需要获取完整数据时请使用 conv_page;直接将 --format jsonl 的输出重定向到文件会静默截断。
  • 线程不包含 channelType——将 originalChannelId 与 conversations channels list 的结果关联以获取类型。线程也可能在对话过程中切换渠道,因此 originalChannelId 描述的是线程的起始渠道,而不是它使用过的所有渠道。
  • 线程上的 subject 字段不保证存在;务必使用 // "(no subject)" 作为回退值。
  • text 与 richText:某些消息类型会将格式化内容放在 richText 而非 text 中。提取摘录时请检查这两个字段。

评论

支持 Markdown