Skip to main content
Glama

OfferUpBot

OfferUpBot 是供外部智能体使用的 OfferUp 执行层。智能体负责识别商品、选择价格、撰写文案并决定回复。OfferUpBot 负责 OfferUp 的访问、登录状态、持久化、浏览器操作、重试、防重以及外部验证。

智能体指南

没有会话历史的智能体从 AGENTS.md 开始。直接使用 OfferUp 执行请参考 skills/operating-offerupbot/SKILL.md;端到端覆盖实体物品的运营请参考 skills/operating-seller-operator/SKILL.md;对图像做出有后果的判断请参考 skills/inspecting-visual-evidence/SKILL.md。每个技能只在任务需要时加载更深层的资料。

运行时行为

默认的机制是按需运行 MCP stdio:

agent launches OfferUpBot
        ↓
MCP tools execute against shared SQLite state and browser profile
        ↓
agent disconnects
        ↓
OfferUpBot exits

状态保存在 SQLite 和专用的 OfferUp 浏览器配置档中。默认进程不会启动调度器、HTTP 监听器或后台监控器。

可选的连续 monitor 模式默认关闭。run:due 会执行一次明确的超出清单补偿操作,然后退出道。

运行要求

  • 装有 Google Chrome 的 macOS,用于认证操作

  • Node.js 22 或更高版本

  • pnpm

不要求 Docker、外部数据库、Redis 独立服务,也不要求单独维护云服务。

安装与构建

git clone https://github.com/yinkev/OfferUpBot.git
cd OfferUpBot
pnpm install
pnpm build

运行开发版 MCP 入口:

pnpm mcp

运行构建后的入口:

pnpm start

默认数据目录是:

~/.offerupbot/
├── state.sqlite
├── browser-profile/
└── agents.json          # only after restricted agents are created

需要隔离时,用 OFFERUPBOT_DATA_DIR 覆盖此目录。

OfferUp 登录

pnpm auth:login

会打开专用的 Chrome 窗口。凭据直接输入到 OfferUp 上。只有当 OfferUn 的已认证账户查询返回真实账户 ID 时,才算登录成功。凭据不会通过聊天获取,也不会存储在 SQLite 中。

MCP 配置

在 pnpm build 之后,把智能体主机指向构造好的 stdio 入口:

{
  "mcpServers": {
    "offerupbot": {
      "command": "node",
      "args": [
        "/absolute/path/to/OfferUpBot/dist/src/index.js"
      ],
      "env": {
        "OFFERUPBOT_DATA_DIR": "/absolute/path/to/.offerupbot"
      }
    }
  }
}

如果不配置 token,本地 stdio 进程会作为受信任的完整操作员运行。

当智能体需要更受限的权限时,可以创建受限身份:

pnpm agent:create -- \
  --id research-agent \
  --name "Research Agent" \
  --permissions market.read,events.read,watch.manage

把返回的 token 放到该智能体的 MCP 环境里,作为 OFFERUPBOT_AGENT_TOKEN。OfferUpBot 只保存它的 SHA-256 哈希值。

智能体工具

工具

用途

权限

offerup.health

本地运行时与缓存会话健康检查

无

offerup.authenticate

验证当前的 OfferUp 账户身份/能力

account.read

offerup.research_market

使用经过验证的 OfferUp 过滤条件搜索

market.read

offerup.inspect_listing

获取完整发布信息;可通过 download_photos 缓存排序后的照片为可读本地文件

market.read

offerup.watch

创建、检查、暂停、恢复、删除或运行一个 watch

watch.manage

offerup.run_due

执行一次所有到期/超时未运行的 watch

watch.manage

offerup.next_events

读取并确认持续事件

events.read

offerup.sync_listings

同步活动、已归档和已保存的账户状态

account.read

offerup.sync_inbox

同步收件箱线程和买家消息的变更

account.read

offerup.get_thread

获取一个完整会话和发布上下文

account.read

offerup.send_message

发送一条幂等、已核验的消息

message.send

offerup.validate_listing

