Skip to content
Merged
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
42 changes: 34 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,25 @@ linear issue comment update <id> # update a comment
linear issue commits # show all commits for an issue (jj only)
```

#### attaching files

attach files to an issue or comment. uploads are **private** by default (readable only by workspace members), matching the Linear web app.

```bash
linear issue attach ENG-123 ./screenshot.png # attach a file to an issue
linear issue attach ENG-123 ./doc.pdf -t "Spec" # custom attachment title
linear issue attach ENG-123 ./img.png -c "see this" # add a linked comment
linear issue comment add ENG-123 -a ./screenshot.png # attach a file to a comment
linear issue comment add ENG-123 -a ./a.png -a ./b.png # attach multiple files
```

by default attachments are private. pass `--public` to upload raster images (png/jpeg/gif/webp/bmp/tiff) to a public `public.linear.app` URL readable by **anyone, unauthenticated** — useful for sharing outside the workspace, but a warning is printed since it bypasses workspace access controls. non-image files cannot be made public.

```bash
linear issue attach ENG-123 ./screenshot.png --public # public image URL
linear issue comment add ENG-123 -a ./screenshot.png --public # public image URL
```

### team commands

```bash
Expand All @@ -167,6 +186,8 @@ 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 "API v2" --team ENG --content-file overview.md
linear project create --name "Mobile launch" --team APP --priority high --label Launch --member jane@example.com
```

### milestone commands
Expand Down Expand Up @@ -200,7 +221,7 @@ linear document list --json # output as JSON
linear document view <slug> # view document rendered in terminal
linear document view <slug> --raw # output raw markdown (for piping)
linear document view <slug> --web # open in browser
linear document view <slug> --json # output as JSON
linear document view <slug> --json # output as JSON, including document comments

# create a document
linear document create --title "My Doc" --content "# Hello" # inline content
Expand All @@ -213,13 +234,16 @@ cat spec.md | linear document create --title "Spec" # from std
linear document update <slug> --title "New Title" # update title
linear document update <slug> --content-file ./updated.md # update content
linear document update <slug> --edit # open in $EDITOR
linear document update <slug> --content-file ./updated.md --force # bypass comment-anchor guard

# delete a document
linear document delete <slug> # soft delete (move to trash)
linear document delete <slug> --permanent # permanent delete
linear document delete --bulk <slug1> <slug2> # bulk delete
```

content updates are refused by default when a document has active inline Linear comments, because replacing markdown can detach or hide those anchors. top-level document comments do not block updates. review the inline comment first, then rerun with `--force` if you intentionally want to replace the content anyway.

### other commands

```bash
Expand All @@ -233,13 +257,15 @@ linear completions # generate shell completions

the CLI supports configuration via environment variables or a `.linear.toml` config file. environment variables take precedence over config file values.

| option | env var | toml key | example | description |
| --------------- | ------------------------ | ----------------- | -------------------------- | ------------------------------------- |
| Team ID | `LINEAR_TEAM_ID` | `team_id` | `"ENG"` | default team for operations |
| Workspace | `LINEAR_WORKSPACE` | `workspace` | `"mycompany"` | workspace slug for web/app URLs |
| Issue sort | `LINEAR_ISSUE_SORT` | `issue_sort` | `"priority"` or `"manual"` | how to sort issue lists |
| VCS | `LINEAR_VCS` | `vcs` | `"git"` or `"jj"` | version control system (default: git) |
| Download images | `LINEAR_DOWNLOAD_IMAGES` | `download_images` | `true` or `false` | download images when viewing issues |
| option | env var | toml key | example | description |
| --------------- | --------------------------------- | -------------------------- | ---------------------------------- | ----------------------------------------------------- |
| Team ID | `LINEAR_TEAM_ID` | `team_id` | `"ENG"` | default team for operations |
| Workspace | `LINEAR_WORKSPACE` | `workspace` | `"mycompany"` | workspace slug for web/app URLs |
| Issue sort | `LINEAR_ISSUE_SORT` | `issue_sort` | `"priority"` or `"manual"` | how to sort issue lists |
| Ask project | `LINEAR_ISSUE_CREATE_ASK_PROJECT` | `issue_create_ask_project` | `true` or `false` | ask for a project during interactive `issue create` |
| Assign self | `LINEAR_ISSUE_CREATE_ASSIGN_SELF` | `issue_create_assign_self` | `"always"`, `"auto"`, or `"never"` | control default self-assignment during issue creation |
| VCS | `LINEAR_VCS` | `vcs` | `"git"` or `"jj"` | version control system (default: git) |
| Download images | `LINEAR_DOWNLOAD_IMAGES` | `download_images` | `true` or `false` | download images when viewing issues |

the config file can be placed at (checked in order, first found is used):

Expand Down
2 changes: 2 additions & 0 deletions docs/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ you can also set the API key in a project's `.linear.toml`:
api_key = "lin_api_..."
workspace = "acme"
team_id = "ENG"
issue_create_assign_self = "always"
issue_create_ask_project = true
```

