linear-github-cli 1.3.0 → 1.3.4

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/README.md CHANGED
@@ -94,9 +94,9 @@ lg parent
94
94
 
95
95
  Follow the interactive prompts:
96
96
  1. Select repository from dropdown
97
- 2. Enter issue title
97
+ 2. Enter issue title (required)
98
98
  3. Enter description (opens in editor)
99
- 4. Optionally set due date (YYYY-MM-DD)
99
+ 4. Set due date (YYYY-MM-DD, required)
100
100
  5. Select GitHub labels (checkboxes). Choices: `feat`, `fix`, `chore`, `docs`, `refactor`, `test`, `research`
101
101
  6. Optionally select GitHub project
102
102
  7. Optionally select Linear project (after sync)
@@ -112,9 +112,9 @@ lg sub
112
112
  Follow the interactive prompts:
113
113
  1. Select repository from dropdown
114
114
  2. Select parent issue from list
115
- 3. Enter sub-issue title
115
+ 3. Enter sub-issue title (required)
116
116
  4. Enter description (opens in editor)
117
- 5. Optionally set due date (YYYY-MM-DD)
117
+ 5. Set due date (YYYY-MM-DD, required)
118
118
  6. Select GitHub labels (same predefined list as above)
119
119
  7. Optionally select Linear project (after sync)
120
120
 
@@ -132,7 +132,7 @@ lg create-sub --help
132
132
  - ✅ **Project autocomplete**: Select GitHub and Linear projects from dropdowns
133
133
  - ✅ **Parent issue selection**: Browse and select parent issues when creating sub-issues
134
134
  - ✅ **GitHub label sync**: Multi-select from the seven standard labels (feat, fix, chore, docs, refactor, test, research); selections are mirrored to matching Linear team labels
135
- - ✅ **Due date input**: Optional date picker with validation
135
+ - ✅ **Due date input**: Required date picker with validation
136
136
  - ✅ **Automatic Linear sync**: Waits for Linear sync and updates metadata (due date, project, labels)
137
137
  - ✅ **Parent-child relationships**: Automatically links sub-issues to parent issues
138
138
  - ✅ **Status automation**: Issues start in Linear backlog; rely on the Linear × GitHub PR automation for status changes
@@ -301,7 +301,7 @@ gh prms # Merge with squash and delete branch
301
301
  ### Workflow Overview
302
302
 
303
303
  1. **Create issue** - Use `lg parent/sub` command
304
- 2. **Create branch** - Include issue number (e.g., `feat/LEA-123-task`)
304
+ 2. **Create branch** - Include issue number (e.g., `username/LEA-123-task`)
305
305
  3. **Create draft PR** - Right after branch creation, before work begins
306
306
  - Include Linear issue ID in title (copy with `Cmd + .` in Linear)
307
307
  - Include `solve: #123` or `Closes #123` in body
@@ -1,37 +1,16 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.sanitizeBranchName = sanitizeBranchName;
7
- exports.selectBranchPrefix = selectBranchPrefix;
8
4
  exports.generateBranchName = generateBranchName;
9
5
  exports.extractLinearIssueId = extractLinearIssueId;
10
6
  exports.extractBranchPrefix = extractBranchPrefix;
11
7
  exports.checkUnpushedCommitsOnCurrentBranch = checkUnpushedCommitsOnCurrentBranch;
12
8
  exports.createGitBranch = createGitBranch;
13
9
  const child_process_1 = require("child_process");
14
- const inquirer_1 = __importDefault(require("inquirer"));
15
10
  /**
16
11
  * Valid branch prefix types (must match commit_typed.sh)
17
12
  */
18
13
  const VALID_BRANCH_PREFIXES = ['feat', 'fix', 'chore', 'docs', 'refactor', 'test', 'research'];