验证当前的发布消费契约

无

offerup.publish_listing

发布并验证发布

listing.publish

offerup.update_listing

编辑并验证支持的字段

listing.update

offerup.close_listing

标记已售或归档,然后验证账户状态

listing.close

先从 skills/operating-offerupbot/SKILL.md 开始;它旁边的 REFERENCE.md 和 WORKFLOWS.md 里有详细契约及操作流程。

卖家运营工具

在这些执行工具之上,还有第二套 MCP 接口,面向端到端负责实体物品的智能体:30 个 seller.* 工具覆盖案件/物品收录、含来源追踪的照片和证据、OCR/规则提取与身份选择、证据充分性澄清、可比研究、定价与处置决策、经验证的发布/关闭草稿、买家分类、出价、独家预约设置、冲突检查的约会(以及幂等提醒)、后台任务循环的操作员、交接记录以及生命周期分析。

权限:

seller.read(只读)、seller.write(可写,publish/close 另外需要 listing.publish/listing.close)、seller.communicate(买家操作)。消息仍然通过已验证的 offerup.send_message 发送。请从 skills/operating-seller-operator/SKILL.md 开始;它旁边的 REFERENCE.md 和 WORKFLOWS.md 涵盖证据、市场、买家和生命周期指导。

ID 与写入

OfferUp 两种发布标识:

  • listing_id:UUID,用于公开详情、会话、验证和编辑变更输入。

  • item_id:纯数字的卖家库存 ID,用于 /selling、标记已售和归档。

每个有实际后果的工具都需要一个稳定的幂等键。超时或崩溃后复用相同的键。一旦换了键,就会把恢复尝试变成重复的外部操作。

显式排程模式

把所有过期的 watch 一次性跑完并退出:

pnpm run:due

只有在刻意需要连续轮询时才在前台保持 watch 预约:

pnpm monitor

monitor 不会被 pnpm mcp、pnpm start、安装或登录启动。关闭它会停止连续轮询。之后可以用 run:due 或用 MCP offerup.run_due 从持久化的 watch 状态进行补偿。

移交业务提供页(可选用)

pnpm handoff 启动一个仅前台的小型 HTTP 页面,用于唯一的人工触点:线下移交。它在 GET / 上列出到期/待处理的移交卡(买家、商定价格、预约、配件、瑕疵、退款/支付政策、备用买家),公开 GET /api/handoffs,并通过 POST /api/handoffs/:itemId/result 把结果记录到同一 SQLite 状态。

它只绑定 127.0.0.1(用 OFFERUPBOT_HANDOFF_HOST 和 OFFERUPBOT_HANDOFF_PORT 覆盖),并要求在 OFFERUPBOT_HANDOFF_TOKEN 里提供 bearer token;如果未设置,启动时会生成并打印一次。和 monitor 一样,它不会由其它机制启动——默认运行时仍是无监听器的按需 MCP stdio。

错误语义

写入只有在其响应包含 verified: true 时才算完成。

重要错误码:

  • OFFERUP_SESSION_EXPIRED / AUTH_REQUIRED:需要先完成 OfferUp 登录,再做认证操作。

  • RESOURCE_BUSY:有另一个进程持有账户或浏览器; 稍后用同一幂等键重试。

  • RESOURCE_LOCK_LOST:不能假设写入成功;请先调用相关读或同步工具。

  • IDEMPOTENCY_CONFLICT:同一个键但目标/意图不一致;请协调调用方的状态。

  • OFFERUP_FILTER_MISMATCH:OfferUp 静默忽略了某个搜索条件;不能把返回的集合当作合法的对比基准。

  • AUTO_CATEGORY_MISMATCH:OfferUp 强制执行了它自己的类别而不是你提交的草稿。

  • CATEGORY_PATH_NOT_TERMINAL:路径停在父类而不是可用的最终类目。

测试验证

常规验证:

pnpm test
pnpm typecheck
pnpm build

只读到的实时验收(针对配置的 OfferUp 配置文件和公共站点):