this is useful for project-specific credentials but less secure than stored credentials since it may be committed to version control.
Expand Down
13 changes: 13 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,19 @@ linear team autolinks

### projects

#### create a project

```bash
# Create with a short description and long-form overview markdown
linear project create --name "API v2" --team ENG --description "Short summary" --content "## Overview"

# Read the project overview body from a markdown file
linear project create --name "API v2" --team ENG --content-file overview.md

# Create with priority, labels, members, icon, and color
linear project create --name "Mobile launch" --team APP --priority high --label Launch --member jane@example.com --icon rocket --color "#5E6AD2"
```

#### list projects

```bash
Expand Down
26 changes: 14 additions & 12 deletions skills/linear-cli/references/document.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,12 @@ Description:

Options:

-h, --help - Show this help.
--workspace <slug> - Target workspace (uses credentials)
--raw - Output raw markdown without rendering
-w, --web - Open document in browser
--json - Output full document as JSON
-h, --help - Show this help.
--workspace <slug> - Target workspace (uses credentials)
--raw - Output raw markdown without rendering
-w, --web - Open document in browser
--json - Output full document as JSON
--no-download - Keep remote URLs instead of downloading files
```

### create
Expand Down Expand Up @@ -105,13 +106,14 @@ Description:

Options:

-h, --help - Show this help.
--workspace <slug> - Target workspace (uses credentials)
-t, --title <title> - New title for the document
-c, --content <content> - New markdown content (inline)
-f, --content-file <path> - Read new content from file
--icon <icon> - New icon (emoji)
-e, --edit - Open current content in $EDITOR for editing
-h, --help - Show this help.
--workspace <slug> - Target workspace (uses credentials)
-t, --title <title> - New title for the document
-c, --content <content> - New markdown content (inline)
-f, --content-file <path> - Read new content from file
--icon <icon> - New icon (emoji)
-e, --edit - Open current content in $EDITOR for editing
--force - Update content even when document comments may lose inline anchors
```

### delete
Expand Down
24 changes: 14 additions & 10 deletions skills/linear-cli/references/issue.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,12 +388,14 @@ Description:

Options:

-h, --help - Show this help.
--workspace <slug> - Target workspace (uses credentials)
-b, --body <text> - Comment body text
--body-file <path> - Read comment body from a file (preferred for markdown content)
-p, --parent <id> - Parent comment ID for replies
-a, --attach <filepath> - Attach a file to the comment (can be used multiple times)
-h, --help - Show this help.
--workspace <slug> - Target workspace (uses credentials)
-b, --body <text> - Comment body text
--body-file <path> - Read comment body from a file (preferred for markdown content)
-p, --parent <id> - Parent comment ID for replies
-a, --attach <filepath> - Attach a file to the comment (can be used multiple times)
--public - Upload attached images to a public, unauthenticated URL (default: private,
workspace-members only)
```

