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
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,19 @@ linear issue comment add ENG-123 -a ./screenshot.png --public # public image U
linear team list # list teams
linear team id # print out the team id (e.g. for scripts)
linear team members # list team members
linear team members --all --json # include inactive members, as JSON
linear team create # create a new team
linear team autolinks # configure GitHub repository autolinks for Linear issues
```

### user commands

```bash
linear user list # list everyone in the workspace
linear user list --all # include deactivated members
linear user list --json # machine-readable output
```

### project commands

```bash
Expand Down
4 changes: 4 additions & 0 deletions skills/linear-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ linear team id
linear team list
linear team members
linear team states

linear user
linear user list
```

## Reference Documentation
Expand All @@ -187,6 +190,7 @@ linear team states
- [project-update](references/project-update.md) - Manage project status updates
- [schema](references/schema.md) - Print the GraphQL schema to stdout
- [team](references/team.md) - Manage Linear teams
- [user](references/user.md) - Manage Linear users

For curated examples of organization features (initiatives, labels, projects, bulk operations), see [organization-features](references/organization-features.md).

Expand Down
1 change: 1 addition & 0 deletions skills/linear-cli/references/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- [project-update](./project-update.md) - Manage project status updates
- [schema](./schema.md) - Print the GraphQL schema to stdout
- [team](./team.md) - Manage Linear teams
- [user](./user.md) - Manage Linear users

## Quick Reference

Expand Down
3 changes: 2 additions & 1 deletion skills/linear-cli/references/team.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ Options:

-h, --help - Show this help.
--workspace <slug> - Target workspace (uses credentials)
-a, --all - Include inactive members
-a, --all - Include inactive members
-j, --json - Output as JSON
```

### states
Expand Down
43 changes: 43 additions & 0 deletions skills/linear-cli/references/user.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# user

> Manage Linear users

## Usage

```
Usage: linear user

Description:

Manage Linear users

Options:

-h, --help - Show this help.
--workspace <slug> - Target workspace (uses credentials)

Commands:

list - List members of the workspace
```

## Subcommands

### list

> List members of the workspace

```
Usage: linear user list

Description:

List members of the workspace

Options:

-h, --help - Show this help.
--workspace <slug> - Target workspace (uses credentials)
-a, --all - Include inactive members
-j, --json - Output as JSON
```
3 changes: 3 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import denoConfig from "../deno.json" with { type: "json" }
import { authCommand } from "./commands/auth/auth.ts"
import { issueCommand } from "./commands/issue/issue.ts"
import { teamCommand } from "./commands/team/team.ts"
import { userCommand } from "./commands/user/user.ts"
import { projectCommand } from "./commands/project/project.ts"
import { projectUpdateCommand } from "./commands/project-update/project-update.ts"
import { cycleCommand } from "./commands/cycle/cycle.ts"
Expand Down Expand Up @@ -48,6 +49,8 @@ Environment Variables:
.alias("i")
.command("team", teamCommand)
.alias("t")
.command("user", userCommand)
.alias("u")
.command("project", projectCommand)
.alias("p")
.command("project-update", projectUpdateCommand)
Expand Down
78 changes: 35 additions & 43 deletions src/commands/team/team-members.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { Command } from "@cliffy/command"
import { getTeamKey, getTeamMembers } from "../../utils/linear.ts"
import { printMembers } from "../../utils/member-display.ts"
import { shouldShowSpinner } from "../../utils/hyperlink.ts"
import { handleError, ValidationError } from "../../utils/errors.ts"

