@oss-autopilot/core 0.41.0 → 0.42.1

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.
Files changed (63) hide show
  1. package/dist/cli.bundle.cjs +1552 -1318
  2. package/dist/cli.js +593 -69
  3. package/dist/commands/check-integration.d.ts +3 -3
  4. package/dist/commands/check-integration.js +10 -43
  5. package/dist/commands/comments.d.ts +6 -9
  6. package/dist/commands/comments.js +102 -252
  7. package/dist/commands/config.d.ts +8 -2
  8. package/dist/commands/config.js +6 -28
  9. package/dist/commands/daily.d.ts +28 -4
  10. package/dist/commands/daily.js +33 -45
  11. package/dist/commands/dashboard-data.js +7 -6
  12. package/dist/commands/dashboard-server.d.ts +14 -0
  13. package/dist/commands/dashboard-server.js +362 -0
  14. package/dist/commands/dashboard.d.ts +5 -0
  15. package/dist/commands/dashboard.js +51 -1
  16. package/dist/commands/dismiss.d.ts +13 -5
  17. package/dist/commands/dismiss.js +4 -24
  18. package/dist/commands/index.d.ts +33 -0
  19. package/dist/commands/index.js +22 -0
  20. package/dist/commands/init.d.ts +5 -4
  21. package/dist/commands/init.js +4 -14
  22. package/dist/commands/local-repos.d.ts +4 -5
  23. package/dist/commands/local-repos.js +6 -33
  24. package/dist/commands/parse-list.d.ts +3 -4
  25. package/dist/commands/parse-list.js +8 -39
  26. package/dist/commands/read.d.ts +11 -5
  27. package/dist/commands/read.js +4 -18
  28. package/dist/commands/search.d.ts +3 -3
  29. package/dist/commands/search.js +39 -65
  30. package/dist/commands/setup.d.ts +34 -5
  31. package/dist/commands/setup.js +75 -166
  32. package/dist/commands/shelve.d.ts +13 -5
  33. package/dist/commands/shelve.js +4 -24
  34. package/dist/commands/snooze.d.ts +15 -9
  35. package/dist/commands/snooze.js +16 -59
  36. package/dist/commands/startup.d.ts +11 -6
  37. package/dist/commands/startup.js +44 -82
  38. package/dist/commands/status.d.ts +3 -3
  39. package/dist/commands/status.js +10 -29
  40. package/dist/commands/track.d.ts +10 -9
  41. package/dist/commands/track.js +17 -39
  42. package/dist/commands/validation.d.ts +2 -2
  43. package/dist/commands/validation.js +7 -15
  44. package/dist/commands/vet.d.ts +3 -3
  45. package/dist/commands/vet.js +16 -26
  46. package/dist/core/errors.d.ts +9 -0
  47. package/dist/core/errors.js +17 -0
  48. package/dist/core/github-stats.d.ts +14 -21
  49. package/dist/core/github-stats.js +84 -138
  50. package/dist/core/http-cache.d.ts +6 -0
  51. package/dist/core/http-cache.js +16 -4
  52. package/dist/core/index.d.ts +2 -1
  53. package/dist/core/index.js +2 -1
  54. package/dist/core/issue-conversation.js +4 -4
  55. package/dist/core/issue-discovery.js +14 -14
  56. package/dist/core/issue-vetting.js +17 -17
  57. package/dist/core/pr-monitor.d.ts +6 -20
  58. package/dist/core/pr-monitor.js +11 -52
  59. package/dist/core/state.js +4 -5
  60. package/dist/core/utils.d.ts +11 -0
  61. package/dist/core/utils.js +21 -0
  62. package/dist/formatters/json.d.ts +58 -0
  63. package/package.json +5 -1
