@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
@@ -4,8 +4,10 @@
4
4
  * v2: Fetches fresh data from GitHub if token available, otherwise uses cached lastDigest.
5
5
  */
6
6
  import * as fs from 'fs';
7
+ import * as path from 'path';
7
8
  import { execFile } from 'child_process';
8
9
  import { getStateManager, getDashboardPath, getGitHubToken } from '../core/index.js';
10
+ import { errorMessage } from '../core/errors.js';
9
11
  import { outputJson } from '../formatters/json.js';
10
12
  import { fetchDashboardData, computePRsByRepo, computeTopRepos, getMonthlyData } from './dashboard-data.js';
11
13
  import { buildDashboardStats, generateDashboardHtml } from './dashboard-templates.js';
@@ -38,7 +40,7 @@ export async function runDashboard(options) {
38
40
  commentedIssues = result.commentedIssues;
39
41
  }
40
42
  catch (error) {
41
- console.error('Failed to fetch fresh data:', error instanceof Error ? error.message : error);
43
+ console.error('Failed to fetch fresh data:', errorMessage(error));
42
44
  console.error('Falling back to cached data (issue conversations unavailable)...');
43
45
  digest = stateManager.getState().lastDigest;
44
46
  }
@@ -132,3 +134,51 @@ export function writeDashboardFromState() {
132
134
  fs.chmodSync(dashboardPath, 0o644);
133
135
  return dashboardPath;
134
136
  }
137
+ /**
138
+ * Resolve the SPA assets directory from packages/dashboard/dist/.
139
+ * Tries multiple strategies to locate it across dev (tsx) and bundled (cjs) modes.
140
+ */
141
+ function resolveAssetsDir() {
142
+ // Strategy 1: relative to this source file (works in dev with tsx)
143
+ const devPath = path.resolve(__dirname, '../../dashboard/dist');
144
+ if (fs.existsSync(path.join(devPath, 'index.html'))) {
145
+ return devPath;
146
+ }
147
+ // Strategy 2: relative to the CLI bundle location (packages/core/dist/cli.bundle.cjs)
148
+ const bundlePath = path.resolve(path.dirname(process.argv[1]), '../../dashboard/dist');
149
+ if (fs.existsSync(path.join(bundlePath, 'index.html'))) {
150
+ return bundlePath;
151
+ }
152
+ // Strategy 3: resolve the dashboard package via require.resolve
153
+ try {
154
+ const dashboardPkgPath = require.resolve('@oss-autopilot/dashboard/package.json');
155
+ const dashboardDist = path.join(path.dirname(dashboardPkgPath), 'dist');
156
+ if (fs.existsSync(path.join(dashboardDist, 'index.html'))) {
157
+ return dashboardDist;
158
+ }
159
+ }
160
+ catch (error) {
161
+ const code = error.code;
162
+ if (code !== 'MODULE_NOT_FOUND') {
163
+ console.error('Error resolving dashboard package:', error);
164
+ }
165
+ }
166
+ return null;
167
+ }
168
+ export async function serveDashboard(options) {
169
+ const assetsDir = resolveAssetsDir();
170
+ if (!assetsDir) {
171
+ console.error('Could not find dashboard SPA assets.');
172
+ console.error('Make sure packages/dashboard has been built:');
173
+ console.error(' cd packages/dashboard && pnpm run build');
174
+ process.exit(1);
175
+ }
176
+ const token = getGitHubToken();
177
+ const { startDashboardServer } = await import('./dashboard-server.js');
178
+ await startDashboardServer({
179
+ port: options.port,
180
+ assetsDir,
181
+ token,
182
+ open: options.open,
183
+ });
184
+ }
@@ -4,10 +4,18 @@
4
4
  * Dismissed issues resurface automatically when new responses arrive after the dismiss timestamp.
5
5
  */
6
6
  import { ISSUE_URL_PATTERN } from './validation.js';
