@@ -64,11 +64,18 @@ Supports: GitHub Copilot, Claude Code, Cursor, and more.`,
6464var startCmd = & cobra.Command {
6565 Use : "start" ,
6666 Short : "Start the collector daemon" ,
67- Long : "Start the collector daemon to monitor AI agent logs" ,
67+ Long : `Start the collector daemon to monitor AI agent logs.
68+
69+ By default, the collector will sync historical data before starting real-time
70+ watching. Use --no-history to skip historical sync (for power users).` ,
6871 RunE : func (cmd * cobra.Command , args []string ) error {
6972 log .Info ("Starting Devlog Collector..." )
7073 log .Infof ("Version: %s" , version )
7174
75+ // Parse flags
76+ skipHistory , _ := cmd .Flags ().GetBool ("no-history" )
77+ initialSyncDays , _ := cmd .Flags ().GetInt ("initial-sync-days" )
78+
7279 // Load configuration
7380 var err error
7481 cfg , err = config .LoadConfig (configPath )
@@ -159,6 +166,70 @@ var startCmd = &cobra.Command{
159166 return fmt .Errorf ("failed to discover logs: %w" , err )
160167 }
161168
169+ // Create context for graceful shutdown
170+ ctx , cancel := context .WithCancel (context .Background ())
171+ defer cancel ()
172+
173+ // Sync historical data before starting watcher (unless --no-history)
174+ if ! skipHistory {
175+ log .Info ("Syncing historical data..." )
176+
177+ // Initialize hierarchy cache with client for backfill
178+ hierarchyCacheWithClient := hierarchy .NewHierarchyCache (apiClient , log )
179+ backfillRegistry := adapters .DefaultRegistry (cfg .ProjectID , hierarchyCacheWithClient , log )
180+
181+ // Create backfill manager
182+ backfillConfig := backfill.Config {
183+ Registry : backfillRegistry ,
184+ Buffer : buf ,
185+ Client : apiClient ,
186+ StateDBPath : cfg .Buffer .DBPath ,
187+ Logger : log ,
188+ }
189+ manager , err := backfill .NewBackfillManager (backfillConfig )
190+ if err != nil {
191+ log .Warnf ("Failed to create backfill manager, skipping historical sync: %v" , err )
192+ } else {
193+ defer manager .Close ()
194+
195+ // Calculate date range for initial sync
196+ fromDate := time .Now ().AddDate (0 , 0 , - initialSyncDays )
197+ toDate := time .Now ()
198+
199+ totalSynced := 0
200+ totalSkipped := 0
201+
202+ for agentName , logs := range discovered {
203+ adapterName := mapAgentName (agentName )
204+
205+ for _ , logInfo := range logs {
206+ log .Infof ("Syncing historical data for %s: %s" , agentName , logInfo .Path )
207+
208+ bfConfig := backfill.BackfillConfig {
209+ AgentName : adapterName ,
210+ LogPath : logInfo .Path ,
211+ FromDate : fromDate ,
212+ ToDate : toDate ,
213+ BatchSize : 100 ,
214+ }
215+
216+ result , err := manager .Backfill (ctx , bfConfig )
217+ if err != nil {
218+ log .Warnf ("Failed to sync historical data for %s: %v" , logInfo .Path , err )
219+ continue
220+ }
221+
222+ totalSynced += result .ProcessedEvents
223+ totalSkipped += result .SkippedEvents
224+ }
225+ }
226+
227+ log .Infof ("Historical sync complete: %d events synced, %d skipped (already synced)" , totalSynced , totalSkipped )
228+ }
229+ } else {
230+ log .Info ("Skipping historical sync (--no-history flag)" )
231+ }
232+
162233 for agentName , logs := range discovered {
163234 adapterName := mapAgentName (agentName )
164235 adapterInstance , err := registry .Get (adapterName )
@@ -176,9 +247,6 @@ var startCmd = &cobra.Command{
176247 }
177248
178249 // Process events from watcher to client
179- ctx , cancel := context .WithCancel (context .Background ())
180- defer cancel ()
181-
182250 go func () {
183251 for {
184252 select {
@@ -289,9 +357,19 @@ This is useful for capturing development history before the collector was instal
289357
290358var backfillRunCmd = & cobra.Command {
291359 Use : "run" ,
292- Short : "Run backfill operation" ,
293- Long : "Process historical logs for the specified agent and date range" ,
360+ Short : "Run backfill operation (deprecated)" ,
361+ Long : `Process historical logs for the specified agent and date range.
362+
363+ DEPRECATED: The 'start' command now automatically syncs historical data.
364+ Use 'devlog-collector start' instead. To skip historical sync, use 'start --no-history'.` ,
294365 RunE : func (cmd * cobra.Command , args []string ) error {
366+ // Show deprecation warning
367+ fmt .Println ("⚠️ DEPRECATED: The 'backfill run' command is deprecated." )
368+ fmt .Println (" The 'start' command now automatically syncs historical data." )
369+ fmt .Println (" Use 'devlog-collector start' for automatic sync," )
370+ fmt .Println (" or 'devlog-collector start --no-history' to skip historical sync." )
371+ fmt .Println ()
372+
295373 // Load configuration
296374 var err error
297375 cfg , err = config .LoadConfig (configPath )
@@ -577,6 +655,117 @@ func progressBar(percentage float64) string {
577655 return bar
578656}
579657
658+ var syncCmd = & cobra.Command {
659+ Use : "sync" ,
660+ Short : "Manage sync state" ,
661+ Long : "View and manage the synchronization state of agent logs" ,
662+ }
663+
664+ var syncStatusCmd = & cobra.Command {
665+ Use : "status" ,
666+ Short : "Check sync status for all agents" ,
667+ Long : "Display the synchronization status for all discovered agent logs" ,
668+ RunE : func (cmd * cobra.Command , args []string ) error {
669+ // Load configuration
670+ var err error
671+ cfg , err = config .LoadConfig (configPath )
672+ if err != nil {
673+ return fmt .Errorf ("failed to load configuration: %w" , err )
674+ }
675+
676+ // Create backfill manager (uses same state store)
677+ backfillConfig := backfill.Config {
678+ StateDBPath : cfg .Buffer .DBPath ,
679+ Logger : log ,
680+ }
681+ manager , err := backfill .NewBackfillManager (backfillConfig )
682+ if err != nil {
683+ return fmt .Errorf ("failed to create sync manager: %w" , err )
684+ }
685+ defer manager .Close ()
686+
687+ // Get agent filter
688+ agentFilter , _ := cmd .Flags ().GetString ("agent" )
689+
690+ // Discover all agent logs
691+ discovered , err := watcher .DiscoverAllAgentLogs ()
692+ if err != nil {
693+ return fmt .Errorf ("failed to discover logs: %w" , err )
694+ }
695+
696+ fmt .Println ("📊 Sync Status" )
697+ fmt .Println ("==============" )
698+ fmt .Println ()
699+
700+ totalSynced := 0
701+ totalPending := 0
702+ totalInProgress := 0
703+
704+ for agentName , logs := range discovered {
705+ adapterName := mapAgentName (agentName )
706+
707+ // Filter by agent if specified
708+ if agentFilter != "" && agentFilter != agentName && agentFilter != adapterName {
709+ continue
710+ }
711+
712+ fmt .Printf ("🤖 %s (%d log sources)\n " , agentName , len (logs ))
713+
714+ states , err := manager .Status (adapterName )
715+ if err != nil {
716+ fmt .Printf (" ❌ Error getting status: %v\n " , err )
717+ continue
718+ }
719+
720+ // Create a map for quick lookup
721+ stateMap := make (map [string ]* backfill.BackfillState )
722+ for _ , state := range states {
723+ stateMap [state .LogFilePath ] = state
724+ }
725+
726+ for _ , logInfo := range logs {
727+ state , exists := stateMap [logInfo .Path ]
728+ if ! exists {
729+ fmt .Printf (" 📁 %s\n " , logInfo .Path )
730+ fmt .Printf (" Status: ⏳ pending (not yet synced)\n " )
731+ totalPending ++
732+ continue
733+ }
734+
735+ fmt .Printf (" 📁 %s\n " , logInfo .Path )
736+ switch state .Status {
737+ case backfill .StatusCompleted :
738+ fmt .Printf (" Status: ✅ synced (%d events)\n " , state .TotalEventsProcessed )
739+ if state .CompletedAt != nil {
740+ fmt .Printf (" Last sync: %s\n " , state .CompletedAt .Format (time .RFC3339 ))
741+ }
742+ totalSynced ++
743+ case backfill .StatusInProgress :
744+ fmt .Printf (" Status: 🔄 syncing (%d events so far)\n " , state .TotalEventsProcessed )
745+ totalInProgress ++
746+ case backfill .StatusPaused :
747+ fmt .Printf (" Status: ⏸️ paused (%d events)\n " , state .TotalEventsProcessed )
748+ totalPending ++
749+ case backfill .StatusFailed :
750+ fmt .Printf (" Status: ❌ failed: %s\n " , state .ErrorMessage )
751+ totalPending ++
752+ default :
753+ fmt .Printf (" Status: ⏳ pending\n " )
754+ totalPending ++
755+ }
756+ }
757+ fmt .Println ()
758+ }
759+
760+ fmt .Println ("Summary:" )
761+ fmt .Printf (" ✅ Synced: %d\n " , totalSynced )
762+ fmt .Printf (" 🔄 In progress: %d\n " , totalInProgress )
763+ fmt .Printf (" ⏳ Pending: %d\n " , totalPending )
764+
765+ return nil
766+ },
767+ }
768+
580769func init () {
581770 // Configure logging
582771 log .SetFormatter (& logrus.TextFormatter {
@@ -589,11 +778,19 @@ func init() {
589778 rootCmd .AddCommand (versionCmd )
590779 rootCmd .AddCommand (statusCmd )
591780 rootCmd .AddCommand (backfillCmd )
781+ rootCmd .AddCommand (syncCmd )
592782
593783 // Add backfill subcommands
594784 backfillCmd .AddCommand (backfillRunCmd )
595785 backfillCmd .AddCommand (backfillStatusCmd )
596786
787+ // Add sync subcommands
788+ syncCmd .AddCommand (syncStatusCmd )
789+
790+ // Start command flags
791+ startCmd .Flags ().Bool ("no-history" , false , "Skip historical sync (only watch for new events)" )
792+ startCmd .Flags ().Int ("initial-sync-days" , 90 , "Number of days to sync on first run" )
793+
597794 // Backfill run flags
598795 backfillRunCmd .Flags ().StringP ("agent" , "a" , "copilot" , "Agent name (copilot, claude, cursor)" )
599796 backfillRunCmd .Flags ().StringP ("from" , "f" , "" , "Start date (YYYY-MM-DD)" )
@@ -606,6 +803,9 @@ func init() {
606803 // Backfill status flags
607804 backfillStatusCmd .Flags ().StringP ("agent" , "a" , "" , "Agent name to check" )
608805
806+ // Sync status flags
807+ syncStatusCmd .Flags ().StringP ("agent" , "a" , "" , "Filter by agent name" )
808+
609809 // Global flags
610810 rootCmd .PersistentFlags ().StringVarP (& configPath , "config" , "c" ,
611811 "~/.devlog/collector.json" , "Path to configuration file" )
0 commit comments