Files
gh-valian-linear-cli-skill-…/skills/linear/scripts/linear-cli.js
2025-11-30 09:04:11 +08:00

1124 lines
30 KiB
JavaScript

#!/usr/bin/env node
import { LinearClient } from "@linear/sdk"
import { config } from "dotenv"
import { fileURLToPath } from "url"
import { dirname, join } from "path"
import { readFileSync } from "fs"
// Get the directory of the linear executable (parent of scripts/)
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
const linearDir = join(__dirname, "..")
// Load environment variables from .env file next to linear executable
config({ path: join(linearDir, ".env") })
function parseArgs(argv) {
const args = []
const flags = {}
let resource = ""
let action = ""
for (let i = 2; i < argv.length; i++) {
const arg = argv[i]
if (arg.startsWith("--")) {
const key = arg.slice(2)
const nextArg = argv[i + 1]
if (nextArg && !nextArg.startsWith("-")) {
// Support multiple values for the same flag (e.g., --label foo --label bar)
if (flags[key]) {
// Convert to array if not already
if (!Array.isArray(flags[key])) {
flags[key] = [flags[key]]
}
flags[key].push(nextArg)
} else {
flags[key] = nextArg
}
i++
} else {
flags[key] = true
}
} else if (arg.startsWith("-")) {
flags[arg.slice(1)] = true
} else if (!resource) {
resource = arg
} else if (!action) {
action = arg
} else {
args.push(arg)
}
}
return { resource, action, args, flags }
}
function showHelp() {
console.log(`linear-cli - CLI for working with Linear
Usage: linear-cli <resource> <action> [arguments] [options]
Resources:
issue Work with issues
user Work with users
team Work with teams
project Work with projects
Global Options:
-h, --help Show help
--json Output raw JSON
Run 'linear-cli <resource> --help' for resource-specific help
Run 'linear-cli <resource> <action> --help' for action-specific help
Examples:
linear-cli issue list
linear-cli issue view ENG-123
linear-cli issue create "Fix bug" --team <team-id>
linear-cli user list`)
}
function showUserHelp() {
console.log(`Usage: linear-cli user <action>
Actions:
list List all users
Options:
--json Output raw JSON
-h, --help Show help
Examples:
linear-cli user list
linear-cli user list --json`)
}
function showTeamHelp() {
console.log(`Usage: linear-cli team <action>
Actions:
list List all teams
Options:
--json Output raw JSON
-h, --help Show help
Examples:
linear-cli team list
linear-cli team list --json`)
}
function showProjectHelp() {
console.log(`Usage: linear-cli project <action>
Actions:
list List all projects
Options:
--json Output raw JSON
-h, --help Show help
Examples:
linear-cli project list
linear-cli project list --json`)
}
function showIssueHelp() {
console.log(`Usage: linear-cli issue <action> [arguments] [options]
Actions:
list List issues with filters
view <id-or-key> Get detailed information about an issue
create <title> Create a new issue
update <id-or-key> Update an issue
delete <id-or-key> Delete an issue (moves to trash)
comment <id-or-key> <text> Add a comment to an issue
Global Options:
--json Output raw JSON
-h, --help Show help
Run 'linear-cli issue <action> --help' for action-specific help`)
}
function showIssueListHelp() {
console.log(`Usage: linear-cli issue list [options]
List issues with filters
Options:
--team <id> Filter by team ID
--assignee <id> Filter by assignee user ID
--status <name> Filter by status name
--limit <n> Limit results (default: 50)
--json Output raw JSON
-h, --help Show help
Examples:
linear-cli issue list
linear-cli issue list --team <team-id>
linear-cli issue list --status "In Progress" --limit 10`)
}
function showIssueViewHelp() {
console.log(`Usage: linear-cli issue view <id-or-key> [options]
Get detailed information about an issue
Arguments:
id-or-key Issue identifier (e.g., ENG-123 or full UUID)
Options:
--json Output raw JSON
-h, --help Show help
Examples:
linear-cli issue view ENG-123
linear-cli issue view <issue-uuid> --json`)
}
function showIssueCreateHelp() {
console.log(`Usage: linear-cli issue create <title> [options]
Create a new issue
Arguments:
title Issue title
Options:
--team <id> Team ID (required)
--body <text> Issue description (use --body-file for long text)
--body-file <file> Read description from file (use "-" for stdin)
--assignee <id> Assignee user ID (use "@me" for yourself)
--label <name> Label name(s) - can be specified multiple times or comma-separated
--project <id> Project ID to assign the issue to
--parent <id> Parent issue ID (for creating sub-issues)
--priority <n> Priority (0=None, 1=Urgent/P0, 2=High/P1, 3=Medium/P2, 4=Low/P3)
--estimate <n> Story point estimate
--due-date <date> Due date (YYYY-MM-DD format)
--status <name> Initial status (e.g. "Backlog", "Todo", "In Progress")
--json Output raw JSON
-h, --help Show help
Examples:
linear-cli issue create "Fix bug" --team <team-id>
linear-cli issue create "New feature" --team <team-id> --body "Details" --priority 2
linear-cli issue create "Task" --team <team-id> --label bug --label p0
echo "Long description" | linear-cli issue create "Title" --team <team-id> --body-file -
linear-cli issue create "Sub-task" --team <team-id> --parent PROJ-123 --assignee @me`)
}
function showIssueUpdateHelp() {
console.log(`Usage: linear-cli issue update <id-or-key> [options]
Update an issue
Arguments:
id-or-key Issue identifier (e.g., ENG-123 or full UUID)
Options:
--status <name> Update status
--assignee <id> Update assignee (use "@me" for yourself)
--priority <n> Update priority (0=None, 1=Urgent/P0, 2=High/P1, 3=Medium/P2, 4=Low/P3)
--title <text> Update title
--body <text> Update description
--body-file <file> Read description from file (use "-" for stdin)
--label <name> Add label(s) - can be specified multiple times or comma-separated
--project <id> Assign to project
--parent <id> Set parent issue (for creating sub-issues)
--estimate <n> Update story point estimate
--due-date <date> Set due date (YYYY-MM-DD format)
--json Output raw JSON
-h, --help Show help
Examples:
linear-cli issue update ENG-123 --status "In Progress"
linear-cli issue update ENG-123 --assignee @me --priority 1
linear-cli issue update ENG-123 --label bug --label urgent`)
}
function showIssueDeleteHelp() {
console.log(`Usage: linear-cli issue delete <id-or-key> [options]
Delete an issue (moves to trash)
Arguments:
id-or-key Issue identifier (e.g., ENG-123 or full UUID)
Options:
--json Output raw JSON
-h, --help Show help
Examples:
linear-cli issue delete ENG-123
linear-cli issue delete <issue-uuid>`)
}
function showIssueCommentHelp() {
console.log(`Usage: linear-cli issue comment <id-or-key> <text> [options]
Add a comment to an issue
Arguments:
id-or-key Issue identifier (e.g., ENG-123 or full UUID)
text Comment text
Options:
--json Output raw JSON (comment details)
-h, --help Show help
Examples:
linear-cli issue comment ENG-123 "This looks good"
linear-cli issue comment ENG-123 "Fixed in PR #42" --json`)
}
function getLinearClient() {
const apiKey = process.env.LINEAR_API_KEY
if (!apiKey) {
console.error(`Error: LINEAR_API_KEY not found
Please provide your Linear API key in one of these ways:
1. Environment variable:
export LINEAR_API_KEY="your-api-key"
2. Create a .env file next to the linear executable:
echo 'LINEAR_API_KEY=your-api-key' > ${linearDir}/.env
Get your API key from: https://linear.app/settings/api
Go to Settings > API > Personal API keys > Create key`)
process.exit(1)
}
try {
return new LinearClient({ apiKey })
} catch (error) {
console.error(`Error: Failed to initialize Linear client
Make sure @linear/sdk is installed:
cd linear/
npm install`)
process.exit(1)
}
}
// Helper to read file or stdin
function readBodyFile(path) {
if (path === "-" || path === true) {
// Read from stdin (path might be true if --body-file is passed without value)
try {
return readFileSync(0, "utf-8")
} catch (error) {
console.error("Error: Could not read from stdin")
process.exit(1)
}
} else {
// Read from file
try {
return readFileSync(path, "utf-8")
} catch (error) {
console.error(`Error: Could not read file: ${path}`)
process.exit(1)
}
}
}
// Helper to parse label input (supports comma-separated or array)
function parseLabels(labelInput) {
if (!labelInput) return []
const labels = Array.isArray(labelInput) ? labelInput : [labelInput]
const result = []
for (const label of labels) {
// Split by comma in case user does --label "bug,feature"
const split = label.split(",").map((l) => l.trim()).filter(Boolean)
result.push(...split)
}
return result
}
// Helper to resolve assignee (handle @me)
async function resolveAssignee(client, assigneeInput) {
if (!assigneeInput) return null
if (assigneeInput === "@me") {
const viewer = await client.viewer
return viewer.id
}
return assigneeInput
}
// Helper to find labels by name for a team
async function findLabels(client, teamId, labelNames) {
const graphQLClient = client.client
// Get all labels for the team
const response = await graphQLClient.rawRequest(
`query getTeamLabels($teamId: String!) {
team(id: $teamId) {
labels {
nodes {
id
name
}
}
}
}`,
{ teamId }
)
const availableLabels = response.data.team.labels.nodes
const labelIds = []
const notFound = []
for (const labelName of labelNames) {
const label = availableLabels.find(
(l) => l.name.toLowerCase() === labelName.toLowerCase()
)
if (label) {
labelIds.push(label.id)
} else {
notFound.push(labelName)
}
}
if (notFound.length > 0) {
console.error(`Error: Label(s) not found: ${notFound.join(", ")}`)
console.error(`\nAvailable labels for this team:`)
if (availableLabels.length === 0) {
console.error(" (no labels available)")
} else {
for (const label of availableLabels) {
console.error(` - ${label.name}`)
}
}
process.exit(1)
}
return labelIds
}
async function listUsers(flags) {
const client = getLinearClient()
const users = await client.users()
if (flags.json) {
console.log(JSON.stringify(users.nodes, null, 2))
return
}
console.log("Users\n")
for (const user of users.nodes) {
console.log(`#${user.id}\t${user.name}\t${user.email}`)
}
}
async function listTeams(flags) {
const client = getLinearClient()
const teams = await client.teams()
if (flags.json) {
console.log(JSON.stringify(teams.nodes, null, 2))
return
}
console.log("Teams\n")
for (const team of teams.nodes) {
console.log(`#${team.id}\t${team.name}\t${team.key}`)
}
}
async function listProjects(flags) {
const client = getLinearClient()
const projects = await client.projects()
if (flags.json) {
console.log(JSON.stringify(projects.nodes, null, 2))
return
}
console.log("Projects\n")
for (const project of projects.nodes) {
console.log(`#${project.id}\t${project.name}\t${project.state}`)
}
}
async function listIssues(flags) {
const client = getLinearClient()
// Build filter JSON
const filter = {}
if (flags.team) {
filter.team = { id: { eq: flags.team } }
}
if (flags.assignee) {
filter.assignee = { id: { eq: flags.assignee } }
}
if (flags.status) {
filter.state = { name: { eq: flags.status } }
}
const limit = flags.limit ? parseInt(flags.limit, 10) : 50
// Use GraphQL to preload all relations in a single query
const graphQLClient = client.client
const response = await graphQLClient.rawRequest(
`query listIssues($first: Int!, $filter: IssueFilter, $orderBy: PaginationOrderBy!) {
issues(first: $first, filter: $filter, orderBy: $orderBy) {
nodes {
id
identifier
title
state {
name
}
assignee {
name
email
}
}
}
}`,
{
first: limit,
filter: Object.keys(filter).length > 0 ? filter : undefined,
orderBy: "updatedAt"
}
)
const issues = response.data.issues.nodes
if (flags.json) {
console.log(JSON.stringify(issues, null, 2))
return
}
console.log("Issues\n")
for (const issue of issues) {
const assigneeName = issue.assignee?.name || "Unassigned"
console.log(`#${issue.identifier}\t${issue.title}\t${issue.state?.name}\t${assigneeName}`)
}
}
async function getIssue(identifier, flags) {
const client = getLinearClient()
const graphQLClient = client.client
let issue
try {
if (identifier.includes("-")) {
// Looks like an identifier (ENG-123)
const [teamKey, issueNumber] = identifier.toUpperCase().split("-")
const response = await graphQLClient.rawRequest(
`query getIssueByIdentifier($teamKey: String!, $issueNumber: Float!) {
issues(filter: { team: { key: { eq: $teamKey } }, number: { eq: $issueNumber } }) {
nodes {
id
identifier
title
description
priority
estimate
dueDate
createdAt
updatedAt
state {
name
}
assignee {
name
email
}
team {
name
key
}
parent {
identifier
title
}
project {
id
name
}
labels {
nodes {
name
}
}
comments {
nodes {
body
createdAt
user {
name
}
}
}
}
}
}`,
{ teamKey, issueNumber: parseInt(issueNumber) }
)
issue = response.data.issues.nodes[0]
} else {
// Assume it's a UUID
const response = await graphQLClient.rawRequest(
`query getIssueById($id: String!) {
issue(id: $id) {
id
identifier
title
description
priority
estimate
dueDate
createdAt
updatedAt
state {
name
}
assignee {
name
email
}
team {
name
key
}
parent {
identifier
title
}
project {
id
name
}
labels {
nodes {
name
}
}
comments {
nodes {
body
createdAt
user {
name
}
}
}
}
}`,
{ id: identifier }
)
issue = response.data.issue
}
} catch (error) {
console.error(`Error: Issue not found: ${identifier}`)
process.exit(1)
}
if (!issue) {
console.error(`Error: Issue not found: ${identifier}`)
process.exit(1)
}
if (flags.json) {
console.log(JSON.stringify(issue, null, 2))
return
}
const priorityMap = {
0: "None",
1: "Urgent (P0)",
2: "High (P1)",
3: "Medium (P2)",
4: "Low (P3)",
}
console.log(`Issue: #${issue.identifier}\n`)
console.log(`Title:\t\t${issue.title}`)
console.log(`Status:\t\t${issue.state?.name || "Unknown"}`)
console.log(`Assignee:\t${issue.assignee ? `${issue.assignee.name} (${issue.assignee.email})` : "Unassigned"}`)
console.log(`Team:\t\t${issue.team.name} (${issue.team.key})`)
console.log(`Priority:\t${priorityMap[issue.priority] || "None"}`)
console.log(`Labels:\t\t${issue.labels.nodes.map((l) => l.name).join(", ") || "None"}`)
if (issue.parent) {
console.log(`Parent:\t\t#${issue.parent.identifier} - ${issue.parent.title}`)
}
if (issue.project) {
console.log(`Project:\t${issue.project.name}`)
}
if (issue.estimate) {
console.log(`Estimate:\t${issue.estimate} points`)
}
if (issue.dueDate) {
console.log(`Due Date:\t${issue.dueDate}`)
}
console.log(`Created:\t${new Date(issue.createdAt).toISOString().split("T")[0]}`)
console.log(`Updated:\t${new Date(issue.updatedAt).toISOString().split("T")[0]}`)
if (issue.description) {
console.log(`\nDescription:`)
console.log(issue.description)
}
if (issue.comments.nodes.length > 0) {
console.log(`\nComments:`)
for (const comment of issue.comments.nodes) {
const date = new Date(comment.createdAt).toISOString().split("T")[0]
console.log(` [${date}] ${comment.user?.name}: ${comment.body}`)
}
}
}
async function addComment(identifier, text, flags) {
const client = getLinearClient()
// Find issue first
let issue
try {
if (identifier.includes("-")) {
const issues = await client.issues({ filter: { number: { eq: parseInt(identifier.split("-")[1]) } } })
issue = issues.nodes.find((i) => i.identifier === identifier.toUpperCase())
} else {
issue = await client.issue(identifier)
}
} catch (error) {
console.error(`Error: Issue not found: ${identifier}`)
process.exit(1)
}
if (!issue) {
console.error(`Error: Issue not found: ${identifier}`)
process.exit(1)
}
const response = await client.createComment({
issueId: issue.id,
body: text,
})
const comment = await response.comment
if (flags.json) {
console.log(JSON.stringify(comment, null, 2))
return
}
console.log(`✓ Comment added to #${issue.identifier}`)
}
async function updateIssue(identifier, flags) {
const client = getLinearClient()
// Find issue first
let issue
try {
if (identifier.includes("-")) {
const issues = await client.issues({ filter: { number: { eq: parseInt(identifier.split("-")[1]) } } })
issue = issues.nodes.find((i) => i.identifier === identifier.toUpperCase())
} else {
issue = await client.issue(identifier)
}
} catch (error) {
console.error(`Error: Issue not found: ${identifier}`)
process.exit(1)
}
if (!issue) {
console.error(`Error: Issue not found: ${identifier}`)
process.exit(1)
}
const updates = {}
// Handle status
if (flags.status) {
// Find state by name
const team = await issue.team
const states = await team.states()
const state = states.nodes.find((s) => s.name.toLowerCase() === flags.status.toLowerCase())
if (state) {
updates.stateId = state.id
} else {
console.error(`Error: Status '${flags.status}' not found`)
process.exit(1)
}
}
// Handle assignee with @me support
const assigneeId = await resolveAssignee(client, flags.assignee)
if (assigneeId) {
updates.assigneeId = assigneeId
}
// Handle priority
if (flags.priority !== undefined) {
updates.priority = parseInt(flags.priority, 10)
}
// Handle title
if (flags.title) {
updates.title = flags.title
}
// Handle body/description
if (flags["body-file"]) {
updates.description = readBodyFile(flags["body-file"])
} else if (flags.body) {
updates.description = flags.body
}
// Handle labels
const labelNames = parseLabels(flags.label)
if (labelNames.length > 0) {
const team = await issue.team
const labelIds = await findLabels(client, team.id, labelNames)
updates.labelIds = labelIds
}
// Handle project
if (flags.project) {
updates.projectId = flags.project
}
// Handle parent
if (flags.parent) {
let parentIssue
try {
if (flags.parent.includes("-")) {
const issues = await client.issues({
filter: { number: { eq: parseInt(flags.parent.split("-")[1]) } },
})
parentIssue = issues.nodes.find((i) => i.identifier === flags.parent.toUpperCase())
} else {
parentIssue = await client.issue(flags.parent)
}
} catch (error) {
console.error(`Error: Parent issue not found: ${flags.parent}`)
process.exit(1)
}
if (!parentIssue) {
console.error(`Error: Parent issue not found: ${flags.parent}`)
process.exit(1)
}
updates.parentId = parentIssue.id
}
// Handle estimate
if (flags.estimate !== undefined) {
updates.estimate = parseFloat(flags.estimate)
}
// Handle due date
if (flags["due-date"]) {
const dateRegex = /^\d{4}-\d{2}-\d{2}$/
if (!dateRegex.test(flags["due-date"])) {
console.error(`Error: Invalid date format. Use YYYY-MM-DD (e.g., 2025-12-31)`)
process.exit(1)
}
updates.dueDate = flags["due-date"]
}
if (Object.keys(updates).length === 0) {
console.error(`Error: No updates specified
Run 'linear-cli update --help' for available options`)
process.exit(1)
}
const response = await client.updateIssue(issue.id, updates)
const updatedIssue = await response.issue
if (flags.json) {
console.log(JSON.stringify(updatedIssue, null, 2))
return
}
console.log(`✓ Issue #${issue.identifier} updated`)
}
async function createIssue(title, flags) {
const client = getLinearClient()
if (!flags.team) {
console.error(`Error: --team flag is required
Run 'linear-cli create --help' for usage`)
process.exit(1)
}
const input = {
teamId: flags.team,
title,
}
// Handle body/description
if (flags["body-file"]) {
input.description = readBodyFile(flags["body-file"])
} else if (flags.body) {
input.description = flags.body
}
// Handle assignee with @me support
const assigneeId = await resolveAssignee(client, flags.assignee)
if (assigneeId) {
input.assigneeId = assigneeId
}
// Handle labels
const labelNames = parseLabels(flags.label)
if (labelNames.length > 0) {
const labelIds = await findLabels(client, flags.team, labelNames)
input.labelIds = labelIds
}
// Handle project
if (flags.project) {
input.projectId = flags.project
}
// Handle parent (for sub-issues)
if (flags.parent) {
// Need to resolve parent identifier to ID
let parentIssue
try {
if (flags.parent.includes("-")) {
const issues = await client.issues({
filter: { number: { eq: parseInt(flags.parent.split("-")[1]) } },
})
parentIssue = issues.nodes.find((i) => i.identifier === flags.parent.toUpperCase())
} else {
parentIssue = await client.issue(flags.parent)
}
} catch (error) {
console.error(`Error: Parent issue not found: ${flags.parent}`)
process.exit(1)
}
if (!parentIssue) {
console.error(`Error: Parent issue not found: ${flags.parent}`)
process.exit(1)
}
input.parentId = parentIssue.id
}
// Handle priority
if (flags.priority !== undefined) {
input.priority = parseInt(flags.priority, 10)
}
// Handle estimate
if (flags.estimate !== undefined) {
input.estimate = parseFloat(flags.estimate)
}
// Handle due date
if (flags["due-date"]) {
// Validate date format
const dateRegex = /^\d{4}-\d{2}-\d{2}$/
if (!dateRegex.test(flags["due-date"])) {
console.error(`Error: Invalid date format. Use YYYY-MM-DD (e.g., 2025-12-31)`)
process.exit(1)
}
input.dueDate = flags["due-date"]
}
// Handle status
if (flags.status) {
// Find state by name
const team = await client.team(flags.team)
const states = await team.states()
const state = states.nodes.find((s) => s.name.toLowerCase() === flags.status.toLowerCase())
if (state) {
input.stateId = state.id
} else {
console.error(`Error: Status '${flags.status}' not found`)
process.exit(1)
}
}
const response = await client.createIssue(input)
const issue = await response.issue
if (!issue) {
console.error("Error: Failed to create issue")
process.exit(1)
}
if (flags.json) {
console.log(JSON.stringify(issue, null, 2))
return
}
console.log(`✓ Issue created: #${issue.identifier}`)
console.log(` Title: ${issue.title}`)
console.log(` URL: ${issue.url}`)
}
async function deleteIssue(identifier, flags) {
const client = getLinearClient()
// Find issue first
let issue
try {
if (identifier.includes("-")) {
const issues = await client.issues({ filter: { number: { eq: parseInt(identifier.split("-")[1]) } } })
issue = issues.nodes.find((i) => i.identifier === identifier.toUpperCase())
} else {
issue = await client.issue(identifier)
}
} catch (error) {
console.error(`Error: Issue not found: ${identifier}`)
process.exit(1)
}
if (!issue) {
console.error(`Error: Issue not found: ${identifier}`)
process.exit(1)
}
const response = await client.deleteIssue(issue.id)
const success = await response.success
if (flags.json) {
console.log(JSON.stringify({ success }, null, 2))
return
}
if (success) {
console.log(`✓ Issue #${issue.identifier} deleted (moved to trash)`)
} else {
console.error(`Error: Failed to delete issue #${issue.identifier}`)
process.exit(1)
}
}
async function main() {
const { resource, action, args, flags } = parseArgs(process.argv)
// Handle help flags
if (flags.h || flags.help) {
if (!resource) {
showHelp()
process.exit(0)
}
switch (resource) {
case "user":
showUserHelp()
break
case "team":
showTeamHelp()
break
case "project":
showProjectHelp()
break
case "issue":
if (!action) {
showIssueHelp()
} else {
switch (action) {
case "list":
showIssueListHelp()
break
case "view":
showIssueViewHelp()
break
case "create":
showIssueCreateHelp()
break
case "update":
showIssueUpdateHelp()
break
case "delete":
showIssueDeleteHelp()
break
case "comment":
showIssueCommentHelp()
break
default:
showIssueHelp()
}
}
break
default:
showHelp()
}
process.exit(0)
}
try {
// Route commands
switch (resource) {
case "user":
if (action === "list") {
await listUsers(flags)
} else {
console.error(`Error: Unknown action '${action}' for resource 'user'
Run 'linear-cli user --help' for usage`)
process.exit(1)
}
break
case "team":
if (action === "list") {
await listTeams(flags)
} else {
console.error(`Error: Unknown action '${action}' for resource 'team'
Run 'linear-cli team --help' for usage`)
process.exit(1)
}
break
case "project":
if (action === "list") {
await listProjects(flags)
} else {
console.error(`Error: Unknown action '${action}' for resource 'project'
Run 'linear-cli project --help' for usage`)
process.exit(1)
}
break
case "issue":
switch (action) {
case "list":
await listIssues(flags)
break
case "view":
if (args.length === 0) {
console.error(`Error: Missing issue identifier
Run 'linear-cli issue view --help' for usage`)
process.exit(1)
}
await getIssue(args[0], flags)
break
case "create":
if (args.length === 0) {
console.error(`Error: Missing issue title
Run 'linear-cli issue create --help' for usage`)
process.exit(1)
}
await createIssue(args.join(" "), flags)
break
case "update":
if (args.length === 0) {
console.error(`Error: Missing issue identifier
Run 'linear-cli issue update --help' for usage`)
process.exit(1)
}
await updateIssue(args[0], flags)
break
case "delete":
if (args.length === 0) {
console.error(`Error: Missing issue identifier
Run 'linear-cli issue delete --help' for usage`)
process.exit(1)
}
await deleteIssue(args[0], flags)
break
case "comment":
if (args.length < 2) {
console.error(`Error: Missing required arguments
Run 'linear-cli issue comment --help' for usage`)
process.exit(1)
}
await addComment(args[0], args.slice(1).join(" "), flags)
break
default:
if (action) {
console.error(`Error: Unknown action '${action}' for resource 'issue'
Run 'linear-cli issue --help' for usage`)
} else {
console.error(`Error: Missing action for resource 'issue'
Run 'linear-cli issue --help' for usage`)
}
process.exit(1)
}
break
default:
if (resource) {
console.error(`Error: Unknown resource '${resource}'
Run 'linear-cli --help' for usage`)
process.exit(1)
} else {
showHelp()
}
}
} catch (error) {
if (error.message?.includes("API key")) {
console.error(`Error: Invalid LINEAR_API_KEY
Check your API key is valid: https://linear.app/settings/api`)
} else {
console.error(`Error: ${error.message || "Unknown error occurred"}`)
}
process.exit(1)
}
}
main()