linear-sdk-scripting
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseLinear SDK Scripting
Linear SDK 脚本编写
Drive Linear through the official TypeScript SDK () by writing throwaway Node scripts and executing them. This replaces the Linear MCP server: anything the MCP can do (read issues, create issues, comment, change status, query teams/projects/cycles) you do here by calling SDK methods.
@linear/sdkThe SDK is preferred over hand-written GraphQL because pagination, relationship traversal, and mutation payloads are normalized, and method and input names are predictable. When you need a field or filter you do not know, consult the docs index in and fetch the specific page rather than guessing.
references/docs-index.md通过编写一次性Node脚本并执行,借助官方TypeScript SDK()来操作Linear。这可以替代Linear MCP服务器:MCP能完成的所有操作(读取工单、创建工单、添加评论、更改状态、查询团队/项目/周期),你都可以通过调用SDK方法在此实现。
@linear/sdk相较于手写GraphQL,SDK更为推荐,因为它对分页、关系遍历和变更负载进行了规范化,且方法和输入名称具有可预测性。当你需要某个未知的字段或过滤器时,请查阅中的文档索引,获取具体页面内容,而非自行猜测。
references/docs-index.mdWorkflow at a glance
概览工作流
Be reactive: try the script first, recover on auth failure, and only create a key as a last resort. Do not gate on being set in the current shell, that variable is almost always empty here even when a valid key is already persisted (see Setup).
$LINEAR_API_KEY- Make sure the SDK is installed in the working dir. See Setup.
- Write a small script into the working dir that imports
.mjsand does the task.LinearClient - Run it with , sourcing the shell profiles first so a persisted key is picked up (see Execution pattern).
node - If it fails with a 401 / , source the shell profiles and retry once. Only if it still fails is the key actually missing or invalid: run the API key setup flow with the user.
AuthenticationLinearError
灵活应对:先尝试运行脚本,在认证失败时进行恢复,仅在万不得已时才创建密钥。不要以当前shell中是否设置了作为判断依据,即使已持久化有效密钥,此变量在此处几乎始终为空(请参阅设置部分)。
$LINEAR_API_KEY- 确保工作目录中已安装SDK。请参阅设置部分。
- 在工作目录中编写一个小型脚本,导入
.mjs并完成任务。LinearClient - 使用运行脚本,先加载shell配置文件,以便读取已持久化的密钥(请参阅执行模式)。
node - 如果因401错误/导致失败,加载shell配置文件并重试一次。只有当再次失败时,才说明密钥确实缺失或无效:引导用户完成API密钥设置流程。
AuthenticationLinearError
Setup
设置
1. Personal API key
1. 个人API密钥
The SDK authenticates with a Linear personal API key read from the environment variable.
LINEAR_API_KEYDo not decide whether a key exists by checking the env var up front. Scripts here run in a non-interactive, non-login shell that does not source , , , etc., so reads as empty even when a valid key is already persisted in one of those files. An empty variable in this shell does not mean the key is unconfigured.
~/.zshrc~/.zshenv~/.bashrc$LINEAR_API_KEYInstead, let the script attempt the work (the Execution pattern sources the profiles first), and only treat the key as missing if it still auth-fails after that. See Handling auth failures.
Only when the key is genuinely missing, guide the user through creating one:
-
Open Security and access settings: https://linear.app/settings/account/security
-
Under Personal API keys, create a new key. Copy it (it starts with).
lin_api_ -
Ask the user to paste their key, then load it into the current session so you can use it immediately without a new terminal:sh
export LINEAR_API_KEY="lin_api_REPLACE_ME" -
Ask before persisting. The key is a secret and persisting it writes to a file the user owns, so do not do it on your own initiative. Explicitly ask first, for example: "Do you want me to persist this key to your shell profile () so it's available in future sessions?" Wait for a clear yes.
~/.zshrcOnly after the user confirms, perform the write yourself using the command for the user's shell:- zsh:
sh
echo 'export LINEAR_API_KEY="lin_api_REPLACE_ME"' >> ~/.zshrc - bash (or on macOS login shells):
~/.bash_profileshecho 'export LINEAR_API_KEY="lin_api_REPLACE_ME"' >> ~/.bashrc - fish:
sh
echo 'set -gx LINEAR_API_KEY "lin_api_REPLACE_ME"' >> ~/.config/fish/config.fish
This explicit in-conversation approval is what makes the write a user-requested action rather than an agent-initiated one. If the user declines or does not answer, do not persist the key; it will simply need to be re-set next session. - zsh:
Never print the key value back to the transcript or commit it anywhere. Treat it as a secret.
SDK通过读取环境变量中的Linear个人API密钥进行认证。
LINEAR_API_KEY不要通过预先检查环境变量来判断密钥是否存在。此处运行的脚本处于非交互式、非登录shell环境,不会加载、、等文件,因此即使有效密钥已持久化到上述文件中,仍会显示为空。此shell中的变量为空并不代表密钥未配置。
~/.zshrc~/.zshenv~/.bashrc$LINEAR_API_KEY相反,应让脚本尝试执行任务(执行模式会先加载配置文件),只有在加载配置文件后仍认证失败时,才判定密钥缺失。请参阅认证失败处理部分。
仅当密钥确实缺失时,才引导用户创建密钥:
-
在「个人API密钥」下创建新密钥。复制密钥(以开头)。
lin_api_ -
请用户粘贴密钥,然后将其加载到当前会话中,以便无需重启终端即可立即使用:sh
export LINEAR_API_KEY="lin_api_REPLACE_ME" -
询问用户是否要持久化密钥。密钥属于机密信息,持久化操作会写入用户拥有的文件,因此请勿自行执行。需明确询问,例如:「是否要将此密钥持久化到你的shell配置文件()中,以便在未来会话中可用?」等待用户明确同意。
~/.zshrc仅在用户确认后,才根据用户的shell类型执行写入命令:- zsh:
sh
echo 'export LINEAR_API_KEY="lin_api_REPLACE_ME"' >> ~/.zshrc - bash(macOS登录shell为):
~/.bash_profileshecho 'export LINEAR_API_KEY="lin_api_REPLACE_ME"' >> ~/.bashrc - fish:
sh
echo 'set -gx LINEAR_API_KEY "lin_api_REPLACE_ME"' >> ~/.config/fish/config.fish
这种对话中的明确同意,使得写入操作成为用户请求的行为,而非Agent主动发起的操作。如果用户拒绝或未回应,请不要持久化密钥;下次会话时只需重新设置即可。 - zsh:
切勿将密钥值打印到会话记录中或提交到任何地方。将其视为机密信息。
2. Install the SDK in a working directory
2. 在工作目录中安装SDK
Keep a dedicated working directory with its own . Running scripts from inside it lets you use plain with top-level await and no tricks. Requires Node 18 or newer.
node_modulesimportNODE_PATHsh
LINEAR_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/linear-sdk-scripting"
mkdir -p "$LINEAR_DIR"
cd "$LINEAR_DIR"
[ -f package.json ] || npm init -y >/dev/null
npm ls @linear/sdk >/dev/null 2>&1 || npm install @linear/sdkThis is a one-time setup; reuse the directory afterwards.
保留一个专用工作目录,包含其自身的。在此目录内运行脚本,你可以使用顶层的普通,无需使用技巧。需要Node 18或更高版本。
node_modulesawaitimportNODE_PATHsh
LINEAR_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/linear-sdk-scripting"
mkdir -p "$LINEAR_DIR"
cd "$LINEAR_DIR"
[ -f package.json ] || npm init -y >/dev/null
npm ls @linear/sdk >/dev/null 2>&1 || npm install @linear/sdk这是一次性设置;后续可重复使用此目录。
Execution pattern (canonical)
执行模式(标准)
For each task, write a script into the working directory and run it. Use the env var; do not inline the key.
Source the common shell profiles before invoking so a persisted key is pulled into the environment (this shell does not source them automatically). This preamble is harmless when no key is persisted, so use it as the standard way to run every script:
nodesh
LINEAR_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/linear-sdk-scripting"
cat > "$LINEAR_DIR/task.mjs" <<'EOF'
import { LinearClient } from "@linear/sdk"
const linear = new LinearClient({ apiKey: process.env.LINEAR_API_KEY })
const me = await linear.viewer
const myIssues = await me.assignedIssues({ first: 20 })
for (const issue of myIssues.nodes) {
console.log(`${issue.identifier} ${issue.title}`)
}
EOF
for f in ~/.zshenv ~/.zshrc ~/.zprofile ~/.profile ~/.bashrc ~/.bash_profile; do
[ -f "$f" ] && source "$f"
done
node "$LINEAR_DIR/task.mjs"Notes:
- The script file lives in the working dir, so resolves against that dir's
import "@linear/sdk".node_modules - gives you top-level
.mjs, so no async wrapper is needed.await - Emit machine-readable output () when you need to parse results in a later step.
console.log(JSON.stringify(...))
对于每个任务,在工作目录中编写脚本并运行。使用环境变量;不要将密钥内联到脚本中。
在调用前加载通用shell配置文件,以便将已持久化的密钥加载到环境中(此shell不会自动加载这些文件)。即使未持久化密钥,此预处理步骤也不会产生负面影响,因此请将其作为运行所有脚本的标准方式:
nodesh
LINEAR_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/linear-sdk-scripting"
cat > "$LINEAR_DIR/task.mjs" <<'EOF'
import { LinearClient } from "@linear/sdk"
const linear = new LinearClient({ apiKey: process.env.LINEAR_API_KEY })
const me = await linear.viewer
const myIssues = await me.assignedIssues({ first: 20 })
for (const issue of myIssues.nodes) {
console.log(`${issue.identifier} ${issue.title}`)
}
EOF
for f in ~/.zshenv ~/.zshrc ~/.zprofile ~/.profile ~/.bashrc ~/.bash_profile; do
[ -f "$f" ] && source "$f"
done
node "$LINEAR_DIR/task.mjs"注意:
- 脚本文件位于工作目录中,因此会解析此目录的
import "@linear/sdk"。node_modules - 支持顶层
.mjs,因此无需异步包装器。await - 当你需要在后续步骤中解析结果时,请输出机器可读的内容()。
console.log(JSON.stringify(...))
Handling auth failures
认证失败处理
A missing or invalid key surfaces as an with 401. Detect it and route to the key setup flow:
AuthenticationLinearErrorstatusjs
try {
const me = await linear.viewer
console.log(me.displayName)
} catch (e) {
if (e?.status === 401 || e?.constructor?.name === "AuthenticationLinearError") {
console.error("AUTH_FAILED: set up LINEAR_API_KEY")
process.exit(2)
}
throw e
}If you see (or the script crashes before any data), recover in this order rather than jumping straight to creating a key:
AUTH_FAILED-
Source the shell profiles and retry once. The key may already be persisted in a profile file that this shell never sourced. Pull it in and re-run the same script:sh
for f in ~/.zshenv ~/.zshrc ~/.zprofile ~/.profile ~/.bashrc ~/.bash_profile; do [ -f "$f" ] && source "$f" done node "$LINEAR_DIR/task.mjs"(If you already ran with the canonical Execution pattern preamble, the profiles were sourced, so this retry will only help if you ran without it.) -
Only if it still auth-fails, the key is genuinely missing or invalid. Now do the Personal API key setup with the user (create and persist a new key), then rerun.
缺失或无效的密钥会表现为状态码401的。检测到该错误后,引导至密钥设置流程:
AuthenticationLinearErrorjs
try {
const me = await linear.viewer
console.log(me.displayName)
} catch (e) {
if (e?.status === 401 || e?.constructor?.name === "AuthenticationLinearError") {
console.error("AUTH_FAILED: set up LINEAR_API_KEY")
process.exit(2)
}
throw e
}如果看到(或脚本在输出任何数据前崩溃),请按以下顺序恢复,而非直接创建密钥:
AUTH_FAILED-
加载shell配置文件并重试一次。密钥可能已持久化到某个配置文件中,但此shell从未加载过该文件。加载配置文件后重新运行同一脚本:sh
for f in ~/.zshenv ~/.zshrc ~/.zprofile ~/.profile ~/.bashrc ~/.bash_profile; do [ -f "$f" ] && source "$f" done node "$LINEAR_DIR/task.mjs"(如果你已使用标准执行模式的预处理步骤运行脚本,则已加载配置文件,因此此重试仅在未使用该模式时有效。) -
只有当再次认证失败时,才说明密钥确实缺失或无效。此时请引导用户完成个人API密钥设置(创建并持久化新密钥),然后重新运行脚本。
Common operations
常见操作
Concise recipes are inline below. Fuller examples (filtering, pagination loops, closing issues via workflow states, batch operations) are in . The doc index is in .
references/recipes.mdreferences/docs-index.mdRead:
js
// Current user
const me = await linear.viewer
// List issues (newest first), with a filter
const issues = await linear.issues({
first: 25,
filter: { state: { type: { eq: "started" } } },
})
// A single issue by UUID
const issue = await linear.issue("UUID")
// Teams, users, projects
const teams = await linear.teams()
const users = await linear.users()
const projects = await linear.projects({ first: 50 })Write (mutations return a payload with and the entity, often as a promise):
successjs
// Create
const created = await linear.createIssue({
teamId: "TEAM_UUID",
title: "Title",
description: "Markdown body",
})
const newIssue = await created.issue
// Update (e.g. retitle, reassign)
await linear.updateIssue("ISSUE_UUID", { title: "New title", assigneeId: "USER_UUID" })
// Comment
await linear.createComment({ issueId: "ISSUE_UUID", body: "Comment text" })To resolve human inputs to IDs (team key like , a workflow state name like , an assignee email), look them up first. See for the lookup-then-mutate patterns, including how to close an issue by finding the team's completed workflow state.
ENGDonereferences/recipes.md以下是简明的操作示例。更完整的示例(过滤、分页循环、通过工作流状态关闭工单、批量操作)位于中。文档索引位于中。
references/recipes.mdreferences/docs-index.md读取操作:
js
// 当前用户
const me = await linear.viewer
// 列出工单(最新优先),带过滤器
const issues = await linear.issues({
first: 25,
filter: { state: { type: { eq: "started" } } },
})
// 通过UUID获取单个工单
const issue = await linear.issue("UUID")
// 团队、用户、项目
const teams = await linear.teams()
const users = await linear.users()
const projects = await linear.projects({ first: 50 })写入操作(变更会返回包含和实体的负载,通常为Promise):
successjs
// 创建工单
const created = await linear.createIssue({
teamId: "TEAM_UUID",
title: "Title",
description: "Markdown body",
})
const newIssue = await created.issue
// 更新(例如重命名、重新分配)
await linear.updateIssue("ISSUE_UUID", { title: "New title", assigneeId: "USER_UUID" })
// 添加评论
await linear.createComment({ issueId: "ISSUE_UUID", body: "Comment text" })要将人工输入转换为ID(如团队标识、工作流状态名称、经办人邮箱),请先进行查询。有关查询后变更的模式,包括如何通过查找团队的已完成工作流状态来关闭工单,请参阅。
ENGDonereferences/recipes.mdWhen you need something not covered here
当你需要本文未涵盖的内容时
The Linear schema is large. Instead of guessing field or filter names:
- Open and pick the relevant page.
references/docs-index.md - Fetch that page (filtering, pagination, SDK fetching and modifying data, or the GraphQL schema reference) and use the exact names from it.
Linear的架构非常庞大。请勿猜测字段或过滤器名称:
- 打开并选择相关页面。
references/docs-index.md - 获取该页面内容(过滤、分页、SDK获取和修改数据,或GraphQL架构参考),并使用其中的准确名称。
Gotchas
注意事项
- Run scripts from the working dir (or point at a script inside it) so resolves.
@linear/sdkdoes not resolve packages for ESMNODE_PATH; it only works for CommonJSimport.require - Many SDK properties and nested relations are async and return promises or connections. Await them (,
await issue.state).await issue.assignee - Connections paginate. Use and
connection.pageInfo.hasNextPage, or iterate. Seeawait connection.fetchNext().references/recipes.md - Mutation results expose and the mutated entity; the entity accessor is usually a promise (
success).await payload.issue
- 请从工作目录运行脚本(或指向目录内的脚本),以便能够被解析。
@linear/sdk无法解析ESMNODE_PATH的包;它仅适用于CommonJSimport。require - 许多SDK属性和嵌套关系是异步的,会返回Promise或连接对象。请使用等待结果(
await、await issue.state)。await issue.assignee - 连接对象会分页。使用和
connection.pageInfo.hasNextPage,或进行迭代。请参阅await connection.fetchNext()。references/recipes.md - 变更结果会暴露和变更后的实体;实体访问器通常是Promise(
success)。await payload.issue