19
- /**
20
- * Maps GitHub labels to branch prefix types
21
- * Since GitHub labels are already updated to match the standard list,
22
- * labels should directly map to branch prefixes.
23
- * @param label - GitHub label name (should be one of: feat, fix, chore, docs, refactor, test, research)
24
- * @returns Branch prefix (same as label if valid, otherwise returns label as-is)
25
- */
26
- function mapLabelToBranchPrefix(label) {
27
- const labelLower = label.toLowerCase();
28
- // If label is already a valid branch prefix, return it as-is
29
- if (VALID_BRANCH_PREFIXES.includes(labelLower)) {
30
- return labelLower;
31
- }
32
- // Fallback: return label as-is (shouldn't happen if labels are properly configured)
33
- return labelLower;
34
- }
35
14
  /**
36
15
  * Sanitizes a title string to be used as part of a git branch name
37
16
  * - Converts to lowercase
@@ -65,88 +44,49 @@ function sanitizeBranchName(title) {
65
44
  return sanitized;
66
45
  }
67
46
  /**
68
- * Standard branch prefix options when no labels are available
69
- */
70
- const STANDARD_PREFIXES = [
71
- { name: 'feat', value: 'feat' },
72
- { name: 'fix', value: 'fix' },
73
- { name: 'chore', value: 'chore' },
74
- { name: 'docs', value: 'docs' },
75
- { name: 'refactor', value: 'refactor' },
76
- { name: 'test', value: 'test' },
77
- { name: 'research', value: 'research' },
78
- ];
79
- /**
80
- * Selects a branch prefix from GitHub labels
81
- * - If no labels: prompts user to select from standard prefixes
82
- * - If one label: maps and returns branch prefix
83
- * - If multiple labels: prompts user to select one
47
+ * Sanitizes a branch owner (username) to be used in a git branch name.
48
+ * - Converts to lowercase
49
+ * - Removes special characters (keeps alphanumeric and hyphens)
50
+ * - Collapses multiple consecutive hyphens
51
+ * - Removes leading/trailing hyphens
84
52
  *
85
- * @param labels - Array of GitHub label names
86
- * @returns Branch prefix string or null if user cancels
53
+ * @param owner - The branch owner to sanitize
54
+ * @returns Sanitized branch owner portion
87
55
  */
