Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 13 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ linear issue start ABC-123 # start a specific issue
linear issue view # see current branch's issue as markdown
linear issue pr # makes a PR with title/body preset, using gh cli
linear issue create # create a new issue
linear pr-body --issues ALA-123,ALA-124 # generate the Alavida PR template
```

it aims to be a complement to the web and desktop apps that lets you stay on the command line in an interactive or scripted way.
Expand All @@ -38,28 +39,22 @@ it aims to be a complement to the web and desktop apps that lets you stay on the

## install

### homebrew

```
brew install schpet/tap/linear
```

### deno via jsr

```bash
deno install -A --reload -f -g -n linear jsr:@schpet/linear-cli
deno install -A --reload -f -g -n linear jsr:@alavida/linear-cli
```

### npm / bun / pnpm

install as a dev dependency to pin a version in your project:

```bash
npm install -D @schpet/linear-cli
npm install -D @alavida/linear-cli
# or
bun add -D @schpet/linear-cli
bun add -D @alavida/linear-cli
# or
pnpm add -D @schpet/linear-cli
pnpm add -D @alavida/linear-cli
```

then run via your package manager:
Expand All @@ -71,16 +66,16 @@ bunx linear issue list

> **note:** this package ships pre-built binaries

package on npm: [@schpet/linear-cli](https://www.npmjs.com/package/@schpet/linear-cli)
package on npm: `@alavida/linear-cli`

### binaries

https://github.com/schpet/linear-cli/releases/latest
https://github.com/alavida-ai/linear-cli/releases/latest

### local dev

```bash
git clone https://github.com/schpet/linear-cli
git clone https://github.com/alavida-ai/linear-cli
cd linear-cli
deno task install
```
Expand Down Expand Up @@ -163,6 +158,7 @@ linear team autolinks # configure GitHub repository autolinks for Linear issues
```bash
linear project list # list projects
linear project view # view project details
linear project create --name "Client Deliverable" --team ENG --initiative "Client Delivery"
```

### milestone commands
Expand Down Expand Up @@ -255,11 +251,11 @@ install the skill using [claude code's plugin system](https://code.claude.com/do

```bash
# from claude code
/plugin marketplace add schpet/linear-cli
/plugin marketplace add alavida-ai/linear-cli
/plugin install linear-cli@linear-cli

# from bash
claude plugin marketplace add schpet/linear-cli
claude plugin marketplace add alavida-ai/linear-cli
claude plugin install linear-cli@linear-cli

