@oss-autopilot/core 0.43.1 → 0.44.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.bundle.cjs +85 -87
- package/dist/cli.js +0 -17
- package/dist/commands/dashboard-data.d.ts +10 -7
- package/dist/commands/dashboard-data.js +36 -3
- package/dist/commands/dashboard-formatters.d.ts +2 -10
- package/dist/commands/dashboard-formatters.js +2 -16
- package/dist/commands/dashboard-lifecycle.d.ts +1 -1
- package/dist/commands/dashboard-lifecycle.js +1 -1
- package/dist/commands/dashboard-scripts.d.ts +1 -1
- package/dist/commands/dashboard-server.js +26 -47
- package/dist/commands/dashboard-templates.d.ts +3 -2
- package/dist/commands/dashboard-templates.js +3 -2
- package/dist/commands/dashboard.d.ts +5 -9
- package/dist/commands/dashboard.js +7 -111
- package/dist/commands/startup.d.ts +3 -2
- package/dist/commands/startup.js +33 -57
- package/dist/core/daily-logic.js +1 -1
- package/dist/core/utils.d.ts +3 -6
- package/dist/core/utils.js +3 -6
- package/dist/formatters/json.d.ts +3 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -45,7 +45,6 @@ const LOCAL_ONLY_COMMANDS = [
|
|
|
45
45
|
'version',
|
|
46
46
|
'setup',
|
|
47
47
|
'checkSetup',
|
|
48
|
-
'dashboard',
|
|
49
48
|
'serve',
|
|
50
49
|
'parse-issue-list',
|
|
51
50
|
'check-integration',
|
|
@@ -537,20 +536,6 @@ dashboardCmd
|
|
|
537
536
|
handleCommandError(err);
|
|
538
537
|
}
|
|
539
538
|
});
|
|
540
|
-
// Keep bare `dashboard` (no subcommand) for backward compat — generates static HTML
|
|
541
|
-
dashboardCmd
|
|
542
|
-
.option('--open', 'Open in browser')
|
|
543
|
-
.option('--json', 'Output as JSON')
|
|
544
|
-
.option('--offline', 'Use cached data only (no GitHub API calls)')
|
|
545
|
-
.action(async (options) => {
|
|
546
|
-
try {
|
|
547
|
-
const { runDashboard } = await import('./commands/dashboard.js');
|
|
548
|
-
await runDashboard({ open: options.open, json: options.json, offline: options.offline });
|
|
549
|
-
}
|
|
550
|
-
catch (err) {
|
|
551
|
-
handleCommandError(err, options.json);
|
|
552
|
-
}
|
|
553
|
-
});
|
|
554
539
|
// Parse issue list command (#82)
|
|
555
540
|
program
|
|
556
541
|
.command('parse-issue-list <path>')
|
|
@@ -673,8 +658,6 @@ program
|
|
|
673
658
|
else {
|
|
674
659
|
console.log(`OSS Autopilot v${data.version}`);
|
|
675
660
|
console.log(data.daily?.briefSummary ?? '');
|
|
676
|
-
if (data.dashboardPath)
|
|
677
|
-
console.log(`Dashboard: ${data.dashboardPath}`);
|
|
678
661
|
}
|
|
679
662
|
}
|
|
680
663
|
}
|
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dashboard data fetching and aggregation.
|
|
3
3
|
* Handles GitHub API calls, PR grouping, stats computation, and monthly chart data.
|
|
4
|
-
*
|
|
4
|
+
* Consumed by the dashboard HTTP server (dashboard-server.ts) for the SPA API.
|
|
5
5
|
*/
|
|
6
6
|
import type { DailyDigest, AgentState, CommentedIssue } from '../core/types.js';
|
|
7
|
+
export interface DashboardStats {
|
|
8
|
+
activePRs: number;
|
|
9
|
+
shelvedPRs: number;
|
|
10
|
+
mergedPRs: number;
|
|
11
|
+
closedPRs: number;
|
|
12
|
+
mergeRate: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function buildDashboardStats(digest: DailyDigest, state: Readonly<AgentState>): DashboardStats;
|
|
7
15
|
export interface DashboardFetchResult {
|
|
8
16
|
digest: DailyDigest;
|
|
9
17
|
commentedIssues: CommentedIssue[];
|
|
10
18
|
}
|
|
11
|
-
/**
|
|
12
|
-
* Fetch fresh dashboard data from GitHub.
|
|
13
|
-
* Returns the digest and commented issues, updating state as a side effect.
|
|
14
|
-
* Throws if the fetch fails entirely (caller should fall back to cached data).
|
|
15
|
-
*/
|
|
16
19
|
export declare function fetchDashboardData(token: string): Promise<DashboardFetchResult>;
|
|
17
20
|
/**
|
|
18
21
|
* Compute PRs grouped by repository from a digest and state.
|
|
19
|
-
* Used for chart data in
|
|
22
|
+
* Used for chart data in the dashboard API.
|
|
20
23
|
*/
|
|
21
24
|
export declare function computePRsByRepo(digest: DailyDigest, state: Readonly<AgentState>): Record<string, {
|
|
22
25
|
active: number;
|
|
@@ -1,17 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dashboard data fetching and aggregation.
|
|
3
3
|
* Handles GitHub API calls, PR grouping, stats computation, and monthly chart data.
|
|
4
|
-
*
|
|
4
|
+
* Consumed by the dashboard HTTP server (dashboard-server.ts) for the SPA API.
|
|
5
5
|
*/
|
|
6
6
|
import { getStateManager, PRMonitor, IssueConversationMonitor } from '../core/index.js';
|
|
7
|
-
import { errorMessage } from '../core/errors.js';
|
|
7
|
+
import { errorMessage, getHttpStatusCode } from '../core/errors.js';
|
|
8
8
|
import { emptyPRCountsResult } from '../core/github-stats.js';
|
|
9
9
|
import { toShelvedPRRef } from './daily.js';
|
|
10
|
+
export function buildDashboardStats(digest, state) {
|
|
11
|
+
const summary = digest.summary || {
|
|
12
|
+
totalActivePRs: 0,
|
|
13
|
+
totalMergedAllTime: 0,
|
|
14
|
+
mergeRate: 0,
|
|
15
|
+
totalNeedingAttention: 0,
|
|
16
|
+
};
|
|
17
|
+
return {
|
|
18
|
+
activePRs: summary.totalActivePRs,
|
|
19
|
+
shelvedPRs: (digest.shelvedPRs || []).length,
|
|
20
|
+
mergedPRs: summary.totalMergedAllTime,
|
|
21
|
+
closedPRs: Object.values(state.repoScores || {}).reduce((sum, s) => sum + (s.closedWithoutMergeCount || 0), 0),
|
|
22
|
+
mergeRate: `${(summary.mergeRate ?? 0).toFixed(1)}%`,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
10
25
|
/**
|
|
11
26
|
* Fetch fresh dashboard data from GitHub.
|
|
12
27
|
* Returns the digest and commented issues, updating state as a side effect.
|
|
13
28
|
* Throws if the fetch fails entirely (caller should fall back to cached data).
|
|
14
29
|
*/
|
|
30
|
+
function isRateLimitOrAuthError(err) {
|
|
31
|
+
const status = getHttpStatusCode(err);
|
|
32
|
+
if (status === 401 || status === 429)
|
|
33
|
+
return true;
|
|
34
|
+
if (status === 403) {
|
|
35
|
+
const msg = errorMessage(err).toLowerCase();
|
|
36
|
+
return msg.includes('rate limit') || msg.includes('abuse detection');
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
15
40
|
export async function fetchDashboardData(token) {
|
|
16
41
|
const stateManager = getStateManager();
|
|
17
42
|
const prMonitor = new PRMonitor(token);
|
|
@@ -19,18 +44,26 @@ export async function fetchDashboardData(token) {
|
|
|
19
44
|
const [{ prs, failures }, recentlyClosedPRs, recentlyMergedPRs, mergedResult, closedResult, fetchedIssues] = await Promise.all([
|
|
20
45
|
prMonitor.fetchUserOpenPRs(),
|
|
21
46
|
prMonitor.fetchRecentlyClosedPRs().catch((err) => {
|
|
47
|
+
if (isRateLimitOrAuthError(err))
|
|
48
|
+
throw err;
|
|
22
49
|
console.error(`Warning: Failed to fetch recently closed PRs: ${errorMessage(err)}`);
|
|
23
50
|
return [];
|
|
24
51
|
}),
|
|
25
52
|
prMonitor.fetchRecentlyMergedPRs().catch((err) => {
|
|
53
|
+
if (isRateLimitOrAuthError(err))
|
|
54
|
+
throw err;
|
|
26
55
|
console.error(`Warning: Failed to fetch recently merged PRs: ${errorMessage(err)}`);
|
|
27
56
|
return [];
|
|
28
57
|
}),
|
|
29
58
|
prMonitor.fetchUserMergedPRCounts().catch((err) => {
|
|
59
|
+
if (isRateLimitOrAuthError(err))
|
|
60
|
+
throw err;
|
|
30
61
|
console.error(`Warning: Failed to fetch merged PR counts: ${errorMessage(err)}`);
|
|
31
62
|
return emptyPRCountsResult();
|
|
32
63
|
}),
|
|
33
64
|
prMonitor.fetchUserClosedPRCounts().catch((err) => {
|
|
65
|
+
if (isRateLimitOrAuthError(err))
|
|
66
|
+
throw err;
|
|
34
67
|
console.error(`Warning: Failed to fetch closed PR counts: ${errorMessage(err)}`);
|
|
35
68
|
return emptyPRCountsResult();
|
|
36
69
|
}),
|
|
@@ -113,7 +146,7 @@ export async function fetchDashboardData(token) {
|
|
|
113
146
|
}
|
|
114
147
|
/**
|
|
115
148
|
* Compute PRs grouped by repository from a digest and state.
|
|
116
|
-
* Used for chart data in
|
|
149
|
+
* Used for chart data in the dashboard API.
|
|
117
150
|
*/
|
|
118
151
|
export function computePRsByRepo(digest, state) {
|
|
119
152
|
const prsByRepo = {};
|
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Dashboard
|
|
2
|
+
* Dashboard HTML formatting helpers.
|
|
3
|
+
* Used by dashboard-templates.ts and dashboard-components.ts for static HTML generation.
|
|
3
4
|
*/
|
|
4
|
-
import type { DailyDigest, AgentState } from '../core/types.js';
|
|
5
|
-
export interface DashboardStats {
|
|
6
|
-
activePRs: number;
|
|
7
|
-
shelvedPRs: number;
|
|
8
|
-
mergedPRs: number;
|
|
9
|
-
closedPRs: number;
|
|
10
|
-
mergeRate: string;
|
|
11
|
-
}
|
|
12
5
|
/**
|
|
13
6
|
* Escape HTML special characters to prevent XSS when interpolating
|
|
14
7
|
* user-controlled content (e.g. PR titles, comment bodies, author names) into HTML.
|
|
@@ -17,4 +10,3 @@ export interface DashboardStats {
|
|
|
17
10
|
* the URL scheme if the source is untrusted. GitHub API URLs are trusted.
|
|
18
11
|
*/
|
|
19
12
|
export declare function escapeHtml(text: string): string;
|
|
20
|
-
export declare function buildDashboardStats(digest: DailyDigest, state: Readonly<AgentState>): DashboardStats;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Dashboard
|
|
2
|
+
* Dashboard HTML formatting helpers.
|
|
3
|
+
* Used by dashboard-templates.ts and dashboard-components.ts for static HTML generation.
|
|
3
4
|
*/
|
|
4
5
|
/**
|
|
5
6
|
* Escape HTML special characters to prevent XSS when interpolating
|
|
@@ -16,18 +17,3 @@ export function escapeHtml(text) {
|
|
|
16
17
|
.replace(/"/g, '"')
|
|
17
18
|
.replace(/'/g, ''');
|
|
18
19
|
}
|
|
19
|
-
export function buildDashboardStats(digest, state) {
|
|
20
|
-
const summary = digest.summary || {
|
|
21
|
-
totalActivePRs: 0,
|
|
22
|
-
totalMergedAllTime: 0,
|
|
23
|
-
mergeRate: 0,
|
|
24
|
-
totalNeedingAttention: 0,
|
|
25
|
-
};
|
|
26
|
-
return {
|
|
27
|
-
activePRs: summary.totalActivePRs,
|
|
28
|
-
shelvedPRs: (digest.shelvedPRs || []).length,
|
|
29
|
-
mergedPRs: summary.totalMergedAllTime,
|
|
30
|
-
closedPRs: Object.values(state.repoScores || {}).reduce((sum, s) => sum + (s.closedWithoutMergeCount || 0), 0),
|
|
31
|
-
mergeRate: `${(summary.mergeRate ?? 0).toFixed(1)}%`,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
@@ -12,7 +12,7 @@ export interface LaunchResult {
|
|
|
12
12
|
* Launch the interactive dashboard SPA server as a detached background process.
|
|
13
13
|
*
|
|
14
14
|
* Returns the server URL if launched successfully, or null if the SPA assets
|
|
15
|
-
* are not available (
|
|
15
|
+
* are not available (dashboard is skipped).
|
|
16
16
|
*
|
|
17
17
|
* If a server is already running (detected via PID file + health probe),
|
|
18
18
|
* returns its URL without launching a new one.
|
|
@@ -16,7 +16,7 @@ function sleep(ms) {
|
|
|
16
16
|
* Launch the interactive dashboard SPA server as a detached background process.
|
|
17
17
|
*
|
|
18
18
|
* Returns the server URL if launched successfully, or null if the SPA assets
|
|
19
|
-
* are not available (
|
|
19
|
+
* are not available (dashboard is skipped).
|
|
20
20
|
*
|
|
21
21
|
* If a server is already running (detected via PID file + health probe),
|
|
22
22
|
* returns its URL without launching a new one.
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
* Client-side JavaScript for the dashboard: theme toggle, filtering, Chart.js charts.
|
|
3
3
|
*/
|
|
4
4
|
import type { DailyDigest, AgentState } from '../core/types.js';
|
|
5
|
-
import type { DashboardStats } from './dashboard-
|
|
5
|
+
import type { DashboardStats } from './dashboard-data.js';
|
|
6
6
|
/** Generate the Chart.js JavaScript for the dashboard. */
|
|
7
7
|
export declare function generateDashboardScripts(stats: DashboardStats, monthlyMerged: Record<string, number>, monthlyClosed: Record<string, number>, monthlyOpened: Record<string, number>, digest: DailyDigest, state: Readonly<AgentState>): string;
|
|
@@ -11,8 +11,8 @@ import * as path from 'path';
|
|
|
11
11
|
import { getStateManager, getGitHubToken, getDataDir } from '../core/index.js';
|
|
12
12
|
import { errorMessage, ValidationError } from '../core/errors.js';
|
|
13
13
|
import { validateUrl, validateGitHubUrl, validateMessage, PR_URL_PATTERN } from './validation.js';
|
|
14
|
-
import { fetchDashboardData, computePRsByRepo, computeTopRepos, getMonthlyData } from './dashboard-data.js';
|
|
15
|
-
import {
|
|
14
|
+
import { fetchDashboardData, computePRsByRepo, computeTopRepos, getMonthlyData, buildDashboardStats, } from './dashboard-data.js';
|
|
15
|
+
import { openInBrowser } from './startup.js';
|
|
16
16
|
// ── Constants ────────────────────────────────────────────────────────────────
|
|
17
17
|
const VALID_ACTIONS = new Set(['shelve', 'unshelve', 'snooze', 'unsnooze']);
|
|
18
18
|
const MAX_BODY_BYTES = 10_240;
|
|
@@ -109,7 +109,6 @@ export async function findRunningDashboardServer() {
|
|
|
109
109
|
// ── Helpers ────────────────────────────────────────────────────────────────────
|
|
110
110
|
/**
|
|
111
111
|
* Build the JSON payload that the SPA expects from GET /api/data.
|
|
112
|
-
* Same shape as the existing `dashboard --json` output.
|
|
113
112
|
*/
|
|
114
113
|
function buildDashboardJson(digest, state, commentedIssues) {
|
|
115
114
|
const prsByRepo = computePRsByRepo(digest, state);
|
|
@@ -126,6 +125,9 @@ function buildDashboardJson(digest, state, commentedIssues) {
|
|
|
126
125
|
monthlyClosed,
|
|
127
126
|
activePRs: digest.openPRs || [],
|
|
128
127
|
shelvedPRUrls: state.config.shelvedPRUrls || [],
|
|
128
|
+
recentlyMergedPRs: digest.recentlyMergedPRs || [],
|
|
129
|
+
recentlyClosedPRs: digest.recentlyClosedPRs || [],
|
|
130
|
+
autoUnshelvedPRs: digest.autoUnshelvedPRs || [],
|
|
129
131
|
commentedIssues,
|
|
130
132
|
issueResponses,
|
|
131
133
|
};
|
|
@@ -183,25 +185,11 @@ export async function startDashboardServer(options) {
|
|
|
183
185
|
const stateManager = getStateManager();
|
|
184
186
|
const resolvedAssetsDir = path.resolve(assetsDir);
|
|
185
187
|
// ── Cached data ──────────────────────────────────────────────────────────
|
|
186
|
-
|
|
188
|
+
// Start immediately with state.json data (written by the daily check that
|
|
189
|
+
// precedes this server launch). A background GitHub fetch refreshes the
|
|
190
|
+
// cache after the port is bound, so the startup poller sees us in time.
|
|
191
|
+
let cachedDigest = stateManager.getState().lastDigest;
|
|
187
192
|
let cachedCommentedIssues = [];
|
|
188
|
-
// Fetch initial data
|
|
189
|
-
if (token) {
|
|
190
|
-
try {
|
|
191
|
-
console.error('Fetching dashboard data from GitHub...');
|
|
192
|
-
const result = await fetchDashboardData(token);
|
|
193
|
-
cachedDigest = result.digest;
|
|
194
|
-
cachedCommentedIssues = result.commentedIssues;
|
|
195
|
-
}
|
|
196
|
-
catch (error) {
|
|
197
|
-
console.error('Failed to fetch data from GitHub:', error);
|
|
198
|
-
console.error('Falling back to cached data...');
|
|
199
|
-
cachedDigest = stateManager.getState().lastDigest;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
else {
|
|
203
|
-
cachedDigest = stateManager.getState().lastDigest;
|
|
204
|
-
}
|
|
205
193
|
if (!cachedDigest) {
|
|
206
194
|
console.error('No dashboard data available. Run the daily check first:');
|
|
207
195
|
console.error(' GITHUB_TOKEN=$(gh auth token) npm start -- daily');
|
|
@@ -330,9 +318,7 @@ export async function startDashboardServer(options) {
|
|
|
330
318
|
return;
|
|
331
319
|
}
|
|
332
320
|
// Rebuild dashboard data from cached digest + updated state
|
|
333
|
-
|
|
334
|
-
cachedJsonData = buildDashboardJson(cachedDigest, stateManager.getState(), cachedCommentedIssues);
|
|
335
|
-
}
|
|
321
|
+
cachedJsonData = buildDashboardJson(cachedDigest, stateManager.getState(), cachedCommentedIssues);
|
|
336
322
|
sendJson(res, 200, cachedJsonData);
|
|
337
323
|
}
|
|
338
324
|
// ── POST /api/refresh handler ────────────────────────────────────────────
|
|
@@ -446,31 +432,24 @@ export async function startDashboardServer(options) {
|
|
|
446
432
|
writeDashboardServerInfo({ pid: process.pid, port: actualPort, startedAt: new Date().toISOString() });
|
|
447
433
|
const serverUrl = `http://localhost:${actualPort}`;
|
|
448
434
|
console.error(`Dashboard server running at ${serverUrl}`);
|
|
435
|
+
// ── Background refresh ─────────────────────────────────────────────────
|
|
436
|
+
// Port is bound and PID file written — now fetch fresh data from GitHub
|
|
437
|
+
// so subsequent /api/data requests get live data instead of cached state.
|
|
438
|
+
if (token) {
|
|
439
|
+
fetchDashboardData(token)
|
|
440
|
+
.then((result) => {
|
|
441
|
+
cachedDigest = result.digest;
|
|
442
|
+
cachedCommentedIssues = result.commentedIssues;
|
|
443
|
+
cachedJsonData = buildDashboardJson(cachedDigest, stateManager.getState(), cachedCommentedIssues);
|
|
444
|
+
console.error('Background data refresh complete');
|
|
445
|
+
})
|
|
446
|
+
.catch((error) => {
|
|
447
|
+
console.error('Background data refresh failed (serving cached data):', errorMessage(error));
|
|
448
|
+
});
|
|
449
|
+
}
|
|
449
450
|
// ── Open browser ─────────────────────────────────────────────────────────
|
|
450
451
|
if (open) {
|
|
451
|
-
|
|
452
|
-
let openCmd;
|
|
453
|
-
let args;
|
|
454
|
-
switch (process.platform) {
|
|
455
|
-
case 'darwin':
|
|
456
|
-
openCmd = 'open';
|
|
457
|
-
args = [serverUrl];
|
|
458
|
-
break;
|
|
459
|
-
case 'win32':
|
|
460
|
-
openCmd = 'cmd';
|
|
461
|
-
args = ['/c', 'start', '', serverUrl];
|
|
462
|
-
break;
|
|
463
|
-
default:
|
|
464
|
-
openCmd = 'xdg-open';
|
|
465
|
-
args = [serverUrl];
|
|
466
|
-
break;
|
|
467
|
-
}
|
|
468
|
-
execFile(openCmd, args, (error) => {
|
|
469
|
-
if (error) {
|
|
470
|
-
console.error('Failed to open browser:', error.message);
|
|
471
|
-
console.error(`Open manually: ${serverUrl}`);
|
|
472
|
-
}
|
|
473
|
-
});
|
|
452
|
+
openInBrowser(serverUrl);
|
|
474
453
|
}
|
|
475
454
|
// ── Clean shutdown ───────────────────────────────────────────────────────
|
|
476
455
|
const shutdown = () => {
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* Pure functions with no side effects — all data is passed in as arguments.
|
|
7
7
|
*/
|
|
8
8
|
import type { DailyDigest, AgentState, CommentedIssueWithResponse } from '../core/types.js';
|
|
9
|
-
import {
|
|
10
|
-
export { escapeHtml
|
|
9
|
+
import type { DashboardStats } from './dashboard-data.js';
|
|
10
|
+
export { escapeHtml } from './dashboard-formatters.js';
|
|
11
|
+
export { buildDashboardStats, type DashboardStats } from './dashboard-data.js';
|
|
11
12
|
export declare function generateDashboardHtml(stats: DashboardStats, monthlyMerged: Record<string, number>, monthlyClosed: Record<string, number>, monthlyOpened: Record<string, number>, digest: DailyDigest, state: Readonly<AgentState>, issueResponses?: CommentedIssueWithResponse[]): string;
|
|
@@ -9,8 +9,9 @@ import { escapeHtml } from './dashboard-formatters.js';
|
|
|
9
9
|
import { DASHBOARD_CSS } from './dashboard-styles.js';
|
|
10
10
|
import { SVG_ICONS, truncateTitle, renderHealthItems, titleMeta } from './dashboard-components.js';
|
|
11
11
|
import { generateDashboardScripts } from './dashboard-scripts.js';
|
|
12
|
-
// Re-export public API so
|
|
13
|
-
export { escapeHtml
|
|
12
|
+
// Re-export public API so consumers can import from this module
|
|
13
|
+
export { escapeHtml } from './dashboard-formatters.js';
|
|
14
|
+
export { buildDashboardStats } from './dashboard-data.js';
|
|
14
15
|
export function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpened, digest, state, issueResponses = []) {
|
|
15
16
|
const approachingDormantDays = state.config?.approachingDormantDays ?? 25;
|
|
16
17
|
const shelvedPRs = digest.shelvedPRs || [];
|
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Dashboard command —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Dashboard command — serves the interactive Preact SPA dashboard.
|
|
3
|
+
* Also provides writeDashboardFromState() for generating a static HTML fallback
|
|
4
|
+
* when the SPA cannot be launched (e.g., assets not built).
|
|
5
5
|
*/
|
|
6
|
-
interface DashboardOptions {
|
|
7
|
-
open?: boolean;
|
|
8
|
-
json?: boolean;
|
|
9
|
-
offline?: boolean;
|
|
10
|
-
}
|
|
11
|
-
export declare function runDashboard(options: DashboardOptions): Promise<void>;
|
|
12
6
|
/**
|
|
13
7
|
* Generate dashboard HTML from state (no GitHub fetch).
|
|
14
8
|
* Call after executeDailyCheck() which saves fresh data to state.
|
|
15
9
|
* Returns the path to the generated dashboard HTML file.
|
|
10
|
+
*
|
|
11
|
+
* Used as a safety net when the interactive SPA dashboard cannot be launched.
|
|
16
12
|
*/
|
|
17
13
|
export declare function writeDashboardFromState(): string;
|
|
18
14
|
interface ServeOptions {
|
|
@@ -1,124 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Dashboard command —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Dashboard command — serves the interactive Preact SPA dashboard.
|
|
3
|
+
* Also provides writeDashboardFromState() for generating a static HTML fallback
|
|
4
|
+
* when the SPA cannot be launched (e.g., assets not built).
|
|
5
5
|
*/
|
|
6
6
|
import * as fs from 'fs';
|
|
7
7
|
import * as path from 'path';
|
|
8
|
-
import { execFile } from 'child_process';
|
|
9
8
|
import { getStateManager, getDashboardPath, getGitHubToken } from '../core/index.js';
|
|
10
|
-
import {
|
|
11
|
-
import { outputJson } from '../formatters/json.js';
|
|
12
|
-
import { fetchDashboardData, computePRsByRepo, computeTopRepos, getMonthlyData } from './dashboard-data.js';
|
|
9
|
+
import { getMonthlyData } from './dashboard-data.js';
|
|
13
10
|
import { buildDashboardStats, generateDashboardHtml } from './dashboard-templates.js';
|
|
14
|
-
|
|
15
|
-
const stateManager = getStateManager();
|
|
16
|
-
const token = options.offline ? null : getGitHubToken();
|
|
17
|
-
let digest;
|
|
18
|
-
let commentedIssues = [];
|
|
19
|
-
// In offline mode, skip all GitHub API calls
|
|
20
|
-
if (options.offline) {
|
|
21
|
-
const state = stateManager.getState();
|
|
22
|
-
digest = state.lastDigest;
|
|
23
|
-
if (!digest) {
|
|
24
|
-
if (options.json) {
|
|
25
|
-
outputJson({ error: 'No cached data found. Run without --offline first.', offline: true });
|
|
26
|
-
}
|
|
27
|
-
else {
|
|
28
|
-
console.error('No cached data found. Run without --offline first.');
|
|
29
|
-
}
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
const lastUpdated = digest.generatedAt || state.lastDigestAt || state.lastRunAt;
|
|
33
|
-
console.error(`Offline mode: using cached data from ${lastUpdated}`);
|
|
34
|
-
}
|
|
35
|
-
else if (token) {
|
|
36
|
-
console.error('Fetching fresh data from GitHub...');
|
|
37
|
-
try {
|
|
38
|
-
const result = await fetchDashboardData(token);
|
|
39
|
-
digest = result.digest;
|
|
40
|
-
commentedIssues = result.commentedIssues;
|
|
41
|
-
}
|
|
42
|
-
catch (error) {
|
|
43
|
-
console.error('Failed to fetch fresh data:', errorMessage(error));
|
|
44
|
-
console.error('Falling back to cached data (issue conversations unavailable)...');
|
|
45
|
-
digest = stateManager.getState().lastDigest;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
else {
|
|
49
|
-
// No token and not offline — fall back to cached digest with warning
|
|
50
|
-
console.error('Warning: No GitHub token found. Using cached data (may be stale).');
|
|
51
|
-
console.error('Set GITHUB_TOKEN or run `gh auth login` for fresh data.');
|
|
52
|
-
digest = stateManager.getState().lastDigest;
|
|
53
|
-
}
|
|
54
|
-
// Check if we have a digest to display
|
|
55
|
-
if (!digest) {
|
|
56
|
-
if (options.json) {
|
|
57
|
-
outputJson({ error: 'No data available. Run daily check first with GITHUB_TOKEN.' });
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
60
|
-
console.error('No dashboard data available. Run the daily check first:');
|
|
61
|
-
console.error(' GITHUB_TOKEN=$(gh auth token) npm start -- daily');
|
|
62
|
-
}
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
const state = stateManager.getState();
|
|
66
|
-
// Gather data for charts from digest
|
|
67
|
-
const prsByRepo = computePRsByRepo(digest, state);
|
|
68
|
-
const topRepos = computeTopRepos(prsByRepo);
|
|
69
|
-
const { monthlyMerged, monthlyClosed, monthlyOpened } = getMonthlyData(state);
|
|
70
|
-
const stats = buildDashboardStats(digest, state);
|
|
71
|
-
if (options.json) {
|
|
72
|
-
const issueResponses = commentedIssues.filter((i) => i.status === 'new_response');
|
|
73
|
-
const jsonData = {
|
|
74
|
-
stats,
|
|
75
|
-
prsByRepo,
|
|
76
|
-
topRepos: topRepos.map(([repo, data]) => ({ repo, ...data })),
|
|
77
|
-
monthlyMerged,
|
|
78
|
-
activePRs: digest.openPRs || [],
|
|
79
|
-
commentedIssues,
|
|
80
|
-
issueResponses,
|
|
81
|
-
};
|
|
82
|
-
if (options.offline) {
|
|
83
|
-
jsonData.offline = true;
|
|
84
|
-
jsonData.lastUpdated = digest.generatedAt || state.lastDigestAt || state.lastRunAt;
|
|
85
|
-
}
|
|
86
|
-
outputJson(jsonData);
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
const issueResponses = commentedIssues.filter((i) => i.status === 'new_response');
|
|
90
|
-
const html = generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpened, digest, state, issueResponses);
|
|
91
|
-
// Write to file in ~/.oss-autopilot/
|
|
92
|
-
const dashboardPath = getDashboardPath();
|
|
93
|
-
fs.writeFileSync(dashboardPath, html, { mode: 0o644 });
|
|
94
|
-
if (options.offline) {
|
|
95
|
-
const lastUpdated = digest.generatedAt || state.lastDigestAt || state.lastRunAt;
|
|
96
|
-
console.log(`\n📊 Dashboard generated (offline, cached data from ${lastUpdated}): ${dashboardPath}`);
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
console.log(`\n📊 Dashboard generated: ${dashboardPath}`);
|
|
100
|
-
}
|
|
101
|
-
if (options.open) {
|
|
102
|
-
// Use platform-specific open command - path is hardcoded, not user input
|
|
103
|
-
const isWindows = process.platform === 'win32';
|
|
104
|
-
const openCmd = process.platform === 'darwin' ? 'open' : isWindows ? 'cmd' : 'xdg-open';
|
|
105
|
-
const args = isWindows ? ['/c', 'start', '', dashboardPath] : [dashboardPath];
|
|
106
|
-
console.log(`Dashboard: ${dashboardPath}`);
|
|
107
|
-
execFile(openCmd, args, (error) => {
|
|
108
|
-
if (error) {
|
|
109
|
-
console.error('Failed to open browser:', error.message);
|
|
110
|
-
console.error(`Open manually: ${dashboardPath}`);
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
console.log('Run with --open to open in browser');
|
|
116
|
-
}
|
|
117
|
-
}
|
|
11
|
+
// ── Static HTML fallback ────────────────────────────────────────────────────
|
|
118
12
|
/**
|
|
119
13
|
* Generate dashboard HTML from state (no GitHub fetch).
|
|
120
14
|
* Call after executeDailyCheck() which saves fresh data to state.
|
|
121
15
|
* Returns the path to the generated dashboard HTML file.
|
|
16
|
+
*
|
|
17
|
+
* Used as a safety net when the interactive SPA dashboard cannot be launched.
|
|
122
18
|
*/
|
|
123
19
|
export function writeDashboardFromState() {
|
|
124
20
|
const stateManager = getStateManager();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Startup command
|
|
3
3
|
* Combines all pre-flight checks into a single CLI invocation:
|
|
4
|
-
* auth check, setup check, daily fetch, dashboard
|
|
4
|
+
* auth check, setup check, daily fetch, dashboard launch, version detection, issue list detection.
|
|
5
5
|
*
|
|
6
6
|
* Replaces the ~100-line inline bash script in commands/oss.md with a single
|
|
7
7
|
* `node cli.bundle.cjs startup --json` call, reducing UI noise in Claude Code.
|
|
@@ -26,12 +26,13 @@ export declare function countIssueListItems(content: string): {
|
|
|
26
26
|
* Returns IssueListInfo or undefined if no list found.
|
|
27
27
|
*/
|
|
28
28
|
export declare function detectIssueList(): IssueListInfo | undefined;
|
|
29
|
+
export declare function openInBrowser(url: string): void;
|
|
29
30
|
/**
|
|
30
31
|
* Run startup checks and return structured output.
|
|
31
32
|
* Returns StartupOutput with one of three shapes:
|
|
32
33
|
* 1. Setup incomplete: { version, setupComplete: false }
|
|
33
34
|
* 2. Auth failure: { version, setupComplete: true, authError: "..." }
|
|
34
|
-
* 3. Success: { version, setupComplete: true, daily,
|
|
35
|
+
* 3. Success: { version, setupComplete: true, daily, dashboardUrl?, dashboardPath?, issueList? }
|
|
35
36
|
*
|
|
36
37
|
* Errors from the daily check propagate to the caller.
|
|
37
38
|
*/
|