Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions cmd/gdocs-cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func main() {
initFlag := flag.Bool("init", false, "Initialize OAuth and save token to default location")
cleanFlag := flag.Bool("clean", false, "Clean output (suppress all logs, only output markdown)")
commentsFlag := flag.Bool("comments", false, "Include document comments in the markdown output")
metaFlag := flag.Bool("meta", false, "Show document metadata (title, tabs) and exit")
instructionFlag := flag.Bool("instruction", false, "Print integration instructions for AI coding agents")
flag.Parse()

Expand Down Expand Up @@ -66,6 +67,15 @@ func main() {
os.Exit(1)
}

// Handle meta mode
if *metaFlag {
if err := showMeta(*urlFlag, configPath); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
return
}
Comment on lines +70 to +77
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

--clean and --meta should be mutually exclusive.

At Line 38, --clean promises markdown-only stdout, but Line 72 routes to showMeta, which prints non-markdown output. This breaks clean-mode piping expectations.

💡 Proposed fix
 	// Handle clean mode - suppress all logs
 	if *cleanFlag {
 		log.SetOutput(io.Discard)
 	}
+
+	// Validate incompatible output modes
+	if *cleanFlag && *metaFlag {
+		fmt.Fprintln(os.Stderr, "Error: --clean and --meta cannot be used together")
+		os.Exit(1)
+	}

As per coding guidelines: "The --clean flag should only output markdown to stdout, making it easy to pipe output directly into AI systems or other tools".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Handle meta mode
if *metaFlag {
if err := showMeta(*urlFlag, configPath); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
return
}
// Handle clean mode - suppress all logs
if *cleanFlag {
log.SetOutput(io.Discard)
}
// Validate incompatible output modes
if *cleanFlag && *metaFlag {
fmt.Fprintln(os.Stderr, "Error: --clean and --meta cannot be used together")
os.Exit(1)
}
// Handle meta mode
if *metaFlag {
if err := showMeta(*urlFlag, configPath); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
return
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/gdocs-cli/main.go` around lines 70 - 77, Make --clean and --meta mutually
exclusive by validating flags early: if *cleanFlag && *metaFlag then print a
clear error to stderr and exit non-zero. Update main's flag-handling logic so
the exclusivity check runs before any branch (before calling showMeta or the
normal output path), and ensure showMeta is never called when cleanFlag is set;
use the existing metaFlag, cleanFlag, and showMeta symbols to locate the code to
change.


// Run the main logic
if err := run(*urlFlag, configPath, *commentsFlag); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
Expand Down Expand Up @@ -184,6 +194,60 @@ func initAuth(credPath string) error {
return nil
}

// showMeta fetches a document and prints metadata: title, ID, and tabs.
func showMeta(docURL, credPath string) error {
ctx := context.Background()

docID, err := gdocs.ExtractDocumentID(docURL)
if err != nil {
return fmt.Errorf("invalid URL: %w", err)
}

authenticator, err := auth.NewAuthenticator(credPath)
if err != nil {
return fmt.Errorf("authentication setup failed: %w", err)
}

httpClient, err := authenticator.GetClient(ctx)
if err != nil {
return fmt.Errorf("authentication failed: %w", err)
}

client, err := gdocs.NewClient(ctx, httpClient)
if err != nil {
return fmt.Errorf("failed to create Docs client: %w", err)
}

doc, err := client.FetchDocument(docID)
if err != nil {
return fmt.Errorf("failed to fetch document: %w", err)
}

fmt.Printf("Title: %s\n", doc.Title)
fmt.Printf("Document ID: %s\n", doc.DocumentId)
if doc.RevisionId != "" {
fmt.Printf("Revision ID: %s\n", doc.RevisionId)
}

tabs := gdocs.ListTabs(doc)
if len(tabs) > 0 {
fmt.Printf("\nTabs (%d):\n", len(tabs))
for _, tab := range tabs {
indent := ""
for i := 0; i < tab.Depth; i++ {
indent += " "
}
emoji := ""
if tab.Emoji != "" {
emoji = tab.Emoji + " "
}
fmt.Printf(" %s%s%s\t(tab=%s)\n", indent, emoji, tab.Title, tab.ID)
}
}

return nil
}

// getDefaultConfigPath returns the default path for the config file.
func getDefaultConfigPath() (string, error) {
configDir, err := auth.EnsureConfigDir()
Expand Down
37 changes: 37 additions & 0 deletions internal/gdocs/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,40 @@ func GetFirstTab(doc *docs.Document) *docs.Tab {
}
return doc.Tabs[0]
}

// TabInfo holds metadata about a tab for listing purposes.
type TabInfo struct {
ID string
Title string
Emoji string
Depth int
}

// ListTabs returns all tabs in the document as a flat list with depth info.
func ListTabs(doc *docs.Document) []TabInfo {
if doc == nil || doc.Tabs == nil {
return nil
}

var tabs []TabInfo
for _, tab := range doc.Tabs {
collectTabs(tab, 0, &tabs)
}
return tabs
}

func collectTabs(tab *docs.Tab, depth int, tabs *[]TabInfo) {
if tab == nil {
return
}
info := TabInfo{Depth: depth}
if tab.TabProperties != nil {
info.ID = tab.TabProperties.TabId
info.Title = tab.TabProperties.Title
info.Emoji = tab.TabProperties.IconEmoji
}
*tabs = append(*tabs, info)
for _, child := range tab.ChildTabs {
collectTabs(child, depth+1, tabs)
}
}
Loading