From 4b9c99e92f8a19a88187bb746e97910b8ea0cba7 Mon Sep 17 00:00:00 2001 From: Scott Carleton <313254+ScotterC@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:50:29 -0400 Subject: [PATCH] feat: add --meta flag to show document metadata and tabs Adds a --meta flag that displays document title, ID, revision, and all tabs with their IDs. Useful for discovering tab IDs before fetching specific tab content. Co-Authored-By: Claude Opus 4.6 (1M context) --- cmd/gdocs-cli/main.go | 64 ++++++++++++++++++++++++++++++++++++++++ internal/gdocs/client.go | 37 +++++++++++++++++++++++ 2 files changed, 101 insertions(+) diff --git a/cmd/gdocs-cli/main.go b/cmd/gdocs-cli/main.go index b152d20..01f7e18 100644 --- a/cmd/gdocs-cli/main.go +++ b/cmd/gdocs-cli/main.go @@ -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() @@ -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 + } + // Run the main logic if err := run(*urlFlag, configPath, *commentsFlag); err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) @@ -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() diff --git a/internal/gdocs/client.go b/internal/gdocs/client.go index 8b4da64..afd5aa4 100644 --- a/internal/gdocs/client.go +++ b/internal/gdocs/client.go @@ -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) + } +}