OFFERUPBOT_LIVE_TESTS=1 pnpm exec tsx --test \
  tests/account/live.test.ts \
  tests/auth/live.test.ts \
  tests/offerup/public/live.test.ts

被截取的写入契约验收:

OFFERUPBOT_LIVE=1 pnpm exec tsx --test --test-concurrency=1 \
  tests/messages/live-direct.test.ts \
  tests/listings/live-browser.test.ts \
  tests/listings/live-close.test.ts

这些被截取的测试会在真实 OfferUp 界面上执行当前的消息、消费者发布、编辑以及媒体上传、标记已售和归档请求,同时只在本机完成有后果的变更。他们不会创建、修改、出售或归档任何真实发布。

当前合规范围

现有的代码、公开读取、认证读取、当前网页消息更新、消费者发布/编辑的 UI 契约,以及关闭清单,全部已实现并测试。一个受控的内存 Agent 测试覆盖了从研究、发布、收件箱、消息、更新直到关闭的完整流程。

一个指定的真实发布生命周期已于 2026-08-24 在既定验收账户上完成:一台 Philips Norelco BG7030/49 配合六张照片发布并外部核验,随后从 $60 改为 $55 且保留 New 状态,再在已认证的库存中归档并核验为 UNLISTED。验证过程暴露出并修复两个真实缺陷:外层帖子对话框仍可见时的终端类别选择,以及仅改价时的「New」状态丢失。

同一天也通过了真实消息传输门禁:offerup.send_message 通过直接适配器发出一条 $50 现金出价,返回的消息 ID 在相应会话中被精确地重读,且已认证的收件箱同步确认了这条会话。卖家后来回复“Its a pokemon center etb”,这揭示了一个单独的推理失败:发布信息的首张照片明确展示了 Pokémon Center 专属版本,但智能体却按标准出货价格(692949 约 $120)估了值,因此 $50 的实际出价仅为精准市场价的 42%,而不是 78%。该操作验证已经完全满足“最多一次交付”的要求;只暴露了变体识别、定价和目标选择方面的不足写在 docs/acceptance/2026-08-24-real-message-send.md 中。

两项指定的外部写入传输验收均已完成。未来任何真实的外部写入仍然要求必须指定明确的发布物或会话;可重复的回归测试仍然要拦截并且不能修改任何没有明示的账户对象。

卖家运营层(2026-08 新增)