export const membersCommand = new Command()
.name("members")
.description("List team members")
.arguments("[teamKey:string]")
.option("-a, --all", "Include inactive members")
.action(async (options, teamKey?: string) => {
.option("-j, --json", "Output as JSON")
.action(async ({ all, json }, teamKey?: string) => {
const showSpinner = !json && shouldShowSpinner()
let spinner: { start: () => void; stop: () => void } | null = null

try {
const resolvedTeamKey = teamKey || getTeamKey()
if (!resolvedTeamKey) {
Expand All @@ -17,60 +23,46 @@ export const membersCommand = new Command()
)
}

const members = await getTeamMembers(resolvedTeamKey)
if (showSpinner) {
const { Spinner } = await import("@std/cli/unstable-spinner")
spinner = new Spinner()
spinner.start()
}

if (members.length === 0) {
console.log("No members found for this team.")
const includeDisabled = all === true
const { nodes, pageInfo } = await getTeamMembers(
resolvedTeamKey,
includeDisabled,
)

spinner?.stop()

// --json is an output format, not a raw dump: it must respect --all just
// as the human output does.
const members = includeDisabled
? nodes
: nodes.filter((member) => member.active)

if (json) {
console.log(JSON.stringify({ nodes: members, pageInfo }, null, 2))
return
}

const filteredMembers = options.all
? members
: members.filter((member) => member.active)
if (nodes.length === 0) {
console.log("No members found for this team.")
return
}

if (filteredMembers.length === 0) {
if (members.length === 0) {
console.log(
"No active members found for this team. Use --all to include inactive members.",
)
return
}

console.log(`Team Members (${filteredMembers.length}):`)
console.log("")

for (const member of filteredMembers) {
const status = member.active ? "" : " (inactive)"
const guestStatus = member.guest ? " (guest)" : ""
const assignableStatus = !member.isAssignable ? " (not assignable)" : ""
const displayName = member.displayName || member.name
const fullName = member.name !== member.displayName
? ` (${member.name})`
: ""

console.log(
`${displayName}${fullName} [${member.initials}]${status}${guestStatus}${assignableStatus}`,
)
if (member.email) {
console.log(` Email: ${member.email}`)
}
if (member.description) {
console.log(` Role: ${member.description}`)
}
if (member.timezone) {
console.log(` Timezone: ${member.timezone}`)
}
if (member.statusEmoji && member.statusLabel) {
console.log(
` Status: ${member.statusEmoji} ${member.statusLabel}`,
)
}
if (member.lastSeen) {
const lastSeenDate = new Date(member.lastSeen)
console.log(` Last seen: ${lastSeenDate.toLocaleString()}`)
}
console.log("")
}
printMembers(members, "Team Members")
} catch (error) {
spinner?.stop()
handleError(error, "Failed to fetch team members")
}
})
54 changes: 54 additions & 0 deletions src/commands/user/user-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Command } from "@cliffy/command"
import { getOrganizationMembers } from "../../utils/linear.ts"
import { printMembers } from "../../utils/member-display.ts"
import { shouldShowSpinner } from "../../utils/hyperlink.ts"
import { handleError } from "../../utils/errors.ts"

export const listCommand = new Command()
.name("list")
.description("List members of the workspace")
.option("-a, --all", "Include inactive members")
.option("-j, --json", "Output as JSON")
.action(async ({ all, json }) => {
const showSpinner = !json && shouldShowSpinner()
let spinner: { start: () => void; stop: () => void } | null = null

try {
if (showSpinner) {
const { Spinner } = await import("@std/cli/unstable-spinner")
spinner = new Spinner()
spinner.start()
}

const includeDisabled = all === true
const { nodes, pageInfo } = await getOrganizationMembers(includeDisabled)

spinner?.stop()

const members = includeDisabled
? nodes
: nodes.filter((member) => member.active)

if (json) {
console.log(JSON.stringify({ nodes: members, pageInfo }, null, 2))
return
}

if (nodes.length === 0) {
console.log("No members found in this workspace.")
return
}

if (members.length === 0) {
console.log(
"No active members found in this workspace. Use --all to include inactive members.",
)
return
}

printMembers(members, "Workspace Members")
} catch (error) {
spinner?.stop()
handleError(error, "Failed to fetch workspace members")
}
})
10 changes: 10 additions & 0 deletions src/commands/user/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Command } from "@cliffy/command"

import { listCommand } from "./user-list.ts"

export const userCommand = new Command()
.description("Manage Linear users")
.action(function () {
this.showHelp()
})
.command("list", listCommand)
Loading
Loading