@haystackeditor/cli 0.13.0 → 0.13.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.
@@ -11,6 +11,7 @@ export interface CreatePROptions {
11
11
  base: string;
12
12
  body?: string;
13
13
  draft?: boolean;
14
+ token?: string;
14
15
  }
15
16
  export interface PRResponse {
16
17
  number: number;
@@ -29,11 +30,33 @@ export interface LabelInfo {
29
30
  color: string;
30
31
  description?: string;
31
32
  }
33
+ export interface GitHubUser {
34
+ login: string;
35
+ id: number;
36
+ }
37
+ export interface RepositoryPermissions {
38
+ admin?: boolean;
39
+ maintain?: boolean;
40
+ push?: boolean;
41
+ triage?: boolean;
42
+ pull?: boolean;
43
+ }
44
+ export interface RepositoryAccess {
45
+ full_name: string;
46
+ owner: {
47
+ login: string;
48
+ };
49
+ permissions?: RepositoryPermissions;
50
+ }
32
51
  export declare const HAYSTACK_LABELS: Record<string, LabelInfo>;
33
52
  /**
34
- * Get stored GitHub token, throw if not authenticated
53
+ * Get a GitHub token, throw if not authenticated
35
54
  */
36
- export declare function requireGitHubAuth(): Promise<string>;
55
+ export declare function requireGitHubAuth(options?: {
56
+ preferredLogin?: string;
57
+ owner?: string;
58
+ repo?: string;
59
+ }): Promise<string>;
37
60
  /**
38
61
  * Create a pull request
39
62
  */
@@ -41,43 +64,41 @@ export declare function createPullRequest(options: CreatePROptions): Promise<PRR
41
64
  /**
42
65
  * Check if a PR already exists for a branch
43
66
  */
44
- export declare function findExistingPR(owner: string, repo: string, head: string): Promise<PRResponse | null>;
67
+ export declare function findExistingPR(owner: string, repo: string, head: string, token?: string): Promise<PRResponse | null>;
45
68
  /**
46
69
  * Add labels to an issue/PR
47
70
  */
48
- export declare function addLabelsToIssue(owner: string, repo: string, issueNumber: number, labels: string[]): Promise<void>;
71
+ export declare function addLabelsToIssue(owner: string, repo: string, issueNumber: number, labels: string[], token?: string): Promise<void>;
49
72
  /**
50
73
  * Get all labels from an issue/PR
51
74
  */
52
- export declare function getLabelsFromIssue(owner: string, repo: string, issueNumber: number): Promise<string[]>;
75
+ export declare function getLabelsFromIssue(owner: string, repo: string, issueNumber: number, token?: string): Promise<string[]>;
53
76
  /**
54
77
  * Remove a label from an issue/PR
55
78
  */
56
- export declare function removeLabelFromIssue(owner: string, repo: string, issueNumber: number, labelName: string): Promise<void>;
79
+ export declare function removeLabelFromIssue(owner: string, repo: string, issueNumber: number, labelName: string, token?: string): Promise<void>;
57
80
  /**
58
81
  * Check if a label exists in the repo
59
82
  */
60
- export declare function labelExists(owner: string, repo: string, labelName: string): Promise<boolean>;
83
+ export declare function labelExists(owner: string, repo: string, labelName: string, token?: string): Promise<boolean>;
61
84
  /**
62
85
  * Create a label in the repo
63
86
  */
64
- export declare function createLabel(owner: string, repo: string, label: LabelInfo): Promise<void>;
87
+ export declare function createLabel(owner: string, repo: string, label: LabelInfo, token?: string): Promise<void>;
65
88
  /**
66
89
  * Ensure all required Haystack labels exist in the repo
67
90
  */
68
- export declare function ensureHaystackLabels(owner: string, repo: string, labelNames: string[]): Promise<void>;
91
+ export declare function ensureHaystackLabels(owner: string, repo: string, labelNames: string[], token?: string): Promise<void>;
69
92
  /**
70
93
  * Request reviewers on a pull request
71
94
  */
72
- export declare function requestReviewers(owner: string, repo: string, prNumber: number, reviewers: string[]): Promise<void>;
95
+ export declare function requestReviewers(owner: string, repo: string, prNumber: number, reviewers: string[], token?: string): Promise<void>;
73
96
  /**
74
97
  * Get the authenticated user's info
75
98
  */
76
- export declare function getCurrentUser(): Promise<{
77
- login: string;
78
- id: number;
79
- }>;
99
+ export declare function getCurrentUser(token?: string): Promise<GitHubUser>;
100
+ export declare function getRepositoryAccess(owner: string, repo: string, token?: string): Promise<RepositoryAccess>;
80
101
  /**
81
102
  * Check if user has push access to a repo
82
103
  */
83
- export declare function hasPushAccess(owner: string, repo: string): Promise<boolean>;
104
+ export declare function hasPushAccess(owner: string, repo: string, token?: string): Promise<boolean>;
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Provides functions for creating PRs, managing labels, etc.
5
5
  */
6
- import { loadToken } from '../commands/login.js';
6
+ import { resolveAuthContext } from './auth.js';
7
7
  const GITHUB_API_BASE = 'https://api.github.com';
8
8
  // Label definitions for Haystack workflow
9
9
  export const HAYSTACK_LABELS = {
@@ -27,14 +27,14 @@ export const HAYSTACK_LABELS = {
27
27
  // Authentication
28
28
  // ============================================================================
29
29
  /**
30
- * Get stored GitHub token, throw if not authenticated
30
+ * Get a GitHub token, throw if not authenticated
31
31
  */
32
- export async function requireGitHubAuth() {
33
- const token = await loadToken();
34
- if (!token) {
35
- throw new Error('Not logged in. Run `haystack login` first.');
36
- }
37
- return token;
32
+ export async function requireGitHubAuth(options) {
33
+ const context = await resolveAuthContext(options);
34
+ return context.token;
35
+ }
36
+ async function resolveToken(token, options) {
37
+ return token ?? requireGitHubAuth(options);
38
38
  }
39
39
  // ============================================================================
40
40
  // API helpers
@@ -88,7 +88,10 @@ async function handleApiError(response, context) {
88
88
  * Create a pull request
89
89
  */
90
90
  export async function createPullRequest(options) {
91
- const token = await requireGitHubAuth();
91
+ const token = await resolveToken(options.token, {
92
+ owner: options.owner,
93
+ repo: options.repo,
94
+ });
92
95
  const response = await githubApiRequest('POST', `/repos/${options.owner}/${options.repo}/pulls`, token, {
93
96
  title: options.title,
94
97
  head: options.head,
@@ -139,11 +142,11 @@ async function findMergedPRForBranch(owner, repo, head, token) {
139
142
  /**
140
143
  * Check if a PR already exists for a branch
141
144
  */
142
- export async function findExistingPR(owner, repo, head) {
143
- const token = await requireGitHubAuth();
145
+ export async function findExistingPR(owner, repo, head, token) {
146
+ const authToken = await resolveToken(token, { owner, repo });
144
147
  // head format should be "owner:branch" for cross-repo or just "branch" for same-repo
145
148
  const headParam = head.includes(':') ? head : `${owner}:${head}`;
146
- const response = await githubApiRequest('GET', `/repos/${owner}/${repo}/pulls?head=${encodeURIComponent(headParam)}&state=open`, token);
149
+ const response = await githubApiRequest('GET', `/repos/${owner}/${repo}/pulls?head=${encodeURIComponent(headParam)}&state=open`, authToken);
147
150
  if (!response.ok) {
148
151
  if (response.status === 404)
149
152
  return null;
@@ -158,9 +161,9 @@ export async function findExistingPR(owner, repo, head) {
158
161
  /**
159
162
  * Add labels to an issue/PR
160
163
  */
161
- export async function addLabelsToIssue(owner, repo, issueNumber, labels) {
162
- const token = await requireGitHubAuth();
163
- const response = await githubApiRequest('POST', `/repos/${owner}/${repo}/issues/${issueNumber}/labels`, token, { labels });
164
+ export async function addLabelsToIssue(owner, repo, issueNumber, labels, token) {
165
+ const authToken = await resolveToken(token, { owner, repo });
166
+ const response = await githubApiRequest('POST', `/repos/${owner}/${repo}/issues/${issueNumber}/labels`, authToken, { labels });
164
167
  if (!response.ok) {
165
168
  await handleApiError(response, `adding labels to PR #${issueNumber}`);
166
169
  }
@@ -168,9 +171,9 @@ export async function addLabelsToIssue(owner, repo, issueNumber, labels) {
168
171
  /**
169
172
  * Get all labels from an issue/PR
170
173
  */
171
- export async function getLabelsFromIssue(owner, repo, issueNumber) {
172
- const token = await requireGitHubAuth();
173
- const response = await githubApiRequest('GET', `/repos/${owner}/${repo}/issues/${issueNumber}/labels`, token);
174
+ export async function getLabelsFromIssue(owner, repo, issueNumber, token) {
175
+ const authToken = await resolveToken(token, { owner, repo });
176
+ const response = await githubApiRequest('GET', `/repos/${owner}/${repo}/issues/${issueNumber}/labels`, authToken);
174
177
  if (!response.ok) {
175
178
  await handleApiError(response, `getting labels from PR #${issueNumber}`);
176
179
  }
@@ -180,9 +183,9 @@ export async function getLabelsFromIssue(owner, repo, issueNumber) {
180
183
  /**
181
184
  * Remove a label from an issue/PR
182
185
  */
183
- export async function removeLabelFromIssue(owner, repo, issueNumber, labelName) {
184
- const token = await requireGitHubAuth();
185
- const response = await githubApiRequest('DELETE', `/repos/${owner}/${repo}/issues/${issueNumber}/labels/${encodeURIComponent(labelName)}`, token);
186
+ export async function removeLabelFromIssue(owner, repo, issueNumber, labelName, token) {
187
+ const authToken = await resolveToken(token, { owner, repo });
188
+ const response = await githubApiRequest('DELETE', `/repos/${owner}/${repo}/issues/${issueNumber}/labels/${encodeURIComponent(labelName)}`, authToken);
186
189
  // 404 is fine - label wasn't on the issue
187
190
  if (!response.ok && response.status !== 404) {
188
191
  await handleApiError(response, `removing label '${labelName}' from PR #${issueNumber}`);
@@ -191,17 +194,17 @@ export async function removeLabelFromIssue(owner, repo, issueNumber, labelName)
191
194
  /**
192
195
  * Check if a label exists in the repo
193
196
  */
194
- export async function labelExists(owner, repo, labelName) {
195
- const token = await requireGitHubAuth();
196
- const response = await githubApiRequest('GET', `/repos/${owner}/${repo}/labels/${encodeURIComponent(labelName)}`, token);
197
+ export async function labelExists(owner, repo, labelName, token) {
198
+ const authToken = await resolveToken(token, { owner, repo });
199
+ const response = await githubApiRequest('GET', `/repos/${owner}/${repo}/labels/${encodeURIComponent(labelName)}`, authToken);
197
200
  return response.ok;
198
201
  }
199
202
  /**
200
203
  * Create a label in the repo
201
204
  */
202
- export async function createLabel(owner, repo, label) {
203
- const token = await requireGitHubAuth();
204
- const response = await githubApiRequest('POST', `/repos/${owner}/${repo}/labels`, token, {
205
+ export async function createLabel(owner, repo, label, token) {
206
+ const authToken = await resolveToken(token, { owner, repo });
207
+ const response = await githubApiRequest('POST', `/repos/${owner}/${repo}/labels`, authToken, {
205
208
  name: label.name,
206
209
  color: label.color,
207
210
  description: label.description,
@@ -214,14 +217,14 @@ export async function createLabel(owner, repo, label) {
214
217
  /**
215
218
  * Ensure all required Haystack labels exist in the repo
216
219
  */
217
- export async function ensureHaystackLabels(owner, repo, labelNames) {
220
+ export async function ensureHaystackLabels(owner, repo, labelNames, token) {
218
221
  for (const name of labelNames) {
219
222
  const labelInfo = HAYSTACK_LABELS[name];
220
223
  if (!labelInfo)
221
224
  continue;
222
- const exists = await labelExists(owner, repo, name);
225
+ const exists = await labelExists(owner, repo, name, token);
223
226
  if (!exists) {
224
- await createLabel(owner, repo, labelInfo);
227
+ await createLabel(owner, repo, labelInfo, token);
225
228
  }
226
229
  }
227
230
  }
@@ -231,9 +234,9 @@ export async function ensureHaystackLabels(owner, repo, labelNames) {
231
234
  /**
232
235
  * Request reviewers on a pull request
233
236
  */
234
- export async function requestReviewers(owner, repo, prNumber, reviewers) {
235
- const token = await requireGitHubAuth();
236
- const response = await githubApiRequest('POST', `/repos/${owner}/${repo}/pulls/${prNumber}/requested_reviewers`, token, { reviewers });
237
+ export async function requestReviewers(owner, repo, prNumber, reviewers, token) {
238
+ const authToken = await resolveToken(token, { owner, repo });
239
+ const response = await githubApiRequest('POST', `/repos/${owner}/${repo}/pulls/${prNumber}/requested_reviewers`, authToken, { reviewers });
237
240
  if (!response.ok) {
238
241
  await handleApiError(response, `requesting reviewers on PR #${prNumber}`);
239
242
  }
@@ -244,23 +247,28 @@ export async function requestReviewers(owner, repo, prNumber, reviewers) {
244
247
  /**
245
248
  * Get the authenticated user's info
246
249
  */
247
- export async function getCurrentUser() {
248
- const token = await requireGitHubAuth();
249
- const response = await githubApiRequest('GET', '/user', token);
250
+ export async function getCurrentUser(token) {
251
+ const authToken = await resolveToken(token);
252
+ const response = await githubApiRequest('GET', '/user', authToken);
250
253
  if (!response.ok) {
251
254
  await handleApiError(response, 'getting current user');
252
255
  }
253
256
  return response.json();
254
257
  }
258
+ export async function getRepositoryAccess(owner, repo, token) {
259
+ const authToken = await resolveToken(token, { owner, repo });
260
+ const response = await githubApiRequest('GET', `/repos/${owner}/${repo}`, authToken);
261
+ if (!response.ok) {
262
+ await handleApiError(response, `getting repository access for ${owner}/${repo}`);
263
+ }
264
+ return response.json();
265
+ }
255
266
  /**
256
267
  * Check if user has push access to a repo
257
268
  */
258
- export async function hasPushAccess(owner, repo) {
259
- const token = await requireGitHubAuth();
260
- const response = await githubApiRequest('GET', `/repos/${owner}/${repo}`, token);
261
- if (!response.ok) {
262
- return false;
263
- }
264
- const data = await response.json();
265
- return data.permissions?.push === true;
269
+ export async function hasPushAccess(owner, repo, token) {
270
+ const data = await getRepositoryAccess(owner, repo, token);
271
+ return data.permissions?.admin === true
272
+ || data.permissions?.maintain === true
273
+ || data.permissions?.push === true;
266
274
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haystackeditor/cli",
3
- "version": "0.13.0",
3
+ "version": "0.13.2",
4
4
  "description": "Set up Haystack for your project — automated PR review, triage, and merge queue",
5
5
  "type": "module",
6
6
  "bin": {