在上述 agent 层之上是卖家运营体系:以可追踪来源生产证据作为依据的权威库存,通过身份解析,将可比/活跃价格分离完成的市场/定价研究、门控式发布流程、买家/出价/独家预约/预约状态、交接结果记录以及可整体还原的案件上下文。它已完全接入生产环境(启动时保证 schema;runtime.runDue() 只针对卖家和卖家任务钱包准确做一次补偿;带账户同步的公开帖子库存会加上 views/discussions/price时间序列曲线),并通过上面所述 的seller.*` 工具暴露给智能体。

可选集成功能(默认关闭):

  • OFFERUPBOT_READ_ONLY=1 —— 阻止所有对 OfferUp 的外部写入(包括发布、更新、关闭和发送,seller 工具也一并禁止);

  • OFFERUPBOT_EBAY_CLIENT_ID / OFFERUPBOT_EBAY_CLIENT_SECRET —— 打开 eBay 可比价来源(官方 API 的活跃咨询 + 成交记录)。如果没有,可比价来源会报告不可用,可比价项保持为空,而且不刮取任何 web 内容。

  • OCR 在可获时启用:在 macOS Vision 中使用原生的 … 最后一步,不支持时纯靠确定性规则提取。

(在转换时,我删改了部分无意义的 UX helpers,请复核。)# OfferUpBot

OfferUpBot 是 OfferUp 的执行层,供外部智能体调用。智能体负责识别商品、选择价格、撰写文案并决定回复。OfferUpBot 负责 OfferUp 的访问、登录状态、持久化、浏览器操作、重试、重复阻止和外部验证。

智能体指南

没有会话历史的智能体从 AGENTS.md 开始。直接执行 OfferUp 使用 skills/operating-offerupbot/SKILL.md;端到端负责实体商品运营使用 skills/operating-seller-operator/SKILL.md;对图片进行关键判断时使用 skills/inspecting-visual-evidence/SKILL.md。每个技能只在任务要求时加载更深的内容。

运行时决策

默认接口为 按需 MCP stdio:

agent launches OfferUpBot
        ↓
MCP tools execute against shared SQLite state and browser profile
        ↓
agent disconnects
        ↓
OfferUpBot exits

状态保存在 SQLite(数据库)和专用 OfferUp 浏览器配置文件中。默认进程不会启动调度器、HTTP 监听器或后台监控程序。

可选的连续 monitor 模式默认关闭。run:due 仅执行一次补跑并退出。

需求

  • 装有 Google Chrome 的 macOS,用于认证操作

  • Node.js 22 或更高版本

  • pnpm

不要求使用 Docker、外部数据库、Redis 或单独管理的运行时服务。

安装与构建

git clone https://github.com/yinkev/OfferUpBot.git
cd OfferUpBot
pnpm install
pnpm build

运行开发版 MCP 入口点:

pnpm mcp

运行构建后的入口点:

pnpm start

默认数据目录为:

~/.offerupbot/
├── state.sqlite
├── browser-profile/
└── agents.json          # only after restricted agents are created

需要隔离时,请通过 OFFERUPBOT_DATA_DIR 覆盖该目录。

OfferUp 登录

pnpm auth:login

会打开专属 Google Chrome 窗口。请直接在 OfferUp 对话框中输入凭据。只有当 OfferUp 已认证的账户查询限定真实账户 ID 时,登录才会被接受。系统不会通过聊天获取凭据,也不会将它们存储在 SQLite 中。

MCP配置

运行 pnpm build 后,将你的智能体主机指向构建完成的 stdio 入口点:

{
  "mcpServers": {
    "offerupbot": {
      "command": "node",
      "args": [
        "/absolute/path/to/OfferUpBot/dist/src/index.js"
      ],
      "env": {
        "OFFERUPBOT_DATA_DIR": "/absolute/path/to/.offerupbot"
      }
    }
  }
}

如果未设置智能体令牌,本地 stdio 进程将以受信任的完整操作员权限运行。

当智能体被授予更窄权限时,创建一个受限出口:

pnpm agent:create -- \
  --id research-agent \
  --name "Research Agent" \
  --permissions market.read,events.read,watch.manage

把返回的令牌放到该智能体的 MCP 环境中,作为 OFFERUPBOT_AGENT_TOKEN。OfferUpBot 只存储它的 SHA-256 哈希值。

智能体工具

工具

作用

权限

offerup.health

本地运行时和缓存会话健康检查

无

offerup.auth_status

验证当前 OfferUp 账户身份/权限

account.read

offerup.research_mark

通过已验证的 OfferUp 过滤条件进行搜索

market.read

offerup.inspect_listing

完整的商品证据;可选择用 download_photos 将排好序的照片保存为可读本地文件

market.read

offerup.watch_entry

创建、查看、暂停、恢复、删除或运行一个监控

watch.manage

offerup.run_due

对每个已到期的监控执行一次

watch.manage

offerup.next_events

读取并确认持久化事件

events.read

offerup.sync_listings

同步活动/已归档/已存账户状态

account.read

offerup.sync_inbox

同步收件箱线程和买家消息变更

account.read

offerup.get_thread

获取一个完整的对话和商品上下文

account.read

offerup.send_message

发送一条幂等、已验的消息

message.send

offerup.validate_draft

校验当前用户可见的自写商品草稿契约

无

offerup.publish_listing

发布并验证商品

listing.publish

offerup.update_listing

编辑并验证受支持的字段

listing.update

offerup.close_listing

标记为已售或归档,并验证账户状态

listing.close

从 skills/operating-offerupbot/SKILL.md 开始;它旁边的 REFERENCE.md 和 WORKFLOWS.md 提供具体契约和流程。

卖家运营工具

在这些执行工具之上,还有一个面向智能体的 MCP 接口,用于管理实体物品的端到运行:30 个 seller.* 工具,涵盖案件/物品录入、照片与来源可查证据、OCR 确定性提取和身份选择、证据是否完整及澄清、市场对比研究、价格与处置决策、带验证的发布/关闭的草稿、买家分类、出价、专属预留、约见(带冲突检查和幂等提醒)、逾期任务运营循环、交接记录和全程周期分析。

权限:seller.read(读取)、seller.write(写入;发布/关闭还需要 listing.publish/listing.close)、seller.communicate(买家操作)。消息仍通过已验证的 offerup.send_message 发送。先看 skills/operating-seller-operator/SKILL.md;旁边的 REFERENCE.md 和 WORKFLOWS.md 包含证据、市场、买家和生命周期指南。

ID 与写入

OfferUp 使用两种不同的商品标识符:

  • listing_id:用于公开详情、对话、以及编辑变更新操作输入的 UUID。

  • item_id:用于 /selling、标记已售和归档操作的基础数值 ID。

每次重要的工具调用都必须提供持久化的幂等键。超时或崩溃后重复调用必须使用同一键。直接替换键可能把恢复当作了重复的外部动作。

明确的调度模式

运行全部到期 monitor 各一次并退出:

pnpm run:due

仅当确认需要持续轮询时,才通过保持在前台的 watch 调度来持续:

pnpm monitor

monitor 模式绝对不是由 pnpm mcp、pnpm start、安装或登录启动的。关闭它后持续轮询即停止。之后每次 run:due 或对 MCP offerup.run_due 的调用都会基于已保存的 watch 状态进行补偿。

交接界面(可选)

pnpm handoff 会启动一个简短的前台 HTTP 界面,作为人工交付的接入点:线下交付。它在首页 GET / 列出到期/待处理的交接信息(买家、已答应价格、约见时间、配件、问题点、退款政策、备选买家);公开 GET /api/handoffs;并通过 POST /api/handoffs/:itemId/result 在同一条 SQLite 上记录结果。

它绑定 127.0.0.1(可使用 OFFERUPBOT_HANDOFF_HOST 和 OFFERUPBOT_HANDOFF_PORT 覆盖),并需要从 OFFERUPBOT_HANDOFF_TOKEN 获取 bearer token;如果未设置,服务会在启动时一次性生成并打印。它会自动生成一次并将 token 打印出来。和 monitor 一样,它不会由其他任何流程启动——默认运行时仍然是无状态监听器的按需 MCP stdio。

错误语义

一次写入只有在响应中包含 verified: true 才算完成。

常见错误码:

  • OFFERUP_SESSION_EXPIRED / AUTH_REQUIRED:请先完成 OfferUp 登录。

  • RESOURCE_BUSY:另一个进程已持有账户或浏览器租约;请用同一幂等键稍后重试。

  • RESOURCE_LOCK_LOST:不要假定写入已经完成;重试前先通过相关 read/read sync 工具验证。

  • IDEMPOTENCY_CONFLICT:同一幂等键被用在不同意图上;请协调调用方状态。

  • OFFERUP_FILTER_MISMATCH:OfferUp 静默忽略了搜索约束;返回的结果集不可作为有效对照数据。

  • AUTO_CATEGORY_MISMATCH:OfferUp 强制采用了与所提草稿不同的分类。

  • CATEGORY_PATH_FOR:路径停留在父分类,而非可选终端分类。

验证

常规验证:

pnpm test
pnpm typecheck
pnpm build

针对已配置的 OfferUp 配置文件和公开后台进行只读现场验收:

OFFERUPBOT_LIVE_TESTS=1 pnpm exec tsx --test \
  tests/account/live.test.ts \
  tests/auth/live.test.ts \
  tests/offerup/public/live.test.ts

安全的截获式写入契约验收:

OFFERUPBOT_LIVE=1 pnpm exec tsx --test --test-concurrency=1 \
  tests/messages/live-direct.test.ts \
  tests/listings/live-browser.test.ts \
  tests/listings/live-close.test.ts

截获测试会在公开 OfferUp 界面之上执行当前的消息、发布、编辑、媒体上传、标记出售和归档请求路径,同时只在本地完整处理非突变操作。它们不会创建、修改、出售或归档任何真实商品。

当前接受边界

代码、公开读取(analysis)、已验证读取、当前网页消息变更、消费者发布/编辑 UI 契约以及核心变更都已经实现和测试。一个已控制的内存智能体测试覆盖了从一个项目从研究到发布、收件箱、消息、更新和关闭的全部流程。

一个端到端的真实商品周期已经完成 —— 已于 2026-08-24 在既定验收账户中发布:一台飞利浦 Norelco BG7030/49 带六张图、外部验证后发布;价格从 $60 更新为 $55 且保留 New 状态,并再次执行验证、再进行归档并确认库存状态为 UNLISTED。这次运行暴露并修复了两个实时缺陷:外层发帖对话框中选择了非终结分类,编辑价格为 $15 时新的状态被丢掉。

同一天,通过 直接传输机制、消息等效幂等且已验收:offerup.send_message 以 $50 现金报价调用直接 adapter 发送;返回消息 ID 在完全一致的会话中被准确记录;后端收件箱同步确认该会话。卖家后来回复,双方都谈了 ……”,这暴露并修复了“商家是外层对话框”的已修复缺陷。

“Its a pokemon center etb”——这一回应暴露了一个独立的推理错误:商品首张照片上可见地表明了 Pokemon Center 专属限定型,但智能体按普通(“普通” ETB)来处理。根据 TCGplayer 产品代号 692949 的当前数据约为 $120,因此 $50 的报价约为对应市场价的 42%,并非设计目标中提到的 78%。这条真实消息发送验收中,正好一次性(exactly-once)类型的传输没问题;但变体识别、价格判断和目标选择失败。相关英文确认记录详见 docs/acceptance/2026-08-24-real-message-send.md。

两项“必须实写”的真相测试都已完整完成。今后的任何真实写入都必须先由设计好的商品或会话进行验证;定期重复的回归测试依然拦截,不会允许任意环境中的数据被修改。

卖家运营层(2026-08 新增)

在这个执行层之上,是卖家运营系统:以真实的商品为对象,实现来源可验证的证据、身份解析、拥有可比市场的价格/市场研究(相比有效/主动渠道)、带门控的发布流程、买家/出价/预留/约见列表、交接结果记录,以及快速恢复的上下文。

这个系统已完全接入生产在线运行(启动时确保所有表结构;runtime.runDue() 对 watch 和 seller 任务进行一次补跑,并在每次账户同步时追加 views/discussions/price 时间序列),并通过上面的 seller.* 表面暴露给智能体。

集成功能全部默认关闭:

  • OFFERUPBOT_READ_ONLY=1 —— 禁止所有对外 OfferUp 写入(包括 publish、update、close、send 和编排路径)。

  • OFFERUPBOT_EBAY_CLIENT_ID / EBAY_CLIENT_SECRET —— 通过官方 API 提供 eBay 对照来源(活跃报价 + 成交);未配置时相应来源不可用,对照数据为空,不会进行任何外部抓取。

  • OCR 在可用时通过 macOS Vision 执行(macOS(如 darwin + osascript)),其他情况下降级为确定性确定性提取。

结果分析(src/analytics)对已售和未售物品一视同仁地计算完整指标集,而已完成结果的类别先验会反馈到定价与处置(有界、按样本量收缩,并记录在决策理由中)。参见 docs/seller-operator-architecture.md 了解分层映射和不变量,docs/seller-operator-tracker.md 了解构建状态,以及 tests/e2e/seller-operator.test.ts 了解受控的全生命周期验收(模拟外部写入——不触发真实交易市场变更)。

Maintenance

ActivityMaintained
ResponsivenessSyncing

Related MCP Connectors