7
- interface DismissCommandOptions {
8
- issueUrl: string;
9
- json?: boolean;
7
+ export interface DismissOutput {
8
+ dismissed: boolean;
9
+ url: string;
10
+ }
11
+ export interface UndismissOutput {
12
+ undismissed: boolean;
13
+ url: string;
10
14
  }
11
15
  export { ISSUE_URL_PATTERN };
12
- export declare function runDismiss(options: DismissCommandOptions): Promise<void>;
13
- export declare function runUndismiss(options: DismissCommandOptions): Promise<void>;
16
+ export declare function runDismiss(options: {
17
+ issueUrl: string;
18
+ }): Promise<DismissOutput>;
19
+ export declare function runUndismiss(options: {
20
+ issueUrl: string;
21
+ }): Promise<UndismissOutput>;
@@ -4,46 +4,26 @@
4
4
  * Dismissed issues resurface automatically when new responses arrive after the dismiss timestamp.
5
5
  */
6
6
  import { getStateManager } from '../core/index.js';
7
- import { outputJson } from '../formatters/json.js';
8
7
  import { ISSUE_URL_PATTERN, validateGitHubUrl, validateUrl } from './validation.js';
9
8
  // Re-export for backward compatibility with tests
10
9
  export { ISSUE_URL_PATTERN };
11
10
  export async function runDismiss(options) {
12
11
  validateUrl(options.issueUrl);
13
- validateGitHubUrl(options.issueUrl, ISSUE_URL_PATTERN, 'issue', options.json);
12
+ validateGitHubUrl(options.issueUrl, ISSUE_URL_PATTERN, 'issue');
14
13
  const stateManager = getStateManager();
15
14
  const added = stateManager.dismissIssue(options.issueUrl, new Date().toISOString());
16
15
  if (added) {
17
16
  stateManager.save();
18
17
  }
19
- if (options.json) {
20
- outputJson({ dismissed: added, url: options.issueUrl });
21
- }
22
- else if (added) {
23
- console.log(`Dismissed: ${options.issueUrl}`);
24
- console.log('Issue reply notifications are now muted.');
25
- console.log('New responses after this point will resurface automatically.');
26
- }
27
- else {
28
- console.log('Issue is already dismissed.');
29
- }
18
+ return { dismissed: added, url: options.issueUrl };
30
19
  }
31
20
  export async function runUndismiss(options) {
32
21
  validateUrl(options.issueUrl);
33
- validateGitHubUrl(options.issueUrl, ISSUE_URL_PATTERN, 'issue', options.json);
22
+ validateGitHubUrl(options.issueUrl, ISSUE_URL_PATTERN, 'issue');
34
23
  const stateManager = getStateManager();
35
24
  const removed = stateManager.undismissIssue(options.issueUrl);
36
25
  if (removed) {
37
26
  stateManager.save();
38
27
  }
39
- if (options.json) {
40
- outputJson({ undismissed: removed, url: options.issueUrl });
41
- }
42
- else if (removed) {
43
- console.log(`Undismissed: ${options.issueUrl}`);
44
- console.log('Issue reply notifications are active again.');
45
- }
46
- else {
47
- console.log('Issue was not dismissed.');
48
- }
28
+ return { undismissed: removed, url: options.issueUrl };
49
29
  }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Barrel export for all command functions and their output types.
3
+ * Used by @oss-autopilot/mcp to import command functions directly.
4
+ */
5
+ export { runDaily, runDailyForDisplay, executeDailyCheck } from './daily.js';
6
+ export { runStatus } from './status.js';
7
+ export { runSearch } from './search.js';
8
+ export { runVet } from './vet.js';
9
+ export { runTrack, runUntrack } from './track.js';
10
+ export { runRead } from './read.js';
11
+ export { runComments, runPost, runClaim } from './comments.js';
12
+ export { runConfig } from './config.js';
13
+ export { runInit } from './init.js';
14
+ export { runSetup, runCheckSetup } from './setup.js';
15
+ export { runShelve, runUnshelve } from './shelve.js';
16
+ export { runDismiss, runUndismiss } from './dismiss.js';
17
+ export { runSnooze, runUnsnooze } from './snooze.js';
18
+ export { runStartup } from './startup.js';
19
+ export { runParseList } from './parse-list.js';
20
+ export { runCheckIntegration } from './check-integration.js';
21
+ export { runLocalRepos } from './local-repos.js';
22
+ export type { DailyOutput, SearchOutput, StartupOutput, StatusOutput, TrackOutput } from '../formatters/json.js';
23
+ export type { VetOutput, CommentsOutput, PostOutput, ClaimOutput } from '../formatters/json.js';
24
+ export type { ConfigOutput, ParseIssueListOutput, CheckIntegrationOutput, LocalReposOutput, } from '../formatters/json.js';
25
+ export type { ReadOutput } from './read.js';
26
+ export type { ShelveOutput, UnshelveOutput } from './shelve.js';
27
+ export type { DismissOutput, UndismissOutput } from './dismiss.js';
28
+ export type { SnoozeOutput, UnsnoozeOutput } from './snooze.js';
29
+ export type { UntrackOutput } from './track.js';
30
+ export type { InitOutput } from './init.js';
31
+ export type { ConfigSetOutput, ConfigCommandOutput } from './config.js';
32
+ export type { SetupSetOutput, SetupCompleteOutput, SetupRequiredOutput, SetupOutput, CheckSetupOutput, } from './setup.js';
33
+ export type { DailyCheckResult } from './daily.js';
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Barrel export for all command functions and their output types.
3
+ * Used by @oss-autopilot/mcp to import command functions directly.
4
+ */
5
+ // Command functions
6
+ export { runDaily, runDailyForDisplay, executeDailyCheck } from './daily.js';
7
+ export { runStatus } from './status.js';
8
+ export { runSearch } from './search.js';
9
+ export { runVet } from './vet.js';
10
+ export { runTrack, runUntrack } from './track.js';
11
+ export { runRead } from './read.js';
12
+ export { runComments, runPost, runClaim } from './comments.js';
13
+ export { runConfig } from './config.js';
14
+ export { runInit } from './init.js';
15
+ export { runSetup, runCheckSetup } from './setup.js';
16
+ export { runShelve, runUnshelve } from './shelve.js';
17
+ export { runDismiss, runUndismiss } from './dismiss.js';
18
+ export { runSnooze, runUnsnooze } from './snooze.js';
19
+ export { runStartup } from './startup.js';
20
+ export { runParseList } from './parse-list.js';
21
+ export { runCheckIntegration } from './check-integration.js';
22
+ export { runLocalRepos } from './local-repos.js';
@@ -2,9 +2,10 @@
2
2
  * Init command
3
3
  * Initialize with GitHub username. In v2, PRs are fetched fresh on each daily run.
4
4
  */
5
- interface InitOptions {
5
+ export interface InitOutput {
6
6
  username: string;
7
- json?: boolean;
7
+ message: string;
8
8
  }
9
- export declare function runInit(options: InitOptions): Promise<void>;
10
- export {};
9
+ export declare function runInit(options: {
10
+ username: string;
11
+ }): Promise<InitOutput>;
@@ -3,25 +3,15 @@
3
3
  * Initialize with GitHub username. In v2, PRs are fetched fresh on each daily run.
4
4
  */
5
5
  import { getStateManager } from '../core/index.js';
6
- import { outputJson } from '../formatters/json.js';
7
6
  import { validateGitHubUsername } from './validation.js';
8
7
  export async function runInit(options) {
9
8
  validateGitHubUsername(options.username);
10
9
  const stateManager = getStateManager();
11
- if (!options.json) {
12
- console.log(`\nšŸš€ Initializing for @${options.username}...\n`);
13
- }
14
10
  // Set username in config
15
11
  stateManager.updateConfig({ githubUsername: options.username });
16
12
  stateManager.save();
17
- if (options.json) {
18
- outputJson({
19
- username: options.username,
20
- message: 'Username saved. Run `daily` to fetch your open PRs from GitHub.',
21
- });
22
- }
23
- else {
24
- console.log(`Username set to @${options.username}.`);
25
- console.log('Run `oss-autopilot daily` to fetch your open PRs from GitHub.');
26
- }
13
+ return {
14
+ username: options.username,
15
+ message: 'Username saved. Run `daily` to fetch your open PRs from GitHub.',
16
+ };
27
17
  }
@@ -2,13 +2,12 @@
2
2
  * Local repos command (#84)
3
3
  * Scans configurable directories for local git clones and caches results
4
4
  */
5
- import { type LocalRepoInfo } from '../formatters/json.js';
5
+ import type { LocalReposOutput, LocalRepoInfo } from '../formatters/json.js';
6
6
  interface LocalReposOptions {
7
7
  scan?: boolean;
8
8
  paths?: string[];
9
- json?: boolean;
10
9
  }
11
- /** Scan directories for git repos, returning a map of owner/repo → local path */
10
+ export type { LocalReposOutput, LocalRepoInfo };
11
+ /** Scan directories for git repos, returning a map of owner/repo -> local path */
12
12
  export declare function scanForRepos(scanPaths: string[]): Record<string, LocalRepoInfo>;
13
- export declare function runLocalRepos(options: LocalReposOptions): Promise<void>;
14
- export {};
13
+ export declare function runLocalRepos(options: LocalReposOptions): Promise<LocalReposOutput>;
@@ -7,7 +7,7 @@ import * as path from 'path';
7
7
  import * as os from 'os';
8
8
  import { execFileSync } from 'child_process';
9
9
  import { getStateManager, debug } from '../core/index.js';
10
- import { outputJson } from '../formatters/json.js';
10
+ import { errorMessage } from '../core/errors.js';
11
11
  /** Default directories to scan for local clones */
12
12
  const DEFAULT_SCAN_PATHS = [
13
13
  path.join(os.homedir(), 'Documents', 'oss'),
@@ -56,7 +56,7 @@ function getCurrentBranch(repoPath) {
56
56
  return null;
57
57
  }
58
58
  }
59
- /** Scan directories for git repos, returning a map of owner/repo → local path */
59
+ /** Scan directories for git repos, returning a map of owner/repo -> local path */
60
60
  export function scanForRepos(scanPaths) {
61
61
  const repos = {};
62
62
  for (const scanPath of scanPaths) {
@@ -101,26 +101,14 @@ export async function runLocalRepos(options) {
101
101
  // Use cached data unless --scan is specified
102
102
  if (!options.scan && state.localRepoCache) {
103
103
  const cache = state.localRepoCache;
104
- const result = {
104
+ return {
105
105
  repos: cache.repos,
106
106
  scanPaths: cache.scanPaths,
107
107
  cachedAt: cache.cachedAt,
108
108
  fromCache: true,
109
109
  };
110
- if (options.json) {
111
- outputJson(result);
112
- }
113
- else {
114
- console.log(`\nšŸ“ Local Repos (cached ${cache.cachedAt})\n`);
115
- printRepos(cache.repos);
116
- }
117
- return;
118
- }
119
- if (!options.json) {
120
- console.log(`\nšŸ” Scanning for local repos in ${scanPaths.length} directories...\n`);
121
110
  }
122
111
  const repos = scanForRepos(scanPaths);
123
- const repoCount = Object.keys(repos).length;
124
112
  // Cache the results in state
125
113
  const cachedAt = new Date().toISOString();
126
114
  try {
@@ -128,28 +116,13 @@ export async function runLocalRepos(options) {
128
116
  stateManager.save();
129
117
  }
130
118
  catch (error) {
131
- const msg = error instanceof Error ? error.message : String(error);
132
- console.error(`Warning: Failed to cache scan results: ${msg}`);
119
+ const msg = errorMessage(error);
120
+ debug('local-repos', `Failed to cache scan results: ${msg}`);
133
121
  }
134
- const result = {
122
+ return {
135
123
  repos,
136
124
  scanPaths,
137
125
  cachedAt,
138
126
  fromCache: false,
139
127
  };
140
- if (options.json) {
141
- outputJson(result);
142
- }
143
- else {
144
- console.log(`Found ${repoCount} repos:\n`);
145
- printRepos(repos);
146
- }
147
- }
148
- function printRepos(repos) {
149
- const entries = Object.entries(repos).sort(([a], [b]) => a.localeCompare(b));
150
- for (const [remote, info] of entries) {
151
- const branch = info.currentBranch ? ` (${info.currentBranch})` : '';
152
- console.log(` ${remote}${branch}`);
153
- console.log(` ${info.path}`);
154
- }
155
128
  }
@@ -2,12 +2,11 @@
2
2
  * Parse issue list command (#82)
3
3
  * Parses markdown issue lists into structured JSON with tier classification
4
4
  */
5
- import { type ParseIssueListOutput } from '../formatters/json.js';
5
+ import type { ParseIssueListOutput, ParsedIssueItem } from '../formatters/json.js';
6
6
  interface ParseListOptions {
7
7
  filePath: string;
8
- json?: boolean;
9
8
  }
9
+ export type { ParseIssueListOutput, ParsedIssueItem };
10
10
  /** Parse a markdown string into structured issue items */
11
11
  export declare function parseIssueList(content: string): ParseIssueListOutput;
12
- export declare function runParseList(options: ParseListOptions): Promise<void>;
13
- export {};
12
+ export declare function runParseList(options: ParseListOptions): Promise<ParseIssueListOutput>;
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import * as fs from 'fs';
6
6
  import * as path from 'path';
7
- import { outputJson, outputJsonError } from '../formatters/json.js';
7
+ import { errorMessage } from '../core/errors.js';
8
8
  /** Extract GitHub issue/PR URLs from a markdown line */
9
9
  function extractGitHubUrl(line) {
10
10
  const match = line.match(/https:\/\/github\.com\/([^/]+\/[^/]+)\/issues\/(\d+)/);
@@ -19,7 +19,7 @@ function extractGitHubUrl(line) {
19
19
  }
20
20
  /** Extract issue title from a markdown line (text after URL or checkbox) */
21
21
  function extractTitle(line) {
22
- // Remove markdown link syntax: [title](url) → title
22
+ // Remove markdown link syntax: [title](url) -> title
23
23
  let cleaned = line.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1');
24
24
  // Remove bare URLs
25
25
  cleaned = cleaned.replace(/https?:\/\/\S+/g, '');
@@ -32,7 +32,7 @@ function extractTitle(line) {
32
32
  // Remove "Done" markers
33
33
  cleaned = cleaned.replace(/\b(Done|DONE|done)\b/g, '');
34
34
  // Remove leading/trailing punctuation and whitespace
35
- cleaned = cleaned.replace(/^[\s\-–—:]+/, '').replace(/[\s\-–—:]+$/, '');
35
+ cleaned = cleaned.replace(/^[\s\-\u2013\u2014:]+/, '').replace(/[\s\-\u2013\u2014:]+$/, '');
36
36
  return cleaned.trim();
37
37
  }
38
38
  /** Determine if a line represents a completed item */
@@ -65,7 +65,7 @@ export function parseIssueList(content) {
65
65
  if (!line.trim() || !/^\s*[-*+]|\s*\d+\.|\s*\[[ xX]\]/.test(line)) {
66
66
  continue;
67
67
  }
68
- // Extract GitHub URL — skip lines without one
68
+ // Extract GitHub URL -- skip lines without one
69
69
  const ghUrl = extractGitHubUrl(line);
70
70
  if (!ghUrl)
71
71
  continue;
@@ -94,46 +94,15 @@ export function parseIssueList(content) {
94
94
  export async function runParseList(options) {
95
95
  const filePath = path.resolve(options.filePath);
96
96
  if (!fs.existsSync(filePath)) {
97
- if (options.json) {
98
- outputJsonError(`File not found: ${filePath}`);
99
- }
100
- else {
101
- console.error(`Error: File not found: ${filePath}`);
102
- }
103
- process.exit(1);
97
+ throw new Error(`File not found: ${filePath}`);
104
98
  }
105
99
  let content;
106
100
  try {
107
101
  content = fs.readFileSync(filePath, 'utf-8');
108
102
  }
109
103
  catch (error) {
110
- const msg = error instanceof Error ? error.message : String(error);
111
- if (options.json) {
112
- outputJsonError(`Failed to read file: ${msg}`);
113
- }
114
- else {
115
- console.error(`Error: Failed to read file: ${msg}`);
116
- }
117
- process.exit(1);
118
- }
119
- const result = parseIssueList(content);
120
- if (options.json) {
121
- outputJson(result);
122
- }
123
- else {
124
- console.log(`\nšŸ“‹ Issue List: ${filePath}\n`);
125
- console.log(`Available: ${result.availableCount} | Completed: ${result.completedCount}\n`);
126
- if (result.available.length > 0) {
127
- console.log('--- Available ---');
128
- for (const item of result.available) {
129
- console.log(` [${item.tier}] ${item.repo}#${item.number}: ${item.title}`);
130
- }
131
- }
132
- if (result.completed.length > 0) {
133
- console.log('\n--- Completed ---');
134
- for (const item of result.completed) {
135
- console.log(` [${item.tier}] ${item.repo}#${item.number}: ${item.title}`);
136
- }
137
- }
104
+ const msg = errorMessage(error);
105
+ throw new Error(`Failed to read file: ${msg}`, { cause: error });
138
106
  }
107
+ return parseIssueList(content);
139
108
  }
@@ -3,10 +3,16 @@
3
3
  * In v2, PR read/unread state is not tracked locally.
4
4
  * This command is a no-op preserved for backward compatibility.
5
5
  */
6
- interface ReadOptions {
6
+ export type ReadOutput = {
7
+ markedAsRead: number;
8
+ all: true;
9
+ message: string;
10
+ } | {
11
+ marked: boolean;
12
+ url: string | undefined;
13
+ message: string;
14
+ };
15
+ export declare function runRead(options: {
7
16
  prUrl?: string;
8
17
  all?: boolean;
9
- json?: boolean;
10
- }
11
- export declare function runRead(options: ReadOptions): Promise<void>;
12
- export {};
18
+ }): Promise<ReadOutput>;
@@ -3,31 +3,17 @@
3
3
  * In v2, PR read/unread state is not tracked locally.
4
4
  * This command is a no-op preserved for backward compatibility.
5
5
  */
6
- import { outputJson, outputJsonError } from '../formatters/json.js';
7
6
  import { validateUrl } from './validation.js';
8
7
  export async function runRead(options) {
9
8
  if (!options.all && !options.prUrl) {
10
- if (options.json) {
11
- outputJsonError('PR URL or --all flag required');
12
- }
13
- else {
14
- console.error('Usage: oss-autopilot read <pr-url> or oss-autopilot read --all');
15
- }
16
- process.exit(1);
9
+ throw new Error('PR URL or --all flag required');
17
10
  }
18
11
  if (options.prUrl) {
19
12
  validateUrl(options.prUrl);
20
13
  }
21
14
  // In v2, unread state is not tracked locally — PRs are fetched fresh each run.
22
- if (options.json) {
23
- if (options.all) {
24
- outputJson({ markedAsRead: 0, all: true, message: 'In v2, PR read state is not tracked locally.' });
25
- }
26
- else {
27
- outputJson({ marked: false, url: options.prUrl, message: 'In v2, PR read state is not tracked locally.' });
28
- }
29
- }
30
- else {
31
- console.log('Note: In v2, PR read state is not tracked locally. PRs are fetched fresh on each daily run.');
15
+ if (options.all) {
16
+ return { markedAsRead: 0, all: true, message: 'In v2, PR read state is not tracked locally.' };
32
17
  }
18
+ return { marked: false, url: options.prUrl, message: 'In v2, PR read state is not tracked locally.' };
33
19
  }
@@ -2,9 +2,9 @@
2
2
  * Search command
3
3
  * Searches for new issues to work on
4
4
  */
5
+ import { type SearchOutput } from '../formatters/json.js';
6
+ export { type SearchOutput } from '../formatters/json.js';
5
7
  interface SearchOptions {
6
8
  maxResults: number;
7
- json?: boolean;
8
9
  }
9
- export declare function runSearch(options: SearchOptions): Promise<void>;
10
- export {};
10
+ export declare function runSearch(options: SearchOptions): Promise<SearchOutput>;
@@ -2,73 +2,47 @@
2
2
  * Search command
3
3
  * Searches for new issues to work on
4
4
  */
5
- import { IssueDiscovery, getGitHubToken, getStateManager, DEFAULT_CONFIG } from '../core/index.js';
6
- import { outputJson } from '../formatters/json.js';
5
+ import { IssueDiscovery, requireGitHubToken, getStateManager, DEFAULT_CONFIG } from '../core/index.js';
7
6
  export async function runSearch(options) {
8
- // Token is guaranteed by the preAction hook in cli.ts for non-LOCAL_ONLY_COMMANDS.
9
- const token = getGitHubToken();
7
+ const token = requireGitHubToken();
10
8
  const discovery = new IssueDiscovery(token);
11
- if (!options.json) {
12
- console.log(`\nšŸ” Searching for issues (max ${options.maxResults})...\n`);
13
- }
14
9
  const candidates = await discovery.searchIssues({ maxResults: options.maxResults });
15
- if (options.json) {
16
- const stateManager = getStateManager();
17
- const { config } = stateManager.getState();
18
- const excludedRepos = config.excludeRepos || [];
19
- const aiPolicyBlocklist = config.aiPolicyBlocklist ?? DEFAULT_CONFIG.aiPolicyBlocklist ?? [];
20
- const searchOutput = {
21
- candidates: candidates.map((c) => {
22
- const repoScoreRecord = stateManager.getRepoScore(c.issue.repo);
23
- return {
24
- issue: {
25
- repo: c.issue.repo,
26
- number: c.issue.number,
27
- title: c.issue.title,
28
- url: c.issue.url,
29
- labels: c.issue.labels,
30
- },
31
- recommendation: c.recommendation,
32
- reasonsToApprove: c.reasonsToApprove,
33
- reasonsToSkip: c.reasonsToSkip,
34
- searchPriority: c.searchPriority,
35
- viabilityScore: c.viabilityScore,
36
- repoScore: repoScoreRecord
37
- ? {
38
- score: repoScoreRecord.score,
39
- mergedPRCount: repoScoreRecord.mergedPRCount,
40
- closedWithoutMergeCount: repoScoreRecord.closedWithoutMergeCount,
41
- isResponsive: repoScoreRecord.signals?.isResponsive ?? false,
42
- lastMergedAt: repoScoreRecord.lastMergedAt,
43
- }
44
- : undefined,
45
- };
46
- }),
47
- excludedRepos,
48
- aiPolicyBlocklist,
49
- };
50
- if (discovery.rateLimitWarning) {
51
- searchOutput.rateLimitWarning = discovery.rateLimitWarning;
52
- }
53
- outputJson(searchOutput);
54
- }
55
- else {
56
- if (candidates.length === 0) {
57
- if (discovery.rateLimitWarning) {
58
- console.warn(`\n⚠ ${discovery.rateLimitWarning}\n`);
59
- }
60
- else {
61
- console.log('No matching issues found.');
62
- }
63
- return;
64
- }
65
- if (discovery.rateLimitWarning) {
66
- console.warn(`\n⚠ ${discovery.rateLimitWarning}\n`);
67
- }
68
- console.log(`Found ${candidates.length} candidates:\n`);
69
- for (const candidate of candidates) {
70
- console.log(discovery.formatCandidate(candidate));
71
- console.log('---');
72
- }
10
+ const stateManager = getStateManager();
11
+ const { config } = stateManager.getState();
12
+ const excludedRepos = config.excludeRepos || [];
13
+ const aiPolicyBlocklist = config.aiPolicyBlocklist ?? DEFAULT_CONFIG.aiPolicyBlocklist ?? [];
14
+ const searchOutput = {
15
+ candidates: candidates.map((c) => {
16
+ const repoScoreRecord = stateManager.getRepoScore(c.issue.repo);
17
+ return {
18
+ issue: {
19
+ repo: c.issue.repo,
20
+ number: c.issue.number,
21
+ title: c.issue.title,
22
+ url: c.issue.url,
23
+ labels: c.issue.labels,
24
+ },
25
+ recommendation: c.recommendation,
26
+ reasonsToApprove: c.reasonsToApprove,
27
+ reasonsToSkip: c.reasonsToSkip,
28
+ searchPriority: c.searchPriority,
29
+ viabilityScore: c.viabilityScore,
30
+ repoScore: repoScoreRecord
31
+ ? {
32
+ score: repoScoreRecord.score,
33
+ mergedPRCount: repoScoreRecord.mergedPRCount,
34
+ closedWithoutMergeCount: repoScoreRecord.closedWithoutMergeCount,
35
+ isResponsive: repoScoreRecord.signals?.isResponsive ?? false,
36
+ lastMergedAt: repoScoreRecord.lastMergedAt,
37
+ }
38
+ : undefined,
39
+ };
40
+ }),
41
+ excludedRepos,
42
+ aiPolicyBlocklist,
43
+ };
44
+ if (discovery.rateLimitWarning) {
45
+ searchOutput.rateLimitWarning = discovery.rateLimitWarning;
73
46
  }
47
+ return searchOutput;
74
48
  }