88
- async function selectBranchPrefix(labels) {
89
- // Map all labels to branch prefixes
90
- const mappedPrefixes = labels && labels.length > 0
91
- ? labels.map(label => ({
92
- label,
93
- prefix: mapLabelToBranchPrefix(label),
94
- }))
95
- : [];
96
- // If no labels: prompt user to select from standard prefixes
97
- if (mappedPrefixes.length === 0) {
98
- const { selectedPrefix } = await inquirer_1.default.prompt([
99
- {
100
- type: 'list',
101
- name: 'selectedPrefix',
102
- message: 'Select branch prefix (no labels selected):',
103
- choices: STANDARD_PREFIXES,
104
- },
105
- ]);
106
- return selectedPrefix;
107
- }
108
- // If only one label, return its mapped prefix
109
- if (mappedPrefixes.length === 1) {
110
- return mappedPrefixes[0].prefix;
56
+ function sanitizeBranchOwner(owner) {
57
+ if (!owner || owner.trim().length === 0) {
58
+ return '';
111
59
  }
112
- // Multiple labels: prompt user to select
113
- const choices = mappedPrefixes.map(({ label, prefix }) => ({
114
- name: `${label} ${prefix}`,
115
- value: prefix,
116
- }));
117
- const { selectedPrefix } = await inquirer_1.default.prompt([
118
- {
119
- type: 'list',
120
- name: 'selectedPrefix',
121
- message: 'Select branch prefix (multiple labels selected):',
122
- choices,
123
- },
124
- ]);
125
- return selectedPrefix;
60
+ let sanitized = owner.toLowerCase().replace(/[^a-z0-9-]/g, '');
61
+ sanitized = sanitized.replace(/-+/g, '-');
62
+ sanitized = sanitized.replace(/^-+|-+$/g, '');
63
+ return sanitized;
126
64
  }
127
65
  /**
128
- * Generates a branch name from prefix, Linear ID, and title
129
- * Format: prefix/LinearID-sanitized-title
66
+ * Generates a branch name from owner, Linear ID, and title
67
+ * Format: owner/LinearID-sanitized-title
130
68
  *
131
- * @param prefix - Branch prefix (e.g., 'feat', 'docs')
69
+ * @param owner - Branch owner (e.g., GitHub username)
132
70
  * @param linearId - Linear issue ID (e.g., 'LEA-123')
133
71
  * @param title - Issue title to sanitize
134
72
  * @returns Full branch name
135
73
  */
136
- function generateBranchName(prefix, linearId, title) {
74
+ function generateBranchName(owner, linearId, title) {
75
+ const sanitizedOwner = sanitizeBranchOwner(owner);
137
76
  const sanitizedTitle = sanitizeBranchName(title);
77
+ const ownerSegment = sanitizedOwner || 'user';
138
78
  if (!sanitizedTitle) {
139
- // If title is empty after sanitization, just use prefix and ID
140
- return `${prefix}/${linearId}`;
79
+ // If title is empty after sanitization, just use owner and ID
80
+ return `${ownerSegment}/${linearId}`;
141
81
  }
142
- return `${prefix}/${linearId}-${sanitizedTitle}`;
82
+ return `${ownerSegment}/${linearId}-${sanitizedTitle}`;
143
83
  }
144
84
  /**
145
85
  * Extracts Linear issue ID from a branch name
146
- * - Matches pattern: prefix/LEA-123-title or prefix/LEA-123
86
+ * - Matches pattern: owner/LEA-123-title or owner/LEA-123
147
87
  * - Uses regex to find Linear issue ID format: [A-Z]+-\d+
148
88
  *
149
- * @param branchName - Branch name (e.g., 'feat/LEA-123-implement-login')
89
+ * @param branchName - Branch name (e.g., 'negoth/LEA-123-implement-login')
150
90
  * @returns Linear issue ID (e.g., 'LEA-123') or null if not found
151
91
  */
152
92
  function extractLinearIssueId(branchName) {
@@ -161,27 +101,25 @@ function extractLinearIssueId(branchName) {
161
101
  * Extracts branch prefix (commit type) from a branch name
162
102
  * - Extracts the part before the first '/' (e.g., 'research' from 'research/LEA-75-probit-model')
163
103
  * - Validates against VALID_BRANCH_PREFIXES
164
- * - Returns 'feat' as default if prefix is not found or invalid
165
104
  *
166
105
  * @param branchName - Branch name (e.g., 'research/LEA-75-probit-model')
167
- * @returns Branch prefix (e.g., 'research') or 'feat' as default
106
+ * @returns Branch prefix (e.g., 'research') or null if not found/invalid
168
107
  */
169
108
  function extractBranchPrefix(branchName) {
170
109
  if (!branchName || branchName.trim().length === 0) {
171
- return 'feat';
110
+ return null;
172
111
  }
173
112
  // Extract prefix before first '/'
174
113
  const parts = branchName.split('/');
175
114
  if (parts.length === 0 || !parts[0]) {
176
- return 'feat';
115
+ return null;
177
116
  }
178
117
  const prefix = parts[0].toLowerCase();
179
118
  // Validate against valid prefixes
180
119
  if (VALID_BRANCH_PREFIXES.includes(prefix)) {
181
120
  return prefix;
182
121
  }
183
- // Return 'feat' as default if prefix is invalid
184
- return 'feat';
122
+ return null;
185
123
  }
186
124
  /**
187
125
  * Checks for unpushed commits on the current branch
package/dist/cli.js CHANGED
@@ -13,9 +13,16 @@ const create_sub_1 = require("./commands/create-sub");
13
13
  const env_utils_1 = require("./env-utils");
14
14
  // Load .env file from current working directory, parent directories, or home directory
15
15
  (0, env_utils_1.loadEnvFile)();
16
- // Check for updates
17
- const pkg = JSON.parse((0, fs_1.readFileSync)((0, path_1.resolve)(__dirname, '..', 'package.json'), 'utf-8'));
18
- (0, update_notifier_1.default)({ pkg }).notify();
16
+ // Check for updates (support both dev and built paths)
17
+ const pkgPathCandidates = [
18
+ (0, path_1.resolve)(__dirname, 'package.json'),
19
+ (0, path_1.resolve)(__dirname, '..', 'package.json'),
20
+ ];
21
+ const pkgPath = pkgPathCandidates.find(candidate => (0, fs_1.existsSync)(candidate));
22
+ if (pkgPath) {
23
+ const pkg = JSON.parse((0, fs_1.readFileSync)(pkgPath, 'utf-8'));
24
+ (0, update_notifier_1.default)({ pkg }).notify();
25
+ }
19
26
  const program = new commander_1.Command();
20
27
  program
21
28
  .name('lg')
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.commitFirst = commitFirst;
4
7
  const child_process_1 = require("child_process");
8
+ const inquirer_1 = __importDefault(require("inquirer"));
5
9
  const branch_utils_1 = require("../branch-utils");
6
10
  const linear_client_1 = require("../linear-client");
7
11
  const env_utils_1 = require("../env-utils");
@@ -37,14 +41,25 @@ async function commitFirst() {
37
41
  console.error('❌ Error: Not in a git repository or unable to get branch name');
38
42
  process.exit(1);
39
43
  }
40
- // Step 2: Extract branch prefix and Linear issue ID from branch name
41
- const prefix = (0, branch_utils_1.extractBranchPrefix)(branchName);
44
+ // Step 2: Extract branch prefix (if any) and Linear issue ID from branch name
45
+ let prefix = (0, branch_utils_1.extractBranchPrefix)(branchName);
42
46
  const linearId = (0, branch_utils_1.extractLinearIssueId)(branchName);
43
47
  if (!linearId) {
44
48
  console.error(`❌ Error: Could not extract Linear issue ID from branch name: ${branchName}`);
45
- console.error(' Branch name should follow pattern: prefix/LEA-123-title');
49
+ console.error(' Branch name should follow pattern: username/LEA-123-title');
46
50
  process.exit(1);
47
51
  }
52
+ if (!prefix) {
53
+ const { selectedPrefix } = await inquirer_1.default.prompt([
54
+ {
55
+ type: 'list',
56
+ name: 'selectedPrefix',
57
+ message: 'Select commit type:',
58
+ choices: ['feat', 'fix', 'chore', 'docs', 'refactor', 'test', 'research'],
59
+ },
60
+ ]);
61
+ prefix = selectedPrefix;
62
+ }
48
63
  console.log(`📋 Found Linear issue ID: ${linearId}`);
49
64
  console.log(`📋 Using commit type: ${prefix}`);
50
65
  // Step 3: Initialize Linear client and fetch issue data
@@ -83,22 +83,30 @@ async function createParentIssue() {
83
83
  });
84
84
  if (linearIssueId) {
85
85
  console.log('✅ Found Linear issue, updating metadata...');
86
- // Auto-find Linear project if GitHub project was selected
86
+ // Auto-find/create Linear project if GitHub project was selected
87
87
  let linearProjectId = null;
88
+ let linearProjectName = null;
88
89
  if (githubProject) {
89
90
  console.log(` Looking for Linear project matching "${githubProject}"...`);
90
91
  linearProjectId = await linearClient.findProjectByName(githubProject);
91
92
  if (linearProjectId) {
93
+ linearProjectName = githubProject;
92
94
  console.log(` ✅ Found matching Linear project: ${githubProject}`);
93
95
  }
94
96
  else {
95
- console.log(` ⚠️ No matching Linear project found. You can set it manually.`);
97
+ console.log(` ⚠️ No matching Linear project found. Creating "${githubProject}"...`);
98
+ const teamId = await linearClient.getIssueTeamId(linearIssueId);
99
+ const createdProject = await linearClient.createProject(githubProject, teamId || undefined);
100
+ if (createdProject) {
101
+ linearProjectId = createdProject.id;
102
+ linearProjectName = createdProject.name;
103
+ console.log(` ✅ Created Linear project: ${createdProject.name}`);
104
+ }
105
+ else {
106
+ console.log(` ⚠️ Failed to create Linear project. You can set it manually.`);
107
+ }
96
108
  }
97
109
  }
98
- // If no auto-match, ask user (only if GitHub project wasn't selected)
99
- if (!linearProjectId && !githubProject) {
100
- linearProjectId = await inputHandler.selectLinearProject();
101
- }
102
110
  // Set labels on Linear issue
103
111
  if (details.labels && details.labels.length > 0) {
104
112
  console.log(` Setting labels: ${details.labels.join(', ')}`);
@@ -115,7 +123,7 @@ async function createParentIssue() {
115
123
  if (success) {
116
124
  console.log('✅ Linear issue metadata updated!');
117
125
  if (linearProjectId) {
118
- console.log(` Project: ${githubProject || 'selected project'}`);
126
+ console.log(` Project: ${linearProjectName || githubProject || 'linked'}`);
119
127
  }
120
128
  if (details.dueDate) {
121
129
  console.log(` Due date: ${details.dueDate}`);
@@ -144,18 +152,22 @@ async function createParentIssue() {
144
152
  console.log(` GitHub issue #${issue.number}`);
145
153
  }
146
154
  else {
147
- const branchPrefix = await (0, branch_utils_1.selectBranchPrefix)(details.labels);
148
- if (branchPrefix) {
149
- const branchName = (0, branch_utils_1.generateBranchName)(branchPrefix, linearIssueIdentifier, details.title);
150
- const success = await (0, branch_utils_1.createGitBranch)(branchName);
151
- if (success) {
152
- console.log(`✅ Branch created: ${branchName}`);
153
- console.log(` Linear issue ID: ${linearIssueIdentifier}`);
154
- console.log(` GitHub issue #${issue.number}`);
155
- }
155
+ let branchOwner = await githubClient.getCurrentUsername();
156
+ if (!branchOwner) {
157
+ const { ownerInput } = await inquirer_1.default.prompt([
158
+ {
159
+ type: 'input',
160
+ name: 'ownerInput',
161
+ message: 'Branch username for naming (e.g., your GitHub login):',
162
+ validate: (input) => input.trim().length > 0 || 'Username is required',
163
+ },
164
+ ]);
165
+ branchOwner = ownerInput.trim();
156
166
  }
157
- else {
158
- console.log('⚠️ No suitable label found for branch prefix. Branch creation skipped.');
167
+ const branchName = (0, branch_utils_1.generateBranchName)(branchOwner ?? 'user', linearIssueIdentifier, details.title);
168
+ const success = await (0, branch_utils_1.createGitBranch)(branchName);
169
+ if (success) {
170
+ console.log(`✅ Branch created: ${branchName}`);
159
171
  console.log(` Linear issue ID: ${linearIssueIdentifier}`);
160
172
  console.log(` GitHub issue #${issue.number}`);
161
173
  }
@@ -110,10 +110,7 @@ async function createSubIssue() {
110
110
  else {
111
111
  console.log(` ⚠️ Parent Linear issue not found yet (may need more time to sync)`);
112
112
  }
113
- // If no parent project, ask user (only if no parent project found)
114
- if (!linearProjectId) {
115
- linearProjectId = await inputHandler.selectLinearProject();
116
- }
113
+ // If no parent project, leave project unset (no prompt)
117
114
  // Set labels on Linear issue
118
115
  if (details.labels && details.labels.length > 0) {
119
116
  console.log(` Setting labels: ${details.labels.join(', ')}`);
@@ -172,18 +169,22 @@ async function createSubIssue() {
172
169
  console.log(` GitHub issue #${subIssue.number}`);
173
170
  }
174
171
  else {
175
- const branchPrefix = await (0, branch_utils_1.selectBranchPrefix)(details.labels);
176
- if (branchPrefix) {
177
- const branchName = (0, branch_utils_1.generateBranchName)(branchPrefix, linearIssueIdentifier, details.title);
178
- const success = await (0, branch_utils_1.createGitBranch)(branchName);
179
- if (success) {
180
- console.log(`✅ Branch created: ${branchName}`);
181
- console.log(` Linear issue ID: ${linearIssueIdentifier}`);
182
- console.log(` GitHub issue #${subIssue.number}`);
183
- }
172
+ let branchOwner = await githubClient.getCurrentUsername();
173
+ if (!branchOwner) {
174
+ const { ownerInput } = await inquirer_1.default.prompt([
175
+ {
176
+ type: 'input',
177
+ name: 'ownerInput',
178
+ message: 'Branch username for naming (e.g., your GitHub login):',
179
+ validate: (input) => input.trim().length > 0 || 'Username is required',
180
+ },
181
+ ]);
182
+ branchOwner = ownerInput.trim();
184
183
  }
185
- else {
186
- console.log('⚠️ No suitable label found for branch prefix. Branch creation skipped.');
184
+ const branchName = (0, branch_utils_1.generateBranchName)(branchOwner ?? 'user', linearIssueIdentifier, details.title);
185
+ const success = await (0, branch_utils_1.createGitBranch)(branchName);
186
+ if (success) {
187
+ console.log(`✅ Branch created: ${branchName}`);
187
188
  console.log(` Linear issue ID: ${linearIssueIdentifier}`);
188
189
  console.log(` GitHub issue #${subIssue.number}`);
189
190
  }
@@ -12,6 +12,18 @@ class GitHubClientWrapper {
12
12
  this.octokit = new rest_1.Octokit({ auth: token });
13
13
  }
14
14
  }
15
+ async getCurrentUsername() {
16
+ try {
17
+ const output = (0, child_process_1.execSync)('gh api user -q .login', {
18
+ encoding: 'utf-8',
19
+ stdio: 'pipe',
20
+ }).trim();
21
+ return output || null;
22
+ }
23
+ catch (error) {
24
+ return null;
25
+ }
26
+ }
15
27
  async getRepositories() {
16
28
  // Use gh CLI to get accessible repos
17
29
  const output = (0, child_process_1.execSync)('gh repo list --limit 100 --json nameWithOwner', {
@@ -6,6 +6,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.InputHandler = void 0;
7
7
  const child_process_1 = require("child_process");
8
8
  const inquirer_1 = __importDefault(require("inquirer"));
9
+ const isValidDateYmd = (input) => {
10
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(input))
11
+ return false;
12
+ const [year, month, day] = input.split('-').map(Number);
13
+ const date = new Date(Date.UTC(year, month - 1, day));
14
+ return (date.getUTCFullYear() === year &&
15
+ date.getUTCMonth() === month - 1 &&
16
+ date.getUTCDate() === day);
17
+ };
9
18
  class InputHandler {
10
19
  linearClient;
11
20
  githubClient;
@@ -150,7 +159,7 @@ class InputHandler {
150
159
  {
151
160
  type: 'input',
152
161
  name: 'title',
153
- message: 'Issue title:',
162
+ message: 'Issue title (required):',
154
163
  validate: (input) => input.length > 0 || 'Title is required',
155
164
  },
156
165
  {
@@ -169,29 +178,24 @@ class InputHandler {
169
178
  validate: (input) => {
170
179
  if (!input)
171
180
  return true; // Optional
172
- const date = new Date(input);
173
- return !isNaN(date.getTime()) || 'Invalid date format';
181
+ return isValidDateYmd(input) || 'Invalid date format';
174
182
  },
175
183
  },
176
184
  {
177
185
  type: 'input',
178
186
  name: 'dueDate',
179
- message: 'Due date (YYYY-MM-DD):',
187
+ message: 'Due date (YYYY-MM-DD, required):',
180
188
  validate: (input) => {
181
189
  if (!input)
182
- return true; // Optional
183
- const date = new Date(input);
184
- return !isNaN(date.getTime()) || 'Invalid date format';
190
+ return 'Due date is required';
191
+ return isValidDateYmd(input) || 'Invalid date format';
185
192
  },
186
193
  },
187
194
  {
188
195
  type: 'checkbox',
189
196
  name: 'labels',
190
- message: 'Select GitHub labels (at least one required):',
197
+ message: 'Select GitHub labels (optional):',
191
198
  choices: labelChoices,
192
- validate: (input) => {
193
- return input.length > 0 || 'At least one label is required';
194
- },
195
199
  },
196
200
  ]);
197
201
  let description = '';
@@ -28,6 +28,41 @@ class LinearClientWrapper {
28
28
  return project?.id || null;
29
29
  }
30
30
  }
31
+ async createProject(projectName, teamId) {
32
+ try {
33
+ const mutation = `mutation CreateProject($input: ProjectCreateInput!) {
34
+ projectCreate(input: $input) {
35
+ success
36
+ project { id name }
37
+ }
38
+ }`;
39
+ const input = { name: projectName };
40
+ if (teamId) {
41
+ input.teamIds = [teamId];
42
+ }
43
+ const response = await fetch('https://api.linear.app/graphql', {
44
+ method: 'POST',
45
+ headers: {
46
+ 'Content-Type': 'application/json',
47
+ 'Authorization': linearApiKey,
48
+ },
49
+ body: JSON.stringify({
50
+ query: mutation,
51
+ variables: { input },
52
+ }),
53
+ });
54
+ const result = await response.json();
55
+ if (result.errors || !result.data?.projectCreate?.success) {
56
+ console.error(`❌ Failed to create project "${projectName}":`, result.errors);
57
+ return null;
58
+ }
59
+ return result.data.projectCreate.project ?? null;
60
+ }
61
+ catch (error) {
62
+ console.error(`❌ Error creating project "${projectName}":`, error);
63
+ return null;
64
+ }
65
+ }
31
66
  async getWorkflowStates(teamId) {
32
67
  try {
33
68
  // Get workflow states from teams
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linear-github-cli",
3
- "version": "1.3.0",
3
+ "version": "1.3.4",
4
4
  "description": "CLI tool for creating GitHub issues with Linear integration",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {