@oss-autopilot/core 0.52.0 → 0.53.0

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/dist/cli.js CHANGED
@@ -9,7 +9,7 @@
9
9
  * handlers so only the invoked command's dependencies are evaluated.
10
10
  */
11
11
  import { Command } from 'commander';
12
- import { getGitHubTokenAsync, enableDebug, debug, getCLIVersion } from './core/index.js';
12
+ import { getGitHubTokenAsync, enableDebug, debug, getCLIVersion, stateFileExists } from './core/index.js';
13
13
  import { commands } from './cli-registry.js';
14
14
  const VERSION = getCLIVersion();
15
15
  const program = new Command();
@@ -51,5 +51,23 @@ program.hook('preAction', async (thisCommand, actionCommand) => {
51
51
  }
52
52
  }
53
53
  });
54
+ // First-run detection: if no subcommand was provided and no state file exists,
55
+ // show a quick-start guide and exit before Commander displays generic help.
56
+ const userArgs = process.argv.slice(2);
57
+ const hasSubcommand = userArgs.some((a) => !a.startsWith('-'));
58
+ const hasHelpOrVersion = userArgs.some((a) => a === '--help' || a === '-h' || a === '--version' || a === '-V');
59
+ if (!hasSubcommand && !hasHelpOrVersion && !stateFileExists()) {
60
+ console.log(`
61
+ OSS Autopilot — AI copilot for open source contributions
62
+
63
+ Looks like this is your first run! Quick start:
64
+ 1. Initialize: oss-autopilot init <github-username>
65
+ 2. Find issues: oss-autopilot search 10
66
+ 3. Daily check: oss-autopilot daily
67
+
68
+ Run oss-autopilot --help for all commands.
69
+ `);
70
+ process.exit(0);
71
+ }
54
72
  // Parse and execute
55
73
  program.parse();
@@ -11,6 +11,11 @@ export interface RateLimitInfo {
11
11
  /** ISO timestamp when the rate limit window resets. */
12
12
  resetAt: string;
13
13
  }
14
+ /** Throttle callbacks used by the Octokit client. Exported for testability. */
15
+ export declare function getRateLimitCallbacks(): {
16
+ onRateLimit: (retryAfter: number, options: unknown, _octokit: unknown, retryCount: number) => boolean;
17
+ onSecondaryRateLimit: (retryAfter: number, options: unknown, _octokit: unknown, retryCount: number) => boolean;
18
+ };
14
19
  export declare function getOctokit(token: string): Octokit;