# to update
Expand All @@ -272,10 +268,10 @@ claude plugin update linear-cli@linear-cli
install the skill using [skills.sh](https://skills.sh):

```bash
npx skills add schpet/linear-cli
npx skills add alavida-ai/linear-cli
```

view the skill at [skills.sh/schpet/linear-cli/linear-cli](https://skills.sh/schpet/linear-cli/linear-cli)
view the skill at [skills.sh/alavida-ai/linear-cli/linear-cli](https://skills.sh/alavida-ai/linear-cli/linear-cli)

## development

Expand Down
2 changes: 1 addition & 1 deletion deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/denoland/deno/main/cli/schemas/config-file.v1.json",
"name": "@schpet/linear-cli",
"name": "@alavida/linear-cli",
"version": "1.11.1",
"exports": "./src/main.ts",
"license": "MIT",
Expand Down
12 changes: 5 additions & 7 deletions dist-workspace.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ name = "linear"
description = "CLI tool for linear.app that uses git branch names and directory names to open issues and team pages"
version = "1.11.1"
license = "MIT"
repository = "https://github.com/schpet/linear-cli"
homepage = "https://github.com/schpet/linear-cli"
repository = "https://github.com/alavida-ai/linear-cli"
homepage = "https://github.com/alavida-ai/linear-cli"
binaries = ["linear"]
build-command = [
"sh",
Expand All @@ -23,15 +23,13 @@ cargo-dist-version = "0.31.0"
# CI backends to support
ci = "github"
# The installers to generate for each app
installers = ["shell", "npm", "homebrew"]
# A GitHub repo to push Homebrew formulas to
tap = "schpet/homebrew-tap"
installers = ["shell", "npm"]
# Target platforms to build apps for (Rust target-triple syntax)
targets = ["aarch64-apple-darwin", "aarch64-unknown-linux-gnu", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"]
# Publish jobs to run in CI
publish-jobs = ["homebrew", "npm"]
publish-jobs = ["npm"]
# A namespace to use when publishing this package to the npm registry
npm-scope = "@schpet"
npm-scope = "@alavida"
# The npm package should have this name
npm-package = "linear-cli"
# Which actions to run on pull requests
Expand Down
2 changes: 1 addition & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,5 @@ claude-install-local:
claude plugin install linear-cli@linear-cli

claude-install-github:
claude plugin marketplace add schpet/linear-cli
claude plugin marketplace add alavida-ai/linear-cli
claude plugin install linear-cli@linear-cli
9 changes: 6 additions & 3 deletions skills/linear-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ allowed-tools: Bash(linear:*), Bash(curl:*)

A CLI to manage Linear issues from the command line, with git and jj integration.

Use the `linear` CLI directly. The `allowed-tools` metadata only grants execution permission for the installed CLI; it is not a separate wrapper interface.

## Prerequisites

The `linear` command must be available on PATH. To check:
Expand All @@ -19,11 +21,11 @@ linear --version
If not installed globally, you can run it without installing via npx:

```bash
npx @schpet/linear-cli --version
npx @alavida/linear-cli --version
```

All subsequent commands can be prefixed with `npx @schpet/linear-cli` in place of `linear`. Otherwise, follow the install instructions at:\
https://github.com/schpet/linear-cli?tab=readme-ov-file#install
All subsequent commands can be prefixed with `npx @alavida/linear-cli` in place of `linear`. Otherwise, follow the install instructions at:\
https://github.com/alavida-ai/linear-cli?tab=readme-ov-file#install

## Best Practices for Markdown Content

Expand Down Expand Up @@ -80,6 +82,7 @@ linear document # Manage Linear documents
linear config # Interactively generate .linear.toml configuration
linear schema # Print the GraphQL schema to stdout
linear api # Make a raw GraphQL API request
linear pr-body # Generate the Alavida PR template from one or more issues
```

## Reference Documentation
Expand Down
8 changes: 5 additions & 3 deletions skills/linear-cli/SKILL.template.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ allowed-tools: Bash(linear:*), Bash(curl:*)

A CLI to manage Linear issues from the command line, with git and jj integration.

Use the `linear` CLI directly. The `allowed-tools` metadata only grants execution permission for the installed CLI; it is not a separate wrapper interface.

## Prerequisites

The `linear` command must be available on PATH. To check:
Expand All @@ -19,11 +21,11 @@ linear --version
If not installed globally, you can run it without installing via npx:

```bash
npx @schpet/linear-cli --version
npx @alavida/linear-cli --version
```

All subsequent commands can be prefixed with `npx @schpet/linear-cli` in place of `linear`. Otherwise, follow the install instructions at:\
https://github.com/schpet/linear-cli?tab=readme-ov-file#install
All subsequent commands can be prefixed with `npx @alavida/linear-cli` in place of `linear`. Otherwise, follow the install instructions at:\
https://github.com/alavida-ai/linear-cli?tab=readme-ov-file#install

## Best Practices for Markdown Content

Expand Down
2 changes: 1 addition & 1 deletion src/commands/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const apiCommand = new Command()
const headers = {
"Content-Type": "application/json",
Authorization: apiKey,
"User-Agent": `schpet-linear-cli/${denoConfig.version}`,
"User-Agent": `alavida-linear-cli/${denoConfig.version}`,
}

if (options.paginate) {
Expand Down
2 changes: 1 addition & 1 deletion src/commands/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export const configCommand = new Command()
}

const tomlContent = `# linear cli
# https://github.com/schpet/linear-cli
# https://github.com/alavida-ai/linear-cli

workspace = "${workspace}"
team_id = "${teamKey}"
Expand Down
128 changes: 128 additions & 0 deletions src/commands/pr-body.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { Command } from "@cliffy/command"
import { gql } from "../__codegen__/gql.ts"
import { getGraphQLClient } from "../utils/graphql.ts"
import { handleError, ValidationError } from "../utils/errors.ts"

const GetIssuesForPrBody = gql(`
query GetIssuesForPrBody($ids: [ID!]) {
issues(filter: { id: { in: $ids } }) {
nodes {
identifier
title
labels {
nodes {
name
}
}
}
}
}
`)

const WORK_TYPE_LABELS = [
"Unplanned",
"Change",
"Business",
"Internal",
] as const

type WorkType = (typeof WORK_TYPE_LABELS)[number]

function parseIssueIdentifiers(raw: string): string[] {
const ids = raw
.split(",")
.map((id) => id.trim())
.filter(Boolean)

if (ids.length === 0) {
throw new ValidationError("At least one issue ID is required", {
suggestion: "Pass a comma-separated list, e.g. --issues ALA-123,ALA-124",
})
}

return ids
}

function inferWorkType(
issues: Array<{ labels?: { nodes?: Array<{ name: string }> } | null }>,
): WorkType {
for (const label of WORK_TYPE_LABELS) {
if (
issues.some((issue) =>
issue.labels?.nodes?.some((issueLabel) => issueLabel.name === label)
)
) {
return label
}
}

return "Internal"
}

function renderPrBody(
issues: Array<{ identifier: string; title: string }>,
workType: WorkType,
): string {
const ticketLines = issues.map((issue) =>
`- ${issue.identifier} — ${issue.title}`
).join("\n")

return `## What this does
[Describe what changed in plain English — one paragraph, no jargon]

## Tickets addressed
${ticketLines}

## Work type
${workType}

## Review checklist
- [ ] Changes reviewed
- [ ] Ready to merge`
}

export const prBodyCommand = new Command()
.name("pr-body")
.description("Generate an Alavida PR body from Linear issues")
.option(
"--issues <issues:string>",
"Comma-separated issue identifiers, e.g. ALA-123,ALA-124",
{ required: true },
)
.action(async ({ issues: rawIssues }) => {
try {
const issueIds = parseIssueIdentifiers(rawIssues)
const client = getGraphQLClient()
const result = await client.request(GetIssuesForPrBody, { ids: issueIds })
const issues = result.issues?.nodes || []

const issuesById = new Map(
issues.map((issue) => [issue.identifier, issue] as const),
)
const orderedIssues = issueIds.map((issueId) => issuesById.get(issueId))
const missingIssues = issueIds.filter((issueId) =>
!issuesById.has(issueId)
)

if (missingIssues.length > 0) {
throw new ValidationError(
`Issue not found: ${missingIssues.join(", ")}`,
{
suggestion:
"Check the issue identifiers and make sure they are accessible in the current Linear workspace.",
},
)
}

console.log(
renderPrBody(
orderedIssues.filter((issue): issue is NonNullable<typeof issue> =>
Boolean(issue)
),
inferWorkType(issues),
),
)
} catch (error) {
handleError(error, "Failed to generate PR body")
}
})
2 changes: 2 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { documentCommand } from "./commands/document/document.ts"
import { configCommand } from "./commands/config.ts"
import { schemaCommand } from "./commands/schema.ts"
import { apiCommand } from "./commands/api.ts"
import { prBodyCommand } from "./commands/pr-body.ts"
import { setCliWorkspace } from "./config.ts"

// Import config and credentials setup
Expand Down Expand Up @@ -64,4 +65,5 @@ Environment Variables:
.command("config", configCommand)
.command("schema", schemaCommand)
.command("api", apiCommand)
.command("pr-body", prBodyCommand)
.parse(Deno.args)
2 changes: 1 addition & 1 deletion src/utils/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export function createGraphQLClient(apiKey: string): GraphQLClient {
return new GraphQLClient(getGraphQLEndpoint(), {
headers: {
Authorization: apiKey,
"User-Agent": `schpet-linear-cli/${denoConfig.version}`,
"User-Agent": `alavida-linear-cli/${denoConfig.version}`,
},
})
}
Expand Down
Loading
Loading