1+ package org .example .cli ;
2+
3+ import org .example .event .EventEmitter ;
4+ import org .example .github .GitHubClient ;
5+ import org .example .monitor .MonitorEngine ;
6+ import org .example .state .MonitorState ;
7+ import org .example .state .StateStore ;
8+ import org .slf4j .Logger ;
9+ import org .slf4j .LoggerFactory ;
10+ import picocli .CommandLine ;
11+
12+ import java .nio .file .Path ;
13+ import java .nio .file .Paths ;
14+ import java .util .concurrent .Callable ;
15+
16+ @ CommandLine .Command (
17+ name = "gha-watch" ,
18+ version = "GHAWatch 1.0.0" ,
19+ description = "Monitor GithubActions workflow runs and print one-line events to stdout."
20+ )
21+ public class Main implements Callable <Integer > {
22+
23+ private static final Logger log = LoggerFactory .getLogger (Main .class );
24+
25+ @ CommandLine .Parameters (index = "0" , paramLabel = "<owner/repo>" ,
26+ description = "Repository in the form owner/repo" )
27+ private String repoArg ;
28+
29+ @ CommandLine .Option (names = {"--token" }, description = "Github personal access token (overrides GITHUB_TOKEN env)" )
30+ private String tokenOpt ;
31+
32+ @ CommandLine .Option (names = {"--state" }, description = "Path to the state file (default: ~/.gha-watch/<owner>/<repo>/state.json)" )
33+ private String stateFileOpt ;
34+ @ CommandLine .Option (names = {"--interval" }, description = "Polling interval in seconds (default: ${DEFAULT-VALUE})" , defaultValue = "10" )
35+
36+ private int intervalSeconds ;
37+ @ CommandLine .Option (names = {"--verbose" }, description = "Enable verbose (DEBUG) logging" )
38+ private boolean verbose ;
39+ @ CommandLine .Option (names = {"--since-seconds" }, description = "When first run: look back this many seconds to emit recent completion events (default: 0)" , defaultValue = "0" )
40+ private long sinceSeconds ;
41+
42+ public static void main (String [] args ) {
43+
44+ int exit = new CommandLine (new Main ()).execute (args );
45+ System .exit (exit );
46+ }
47+
48+ @ Override
49+ public Integer call () throws Exception {
50+
51+ configureLogging (verbose );
52+
53+ System .out .println ("GHAWatch v1.0.0 - Github Actions Monitor" );
54+ System .out .println ("JETBRAINS" );
55+ System .out .println ("-----------------------------------------" );
56+
57+ if (repoArg == null || !repoArg .contains ("/" )) {
58+ System .err .println ("Repository must be specified in the form: owner/repo" );
59+ return 2 ;
60+ }
61+
62+ String [] parts = repoArg .split ("/" , 2 );
63+ String owner = parts [0 ];
64+ String repo = parts [1 ];
65+
66+ if (owner .isEmpty () || repo .isEmpty ()) {
67+ System .err .println ("Repository must be specified in the form: owner/repo" );
68+ return 2 ;
69+ }
70+
71+ String token = resolveToken (tokenOpt );
72+ if (token == null || token .isBlank ()) {
73+ System .err .println ("ERROR: GitHub token not provided. Use --token or set GITHUB_TOKEN environment variable." );
74+ return 3 ;
75+ }
76+
77+ Path statePath = resolveStateFilePath (stateFileOpt , owner , repo );
78+ log .info ("Using state file: {}" , statePath .toAbsolutePath ());
79+
80+ GitHubClient client = new GitHubClient (token );
81+ StateStore store = new StateStore (statePath .toString ());
82+ MonitorState state = store .load ();
83+
84+ if (sinceSeconds > 0 && state .getLastProcessedRunId () == 0 ) {
85+ // We won't fetch by timestamp; this flag will be used in Phase 6 to decide lookback;
86+ log .info ("First run lookback requested: {} seconds — (will be applied if implemented)" , sinceSeconds );
87+ }
88+
89+ EventEmitter emitter = new EventEmitter ();
90+ MonitorEngine engine = new MonitorEngine (
91+ client , store , emitter , owner , repo , intervalSeconds * 1000L
92+ );
93+
94+ try {
95+ engine .start ();
96+ return 0 ;
97+ } catch (Exception e ) {
98+ log .error ("Fatal error: {}" , e .getMessage (), e );
99+ System .err .println ("Fatal error: " + e .getMessage ());
100+ return 1 ;
101+ }
102+ }
103+
104+ private void configureLogging (boolean verbose ) {
105+ if (verbose ) {
106+ System .setProperty ("org.slf4j.simpleLogger.defaultLogLevel" , "debug" );
107+ System .out .println ("[verbose] debug logging enabled" );
108+ } else {
109+ System .setProperty ("org.slf4j.simpleLogger.defaultLogLevel" , "info" );
110+ }
111+
112+ System .setProperty ("org.slf4j.simpleLogger.showDateTime" , "true" );
113+ System .setProperty ("org.slf4j.simpleLogger.dateTimeFormat" , "yyyy-MM-dd'T'HH:mm:ss" );
114+ }
115+
116+ private String resolveToken (String tokenOpt ) {
117+ if (tokenOpt != null && !tokenOpt .isBlank ()) return tokenOpt ;
118+ String env = System .getenv ("GITHUB_TOKEN" );
119+ if (env != null && !env .isBlank ()) return env ;
120+ String env2 = System .getenv ("GH_TOKEN" );
121+ if (env2 != null && !env2 .isBlank ()) return env2 ;
122+ return null ;
123+ }
124+
125+ private Path resolveStateFilePath (String explicit , String owner , String repo ) {
126+ if (explicit != null && !explicit .isBlank ()) {
127+ return Paths .get (explicit );
128+ }
129+
130+ String home = System .getProperty ("user.home" );
131+ return Paths .get (home , ".gha-watch" , owner , repo , "state.json" );
132+ }
133+ }
0 commit comments