关于 conversation-history-brief
conversation-history-brief 用于从 HubSpot Conversations 收件箱会话整理客户简报,适合 CRM 和销售工作流。它说明如何完整分页获取数据、解析渠道类型并汇总会话记录。
主要功能
- 01枚举收件箱和渠道
- 02查找关联联系人的会话
- 03完整分页遍历结果
- 04获取并汇总消息
- 05生成结构化客户简报数据
适用场景
- 01通话前准备联系人简报
- 02查看联系人的收件箱会话历史
- 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中。提取摘录时请检查这两个字段。