15
20
  /**
16
21
  * Check the GitHub Search API rate limit quota.
@@ -12,34 +12,39 @@ let _currentToken = null;
12
12
  function formatResetTime(date) {
13
13
  return date.toLocaleTimeString('en-US', { hour12: false });
14
14
  }
15
+ /** Throttle callbacks used by the Octokit client. Exported for testability. */
16
+ export function getRateLimitCallbacks() {
17
+ return {
18
+ onRateLimit: (retryAfter, options, _octokit, retryCount) => {
19
+ const opts = options;
20
+ const resetAt = new Date(Date.now() + retryAfter * 1000);
21
+ if (retryCount < 2) {
22
+ warn(MODULE, `Rate limit hit (retry ${retryCount + 1}/2, waiting ${retryAfter}s, resets at ${formatResetTime(resetAt)}) — ${opts.method} ${opts.url}`);
23
+ return true;
24
+ }
25
+ warn(MODULE, `Rate limit exceeded, not retrying — ${opts.method} ${opts.url} (resets at ${formatResetTime(resetAt)})`);
26
+ return false;
27
+ },
28
+ onSecondaryRateLimit: (retryAfter, options, _octokit, retryCount) => {
29
+ const opts = options;
30
+ const resetAt = new Date(Date.now() + retryAfter * 1000);
31
+ if (retryCount < 1) {
32
+ warn(MODULE, `Secondary rate limit hit (retry ${retryCount + 1}/1, waiting ${retryAfter}s, resets at ${formatResetTime(resetAt)}) — ${opts.method} ${opts.url}`);
33
+ return true;
34
+ }
35
+ warn(MODULE, `Secondary rate limit exceeded, not retrying — ${opts.method} ${opts.url} (resets at ${formatResetTime(resetAt)})`);
36
+ return false;
37
+ },
38
+ };
39
+ }
15
40
  export function getOctokit(token) {
16
41
  // Return cached instance only if token matches
17
42
  if (_octokit && _currentToken === token)
18
43
  return _octokit;
44
+ const callbacks = getRateLimitCallbacks();
19
45
  _octokit = new ThrottledOctokit({
20
46
  auth: token,
21
- throttle: {
22
- onRateLimit: (retryAfter, options, _octokit, retryCount) => {
23
- const opts = options;
24
- const resetAt = new Date(Date.now() + retryAfter * 1000);
25
- if (retryCount < 2) {
26
- warn(MODULE, `Rate limit hit (retry ${retryCount + 1}/2, waiting ${retryAfter}s, resets at ${formatResetTime(resetAt)}) — ${opts.method} ${opts.url}`);
27
- return true;
28
- }
29
- warn(MODULE, `Rate limit exceeded, not retrying — ${opts.method} ${opts.url} (resets at ${formatResetTime(resetAt)})`);
30
- return false;
31
- },
32
- onSecondaryRateLimit: (retryAfter, options, _octokit, retryCount) => {
33
- const opts = options;
34
- const resetAt = new Date(Date.now() + retryAfter * 1000);
35
- if (retryCount < 1) {
36
- warn(MODULE, `Secondary rate limit hit (retry ${retryCount + 1}/1, waiting ${retryAfter}s, resets at ${formatResetTime(resetAt)}) — ${opts.method} ${opts.url}`);
37
- return true;
38
- }
39
- warn(MODULE, `Secondary rate limit exceeded, not retrying — ${opts.method} ${opts.url} (resets at ${formatResetTime(resetAt)})`);
40
- return false;
41
- },
42
- },
47
+ throttle: callbacks,
43
48
  });
44
49
  _currentToken = token;
45
50
  return _octokit;
@@ -8,7 +8,7 @@ export { IssueDiscovery, type IssueCandidate, type SearchPriority, isDocOnlyIssu
8
8
  export { IssueConversationMonitor } from './issue-conversation.js';
9
9
  export { isBotAuthor, isAcknowledgmentComment } from './comment-utils.js';
10
10
  export { getOctokit, checkRateLimit, type RateLimitInfo } from './github.js';
11
- export { parseGitHubUrl, daysBetween, splitRepo, isOwnRepo, getCLIVersion, getDataDir, getStatePath, getBackupDir, getCacheDir, formatRelativeTime, byDateDescending, getGitHubToken, getGitHubTokenAsync, requireGitHubToken, resetGitHubTokenCache, detectGitHubUsername, DEFAULT_CONCURRENCY, } from './utils.js';
11
+ export { parseGitHubUrl, daysBetween, splitRepo, isOwnRepo, getCLIVersion, getDataDir, getStatePath, getBackupDir, getCacheDir, formatRelativeTime, byDateDescending, getGitHubToken, getGitHubTokenAsync, requireGitHubToken, resetGitHubTokenCache, detectGitHubUsername, stateFileExists, DEFAULT_CONCURRENCY, } from './utils.js';
12
12
  export { OssAutopilotError, ConfigurationError, ValidationError, errorMessage, getHttpStatusCode, isRateLimitError, isRateLimitOrAuthError, } from './errors.js';
13
13
  export { enableDebug, isDebugEnabled, debug, info, warn, timed } from './logger.js';
14
14
  export { HttpCache, getHttpCache, cachedRequest, type CacheEntry } from './http-cache.js';
@@ -8,7 +8,7 @@ export { IssueDiscovery, isDocOnlyIssue, applyPerRepoCap, DOC_ONLY_LABELS, } fro
8
8
  export { IssueConversationMonitor } from './issue-conversation.js';
9
9
  export { isBotAuthor, isAcknowledgmentComment } from './comment-utils.js';
10
10
  export { getOctokit, checkRateLimit } from './github.js';
11
- export { parseGitHubUrl, daysBetween, splitRepo, isOwnRepo, getCLIVersion, getDataDir, getStatePath, getBackupDir, getCacheDir, formatRelativeTime, byDateDescending, getGitHubToken, getGitHubTokenAsync, requireGitHubToken, resetGitHubTokenCache, detectGitHubUsername, DEFAULT_CONCURRENCY, } from './utils.js';
11
+ export { parseGitHubUrl, daysBetween, splitRepo, isOwnRepo, getCLIVersion, getDataDir, getStatePath, getBackupDir, getCacheDir, formatRelativeTime, byDateDescending, getGitHubToken, getGitHubTokenAsync, requireGitHubToken, resetGitHubTokenCache, detectGitHubUsername, stateFileExists, DEFAULT_CONCURRENCY, } from './utils.js';
12
12
  export { OssAutopilotError, ConfigurationError, ValidationError, errorMessage, getHttpStatusCode, isRateLimitError, isRateLimitOrAuthError, } from './errors.js';
13
13
  export { enableDebug, isDebugEnabled, debug, info, warn, timed } from './logger.js';
14
14
  export { HttpCache, getHttpCache, cachedRequest } from './http-cache.js';
@@ -246,6 +246,12 @@ export declare function resetGitHubTokenCache(): void;
246
246
  * }
247
247
  */
248
248
  export declare function getGitHubTokenAsync(): Promise<string | null>;
249
+ /**
250
+ * Check whether the state file exists without creating the data directory.
251
+ * Used for first-run detection in the CLI — we don't want to create
252
+ * `~/.oss-autopilot/` just to check if the user has ever run the tool.
253
+ */
254
+ export declare function stateFileExists(): boolean;
249
255
  /**
250
256
  * Detect the authenticated GitHub username via the `gh` CLI.
251
257
  *
@@ -439,6 +439,15 @@ export async function getGitHubTokenAsync() {
439
439
  * (but not consecutive ones and not at the end), and be 1-39 characters.
440
440
  */
441
441
  const GITHUB_USERNAME_RE = /^[a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38}$/;
442
+ /**
443
+ * Check whether the state file exists without creating the data directory.
444
+ * Used for first-run detection in the CLI — we don't want to create
445
+ * `~/.oss-autopilot/` just to check if the user has ever run the tool.
446
+ */
447
+ export function stateFileExists() {
448
+ const stateFile = path.join(os.homedir(), '.oss-autopilot', 'state.json');
449
+ return fs.existsSync(stateFile);
450
+ }
442
451
  /**
443
452
  * Detect the authenticated GitHub username via the `gh` CLI.
444
453
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oss-autopilot/core",
3
- "version": "0.52.0",
3
+ "version": "0.53.0",
4
4
  "description": "CLI and core library for managing open source contributions",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,7 +21,9 @@
21
21
  }
22
22
  },
23
23
  "files": [
24
- "dist/"
24
+ "dist/",
25
+ "!dist/**/*.map",
26
+ "!dist/core/test-utils.*"
25
27
  ],
26
28
  "keywords": [
27
29
  "open-source",