
基于 Omi 开源仓库的 Linear 语音集成插件实战指南从 OAuth 接入到 Chat Tools 部署【免费下载链接】FriendAI that sees your screen, listens to your conversations and tells you what to do项目地址: https://gitcode.com/GitHub_Trending/fr/Friend本指南以开源仓库 Friend 中plugins/omi-linear-app目录下的 Linear 集成插件为核心完整讲解如何通过 Omi 设备用语音管理 Linear 事务创建 issue、查看指派任务、更新工作流状态、关键词搜索、查看详情与添加评论。文章既覆盖 Omi App Store 上架配置、Linear OAuth 应用创建、本地开发与 Railway 云部署的完整操作步骤也结合插件源码main.py、db.py、models.py及三份回归测试深入剖析其 OAuth 令牌刷新、GraphQL 请求封装、工作流状态匹配等底层实现帮助读者既能跑通整套集成也能理解其设计细节与安全约束。一、插件概览与核心功能该插件是一个部署在服务端的 FastAPI 应用对应 plugins/omi-linear-app/main.py通过 OAuth 2.0 授权码模式接入 Linear再以 Omi 的 Chat Tools 协议暴露六个语音可调用的工具端点。整体工作方式为用户在 Omi 设备上说出指令 → Omi 后端解析为对工具端点的 POST 调用 → 插件以 Linear GraphQL API 完成读写操作 → 结果以文本回复给用户全程无需手动打开电脑或手机。功能说明创建 Issue以标题、描述、优先级创建新事务列出我的 Issue查看指派给当前用户的事务更新状态在工作流状态间移动Todo → In Progress → Done搜索 Issue按关键词或主题查找事务获取 Issue 详情查看任意事务的完整信息添加评论为已有事务追加评论与进展更新这些能力在 plugins/omi-linear-app/templates/setup.html 的设置页中也有完整展示同时该页面还提供“Connect Linear”授权入口、默认团队下拉选择以及断开连接操作。二、语音指令速查表插件 README 给出的指令示例覆盖了六类核心操作用户在 Omi 设备上可直接照此表达指令作用Create an issue: Fix login bug创建新 issueCreate urgent issue: Server is down带优先级创建Show my issues列出指派给你的 issueShow my in-progress issues按状态过滤Move ENG-123 to Done更新 issue 状态Mark PROD-456 as In Progress开始处理某 issueSearch for authentication issues按关键词查找 issueWhats the status of ENG-789?获取 issue 详情Tell me about PROD-123获取完整 issue 信息Add comment to ENG-456: Fixed the bug为 issue 添加评论从源码看指令中的“Move ENG-123 to Done”对应tool_update_issue_statusmain.py中app.post(/tools/update_issue_status)其中ENG-123这类短标识符会经由get_issue_by_identifier()以 Linear 的精确issue(id:)查询解析而不是模糊搜索结果Show my issues 对应tool_list_my_issues默认按assignee isMe过滤并以updatedAt排序。三、Omi App Store 上架配置要让该插件出现在 Omi 的 App Store 中需要填写应用信息与三类 URL。README 中给出了一套可直接复制的配置示例App Store 表单字段字段值App NameLinearCategoryProductivityDescriptionManage your Linear issues with voice commands. Create issues, track work, update statuses, and add comments – all hands-free through your Omi device.在 Omi App Store 中填写的 URL字段URLApp Home URLhttps://YOUR-DOMAIN/Setup Completed URLhttps://YOUR-DOMAIN/setup/linearChat Tools Manifest URLhttps://YOUR-DOMAIN/.well-known/omi-tools.json需要开启的能力Capabilities✅External IntegrationChat Tools 必需✅Chat用于语音指令响应这里需要特别留意两点Omi 会自动在 URL 末尾追加?uidUSER_ID参数因此上架填写 URL 时不要自行拼上{uid}占位符。Setup Completed URL对应的/setup/linear端点源码实现在main.py的app.get(/setup/linear)它读取当前uid是否已存储 Linear 令牌并返回{is_setup_completed: tokens is not None}Omi 据此判断用户是否完成了授权。四、Chat Tools 清单机制与可用工具插件在/.well-known/omi-tools.json暴露工具清单源码位于 main.py 的get_omi_tools_manifest()Omi 在应用创建或更新时会自动拉取该清单。清单中每个工具都声明了名称、描述、调用端点、HTTP 方法、参数 Schema、是否需要鉴权以及执行时的状态提示文案。工具描述create_issue在 Linear 中创建新 issuelist_my_issues列出指派给用户的事务update_issue_status更新 issue 的工作流状态search_issues按文本搜索 issueget_issue获取 issue 详细信息add_comment为已有 issue 添加评论以清单中的实际定义为例端点/tools/create_issue{ name: linear_create_issue, description: Create a new issue in Linear. Use this when the user wants to create a Linear task, ticket, issue, or bug report., endpoint: /tools/create_issue, method: POST, parameters: { properties: { title: { type: string, description: Title of the Linear issue }, description: { type: string, description: Detailed description of the Linear issue }, priority: { type: string, description: Priority level: urgent, high, medium, low, or none } }, required: [title] }, auth_required: true, status_message: Creating Linear issue... }除 README 中列出的六种工具外源码清单中还额外声明了linear_list_recent_issues对应端点/tools/list_recent_issues用于列出工作区内与指派者无关的最新事务并支持按团队 key如OMI、ENG过滤。每个工具的auth_required均为true说明 Omi 在调用这些端点前会确认用户已完成 Linear 授权。工具调用的请求/响应模型定义在 plugins/omi-linear-app/models.py所有请求都继承ChatToolRequest包含uid、app_id、tool_nameChatToolResponse则统一返回result成功文本或error失败信息二选一与 Chat Tools 协议约定一致。五、Linear 开发者后台配置OAuth 应用与 Scope5.1 创建 OAuth 应用打开 Linear Settings 的 API 页面点击Create OAuth application填写以下信息Application name:Omi IntegrationDeveloper name:你的名字Developer URL:https://omi.meRedirect URI:https://YOUR-DOMAIN/auth/linear/callback保存后记录Client ID与Client Secret。5.2 请求的 Scope插件在源码中以常量LINEAR_SCOPES声明了授权时请求的全部权限见main.py顶部配置区LINEAR_SCOPES [ read, # 读取工作区数据 write, # 写入 issue issues:create, # 创建新 issue comments:create,# 添加评论 ]这些 scope 会在linear_auth()端点中通过scope: ,.join(LINEAR_SCOPES)拼进 Linear 的授权 URL用户授权后 Linear 才会向插件发放令牌。5.3 关键 API 端点常量源码中同样集中定义了所有 Linear API 地址main.pyLINEAR_AUTH_URL https://linear.app/oauth/authorize LINEAR_TOKEN_URL https://api.linear.app/oauth/token LINEAR_API_URL https://api.linear.app/graphql即授权页、令牌交换与 GraphQL 数据接口三处端点后续所有 OAuth 与数据操作都围绕这三个常量展开。六、本地开发与运行6.1 环境要求Python 3.8拥有管理员权限的 Linear 工作区可选Redis用于生产环境的持久化存储6.2 本地启动步骤# 进入插件目录 cd plugins/omi-linear-app # 创建虚拟环境 python3 -m venv venv source venv/bin/activate # 安装依赖 pip install -r requirements.txt # 复制环境变量文件并配置 cp .env.example .env # 编辑 .env 填入你的凭据 # 启动服务 python main.py依赖清单见 plugins/omi-linear-app/requirements.txt核心包括fastapi0.111.1、uvicorn0.30.3、pydantic2.8.2、requests2.34.2、redis5.0.1同时通过../omi-plugin-sdk引用仓库根目录下的插件 SDK。6.3 环境变量说明LINEAR_CLIENT_IDyour_client_id LINEAR_CLIENT_SECRETyour_client_secret LINEAR_REDIRECT_URIhttps://your-domain.com/auth/linear/callback PORT8080 REDIS_URL # 可选生产环境使用从源码可以看到环境变量的实际用途LINEAR_CLIENT_ID/LINEAR_CLIENT_SECRETOAuth 应用凭据用于发起授权与交换令牌LINEAR_REDIRECT_URI授权回调地址main.py中默认值为http://localhost:8000/auth/linear/callbackPORTmain.py末尾以uvicorn.run(app, host0.0.0.0, port8080)启动Procfile中则使用${PORT:-8080}动态读取平台注入的端口REDIS_URL由 db.py 读取若 Redis 可用则以其存储令牌与用户设置否则自动回退到本地 JSON 文件data/tokens.json、data/user_settings.json便于本地调试。6.4 本地联调ngrok 暴露服务由于 Omi 后端需要能访问到你的服务本地调试时可用 ngrok 将 8080 端口暴露到公网# 启动 ngrok ngrok http 8080 # 将 .env 中的 LINEAR_REDIRECT_URI 更新为 ngrok 地址 # 同步更新 Linear OAuth 应用设置里的 redirect URI6.5 存储层设计Redis 优先、文件兜底令牌与设置的读写全部收敛在 db.py 中采用双通道策略若安装了redis且REDIS_URL/REDIS_PRIVATE_URL可达则以linear:tokens:{uid}与linear:settings:{uid}为 key 存储并为令牌设置 365 天过期时间否则回退到本地 JSON 文件方便零依赖跑通本地开发。令牌写入时会记录expires_at时间戳is_token_expired()判断过期时会额外预留 60 秒缓冲。当令牌过期时get_valid_access_token()会调用refresh_access_token()用 refresh_token 换取新令牌并回写存储见main.py。README 提到 Linear 令牌有效期可达 10 年但代码中仍按expires_in字段缺省315360000秒设置了一个合理的过期时间保证刷新逻辑始终可用。七、数据模型一览插件把 Linear 的领域对象映射为 Pydantic 模型models.py这些模型既是 GraphQL 响应的解析目标也构成了工具响应的类型骨架模型字段要点LinearUserid、name、email、display_name、avatar_urlLinearTeamid、name、key如 ENG/OMI、descriptionLinearProjectid、name、description、state、urlWorkflowStateid、name、type、color、positionLinearLabelid、name、colorLinearIssueid、identifier、title、description、priority、estimate、state、assignee、creator、team、project、labels、url、created_at、updated_atLinearCommentid、body、user、created_at、updated_atChatToolRequest/ 各工具请求模型均含 uid、app_id、tool_name并按工具补充 title、limit、status、query、issue_identifier、comment 等字段ChatToolResponseresult / error 二选一八、Hermetic 回归测试短标识符精确解析与参数安全README 特别强调了仓库内新增的“无网络回归测试”这是该插件工程化质量的重要佐证。在仓库根目录执行python3 -B plugins/omi-linear-app/test_issue_lookup.py8.1 精确解析测试test_issue_lookup.pytest_issue_lookup.py 是一套仅依赖标准库的测试它通过importlib在 stub 掉fastapi、requests、db、models后加载真实的生产模块再以模拟的 GraphQL 契约执行真实 handler。覆盖点包括get_issue、update_issue_status、add_comment三个 handler 对短标识符如ENG-123必须走 Linear 的精确issue(id:)查询而不是排名式搜索避免把ENG-1234误当成ENG-123目标缺失或不可访问{issue: None}、{}、{error: Access denied}时三个 handler均不产生任何变更操作不会触发 mutation鉴权失败或参数校验失败时完全不发起 GraphQL 请求mutation 错误如Mutation denied能正确向上传播为工具 error关键词搜索search_issues保持其排名式搜索行为不变find_state_by_name()的匹配优先级精确名称匹配 → 工作流类型别名匹配 → 子串部分匹配例如done会优先映射到类型为completed的状态如名为Shipped的状态而不是命中Not Done中的子串。8.2 参数安全测试test_list_issues_params.pytest_list_issues_params.py 针对回归问题对应 issue #13920编写早期版本曾把limit与team直接插值进 GraphQL 文本first: {limit}、eq: {team}导致空值/字符串/超上限的 limit 生成畸形查询团队 key 含引号或花括号时甚至可能注入任意 GraphQL。修复后的约束为limit一律经coerce_limit()处理非数字回退默认值list_my_issues为 10、list_recent_issues为 5并钳制在[1, 50]区间$first与$filter一律以变量形式传参绝不插值进查询文本团队 key 会被strip().upper()规范化后作为{team: {key: {eq: ...}}}结构化过滤数据list_my_issues的状态过滤只允许白名单内的状态类型常量backlog/unstarted/started/completed/canceled原始状态文本永不进入查询文本。运行测试使用标准库unittest因此既可以在本地执行也可以在 CI 的 preflight 清单通道中直接运行。测试套件明确不覆盖的边界包括Linear 真实排名行为、OAuth 全流程、HTTP 传输层以及 FastAPI/Pydantic 的运行时行为。九、部署到 Railway第 1 步创建 Railway 项目登录 Railway点击New Project→Deploy from GitHub repo选择你的仓库并选中plugins/omi-linear-app目录。第 2 步添加 Redis 数据库在 Railway 项目中点击 New→Database→Add RedisRailway 会自动创建并连接 Redis 实例REDIS_URL环境变量由平台自动注入。仓库根目录的 railway.toml 已为部署预置了构建与启动配置使用 Nixpacks 构建、启动命令uvicorn main:app --host 0.0.0.0 --port ${PORT:-8080}、健康检查路径/health对应main.py中的app.get(/health)端点、失败自动重启策略。第 3 步配置环境变量在服务Variables标签页中添加变量值LINEAR_CLIENT_ID你的 Linear OAuth Client IDLINEAR_CLIENT_SECRET你的 Linear OAuth Client SecretLINEAR_REDIRECT_URIhttps://YOUR-APP.up.railway.app/auth/linear/callback注意将YOUR-APP替换为 Railway 实际分配的域名见 Settings → Domains。第 4 步配置根目录若从主仓库部署需在Settings→Build→Root Directory中填写plugins/omi-linear-app。第 5 步更新 Linear OAuth 应用将 Railway 地址添加为 Linear OAuth 应用的 redirect URIhttps://YOUR-APP.up.railway.app/auth/linear/callback第 6 步更新 Omi App Store 中的 URLURL 类型值App Home URLhttps://YOUR-APP.up.railway.app/Setup Completed URLhttps://YOUR-APP.up.railway.app/setup/linearChat Tools Manifest URLhttps://YOUR-APP.up.railway.app/.well-known/omi-tools.jsonRailway 架构示意┌─────────────────────────────────────────────────┐ │ Railway Project │ ├─────────────────────────────────────────────────┤ │ ┌───────────────┐ ┌───────────────────┐ │ │ │ Linear App │────▶│ Redis Database │ │ │ │ (FastAPI) │ │ (Persistent) │ │ │ │ │ │ │ │ │ │ - OAuth │ │ - User tokens │ │ │ │ - Chat tools │ │ - Settings │ │ │ │ - GraphQL │ │ - Default teams │ │ │ └───────────────┘ └───────────────────┘ │ │ │ │ │ ▼ │ │ https://YOUR-APP.up.railway.app │ └─────────────────────────────────────────────────┘从源码结构看这一架构与 db.py 的分层完全对应FastAPI 应用负责 OAuth 流程与 Chat Tools 端点Redis 负责持久化用户令牌、默认团队等设置无 Redis 时本地文件兜底保证任何环境下都能运行。十、API 端点一览README 给出了完整端点清单全部在 main.py 中实现端点方法说明/GET首页 / 应用设置页setup.html/healthGET健康检查/auth/linearGET发起 OAuth 流程/auth/linear/callbackGETOAuth 回调/setup/linearGET查询设置完成状态/disconnectGET断开账户连接/tools/create_issuePOSTChat Tool创建 issue/tools/list_my_issuesPOSTChat Tool列出我的 issue/tools/update_issue_statusPOSTChat Tool更新状态/tools/search_issuesPOSTChat Tool搜索 issue/tools/get_issuePOSTChat Tool获取 issue 详情/tools/add_commentPOSTChat Tool添加评论值得展开的端点细节OAuth 发起/auth/linear将uid作为state参数随授权链接发出回调时从state还原uid保证授权结果能对应到正确的 Omi 用户授权成功换取令牌后即重定向回/?uid{uid}。默认团队设置POST /settings/default-team创建 issue 时若请求未携带team_id插件会依次回退到用户设置的默认团队 → 用户所属的第一个团队setup.html中的下拉选择框即调用此端点。搜索回退tool_search_issues优先调用 Linear 的searchIssues(term:)全文本搜索若该查询报错则回退为基于issues(filter: {title: {containsIgnoreCase}})的过滤式搜索保证弱化场景下仍可用。十一、优先级体系创建 issue 时可以指定优先级。插件在tool_create_issue内部将文本优先级映射为 Linear 的数字值见main.py中的priority_map文本Linear 数值颜色说明urgent1 红需要立即处理的严重问题high2 橙需要尽快处理的重要问题medium / normal3 黄标准优先级问题low4 蓝可有可无或待办积压项none0⚪ 灰未设置优先级在列表与详情返回中插件用统一的图标映射{0: ⚪, 1: , 2: , 3: , 4: }渲染每条 issue 的优先级用户一眼即可区分轻重缓急。十二、常见问题排查错误提示排查建议User not authenticated在应用设置页点击Connect Linear完成 OAuth 授权流程No teams found确认你的 Linear 工作区中至少拥有一个团队可尝试重新连接 Linear 账户Could not find issue核对 issue 标识符是否正确如 ENG-123确认该 issue 存在于你有权限访问的团队中Failed to update status确认状态名对该团队工作流有效建议使用标准名称Backlog、Todo、In Progress、Done结合源码进一步说明“Could not find issue” 对应get_issue_by_identifier()返回空或错误的情况——精确issue(id:)查询查不到目标或 Linear 返回权限错误如Access denied时插件都不会执行后续的更新/评论操作“Failed to update status” 的另一常见原因是目标状态名无法匹配find_state_by_name()会先在团队的工作流状态中精确匹配再按类型别名backlog/unstarted/started/completed/canceled匹配最后做子串部分匹配若全部失败插件会返回该团队所有可用状态名提示用户正确的状态写法。十三、项目结构与许可证插件的完整代码布局如下plugins/omi-linear-app/ ├── main.py # FastAPI 应用OAuth、Chat Tools、GraphQL 封装 ├── models.py # Pydantic 数据模型 ├── db.py # Redis/文件双通道存储 ├── templates/setup.html # 设置页授权、默认团队、指令示例 ├── requirements.txt # 依赖清单 ├── Procfile # web 启动命令 ├── railway.toml # Railway 构建/部署配置 ├── test_issue_lookup.py # 精确解析回归测试 ├── test_list_issues_params.py # 参数安全回归测试 └── test_graphql_errors.py # GraphQL 错误处理测试插件以 MIT License 开源可自由修改与分发。若在使用中发现问题或有功能诉求可在 GitHub 仓库提交 issue 或联系 Omi 社区。结语本指南完整覆盖了plugins/omi-linear-app从设计到上线的全链路Omi App Store 的 URL 与能力配置、Linear OAuth 应用与 Scope 申请、本地开发与 ngrok 联调、Chat Tools 清单与六个工具端点、Railway 云部署与 Redis 持久化以及工程化层面的无网络回归测试与参数安全设计。无论你是想在 Omi 设备上语音管理 Linear还是想以此为模板开发自己的外部集成插件都可以直接复用本插件的架构与代码OAuth 授权码 令牌刷新、结构化 GraphQL 变量封装、coerce_limit参数钳制以及“精确查询优先于排名搜索”的语义设计都是值得借鉴的实战模式。【免费下载链接】FriendAI that sees your screen, listens to your conversations and tells you what to do项目地址: https://gitcode.com/GitHub_Trending/fr/Friend创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考