##### delete
Expand Down Expand Up @@ -457,10 +459,12 @@ Description:

Options:

-h, --help - Show this help.
--workspace <slug> - Target workspace (uses credentials)
-t, --title <title> - Custom title for the attachment
-c, --comment <body> - Add a comment body linked to the attachment
-h, --help - Show this help.
--workspace <slug> - Target workspace (uses credentials)
-t, --title <title> - Custom title for the attachment
-c, --comment <body> - Add a comment body linked to the attachment
--public - Upload images to a public, unauthenticated URL (default: private,
workspace-members only)
```

### link
Expand Down
75 changes: 8 additions & 67 deletions src/commands/document/document-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Command } from "@cliffy/command"
import { Input, Select } from "@cliffy/prompt"
import { gql } from "../../__codegen__/gql.ts"
import { getGraphQLClient } from "../../utils/graphql.ts"
import { resolveProjectId } from "../../utils/linear.ts"
import { getEditor, openEditor } from "../../utils/editor.ts"
import { readIdsFromStdin } from "../../utils/bulk.ts"
import {
Expand Down Expand Up @@ -43,7 +44,10 @@ export const createCommand = new Command()
.option("-t, --title <title:string>", "Document title (required)")
.option("-c, --content <content:string>", "Markdown content (inline)")
.option("-f, --content-file <path:string>", "Read content from file")
.option("--project <project:string>", "Attach to project (slug or ID)")
.option(
"--project <project:string>",
"Attach to project (UUID, slug ID, or name)",
)
.option("--issue <issue:string>", "Attach to issue (identifier like TC-123)")
.option("--icon <icon:string>", "Document icon (emoji)")
.option("-i, --interactive", "Interactive mode with prompts")
Expand Down Expand Up @@ -146,12 +150,7 @@ export const createCommand = new Command()
// Resolve project ID if provided
let projectId: string | undefined
if (project) {
projectId = await resolveProjectId(client, project)
if (!projectId) {
throw new NotFoundError("Project", project, {
suggestion: "Provide a valid project slug or ID.",
})
}
projectId = await resolveProjectId(project)
}

// Resolve issue ID if provided
Expand Down Expand Up @@ -273,15 +272,9 @@ async function promptInteractiveCreate(): Promise<{

if (attachTo === "project") {
const projectInput = await Input.prompt({
message: "Project slug or ID",
message: "Project (UUID, slug ID, or name)",
})
const client = getGraphQLClient()
projectId = await resolveProjectId(client, projectInput)
if (!projectId) {
throw new NotFoundError("Project", projectInput, {
suggestion: "Provide a valid project slug or ID.",
})
}
projectId = await resolveProjectId(projectInput)
} else if (attachTo === "issue") {
const issueInput = await Input.prompt({
message: "Issue identifier (e.g., TC-123)",
Expand All @@ -304,58 +297,6 @@ async function promptInteractiveCreate(): Promise<{
}
}

async function resolveProjectId(
// deno-lint-ignore no-explicit-any
client: any,
projectInput: string,
): Promise<string | undefined> {
// First try to get by slug/ID directly
const projectQuery = gql(`
query GetProjectForDocument($slugId: String!) {
project(id: $slugId) {
id
name
}
}
`)

try {
const result = await client.request(projectQuery, { slugId: projectInput })
if (result.project) {
return result.project.id
}
} catch {
// Project not found by ID, try searching by name
}

// Search by name
const searchQuery = gql(`
query SearchProjectsForDocument($filter: ProjectFilter) {
projects(filter: $filter, first: 1) {
nodes {
id
name
}
}
}
`)

try {
const result = await client.request(searchQuery, {
filter: {
name: { containsIgnoreCase: projectInput },
},
})
if (result.projects.nodes.length > 0) {
return result.projects.nodes[0].id
}
} catch {
// Search failed
}

return undefined
}

async function resolveIssueId(
// deno-lint-ignore no-explicit-any
client: any,
Expand Down
Loading
Loading