@dabble/linear-cli 1.1.1 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/linear.mjs CHANGED
@@ -12,9 +12,12 @@ import { exec, execSync } from 'child_process';
12
12
 
13
13
  const API_URL = 'https://api.linear.app/graphql';
14
14
  let CONFIG_FILE = '';
15
+ let AUTH_CONFIG_FILE = '';
15
16
  let LINEAR_API_KEY = '';
16
17
  let TEAM_KEY = '';
17
18
  let ALIASES = {};
19
+ let DEFAULT_PROJECT = '';
20
+ let DEFAULT_MILESTONE = '';
18
21
 
19
22
  // Colors (ANSI)
20
23
  const colors = {
@@ -34,16 +37,16 @@ function loadConfig() {
34
37
  const localPath = join(process.cwd(), '.linear');
35
38
  const globalPath = join(homedir(), '.linear');
36
39
 
37
- // Priority: ./.linear > ~/.linear > env vars
38
- if (existsSync(localPath)) {
39
- CONFIG_FILE = localPath;
40
- } else if (existsSync(globalPath)) {
41
- CONFIG_FILE = globalPath;
42
- }
40
+ // Layer: read global first, then local on top (local values override global)
41
+ const filesToLoad = [];
42
+ if (existsSync(globalPath)) filesToLoad.push(globalPath);
43
+ if (existsSync(localPath)) filesToLoad.push(localPath);
43
44
 
44
- // Load from config file first (highest priority)
45
- if (CONFIG_FILE) {
46
- const content = readFileSync(CONFIG_FILE, 'utf-8');
45
+ // CONFIG_FILE is used for local/project writes (open/close) — always local
46
+ CONFIG_FILE = localPath;
47
+
48
+ for (const filePath of filesToLoad) {
49
+ const content = readFileSync(filePath, 'utf-8');
47
50
  let inAliasSection = false;
48
51
 
49
52
  for (const line of content.split('\n')) {
@@ -64,11 +67,17 @@ function loadConfig() {
64
67
  const value = rest.join('=').trim();
65
68
 
66
69
  if (inAliasSection) {
67
- // Store aliases with uppercase keys
70
+ // Store aliases with uppercase keys (later files override)
68
71
  ALIASES[key.trim().toUpperCase()] = value;
69
72
  } else {
70
- if (key.trim() === 'api_key') LINEAR_API_KEY = value;
71
- if (key.trim() === 'team') TEAM_KEY = value;
73
+ const k = key.trim();
74
+ if (k === 'api_key') {
75
+ LINEAR_API_KEY = value;
76
+ AUTH_CONFIG_FILE = filePath;
77
+ }
78
+ if (k === 'team') TEAM_KEY = value;
79
+ if (k === 'project') DEFAULT_PROJECT = value;
80
+ if (k === 'milestone') DEFAULT_MILESTONE = value;
72
81
  }
73
82
  }
74
83
  }
@@ -76,6 +85,8 @@ function loadConfig() {
76
85
  // Fall back to env vars if not set by config file
77
86
  if (!LINEAR_API_KEY) LINEAR_API_KEY = process.env.LINEAR_API_KEY || '';
78
87
  if (!TEAM_KEY) TEAM_KEY = process.env.LINEAR_TEAM || '';
88
+ if (!DEFAULT_PROJECT) DEFAULT_PROJECT = process.env.LINEAR_PROJECT || '';
89
+ if (!DEFAULT_MILESTONE) DEFAULT_MILESTONE = process.env.LINEAR_MILESTONE || '';
79
90
  }
80
91
 
81
92
  function resolveAlias(nameOrAlias) {
@@ -83,13 +94,39 @@ function resolveAlias(nameOrAlias) {
83
94
  return ALIASES[nameOrAlias.toUpperCase()] || nameOrAlias;
84
95
  }
85
96
 
86
- function saveAlias(code, name) {
87
- if (!CONFIG_FILE) {
88
- console.error(colors.red('Error: No config file found. Run "linear login" first.'));
97
+ async function ensureAuthConfig() {
98
+ if (AUTH_CONFIG_FILE) return;
99
+
100
+ console.log('No config file found. Where should aliases be saved?\n');
101
+ console.log(' 1. This project only (./.linear)');
102
+ console.log(' 2. Global, for all projects (~/.linear)');
103
+ console.log('');
104
+
105
+ const choice = await prompt('Enter number: ');
106
+ if (choice !== '1' && choice !== '2') {
107
+ console.error(colors.red('Error: Please enter 1 or 2'));
89
108
  process.exit(1);
90
109
  }
91
110
 
92
- const content = readFileSync(CONFIG_FILE, 'utf-8');
111
+ if (choice === '1') {
112
+ AUTH_CONFIG_FILE = join(process.cwd(), '.linear');
113
+ if (!existsSync(AUTH_CONFIG_FILE)) {
114
+ writeFileSync(AUTH_CONFIG_FILE, '');
115
+ ensureGitignore();
116
+ }
117
+ } else {
118
+ AUTH_CONFIG_FILE = join(homedir(), '.linear');
119
+ if (!existsSync(AUTH_CONFIG_FILE)) {
120
+ writeFileSync(AUTH_CONFIG_FILE, '');
121
+ }
122
+ }
123
+ console.log('');
124
+ }
125
+
126
+ async function saveAlias(code, name) {
127
+ await ensureAuthConfig();
128
+
129
+ const content = readFileSync(AUTH_CONFIG_FILE, 'utf-8');
93
130
  const lines = content.split('\n');
94
131
 
95
132
  // Find or create [aliases] section
@@ -136,12 +173,12 @@ function saveAlias(code, name) {
136
173
  lines.push(aliasLine);
137
174
  }
138
175
 
139
- writeFileSync(CONFIG_FILE, lines.join('\n'));
176
+ writeFileSync(AUTH_CONFIG_FILE, lines.join('\n'));
140
177
  ALIASES[upperCode] = name;
141
178
  }
142
179
 
143
- function removeAlias(code) {
144
- if (!CONFIG_FILE) {
180
+ async function removeAlias(code) {
181
+ if (!AUTH_CONFIG_FILE) {
145
182
  console.error(colors.red('Error: No config file found.'));
146
183
  process.exit(1);
147
184
  }
@@ -152,7 +189,7 @@ function removeAlias(code) {
152
189
  process.exit(1);
153
190
  }
154
191
 
155
- const content = readFileSync(CONFIG_FILE, 'utf-8');
192
+ const content = readFileSync(AUTH_CONFIG_FILE, 'utf-8');
156
193
  const lines = content.split('\n');
157
194
  let inAliasSection = false;
158
195
 
@@ -175,10 +212,100 @@ function removeAlias(code) {
175
212
  return true;
176
213
  });
177
214
 
178
- writeFileSync(CONFIG_FILE, newLines.join('\n'));
215
+ writeFileSync(AUTH_CONFIG_FILE, newLines.join('\n'));
179
216
  delete ALIASES[upperCode];
180
217
  }
181
218
 
219
+ function ensureLocalConfig() {
220
+ // CONFIG_FILE always points to local ./.linear
221
+ if (existsSync(CONFIG_FILE)) return;
222
+
223
+ // Create local config file
224
+ writeFileSync(CONFIG_FILE, '');
225
+
226
+ // Add .linear to .gitignore
227
+ ensureGitignore();
228
+ }
229
+
230
+ function ensureGitignore() {
231
+ const gitignorePath = join(process.cwd(), '.gitignore');
232
+ try {
233
+ let gitignore = '';
234
+ if (existsSync(gitignorePath)) {
235
+ gitignore = readFileSync(gitignorePath, 'utf-8');
236
+ }
237
+
238
+ const lines = gitignore.split('\n').map(l => l.trim());
239
+ if (!lines.includes('.linear')) {
240
+ const newline = gitignore.endsWith('\n') || gitignore === '' ? '' : '\n';
241
+ writeFileSync(gitignorePath, gitignore + newline + '.linear\n');
242
+ console.log(colors.green(`Added .linear to .gitignore`));
243
+ }
244
+ } catch (err) {
245
+ // Silently ignore if we can't update .gitignore
246
+ }
247
+ }
248
+
249
+ function writeConfigValue(key, value) {
250
+ ensureLocalConfig();
251
+
252
+ const content = readFileSync(CONFIG_FILE, 'utf-8');
253
+ const lines = content.split('\n');
254
+ let found = false;
255
+ let insertBefore = -1;
256
+
257
+ for (let i = 0; i < lines.length; i++) {
258
+ const trimmed = lines[i].trim();
259
+ // Stop before section headers
260
+ if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
261
+ if (insertBefore === -1) insertBefore = i;
262
+ break;
263
+ }
264
+ if (!trimmed || trimmed.startsWith('#')) continue;
265
+ const [k] = trimmed.split('=');
266
+ if (k.trim() === key) {
267
+ lines[i] = `${key}=${value}`;
268
+ found = true;
269
+ break;
270
+ }
271
+ }
272
+
273
+ if (!found) {
274
+ const newLine = `${key}=${value}`;
275
+ if (insertBefore !== -1) {
276
+ lines.splice(insertBefore, 0, newLine);
277
+ } else {
278
+ lines.push(newLine);
279
+ }
280
+ }
281
+
282
+ writeFileSync(CONFIG_FILE, lines.join('\n'));
283
+ }
284
+
285
+ function removeConfigValue(key) {
286
+ if (!existsSync(CONFIG_FILE)) return null;
287
+
288
+ const content = readFileSync(CONFIG_FILE, 'utf-8');
289
+ const lines = content.split('\n');
290
+ let removed = null;
291
+
292
+ const newLines = lines.filter(line => {
293
+ const trimmed = line.trim();
294
+ if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('[')) return true;
295
+ const [k, ...rest] = trimmed.split('=');
296
+ if (k.trim() === key) {
297
+ removed = rest.join('=').trim();
298
+ return false;
299
+ }
300
+ return true;
301
+ });
302
+
303
+ if (removed !== null) {
304
+ writeFileSync(CONFIG_FILE, newLines.join('\n'));
305
+ }
306
+ return removed;
307
+ }
308
+
182
309
  function checkAuth() {
183
310
  if (!LINEAR_API_KEY) {
184
311
  console.error(colors.red("Error: Not logged in. Run 'linear login' first."));
@@ -325,6 +452,8 @@ async function cmdIssues(args) {
325
452
  status: 'array', s: 'array',
326
453
  project: 'string', p: 'string',
327
454
  milestone: 'string',
455
+ 'no-project': 'boolean',
456
+ 'no-milestone': 'boolean',
328
457
  label: 'array', l: 'array',
329
458
  priority: 'string',
330
459
  });
@@ -334,8 +463,10 @@ async function cmdIssues(args) {
334
463
  const openOnly = opts.open || opts.o;
335
464
  const mineOnly = opts.mine || opts.m;
336
465
  const statusFilter = opts.status || opts.s || [];
337
- const projectFilter = opts.project || opts.p;
338
- const milestoneFilter = opts.milestone;
466
+ const noProject = opts['no-project'];
467
+ const noMilestone = opts['no-milestone'];
468
+ const projectFilter = noProject ? '' : (opts.project || opts.p || DEFAULT_PROJECT);
469
+ const milestoneFilter = noMilestone ? '' : (opts.milestone || DEFAULT_MILESTONE);
339
470
  const labelFilters = opts.label || opts.l || [];
340
471
  const priorityFilter = (opts.priority || '').toLowerCase();
341
472
 
@@ -408,6 +539,15 @@ async function cmdIssues(args) {
408
539
  // Check if any issues have priority set
409
540
  const hasPriority = issues.some(i => i.priority > 0);
410
541
 
542
+ // When filtering to a single project, drop the project column
543
+ const showProjectCol = !projectFilter;
544
+
545
+ // Build context string for header (e.g. "[My Project > Sprint 3]")
546
+ const contextParts = [];
547
+ if (projectFilter) contextParts.push(resolveAlias(projectFilter));
548
+ if (milestoneFilter) contextParts.push(resolveAlias(milestoneFilter));
549
+ const contextStr = contextParts.length > 0 ? ` [${contextParts.join(' > ')}]` : '';
550
+
411
551
  // Helper to format issue row
412
552
  const formatRow = (i) => {
413
553
  const row = [
@@ -419,7 +559,9 @@ async function cmdIssues(args) {
419
559
  const pri = PRIORITY_LABELS[i.priority] || '';
420
560
  row.push(pri ? colors.bold(pri) : '-');
421
561
  }
422
- row.push(i.project?.name || '-');
562
+ if (showProjectCol) {
563
+ row.push(i.project?.name || '-');
564
+ }
423
565
  if (hasAssignees) {
424
566
  const assignee = i.assignee?.id === viewerId ? 'you' : (i.assignee?.name || '-');
425
567
  row.push(assignee);
@@ -488,7 +630,7 @@ async function cmdIssues(args) {
488
630
 
489
631
  filtered = applyFilters(filtered);
490
632
 
491
- console.log(colors.bold('Unblocked Issues:\n'));
633
+ console.log(colors.bold(`Unblocked Issues${contextStr}:\n`));
492
634
  console.log(formatTable(filtered.map(formatRow)));
493
635
  } else if (allStates) {
494
636
  let filtered = issues;
@@ -497,7 +639,7 @@ async function cmdIssues(args) {
497
639
  }
498
640
  filtered = applyFilters(filtered);
499
641
 
500
- console.log(colors.bold('All Issues:\n'));
642
+ console.log(colors.bold(`All Issues${contextStr}:\n`));
501
643
  console.log(formatTable(filtered.map(formatRow)));
502
644
  } else if (openOnly) {
503
645
  let filtered = issues.filter(i =>
@@ -509,21 +651,21 @@ async function cmdIssues(args) {
509
651
 
510
652
  filtered = applyFilters(filtered);
511
653
 
512
- console.log(colors.bold('Open Issues:\n'));
654
+ console.log(colors.bold(`Open Issues${contextStr}:\n`));
513
655
  console.log(formatTable(filtered.map(formatRow)));
514
656
  } else if (resolvedStatusTypes.length > 0) {
515
657
  let filtered = filterByStatus(issues, resolvedStatusTypes);
516
658
  filtered = applyFilters(filtered);
517
659
 
518
660
  const label = statusFilter.join(' + ');
519
- console.log(colors.bold(`Issues (${label}):\n`));
661
+ console.log(colors.bold(`Issues${contextStr} (${label}):\n`));
520
662
  console.log(formatTable(filtered.map(formatRow)));
521
663
  } else {
522
664
  // Default: show backlog + todo
523
665
  let filtered = issues.filter(i => i.state.type === 'backlog' || i.state.type === 'unstarted');
524
666
  filtered = applyFilters(filtered);
525
667
 
526
- console.log(colors.bold('Issues (backlog + todo):\n'));
668
+ console.log(colors.bold(`Issues${contextStr} (backlog + todo):\n`));
527
669
  console.log(formatTable(filtered.map(formatRow)));
528
670
  }
529
671
  }
@@ -714,9 +856,9 @@ async function cmdIssueCreate(args) {
714
856
 
715
857
  const title = opts.title || opts.t || opts._[0];
716
858
  const description = opts.description || opts.d || '';
717
- const project = resolveAlias(opts.project || opts.p);
859
+ const project = resolveAlias(opts.project || opts.p) || DEFAULT_PROJECT;
718
860
  const priority = (opts.priority || '').toLowerCase();
719
- const milestone = resolveAlias(opts.milestone);
861
+ const milestone = resolveAlias(opts.milestone) || DEFAULT_MILESTONE;
720
862
  const parent = opts.parent;
721
863
  const shouldAssign = opts.assign;
722
864
  const estimate = (opts.estimate || opts.e || '').toLowerCase();
@@ -2350,7 +2492,7 @@ async function cmdAlias(args) {
2350
2492
 
2351
2493
  // Remove alias
2352
2494
  if (removeCode) {
2353
- removeAlias(removeCode);
2495
+ await removeAlias(removeCode);
2354
2496
  console.log(colors.green(`Removed alias: ${removeCode.toUpperCase()}`));
2355
2497
  return;
2356
2498
  }
@@ -2364,7 +2506,7 @@ async function cmdAlias(args) {
2364
2506
  process.exit(1);
2365
2507
  }
2366
2508
 
2367
- saveAlias(code, name);
2509
+ await saveAlias(code, name);
2368
2510
  console.log(colors.green(`Alias set: ${code.toUpperCase()} -> ${name}`));
2369
2511
  }
2370
2512
 
@@ -3093,25 +3235,7 @@ team=${selectedKey}
3093
3235
 
3094
3236
  // Add .linear to .gitignore if saving locally
3095
3237
  if (!saveGlobal) {
3096
- const gitignorePath = join(process.cwd(), '.gitignore');
3097
- try {
3098
- let gitignore = '';
3099
- if (existsSync(gitignorePath)) {
3100
- gitignore = readFileSync(gitignorePath, 'utf-8');
3101
- }
3102
-
3103
- // Check if .linear is already in .gitignore
3104
- const lines = gitignore.split('\n').map(l => l.trim());
3105
- if (!lines.includes('.linear')) {
3106
- // Add .linear to .gitignore
3107
- const newline = gitignore.endsWith('\n') || gitignore === '' ? '' : '\n';
3108
- const content = gitignore + newline + '.linear\n';
3109
- writeFileSync(gitignorePath, content);
3110
- console.log(colors.green(`Added .linear to .gitignore`));
3111
- }
3112
- } catch (err) {
3113
- // Silently ignore if we can't update .gitignore
3114
- }
3238
+ ensureGitignore();
3115
3239
 
3116
3240
  // Add .linear to .worktreeinclude for worktree support
3117
3241
  const worktreeIncludePath = join(process.cwd(), '.worktreeinclude');
@@ -3195,8 +3319,10 @@ ISSUES:
3195
3319
  --status, -s <name> Filter by status (repeatable: --status todo --status backlog)
3196
3320
  --all, -a Show all states (including completed)
3197
3321
  --mine, -m Show only issues assigned to you
3198
- --project, -p <name> Filter by project
3199
- --milestone <name> Filter by milestone
3322
+ --project, -p <name> Filter by project (default: open project)
3323
+ --milestone <name> Filter by milestone (default: open milestone)
3324
+ --no-project Bypass default project filter
3325
+ --no-milestone Bypass default milestone filter
3200
3326
  --label, -l <name> Filter by label (repeatable)
3201
3327
  --priority <level> Filter by priority (urgent/high/medium/low/none)
3202
3328
  issues reorder <ids...> Reorder issues by listing IDs in order
@@ -3247,6 +3373,8 @@ PROJECTS:
3247
3373
  --name, -n <name> Project name (required)
3248
3374
  --description, -d <desc> Project description
3249
3375
  project complete <name> Mark project as completed
3376
+ project open <name> Set default project for issues/create
3377
+ project close Clear default project
3250
3378
  project move <name> Move project in sort order
3251
3379
  --before <name> Move before this project
3252
3380
  --after <name> Move after this project
@@ -3264,6 +3392,8 @@ MILESTONES:
3264
3392
  --project, -p <name> Project (required)
3265
3393
  --description, -d <desc> Milestone description
3266
3394
  --target-date <date> Target date (YYYY-MM-DD)
3395
+ milestone open <name> Set default milestone for issues/create
3396
+ milestone close Clear default milestone
3267
3397
  milestone move <name> Move milestone in sort order
3268
3398
  --before <name> Move before this milestone
3269
3399
  --after <name> Move after this milestone
@@ -3300,17 +3430,27 @@ WORKFLOW:
3300
3430
  lnext() { eval "$(linear next "$@")"; }
3301
3431
 
3302
3432
  CONFIGURATION:
3303
- Config is loaded from ./.linear first, then ~/.linear, then env vars.
3433
+ Config is layered: ~/.linear (global) then ./.linear (local override).
3434
+ Local values override global; unset local values inherit from global.
3435
+ Env vars (LINEAR_API_KEY, LINEAR_TEAM, LINEAR_PROJECT, LINEAR_MILESTONE)
3436
+ are used as fallbacks when not set in any config file.
3304
3437
 
3305
3438
  File format:
3306
3439
  api_key=lin_api_xxx
3307
3440
  team=ISSUE
3441
+ project=My Project
3442
+ milestone=Sprint 3
3308
3443
 
3309
3444
  [aliases]
3310
3445
  LWW=Last-Write-Wins Support
3311
3446
  MVP=MVP Release
3312
3447
 
3313
3448
  EXAMPLES:
3449
+ linear project open "Phase 1" # Set default project context
3450
+ linear milestone open "Sprint 3" # Set default milestone context
3451
+ linear issues # Filtered to Phase 1 > Sprint 3
3452
+ linear issues --no-project # Bypass default, show all projects
3453
+ linear project close # Clear default project
3314
3454
  linear roadmap # See all projects and milestones
3315
3455
  linear issues --unblocked # Find workable issues
3316
3456
  linear issues --project "Phase 1" # Issues in a project
@@ -3393,6 +3533,29 @@ async function main() {
3393
3533
  case 'create': await cmdProjectCreate(subargs); break;
3394
3534
  case 'complete': await cmdProjectComplete(subargs); break;
3395
3535
  case 'move': await cmdProjectMove(subargs); break;
3536
+ case 'open': {
3537
+ const name = resolveAlias(subargs[0]);
3538
+ if (!name) {
3539
+ console.error(colors.red('Error: Project name required'));
3540
+ console.error('Usage: linear project open "Project Name"');
3541
+ process.exit(1);
3542
+ }
3543
+ writeConfigValue('project', name);
3544
+ DEFAULT_PROJECT = name;
3545
+ console.log(colors.green(`Opened project: ${name}`));
3546
+ console.log(colors.gray(`Saved to ${CONFIG_FILE}`));
3547
+ break;
3548
+ }
3549
+ case 'close': {
3550
+ const removed = removeConfigValue('project');
3551
+ if (removed) {
3552
+ console.log(colors.green(`Closed project: ${removed}`));
3553
+ DEFAULT_PROJECT = '';
3554
+ } else {
3555
+ console.log(colors.gray('No project was open'));
3556
+ }
3557
+ break;
3558
+ }
3396
3559
  default:
3397
3560
  console.error(`Unknown project command: ${subcmd}`);
3398
3561
  process.exit(1);
@@ -3417,6 +3580,29 @@ async function main() {
3417
3580
  case 'show': await cmdMilestoneShow(subargs); break;
3418
3581
  case 'create': await cmdMilestoneCreate(subargs); break;
3419
3582
  case 'move': await cmdMilestoneMove(subargs); break;
3583
+ case 'open': {
3584
+ const name = resolveAlias(subargs[0]);
3585
+ if (!name) {
3586
+ console.error(colors.red('Error: Milestone name required'));
3587
+ console.error('Usage: linear milestone open "Milestone Name"');
3588
+ process.exit(1);
3589
+ }
3590
+ writeConfigValue('milestone', name);
3591
+ DEFAULT_MILESTONE = name;
3592
+ console.log(colors.green(`Opened milestone: ${name}`));
3593
+ console.log(colors.gray(`Saved to ${CONFIG_FILE}`));
3594
+ break;
3595
+ }
3596
+ case 'close': {
3597
+ const removed = removeConfigValue('milestone');
3598
+ if (removed) {
3599
+ console.log(colors.green(`Closed milestone: ${removed}`));
3600
+ DEFAULT_MILESTONE = '';
3601
+ } else {
3602
+ console.log(colors.gray('No milestone was open'));
3603
+ }
3604
+ break;
3605
+ }
3420
3606
  default:
3421
3607
  console.error(`Unknown milestone command: ${subcmd}`);
3422
3608
  process.exit(1);
@@ -25,12 +25,14 @@ This will:
25
25
 
26
26
  ## Configuration
27
27
 
28
- Config is loaded in order: `./.linear` `~/.linear` env vars
28
+ Config is layered: `~/.linear` (global) is loaded first, then `./.linear` (local) overrides on top. Local values override global; unset local values inherit from global. Env vars (`LINEAR_API_KEY`, `LINEAR_TEAM`, `LINEAR_PROJECT`, `LINEAR_MILESTONE`) are used as fallbacks.
29
29
 
30
30
  ```
31
31
  # .linear file format
32
32
  api_key=lin_api_xxx
33
33
  team=ISSUE
34
+ project=My Project
35
+ milestone=Sprint 3
34
36
 
35
37
  [aliases]
36
38
  V2=Version 2.0 Release
@@ -83,8 +85,16 @@ linear whoami # Show current user/team
83
85
  # Roadmap (overview)
84
86
  linear roadmap # Projects with milestones and progress
85
87
 
88
+ # Default context (sets project/milestone for issues & create)
89
+ linear project open "Phase 1" # Set default project
90
+ linear milestone open "Sprint 3" # Set default milestone
91
+ linear project close # Clear default project
92
+ linear milestone close # Clear default milestone
93
+
86
94
  # Issues
87
- linear issues # Default: backlog + todo issues
95
+ linear issues # Default: backlog + todo issues (filtered by open project/milestone)
96
+ linear issues --no-project # Bypass default project filter
97
+ linear issues --no-milestone # Bypass default milestone filter
88
98
  linear issues --unblocked # Ready to work on (no blockers)
89
99
  linear issues --open # All non-completed issues
90
100
  linear issues --status todo # Only todo issues
@@ -123,11 +133,15 @@ linear projects --all # Include completed
123
133
  linear project show "Phase 1" # Details with issues
124
134
  linear project create "Name" --description "..."
125
135
  linear project complete "Phase 1"
136
+ linear project open "Phase 1" # Set as default project
137
+ linear project close # Clear default project
126
138
 
127
139
  # Milestones
128
140
  linear milestones --project "P1" # Milestones in a project
129
141
  linear milestone show "Beta" # Details with issues
130
142
  linear milestone create "Beta" --project "P1" --target-date 2024-03-01
143
+ linear milestone open "Beta" # Set as default milestone
144
+ linear milestone close # Clear default milestone
131
145
 
132
146
  # Reordering (drag-drop equivalent)
133
147
  linear projects reorder "P1" "P2" "P3" # Set project order
@@ -190,6 +204,16 @@ gh pr create --title "ISSUE-5: Add caching layer"
190
204
 
191
205
  ## Workflow Guidelines
192
206
 
207
+ ### Setting context
208
+ When working on a specific project/milestone, set it as default to avoid repeating flags:
209
+ ```bash
210
+ linear project open "Phase 1" # All commands now default to Phase 1
211
+ linear milestone open "Sprint 3" # And to Sprint 3 milestone
212
+ linear issues # Shows Phase 1 > Sprint 3 issues only
213
+ linear issue create --title "Fix" # Created in Phase 1, Sprint 3
214
+ linear project close # Done? Clear the context
215
+ ```
216
+
193
217
  ### Getting oriented
194
218
  ```bash
195
219
  linear roadmap # See all projects, milestones, progress
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dabble/linear-cli",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "Linear CLI with unblocked issue filtering, built for AI-assisted development",
5
5
  "type": "module",
6
6
  "bin": {