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
6 changes: 6 additions & 0 deletions .linear.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# linear cli
# https://github.com/schpet/linear-cli

workspace = "schpet"
team_id = "CLI"
issue_sort = "priority"
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,20 @@

## [Unreleased]

### Fixed

- add TTY checks before interactive prompts to prevent hanging in non-interactive mode

### Added

- global user config is now merged with project config (`~/.config/linear/linear.toml` on Unix, `%APPDATA%\linear\linear.toml` on Windows); project values override global, env vars override both
- requests now include a User-Agent header (schpet-linear-cli/VERSION)
- initiative management commands (list, view, create, archive, unarchive, update, delete, add-project, remove-project) ([#95](https://github.com/schpet/linear-cli/pull/95); thanks [@skgbafa](https://github.com/skgbafa))
- label management commands (list, create, delete) ([#95](https://github.com/schpet/linear-cli/pull/95); thanks [@skgbafa](https://github.com/skgbafa))
- project create command with team, lead, dates, status, and initiative linking ([#95](https://github.com/schpet/linear-cli/pull/95); thanks [@skgbafa](https://github.com/skgbafa))
- team delete command ([#95](https://github.com/schpet/linear-cli/pull/95); thanks [@skgbafa](https://github.com/skgbafa))
- bulk operations support for issue delete (--bulk flag) ([#95](https://github.com/schpet/linear-cli/pull/95); thanks [@skgbafa](https://github.com/skgbafa))
- document management commands (list, view, create, update, delete) ([#95](https://github.com/schpet/linear-cli/pull/95); thanks [@skgbafa](https://github.com/skgbafa))

## [1.7.0] - 2026-01-09

Expand Down
8 changes: 8 additions & 0 deletions src/commands/document/document-delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ async function handleSingleDelete(

// Confirm deletion
if (!yes) {
if (!Deno.stdin.isTerminal()) {
console.error("Interactive confirmation required. Use --yes to skip.")
Deno.exit(1)
}
const confirmed = await Confirm.prompt({
message: `Are you sure you want to delete "${document.title}"?`,
default: false,
Expand Down Expand Up @@ -161,6 +165,10 @@ async function handleBulkDelete(

// Confirm bulk operation
if (!yes) {
if (!Deno.stdin.isTerminal()) {
console.error("Interactive confirmation required. Use --yes to skip.")
Deno.exit(1)
}
const confirmed = await Confirm.prompt({
message: `Delete ${ids.length} document(s)?`,
default: false,
Expand Down
8 changes: 8 additions & 0 deletions src/commands/initiative/initiative-archive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ async function handleSingleArchive(

// Confirm archival
if (!force) {
if (!Deno.stdin.isTerminal()) {
console.error("Interactive confirmation required. Use --force to skip.")
Deno.exit(1)
}
const confirmed = await Confirm.prompt({
message: `Archive initiative "${initiative.name}"?`,
default: true,
Expand Down Expand Up @@ -182,6 +186,10 @@ async function handleBulkArchive(

// Confirm bulk operation
if (!force) {
if (!Deno.stdin.isTerminal()) {
console.error("Interactive confirmation required. Use --force to skip.")
Deno.exit(1)
}
const confirmed = await Confirm.prompt({
message: `Archive ${ids.length} initiative(s)?`,
default: false,
Expand Down
8 changes: 8 additions & 0 deletions src/commands/initiative/initiative-delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ async function handleSingleDelete(

// Confirm deletion with typed confirmation for safety
if (!force) {
if (!Deno.stdin.isTerminal()) {
console.error("Interactive confirmation required. Use --force to skip.")
Deno.exit(1)
}
console.log(`\n⚠️ This action is PERMANENT and cannot be undone.\n`)

const confirmed = await Confirm.prompt({
Expand Down Expand Up @@ -203,6 +207,10 @@ async function handleBulkDelete(

// Confirm bulk operation
if (!force) {
if (!Deno.stdin.isTerminal()) {
console.error("Interactive confirmation required. Use --force to skip.")
Deno.exit(1)
}
const confirmed = await Confirm.prompt({
message: `Permanently delete ${ids.length} initiative(s)?`,
default: false,
Expand Down
6 changes: 6 additions & 0 deletions src/commands/initiative/initiative-remove-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,12 @@ export const removeProjectCommand = new Command()

// Confirm removal
if (!force) {
if (!Deno.stdin.isTerminal()) {
console.error(
"Interactive confirmation required. Use --force to skip.",
)
Deno.exit(1)
}
const confirmed = await Confirm.prompt({
message:
`Remove "${project.name}" from initiative "${initiative.name}"?`,
Expand Down
4 changes: 4 additions & 0 deletions src/commands/initiative/initiative-unarchive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ export const unarchiveCommand = new Command()

// Confirm unarchive
if (!force) {
if (!Deno.stdin.isTerminal()) {
console.error("Interactive confirmation required. Use --force to skip.")
Deno.exit(1)
}
const confirmed = await Confirm.prompt({
message: `Are you sure you want to unarchive "${initiative.name}"?`,
default: true,
Expand Down
8 changes: 8 additions & 0 deletions src/commands/issue/issue-delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ async function handleSingleDelete(

// Show confirmation prompt unless --confirm flag is used
if (!confirm) {
if (!Deno.stdin.isTerminal()) {
console.error("Interactive confirmation required. Use --confirm to skip.")
Deno.exit(1)
}
const confirmed = await Confirm.prompt({
message: `Are you sure you want to delete "${identifier}: ${title}"?`,
default: false,
Expand Down Expand Up @@ -167,6 +171,10 @@ async function handleBulkDelete(

// Confirm bulk operation
if (!confirm) {
if (!Deno.stdin.isTerminal()) {
console.error("Interactive confirmation required. Use --confirm to skip.")
Deno.exit(1)
}
const confirmed = await Confirm.prompt({
message: `Delete ${ids.length} issue(s)?`,
default: false,
Expand Down
12 changes: 11 additions & 1 deletion src/commands/label/label-delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,13 @@ async function resolveLabelId(
}

// If multiple labels with same name exist, let user choose
if (labels.length > 1 && Deno.stdout.isTerminal()) {
if (labels.length > 1) {
if (!Deno.stdin.isTerminal()) {
console.error(
`Multiple labels named "${nameOrId}" found. Use --team to disambiguate.`,
)
Deno.exit(1)
}
const options = labels.map((l) => ({
name: `${l.name} (${l.team?.key || "Workspace"}) - ${l.color}`,
value: l.id,
Expand Down Expand Up @@ -154,6 +160,10 @@ export const deleteCommand = new Command()

// Confirmation prompt unless --force is used
if (!force) {
if (!Deno.stdin.isTerminal()) {
console.error("Interactive confirmation required. Use --force to skip.")
Deno.exit(1)
}
const confirmed = await Confirm.prompt({
message: `Are you sure you want to delete label "${labelDisplay}"?`,
default: false,
Expand Down
4 changes: 4 additions & 0 deletions src/commands/milestone/milestone-delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ export const deleteCommand = new Command()
.action(async ({ force }, id) => {
// Confirmation prompt unless --force is used
if (!force) {
if (!Deno.stdin.isTerminal()) {
console.error("Interactive confirmation required. Use --force to skip.")
Deno.exit(1)
}
const confirmed = await Confirm.prompt({
message: `Are you sure you want to delete milestone ${id}?`,
default: false,
Expand Down
11 changes: 11 additions & 0 deletions src/commands/team/team-delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ export const deleteCommand = new Command()
"You must move these issues to another team before deletion.\n",
)

if (!Deno.stdin.isTerminal()) {
console.error(
"Interactive selection required. Use --move-issues <teamKey> to specify target team.",
)
Deno.exit(1)
}

const allTeams = await getAllTeams()
const otherTeams = allTeams.filter((t) => t.id !== teamId)

Expand Down Expand Up @@ -118,6 +125,10 @@ export const deleteCommand = new Command()

// Confirm deletion
if (!force) {
if (!Deno.stdin.isTerminal()) {
console.error("Interactive confirmation required. Use --force to skip.")
Deno.exit(1)
}
const confirmed = await Confirm.prompt({
message:
`Are you sure you want to delete team "${team.key}: ${team.name}"?`,
Expand Down
49 changes: 0 additions & 49 deletions test/commands/document/__snapshots__/document-create.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -70,52 +70,3 @@ stderr:
"
`;

snapshot[`Document Create Command - API Error 1`] = `
stdout:
""
stderr:
\`Failed to create document: Error: You don't have permission to create documents: {"response":{"errors":[{"message":"You don't have permission to create documents","extensions":{"code":"FORBIDDEN"}}],"status":200,"headers":{}},"request":{"query":"mutation CreateDocument(\$input: DocumentCreateInput!) {\\\\n documentCreate(input: \$input) {\\\\n success\\\\n document {\\\\n id\\\\n slugId\\\\n title\\\\n url\\\\n }\\\\n }\\\\n}","variables":{"input":{"title":"Test Doc","content":"# Test"}}}}
at runRequest (file:///Users/samgbafa/Library/Caches/deno/npm/registry.npmjs.org/graphql-request/7.2.0/src/legacy/helpers/runRequest.ts:105:12)
at eventLoopTick (ext:core/01_core.js:187:7)
at async GraphQLClient.request (file:///Users/samgbafa/Library/Caches/deno/npm/registry.npmjs.org/graphql-request/7.2.0/src/legacy/classes/GraphQLClient.ts:131:22)
at async createDocument (file:///Users/samgbafa/Documents/github/tinycloud/development/external/linear-cli/src/commands/document/document-create.ts:385:20)
at async Command.actionHandler (file:///Users/samgbafa/Documents/github/tinycloud/development/external/linear-cli/src/commands/document/document-create.ts:176:7)
at async Command.execute (https://jsr.io/@cliffy/command/1.0.0-rc.8/command.ts:1940:7)
at async Command.parseCommand (https://jsr.io/@cliffy/command/1.0.0-rc.8/command.ts:1772:14)
at async Object.fn (file:///Users/samgbafa/Documents/github/tinycloud/development/external/linear-cli/test/commands/document/document-create.test.ts:284:7)
at async runTest (https://jsr.io/@cliffy/testing/1.0.0-rc.8/snapshot.ts:263:5)
at async snapshotTest (https://jsr.io/@cliffy/testing/1.0.0-rc.8/snapshot.ts:112:5) {
response: {
data: undefined,
errors: [
{
message: "You don't have permission to create documents",
extensions: { code: "FORBIDDEN" }
}
],
extensions: undefined,
status: 200,
headers: Headers {
"access-control-allow-origin": "*",
"content-type": "application/json",
date: "Mon, 01 Jan 2024 00:00:00 GMT",
vary: "Accept-Encoding"
}
},
request: {
query: "mutation CreateDocument(\$input: DocumentCreateInput!) {\\\\n" +
" documentCreate(input: \$input) {\\\\n" +
" success\\\\n" +
" document {\\\\n" +
" id\\\\n" +
" slugId\\\\n" +
" title\\\\n" +
" url\\\\n" +
" }\\\\n" +
" }\\\\n" +
"}",
variables: { input: { title: "Test Doc", content: "# Test" } }
}
}
\`
`;
44 changes: 0 additions & 44 deletions test/commands/document/__snapshots__/document-delete.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -49,50 +49,6 @@ stderr:
""
`;

snapshot[`Document Delete Command - Permission Error 1`] = `
stdout:
""
stderr:
\`Failed to delete document: Error: You don't have permission to delete this document: {"response":{"errors":[{"message":"You don't have permission to delete this document","extensions":{"code":"FORBIDDEN"}}],"status":200,"headers":{}},"request":{"query":"mutation DeleteDocument(\$id: String!) {\\\\n documentDelete(id: \$id) {\\\\n success\\\\n }\\\\n}","variables":{"id":"doc-uuid-123"}}}
at runRequest (file:///Users/samgbafa/Library/Caches/deno/npm/registry.npmjs.org/graphql-request/7.2.0/src/legacy/helpers/runRequest.ts:105:12)
at eventLoopTick (ext:core/01_core.js:187:7)
at async GraphQLClient.request (file:///Users/samgbafa/Library/Caches/deno/npm/registry.npmjs.org/graphql-request/7.2.0/src/legacy/classes/GraphQLClient.ts:131:22)
at async handleSingleDelete (file:///Users/samgbafa/Documents/github/tinycloud/development/external/linear-cli/src/commands/document/document-delete.ts:121:20)
at async Command.actionHandler (file:///Users/samgbafa/Documents/github/tinycloud/development/external/linear-cli/src/commands/document/document-delete.ts:60:7)
at async Command.execute (https://jsr.io/@cliffy/command/1.0.0-rc.8/command.ts:1940:7)
at async Command.parseCommand (https://jsr.io/@cliffy/command/1.0.0-rc.8/command.ts:1772:14)
at async Object.fn (file:///Users/samgbafa/Documents/github/tinycloud/development/external/linear-cli/test/commands/document/document-delete.test.ts:215:7)
at async runTest (https://jsr.io/@cliffy/testing/1.0.0-rc.8/snapshot.ts:263:5)
at async snapshotTest (https://jsr.io/@cliffy/testing/1.0.0-rc.8/snapshot.ts:112:5) {
response: {
data: undefined,
errors: [
{
message: "You don't have permission to delete this document",
extensions: { code: "FORBIDDEN" }
}
],
extensions: undefined,
status: 200,
headers: Headers {
"access-control-allow-origin": "*",
"content-type": "application/json",
date: "Mon, 01 Jan 2024 00:00:00 GMT",
vary: "Accept-Encoding"
}
},
request: {
query: "mutation DeleteDocument(\$id: String!) {\\\\n" +
" documentDelete(id: \$id) {\\\\n" +
" success\\\\n" +
" }\\\\n" +
"}",
variables: { id: "doc-uuid-123" }
}
}
\`
`;

snapshot[`Document Delete Command - Missing ID 1`] = `
stdout:
""
Expand Down
28 changes: 0 additions & 28 deletions test/commands/document/__snapshots__/document-list.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -22,34 +22,6 @@ stderr:
""
`;

snapshot[`Document List Command - List All Documents 1`] = `
stdout:
"SLUG TITLE ATTACHMENT UPDATED
d4b93e3b2695 Delegation System Spec TinyCloud SDK 2 days ago
25a3c439c040 Refresh Token Design Doc - 3 days ago
"
stderr:
""
`;

snapshot[`Document List Command - Filter By Project 1`] = `
stdout:
"SLUG TITLE ATTACHMENT UPDATED
d4b93e3b2695 Delegation System Spec TinyCloud SDK 2 days ago
"
stderr:
""
`;

snapshot[`Document List Command - Filter By Issue 1`] = `
stdout:
"SLUG TITLE ATTACHMENT UPDATED
abc123def456 Investigation Notes TC-123 4 days ago
"
stderr:
""
`;

snapshot[`Document List Command - JSON Output 1`] = `
stdout:
'[
Expand Down
Loading