@@ -2,9 +2,9 @@
2
2
  * Check integration command (#83)
3
3
  * Detects new files in the current branch that aren't referenced elsewhere
4
4
  */
5
+ import type { CheckIntegrationOutput, NewFileInfo } from '../formatters/json.js';
5
6
  interface CheckIntegrationOptions {
6
7
  base: string;
7
- json?: boolean;
8
8
  }
9
- export declare function runCheckIntegration(options: CheckIntegrationOptions): Promise<void>;
10
- export {};
9
+ export type { CheckIntegrationOutput, NewFileInfo };
10
+ export declare function runCheckIntegration(options: CheckIntegrationOptions): Promise<CheckIntegrationOutput>;
@@ -4,8 +4,8 @@
4
4
  */
5
5
  import * as path from 'path';
6
6
  import { execFileSync } from 'child_process';
7
- import { outputJson, outputJsonError } from '../formatters/json.js';
8
7
  import { debug } from '../core/index.js';
8
+ import { errorMessage } from '../core/errors.js';
9
9
  /** File extensions we consider "code" that should be imported/referenced */
10
10
  const CODE_EXTENSIONS = new Set([
11
11
  '.ts',
@@ -77,14 +77,8 @@ export async function runCheckIntegration(options) {
77
77
  newFiles = output ? output.split('\n').filter(Boolean) : [];
78
78
  }
79
79
  catch (error) {
80
- const msg = error instanceof Error ? error.message : String(error);
81
- if (options.json) {
82
- outputJsonError(`Failed to run git diff: ${msg}`);
83
- }
84
- else {
85
- console.error(`Error: Failed to run git diff: ${msg}`);
86
- }
87
- process.exit(1);
80
+ const msg = errorMessage(error);
81
+ throw new Error(`Failed to run git diff: ${msg}`, { cause: error });
88
82
  }
89
83
  // Filter to code files, excluding tests, configs, etc.
90
84
  const codeFiles = newFiles.filter((f) => {
@@ -94,14 +88,7 @@ export async function runCheckIntegration(options) {
94
88
  return !IGNORED_PATTERNS.some((p) => p.test(f));
95
89
  });
96
90
  if (codeFiles.length === 0) {
97
- const result = { newFiles: [], unreferencedCount: 0 };
98
- if (options.json) {
99
- outputJson(result);
100
- }
101
- else {
102
- console.log('\nNo new code files to check.');
103
- }
104
- return;
91
+ return { newFiles: [], unreferencedCount: 0 };
105
92
  }
106
93
  // Get all tracked files in the repo for reference checking
107
94
  let allFiles;
@@ -115,7 +102,7 @@ export async function runCheckIntegration(options) {
115
102
  .filter(Boolean);
116
103
  }
117
104
  catch (err) {
118
- // git ls-files failed (e.g. not a git repo) proceed without reference list
105
+ // git ls-files failed (e.g. not a git repo) -- proceed without reference list
119
106
  debug('check-integration', 'git ls-files failed, reference checking will be skipped', err);
120
107
  allFiles = [];
121
108
  }
@@ -147,10 +134,10 @@ export async function runCheckIntegration(options) {
147
134
  }
148
135
  catch (error) {
149
136
  // git grep exit code 1 = no matches (expected), exit code 2+ = real error
150
- const exitCode = error && typeof error === 'object' && 'status' in error ? error.status : null;
151
- if (exitCode !== null && exitCode !== 1) {
152
- const msg = error instanceof Error ? error.message : String(error);
153
- console.error(`Warning: git grep failed for "${pattern}": ${msg}`);
137
+ const exitCode = error && typeof error === 'object' && 'status' in error ? error.status : undefined;
138
+ if (exitCode !== undefined && exitCode !== 1) {
139
+ const msg = errorMessage(error);
140
+ debug('check-integration', `git grep failed for "${pattern}": ${msg}`);
154
141
  }
155
142
  }
156
143
  }
@@ -168,25 +155,5 @@ export async function runCheckIntegration(options) {
168
155
  results.push(info);
169
156
  }
170
157
  const unreferencedCount = results.filter((r) => !r.isIntegrated).length;
171
- const output = { newFiles: results, unreferencedCount };
172
- if (options.json) {
173
- outputJson(output);
174
- }
175
- else {
176
- console.log(`\n🔍 Integration Check (base: ${base})\n`);
177
- console.log(`New files: ${results.length} | Unreferenced: ${unreferencedCount}\n`);
178
- for (const file of results) {
179
- const status = file.isIntegrated ? '✅' : '⚠️';
180
- console.log(`${status} ${file.path}`);
181
- if (file.isIntegrated) {
182
- console.log(` Referenced by: ${file.referencedBy.join(', ')}`);
183
- }
184
- else {
185
- console.log(' Not referenced by any file');
186
- if (file.suggestedEntryPoints && file.suggestedEntryPoints.length > 0) {
187
- console.log(` Suggested entry points: ${file.suggestedEntryPoints.join(', ')}`);
188
- }
189
- }
190
- }
191
- }
158
+ return { newFiles: results, unreferencedCount };
192
159
  }
@@ -2,23 +2,20 @@
2
2
  * Comments, Post, and Claim commands
3
3
  * Handles GitHub comment interactions
4
4
  */
5
+ import { type CommentsOutput, type PostOutput, type ClaimOutput } from '../formatters/json.js';
6
+ export { type CommentsOutput, type PostOutput, type ClaimOutput } from '../formatters/json.js';
5
7
  interface CommentsOptions {
6
8
  prUrl: string;
7
9
  showBots?: boolean;
8
- json?: boolean;
9
10
  }
10
11
  interface PostOptions {
11
12
  url: string;
12
- message?: string;
13
- stdin?: boolean;
14
- json?: boolean;
13
+ message: string;
15
14
  }
16
15
  interface ClaimOptions {
17
16
  issueUrl: string;
18
17
  message?: string;
19
- json?: boolean;
20
18
  }
21
- export declare function runComments(options: CommentsOptions): Promise<void>;
22
- export declare function runPost(options: PostOptions): Promise<void>;
23
- export declare function runClaim(options: ClaimOptions): Promise<void>;
24
- export {};
19
+ export declare function runComments(options: CommentsOptions): Promise<CommentsOutput>;
20
+ export declare function runPost(options: PostOptions): Promise<PostOutput>;
21
+ export declare function runClaim(options: ClaimOptions): Promise<ClaimOutput>;
@@ -2,54 +2,46 @@
2
2
  * Comments, Post, and Claim commands
3
3
  * Handles GitHub comment interactions
4
4
  */
5
- import { getStateManager, getOctokit, parseGitHubUrl, formatRelativeTime, getGitHubToken } from '../core/index.js';
5
+ import { getStateManager, getOctokit, parseGitHubUrl, requireGitHubToken } from '../core/index.js';
6
6
  import { paginateAll } from '../core/pagination.js';
7
- import { outputJson, outputJsonError } from '../formatters/json.js';
8
7
  import { validateUrl, validateMessage } from './validation.js';
9
8
  export async function runComments(options) {
10
9
  validateUrl(options.prUrl);
11
- // Token is guaranteed by the preAction hook in cli.ts for non-LOCAL_ONLY_COMMANDS.
12
- const token = getGitHubToken();
10
+ const token = requireGitHubToken();
13
11
  const stateManager = getStateManager();
14
12
  const octokit = getOctokit(token);
15
13
  // Parse PR URL
16
14
  const parsed = parseGitHubUrl(options.prUrl);
17
15
  if (!parsed || parsed.type !== 'pull') {
18
- if (options.json) {
19
- outputJsonError('Invalid PR URL format');
20
- }
21
- else {
22
- console.error('Invalid PR URL format');
23
- }
24
- process.exit(1);
16
+ throw new Error('Invalid PR URL format');
25
17
  }
26
18
  const { owner, repo, number: pull_number } = parsed;
27
19
  // Get PR details
28
20
  const { data: pr } = await octokit.pulls.get({ owner, repo, pull_number });
29
- // Get review comments (inline code comments)
30
- const reviewComments = await paginateAll((page) => octokit.pulls.listReviewComments({
31
- owner,
32
- repo,
33
- pull_number,
34
- per_page: 100,
35
- page,
36
- }));
37
- // Get issue comments (general PR discussion)
38
- const issueComments = await paginateAll((page) => octokit.issues.listComments({
39
- owner,
40
- repo,
41
- issue_number: pull_number,
42
- per_page: 100,
43
- page,
44
- }));
45
- // Get reviews
46
- const reviews = await paginateAll((page) => octokit.pulls.listReviews({
47
- owner,
48
- repo,
49
- pull_number,
50
- per_page: 100,
51
- page,
52
- }));
21
+ // Fetch review comments, issue comments, and reviews in parallel
22
+ const [reviewComments, issueComments, reviews] = await Promise.all([
23
+ paginateAll((page) => octokit.pulls.listReviewComments({
24
+ owner,
25
+ repo,
26
+ pull_number,
27
+ per_page: 100,
28
+ page,
29
+ })),
30
+ paginateAll((page) => octokit.issues.listComments({
31
+ owner,
32
+ repo,
33
+ issue_number: pull_number,
34
+ per_page: 100,
35
+ page,
36
+ })),
37
+ paginateAll((page) => octokit.pulls.listReviews({
38
+ owner,
39
+ repo,
40
+ pull_number,
41
+ per_page: 100,
42
+ page,
43
+ })),
44
+ ]);
53
45
  // Filter out own comments, optionally show bots
54
46
  const username = stateManager.getState().config.githubUsername;
55
47
  const filterComment = (c) => {
@@ -70,242 +62,100 @@ export async function runComments(options) {
70
62
  const relevantReviews = reviews
71
63
  .filter((r) => filterComment(r) && r.body && r.body.trim())
72
64
  .sort((a, b) => new Date(b.submitted_at || 0).getTime() - new Date(a.submitted_at || 0).getTime());
73
- if (options.json) {
74
- outputJson({
75
- pr: {
76
- title: pr.title,
77
- state: pr.state,
78
- mergeable: pr.mergeable,
79
- head: pr.head.ref,
80
- base: pr.base.ref,
81
- url: pr.html_url,
82
- },
83
- reviews: relevantReviews.map((r) => ({
84
- user: r.user?.login,
85
- state: r.state,
86
- body: r.body,
87
- submittedAt: r.submitted_at,
88
- })),
89
- reviewComments: relevantReviewComments.map((c) => ({
90
- user: c.user?.login,
91
- body: c.body,
92
- path: c.path,
93
- createdAt: c.created_at,
94
- })),
95
- issueComments: relevantIssueComments.map((c) => ({
96
- user: c.user?.login,
97
- body: c.body,
98
- createdAt: c.created_at,
99
- })),
100
- summary: {
101
- reviewCount: relevantReviews.length,
102
- inlineCommentCount: relevantReviewComments.length,
103
- discussionCommentCount: relevantIssueComments.length,
104
- },
105
- });
106
- return;
107
- }
108
- // Text output
109
- console.log(`\n💬 Fetching comments for: ${options.prUrl}\n`);
110
- console.log(`## ${pr.title}\n`);
111
- console.log(`**Status:** ${pr.state} | **Mergeable:** ${pr.mergeable ?? 'checking...'}`);
112
- console.log(`**Branch:** ${pr.head.ref} → ${pr.base.ref}`);
113
- console.log(`**URL:** ${pr.html_url}\n`);
114
- if (relevantReviews.length > 0) {
115
- console.log('### Reviews (newest first)\n');
116
- for (const review of relevantReviews) {
117
- const state = review.state === 'APPROVED' ? '✅' : review.state === 'CHANGES_REQUESTED' ? '❌' : '💬';
118
- const time = review.submitted_at ? formatRelativeTime(review.submitted_at) : '';
119
- console.log(`${state} **@${review.user?.login}** (${review.state}) - ${time}`);
120
- if (review.body) {
121
- console.log(`> ${review.body.split('\n').join('\n> ')}\n`);
122
- }
123
- }
124
- }
125
- if (relevantReviewComments.length > 0) {
126
- console.log('### Inline Comments (newest first)\n');
127
- for (const comment of relevantReviewComments) {
128
- const time = formatRelativeTime(comment.created_at);
129
- console.log(`**@${comment.user?.login}** on \`${comment.path}\` - ${time}`);
130
- console.log(`> ${comment.body.split('\n').join('\n> ')}`);
131
- if (comment.diff_hunk) {
132
- console.log(`\`\`\`diff\n${comment.diff_hunk.slice(-500)}\n\`\`\``);
133
- }
134
- console.log('');
135
- }
136
- }
137
- if (relevantIssueComments.length > 0) {
138
- console.log('### Discussion (newest first)\n');
139
- for (const comment of relevantIssueComments) {
140
- const time = formatRelativeTime(comment.created_at);
141
- console.log(`**@${comment.user?.login}** - ${time}`);
142
- console.log(`> ${comment.body?.split('\n').join('\n> ')}\n`);
143
- }
144
- }
145
- if (relevantReviewComments.length === 0 && relevantIssueComments.length === 0 && relevantReviews.length === 0) {
146
- console.log('No comments from other users.\n');
147
- }
148
- console.log('---');
149
- console.log(`**Summary:** ${relevantReviews.length} reviews, ${relevantReviewComments.length} inline comments, ${relevantIssueComments.length} discussion comments`);
65
+ return {
66
+ pr: {
67
+ title: pr.title,
68
+ state: pr.state,
69
+ mergeable: pr.mergeable,
70
+ head: pr.head.ref,
71
+ base: pr.base.ref,
72
+ url: pr.html_url,
73
+ },
74
+ reviews: relevantReviews.map((r) => ({
75
+ user: r.user?.login,
76
+ state: r.state,
77
+ body: r.body ?? null,
78
+ submittedAt: r.submitted_at ?? null,
79
+ })),
80
+ reviewComments: relevantReviewComments.map((c) => ({
81
+ user: c.user?.login,
82
+ body: c.body,
83
+ path: c.path,
84
+ createdAt: c.created_at,
85
+ })),
86
+ issueComments: relevantIssueComments.map((c) => ({
87
+ user: c.user?.login,
88
+ body: c.body,
89
+ createdAt: c.created_at,
90
+ })),
91
+ summary: {
92
+ reviewCount: relevantReviews.length,
93
+ inlineCommentCount: relevantReviewComments.length,
94
+ discussionCommentCount: relevantIssueComments.length,
95
+ },
96
+ };
150
97
  }
151
98
  export async function runPost(options) {
152
99
  validateUrl(options.url);
153
- // Token is guaranteed by the preAction hook in cli.ts for non-LOCAL_ONLY_COMMANDS.
154
- const token = getGitHubToken();
155
- let message = options.message;
156
- // Read from stdin if specified
157
- if (options.stdin) {
158
- const chunks = [];
159
- for await (const chunk of process.stdin) {
160
- chunks.push(chunk);
161
- }
162
- message = Buffer.concat(chunks).toString('utf-8').trim();
163
- }
164
- if (!message) {
165
- if (options.json) {
166
- outputJsonError('No message provided');
167
- }
168
- else {
169
- console.error('Error: No message provided');
170
- }
171
- process.exit(1);
172
- }
173
- try {
174
- validateMessage(message);
175
- }
176
- catch (error) {
177
- if (options.json) {
178
- outputJsonError(error instanceof Error ? error.message : 'Invalid message');
179
- }
180
- else {
181
- console.error(`Error: ${error instanceof Error ? error.message : 'Invalid message'}`);
182
- }
183
- process.exit(1);
100
+ if (!options.message.trim()) {
101
+ throw new Error('No message provided');
184
102
  }
103
+ validateMessage(options.message);
104
+ const token = requireGitHubToken();
185
105
  // Parse URL
186
106
  const parsed = parseGitHubUrl(options.url);
187
107
  if (!parsed) {
188
- if (options.json) {
189
- outputJsonError('Invalid GitHub URL format');
190
- }
191
- else {
192
- console.error('Invalid GitHub URL format');
193
- }
194
- process.exit(1);
108
+ throw new Error('Invalid GitHub URL format');
195
109
  }
196
110
  const { owner, repo, number } = parsed;
197
111
  const octokit = getOctokit(token);
198
- if (!options.json) {
199
- console.log('\n📝 Posting comment to:', options.url);
200
- console.log('---');
201
- console.log(message);
202
- console.log('---\n');
203
- }
204
- try {
205
- const { data: comment } = await octokit.issues.createComment({
206
- owner,
207
- repo,
208
- issue_number: number,
209
- body: message,
210
- });
211
- if (options.json) {
212
- outputJson({
213
- commentUrl: comment.html_url,
214
- url: options.url,
215
- });
216
- }
217
- else {
218
- console.log('✅ Comment posted successfully!');
219
- console.log(` ${comment.html_url}`);
220
- }
221
- }
222
- catch (error) {
223
- if (options.json) {
224
- outputJsonError(error instanceof Error ? error.message : 'Unknown error');
225
- }
226
- else {
227
- console.error('❌ Failed to post comment:', error instanceof Error ? error.message : error);
228
- }
229
- process.exit(1);
230
- }
112
+ const { data: comment } = await octokit.issues.createComment({
113
+ owner,
114
+ repo,
115
+ issue_number: number,
116
+ body: options.message,
117
+ });
118
+ return {
119
+ commentUrl: comment.html_url,
120
+ url: options.url,
121
+ };
231
122
  }
232
123
  export async function runClaim(options) {
233
124
  validateUrl(options.issueUrl);
234
- // Token is guaranteed by the preAction hook in cli.ts for non-LOCAL_ONLY_COMMANDS.
235
- const token = getGitHubToken();
125
+ const token = requireGitHubToken();
236
126
  // Default claim message or custom
237
127
  const message = options.message || "Hi! I'd like to work on this issue. Could you assign it to me?";
238
- try {
239
- validateMessage(message);
240
- }
241
- catch (error) {
242
- if (options.json) {
243
- outputJsonError(error instanceof Error ? error.message : 'Invalid message');
244
- }
245
- else {
246
- console.error(`Error: ${error instanceof Error ? error.message : 'Invalid message'}`);
247
- }
248
- process.exit(1);
249
- }
128
+ validateMessage(message);
250
129
  // Parse URL
251
130
  const parsed = parseGitHubUrl(options.issueUrl);
252
131
  if (!parsed || parsed.type !== 'issues') {
253
- if (options.json) {
254
- outputJsonError('Invalid issue URL format (must be an issue, not a PR)');
255
- }
256
- else {
257
- console.error('Invalid issue URL format (must be an issue, not a PR)');
258
- }
259
- process.exit(1);
132
+ throw new Error('Invalid issue URL format (must be an issue, not a PR)');
260
133
  }
261
134
  const { owner, repo, number } = parsed;
262
- if (!options.json) {
263
- console.log('\n🙋 Claiming issue:', options.issueUrl);
264
- console.log('---');
265
- console.log(message);
266
- console.log('---\n');
267
- }
268
135
  const octokit = getOctokit(token);
269
- try {
270
- const { data: comment } = await octokit.issues.createComment({
271
- owner,
272
- repo,
273
- issue_number: number,
274
- body: message,
275
- });
276
- // Add to tracked issues
277
- const stateManager = getStateManager();
278
- stateManager.addIssue({
279
- id: number,
280
- url: options.issueUrl,
281
- repo: `${owner}/${repo}`,
282
- number,
283
- title: '(claimed)',
284
- status: 'claimed',
285
- labels: [],
286
- createdAt: new Date().toISOString(),
287
- updatedAt: new Date().toISOString(),
288
- vetted: false,
289
- });
290
- stateManager.save();
291
- if (options.json) {
292
- outputJson({
293
- commentUrl: comment.html_url,
294
- issueUrl: options.issueUrl,
295
- });
296
- }
297
- else {
298
- console.log('✅ Issue claimed!');
299
- console.log(` ${comment.html_url}`);
300
- }
301
- }
302
- catch (error) {
303
- if (options.json) {
304
- outputJsonError(error instanceof Error ? error.message : 'Unknown error');
305
- }
306
- else {
307
- console.error('❌ Failed to claim issue:', error instanceof Error ? error.message : error);
308
- }
309
- process.exit(1);
310
- }
136
+ const { data: comment } = await octokit.issues.createComment({
137
+ owner,
138
+ repo,
139
+ issue_number: number,
140
+ body: message,
141
+ });
142
+ // Add to tracked issues
143
+ const stateManager = getStateManager();
144
+ stateManager.addIssue({
145
+ id: number,
146
+ url: options.issueUrl,
147
+ repo: `${owner}/${repo}`,
148
+ number,
149
+ title: '(claimed)',
150
+ status: 'claimed',
151
+ labels: [],
152
+ createdAt: new Date().toISOString(),
153
+ updatedAt: new Date().toISOString(),
154
+ vetted: false,
155
+ });
156
+ stateManager.save();
157
+ return {
158
+ commentUrl: comment.html_url,
159
+ issueUrl: options.issueUrl,
160
+ };
311
161
  }
@@ -2,10 +2,16 @@
2
2
  * Config command
3
3
  * Shows or updates configuration
4
4
  */
5
+ import type { ConfigOutput } from '../formatters/json.js';
5
6
  interface ConfigOptions {
6
7
  key?: string;
7
8
  value?: string;
8
- json?: boolean;
9
9
  }
10
- export declare function runConfig(options: ConfigOptions): Promise<void>;
10
+ export interface ConfigSetOutput {
11
+ success: true;
12
+ key: string;
13
+ value: string;
14
+ }
15
+ export type ConfigCommandOutput = ConfigOutput | ConfigSetOutput;
16
+ export declare function runConfig(options: ConfigOptions): Promise<ConfigCommandOutput>;
11
17
  export {};
@@ -3,32 +3,15 @@
3
3
  * Shows or updates configuration
4
4
  */
5
5
  import { getStateManager } from '../core/index.js';
6
- import { outputJson, outputJsonError } from '../formatters/json.js';
7
- function exitWithError(msg, json) {
8
- if (json) {
9
- outputJsonError(msg);
10
- }
11
- else {
12
- console.error(msg);
13
- }
14
- process.exit(1);
15
- }
16
6
  export async function runConfig(options) {
17
7
  const stateManager = getStateManager();
18
8
  const currentConfig = stateManager.getState().config;
19
9
  if (!options.key) {
20
10
  // Show current config
21
- if (options.json) {
22
- outputJson({ config: currentConfig });
23
- }
24
- else {
25
- console.log('\n⚙️ Current Configuration:\n');
26
- console.log(JSON.stringify(currentConfig, null, 2));
27
- }
28
- return;
11
+ return { config: currentConfig };
29
12
  }
30
13
  if (!options.value) {
31
- exitWithError('Value required', options.json);
14
+ throw new Error('Value required');
32
15
  }
33
16
  const value = options.value;
34
17
  // Handle specific config keys
@@ -49,7 +32,7 @@ export async function runConfig(options) {
49
32
  case 'exclude-repo': {
50
33
  const parts = value.split('/');
51
34
  if (parts.length !== 2 || !parts[0] || !parts[1]) {
52
- exitWithError(`Invalid repo format "${value}". Use "owner/repo" format. To exclude an entire org, use: config exclude-org ${value}`, options.json);
35
+ throw new Error(`Invalid repo format "${value}". Use "owner/repo" format. To exclude an entire org, use: config exclude-org ${value}`);
53
36
  }
54
37
  const valueLower = value.toLowerCase();
55
38
  if (!currentConfig.excludeRepos.some((r) => r.toLowerCase() === valueLower)) {
@@ -60,7 +43,7 @@ export async function runConfig(options) {
60
43
  }
61
44
  case 'exclude-org': {
62
45
  if (value.includes('/')) {
63
- exitWithError(`Invalid org name "${value}". Use just the org name (e.g., "facebook"), not "owner/repo" format. To exclude a specific repo, use: config exclude-repo ${value}`, options.json);
46
+ throw new Error(`Invalid org name "${value}". Use just the org name (e.g., "facebook"), not "owner/repo" format. To exclude a specific repo, use: config exclude-repo ${value}`);
64
47
  }
65
48
  const currentOrgs = currentConfig.excludeOrgs ?? [];
66
49
  if (!currentOrgs.some((o) => o.toLowerCase() === value.toLowerCase())) {
@@ -70,13 +53,8 @@ export async function runConfig(options) {
70
53
  break;
71
54
  }
72
55
  default:
73
- exitWithError(`Unknown config key: ${options.key}`, options.json);
56
+ throw new Error(`Unknown config key: ${options.key}`);
74
57
  }
75
58
  stateManager.save();
76
- if (options.json) {
77
- outputJson({ success: true, key: options.key, value });
78
- }
79
- else {
80
- console.log(`Set ${options.key} to: ${value}`);
81
- }
59
+ return { success: true, key: options.key, value };
82
60
  }
@@ -6,12 +6,26 @@
6
6
  * Domain logic lives in src/core/daily-logic.ts; this file is a thin
7
7
  * orchestration layer that wires up the phases and handles I/O.
8
8
  */
9
- import { type DailyOutput } from '../formatters/json.js';
9
+ import { type DailyDigest, type CommentedIssue, type PRCheckFailure, type RepoGroup } from '../core/index.js';
10
+ import { type DailyOutput, type CapacityAssessment, type ActionableIssue, type ActionMenu } from '../formatters/json.js';
10
11
  export { computeRepoSignals, groupPRsByRepo, assessCapacity, collectActionableIssues, computeActionMenu, toShelvedPRRef, formatBriefSummary, formatSummary, printDigest, CRITICAL_STATUSES, } from '../core/index.js';
11
- interface DailyOptions {
12
- json?: boolean;
12
+ /**
13
+ * Internal result of the daily check, using full (non-deduplicated) types.
14
+ * Consumed by printDigest() (text mode) and converted to DailyOutput (JSON mode)
15
+ * via toDailyOutput() which deduplicates PR objects.
16
+ */
17
+ export interface DailyCheckResult {
18
+ digest: DailyDigest;
19
+ updates: unknown[];
20
+ capacity: CapacityAssessment;
21
+ summary: string;
22
+ briefSummary: string;
23
+ actionableIssues: ActionableIssue[];
24
+ actionMenu: ActionMenu;
25
+ commentedIssues: CommentedIssue[];
26
+ repoGroups: RepoGroup[];
27
+ failures: PRCheckFailure[];
13
28
  }
14
- export declare function runDaily(options: DailyOptions): Promise<void>;
15
29
  /**
16
30
  * Core daily check logic, extracted for reuse by the startup command.
17
31
  * Fetches all open PRs, updates state, and returns structured output.
@@ -27,3 +41,13 @@ export declare function runDaily(options: DailyOptions): Promise<void>;
27
41
  * 5. generateDigestOutput — capacity, dismiss filter, action menu assembly
28
42
  */
29
43
  export declare function executeDailyCheck(token: string): Promise<DailyOutput>;
44
+ /**
45
+ * Run the daily check and return deduplicated DailyOutput.
46
+ * Errors propagate to the caller.
47
+ */
48
+ export declare function runDaily(): Promise<DailyOutput>;
49
+ /**
50
+ * Run the daily check and return the full (non-deduplicated) result.
51
+ * Used by CLI text mode where printDigest() needs full PR objects.
52
+ */
53
+ export declare function runDailyForDisplay(): Promise<DailyCheckResult>;