package cobra import ( "encoding/json" "fmt" "os" "time" "github.com/spf13/cobra" "github.com/claudia/docs-cli/internal/api" "github.com/claudia/docs-cli/internal/auth" "github.com/claudia/docs-cli/internal/config" "github.com/claudia/docs-cli/internal/output" "github.com/claudia/docs-cli/pkg/types" ) var ( server string token string outputFormat string quiet bool verbose bool ) var rootCmd = &cobra.Command{ Use: "claudia-docs", Short: "Claudia Docs CLI - Interact with Claudia Docs from the command line", Long: `Claudia Docs CLI allows you to manage documents, projects, folders, tags, and more.`, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { return initConfig() }, } func Execute() error { return rootCmd.Execute() } func initConfig() error { cfg, err := config.Load() if err != nil { return err } config.UpdateFromFlags(server, token, outputFormat, quiet, verbose) _ = cfg return nil } func init() { rootCmd.PersistentFlags().StringVar(&server, "server", "", "API server URL") rootCmd.PersistentFlags().StringVar(&token, "token", "", "JWT token") rootCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", "json", "Output format: json, text") rootCmd.PersistentFlags().BoolVar(&quiet, "quiet", false, "Suppress stdout, only JSON") rootCmd.PersistentFlags().BoolVar(&verbose, "verbose", false, "Verbose output") // Auth commands rootCmd.AddCommand(authCmd) // Document commands rootCmd.AddCommand(docCmd) // Project commands rootCmd.AddCommand(projectCmd) // Folder commands rootCmd.AddCommand(folderCmd) // Tag commands rootCmd.AddCommand(tagCmd) // Search command rootCmd.AddCommand(searchCmd) // Reasoning command rootCmd.AddCommand(reasoningCmd) } // Auth command group var authCmd = &cobra.Command{ Use: "auth", Short: "Authentication commands", } var authLoginCmd = &cobra.Command{ Use: "login", Short: "Login to Claudia Docs", RunE: runAuthLogin, } var authLogoutCmd = &cobra.Command{ Use: "logout", Short: "Logout and clear saved token", RunE: runAuthLogout, } var authStatusCmd = &cobra.Command{ Use: "status", Short: "Show authentication status", RunE: runAuthStatus, } func init() { authCmd.AddCommand(authLoginCmd, authLogoutCmd, authStatusCmd) authLoginCmd.Flags().StringP("username", "u", "", "Username (required)") authLoginCmd.Flags().StringP("password", "p", "", "Password (required)") authLoginCmd.Flags().Bool("save", false, "Save token to config file") authLoginCmd.MarkFlagRequired("username") authLoginCmd.MarkFlagRequired("password") } func runAuthLogin(cmd *cobra.Command, args []string) error { username, _ := cmd.Flags().GetString("username") password, _ := cmd.Flags().GetString("password") save, _ := cmd.Flags().GetBool("save") client, err := api.NewClient() if err != nil { return printError("Failed to create client", err) } authClient := auth.NewAuthClient(client) authResp, err := authClient.Login(username, password) if err != nil { return printError("Login failed", err) } if save { if err := config.SaveToken(authResp.Token); err != nil { return printError("Failed to save token", err) } } data := map[string]interface{}{ "token": authResp.Token, "expires_at": authResp.ExpiresAt, } printResponse(cmd, data, "auth login") return nil } func runAuthLogout(cmd *cobra.Command, args []string) error { if err := auth.Logout(); err != nil { return printError("Logout failed", err) } printResponse(cmd, map[string]string{"message": "Logged out successfully"}, "auth logout") return nil } func runAuthStatus(cmd *cobra.Command, args []string) error { status, err := auth.Status() if err != nil { return printError("Status check failed", err) } printResponse(cmd, status, "auth status") return nil } // Document command group var docCmd = &cobra.Command{ Use: "doc", Short: "Document commands", } var docCreateCmd = &cobra.Command{ Use: "create", Short: "Create a new document", RunE: runDocCreate, } var docListCmd = &cobra.Command{ Use: "list", Short: "List documents", RunE: runDocList, } var docGetCmd = &cobra.Command{ Use: "get", Short: "Get a document by ID", RunE: runDocGet, } var docUpdateCmd = &cobra.Command{ Use: "update", Short: "Update a document", RunE: runDocUpdate, } var docDeleteCmd = &cobra.Command{ Use: "delete", Short: "Delete a document", RunE: runDocDelete, } func init() { docCmd.AddCommand(docCreateCmd, docListCmd, docGetCmd, docUpdateCmd, docDeleteCmd) docCreateCmd.Flags().StringP("title", "t", "", "Document title (required)") docCreateCmd.Flags().StringP("content", "c", "", "Document content") docCreateCmd.Flags().StringP("project-id", "p", "", "Project ID (required)") docCreateCmd.Flags().StringP("folder-id", "f", "", "Folder ID") docCreateCmd.Flags().String("metadata", "", "Extra metadata as JSON") docCreateCmd.MarkFlagRequired("title") docCreateCmd.MarkFlagRequired("project-id") docListCmd.Flags().StringP("project-id", "p", "", "Filter by project") docListCmd.Flags().StringP("folder-id", "f", "", "Filter by folder") docListCmd.Flags().Int("limit", 20, "Limit results") docListCmd.Flags().Int("offset", 0, "Offset for pagination") docGetCmd.Flags().Bool("include-reasoning", false, "Include reasoning metadata") docGetCmd.Flags().Bool("include-tags", false, "Include tags") docUpdateCmd.Flags().StringP("title", "t", "", "New title") docUpdateCmd.Flags().StringP("content", "c", "", "New content") docUpdateCmd.Flags().StringP("folder-id", "f", "", "Move to folder") docDeleteCmd.Flags().Bool("force", false, "Skip confirmation") } func runDocCreate(cmd *cobra.Command, args []string) error { title, _ := cmd.Flags().GetString("title") content, _ := cmd.Flags().GetString("content") projectID, _ := cmd.Flags().GetString("project-id") folderID, _ := cmd.Flags().GetString("folder-id") metadataJSON, _ := cmd.Flags().GetString("metadata") var metadata map[string]interface{} if metadataJSON != "" { if err := json.Unmarshal([]byte(metadataJSON), &metadata); err != nil { return printError("Invalid metadata JSON", err) } } client, err := api.NewClient() if err != nil { return printError("Failed to create client", err) } docsClient := api.NewDocumentsClient(client) req := types.CreateDocumentRequest{ Title: title, Content: content, FolderID: folderID, Metadata: metadata, } doc, err := docsClient.Create(projectID, req) if err != nil { return printError("Failed to create document", err) } printResponse(cmd, doc, "doc create") return nil } func runDocList(cmd *cobra.Command, args []string) error { projectID, _ := cmd.Flags().GetString("project-id") folderID, _ := cmd.Flags().GetString("folder-id") limit, _ := cmd.Flags().GetInt("limit") offset, _ := cmd.Flags().GetInt("offset") client, err := api.NewClient() if err != nil { return printError("Failed to create client", err) } docsClient := api.NewDocumentsClient(client) docs, err := docsClient.List(projectID, folderID, limit, offset) if err != nil { return printError("Failed to list documents", err) } printResponse(cmd, docs, "doc list") return nil } func runDocGet(cmd *cobra.Command, args []string) error { if len(args) < 1 { return fmt.Errorf("document ID required") } docID := args[0] includeReasoning, _ := cmd.Flags().GetBool("include-reasoning") includeTags, _ := cmd.Flags().GetBool("include-tags") client, err := api.NewClient() if err != nil { return printError("Failed to create client", err) } docsClient := api.NewDocumentsClient(client) doc, err := docsClient.Get(docID, includeReasoning, includeTags) if err != nil { return printError("Failed to get document", err) } printResponse(cmd, doc, "doc get") return nil } func runDocUpdate(cmd *cobra.Command, args []string) error { if len(args) < 1 { return fmt.Errorf("document ID required") } docID := args[0] title, _ := cmd.Flags().GetString("title") content, _ := cmd.Flags().GetString("content") folderID, _ := cmd.Flags().GetString("folder-id") client, err := api.NewClient() if err != nil { return printError("Failed to create client", err) } docsClient := api.NewDocumentsClient(client) req := types.UpdateDocumentRequest{ Title: title, Content: content, FolderID: folderID, } doc, err := docsClient.Update(docID, req) if err != nil { return printError("Failed to update document", err) } printResponse(cmd, doc, "doc update") return nil } func runDocDelete(cmd *cobra.Command, args []string) error { if len(args) < 1 { return fmt.Errorf("document ID required") } docID := args[0] force, _ := cmd.Flags().GetBool("force") if !force { fmt.Printf("Are you sure you want to delete document %s? (y/N): ", docID) var confirm string fmt.Scanln(&confirm) if confirm != "y" && confirm != "Y" { fmt.Println("Cancelled") return nil } } client, err := api.NewClient() if err != nil { return printError("Failed to create client", err) } docsClient := api.NewDocumentsClient(client) if err := docsClient.Delete(docID); err != nil { return printError("Failed to delete document", err) } printResponse(cmd, map[string]string{"id": docID, "deleted": "true"}, "doc delete") return nil } // Project command group var projectCmd = &cobra.Command{ Use: "project", Short: "Project commands", } var projectListCmd = &cobra.Command{ Use: "list", Short: "List projects", RunE: runProjectList, } var projectGetCmd = &cobra.Command{ Use: "get", Short: "Get a project by ID", RunE: runProjectGet, } func init() { projectCmd.AddCommand(projectListCmd, projectGetCmd) } func runProjectList(cmd *cobra.Command, args []string) error { client, err := api.NewClient() if err != nil { return printError("Failed to create client", err) } projectsClient := api.NewProjectsClient(client) projects, err := projectsClient.List() if err != nil { return printError("Failed to list projects", err) } printResponse(cmd, projects, "project list") return nil } func runProjectGet(cmd *cobra.Command, args []string) error { if len(args) < 1 { return fmt.Errorf("project ID required") } projectID := args[0] client, err := api.NewClient() if err != nil { return printError("Failed to create client", err) } projectsClient := api.NewProjectsClient(client) project, err := projectsClient.Get(projectID) if err != nil { return printError("Failed to get project", err) } printResponse(cmd, project, "project get") return nil } // Folder command group var folderCmd = &cobra.Command{ Use: "folder", Short: "Folder commands", } var folderListCmd = &cobra.Command{ Use: "list", Short: "List folders in a project", RunE: runFolderList, } var folderCreateCmd = &cobra.Command{ Use: "create", Short: "Create a folder", RunE: runFolderCreate, } func init() { folderCmd.AddCommand(folderListCmd, folderCreateCmd) folderListCmd.Flags().StringP("project-id", "p", "", "Project ID (required)") folderListCmd.Flags().String("parent-id", "", "Parent folder ID") folderListCmd.MarkFlagRequired("project-id") folderCreateCmd.Flags().StringP("project-id", "p", "", "Project ID (required)") folderCreateCmd.Flags().StringP("name", "n", "", "Folder name (required)") folderCreateCmd.Flags().String("parent-id", "", "Parent folder ID") folderCreateCmd.MarkFlagRequired("project-id") folderCreateCmd.MarkFlagRequired("name") } func runFolderList(cmd *cobra.Command, args []string) error { projectID, _ := cmd.Flags().GetString("project-id") parentID, _ := cmd.Flags().GetString("parent-id") client, err := api.NewClient() if err != nil { return printError("Failed to create client", err) } foldersClient := api.NewFoldersClient(client) folders, err := foldersClient.List(projectID, parentID) if err != nil { return printError("Failed to list folders", err) } printResponse(cmd, folders, "folder list") return nil } func runFolderCreate(cmd *cobra.Command, args []string) error { projectID, _ := cmd.Flags().GetString("project-id") name, _ := cmd.Flags().GetString("name") parentID, _ := cmd.Flags().GetString("parent-id") client, err := api.NewClient() if err != nil { return printError("Failed to create client", err) } foldersClient := api.NewFoldersClient(client) req := types.CreateFolderRequest{ Name: name, ParentID: parentID, } folder, err := foldersClient.Create(projectID, req) if err != nil { return printError("Failed to create folder", err) } printResponse(cmd, folder, "folder create") return nil } // Tag command group var tagCmd = &cobra.Command{ Use: "tag", Short: "Tag commands", } var tagListCmd = &cobra.Command{ Use: "list", Short: "List all tags", RunE: runTagList, } var tagCreateCmd = &cobra.Command{ Use: "create", Short: "Create a tag", RunE: runTagCreate, } var tagAddCmd = &cobra.Command{ Use: "add", Short: "Add a tag to a document", RunE: runTagAdd, } func init() { tagCmd.AddCommand(tagListCmd, tagCreateCmd, tagAddCmd) tagCreateCmd.Flags().StringP("name", "n", "", "Tag name (required)") tagCreateCmd.Flags().String("color", "", "Tag color (hex)") tagCreateCmd.MarkFlagRequired("name") tagAddCmd.Flags().String("doc-id", "", "Document ID (required)") tagAddCmd.Flags().String("tag-id", "", "Tag ID (required)") tagAddCmd.MarkFlagRequired("doc-id") tagAddCmd.MarkFlagRequired("tag-id") } func runTagList(cmd *cobra.Command, args []string) error { client, err := api.NewClient() if err != nil { return printError("Failed to create client", err) } tagsClient := api.NewTagsClient(client) tags, err := tagsClient.List() if err != nil { return printError("Failed to list tags", err) } printResponse(cmd, tags, "tag list") return nil } func runTagCreate(cmd *cobra.Command, args []string) error { name, _ := cmd.Flags().GetString("name") color, _ := cmd.Flags().GetString("color") client, err := api.NewClient() if err != nil { return printError("Failed to create client", err) } tagsClient := api.NewTagsClient(client) req := types.CreateTagRequest{ Name: name, Color: color, } tag, err := tagsClient.Create(req) if err != nil { return printError("Failed to create tag", err) } printResponse(cmd, tag, "tag create") return nil } func runTagAdd(cmd *cobra.Command, args []string) error { docID, _ := cmd.Flags().GetString("doc-id") tagID, _ := cmd.Flags().GetString("tag-id") client, err := api.NewClient() if err != nil { return printError("Failed to create client", err) } tagsClient := api.NewTagsClient(client) if err := tagsClient.AddToDocument(docID, tagID); err != nil { return printError("Failed to add tag", err) } printResponse(cmd, map[string]string{"doc_id": docID, "tag_id": tagID, "added": "true"}, "tag add") return nil } // Search command var searchCmd = &cobra.Command{ Use: "search", Short: "Search documents", RunE: runSearch, } func init() { searchCmd.Flags().StringP("query", "q", "", "Search query (required)") searchCmd.Flags().StringP("project-id", "p", "", "Filter by project") searchCmd.Flags().String("tags", "", "Filter by tags (comma-separated)") searchCmd.MarkFlagRequired("query") } func runSearch(cmd *cobra.Command, args []string) error { query, _ := cmd.Flags().GetString("query") projectID, _ := cmd.Flags().GetString("project-id") tags, _ := cmd.Flags().GetString("tags") client, err := api.NewClient() if err != nil { return printError("Failed to create client", err) } searchClient := api.NewSearchClient(client) result, err := searchClient.Search(query, projectID, tags) if err != nil { return printError("Search failed", err) } printResponse(cmd, result, "search") return nil } // Reasoning command var reasoningCmd = &cobra.Command{ Use: "reasoning", Short: "Reasoning commands", } var reasoningSaveCmd = &cobra.Command{ Use: "save", Short: "Save reasoning for a document", RunE: runReasoningSave, } func init() { reasoningCmd.AddCommand(reasoningSaveCmd) reasoningSaveCmd.Flags().StringP("doc-id", "d", "", "Document ID (required)") reasoningSaveCmd.Flags().StringP("type", "t", "", "Reasoning type: research, planning, analysis, synthesis (required)") reasoningSaveCmd.Flags().String("steps", "", "JSON array of reasoning steps (required)") reasoningSaveCmd.Flags().Float64("confidence", 0.0, "Confidence score 0.0-1.0") reasoningSaveCmd.Flags().String("model", "", "Source model") reasoningSaveCmd.MarkFlagRequired("doc-id") reasoningSaveCmd.MarkFlagRequired("type") reasoningSaveCmd.MarkFlagRequired("steps") } func runReasoningSave(cmd *cobra.Command, args []string) error { docID, _ := cmd.Flags().GetString("doc-id") reasoningType, _ := cmd.Flags().GetString("type") stepsJSON, _ := cmd.Flags().GetString("steps") confidence, _ := cmd.Flags().GetFloat64("confidence") model, _ := cmd.Flags().GetString("model") var steps []types.ReasoningStep if err := json.Unmarshal([]byte(stepsJSON), &steps); err != nil { return printError("Invalid steps JSON", err) } client, err := api.NewClient() if err != nil { return printError("Failed to create client", err) } reasoningClient := api.NewReasoningClient(client) req := types.SaveReasoningRequest{ Type: reasoningType, Steps: steps, Confidence: confidence, Model: model, } reasoning, err := reasoningClient.Save(docID, req) if err != nil { return printError("Failed to save reasoning", err) } printResponse(cmd, reasoning, "reasoning save") return nil } // Helper functions func printResponse(cmd *cobra.Command, data interface{}, command string) { start := time.Now() cfg := config.GetConfig() server := "http://localhost:8000" if cfg != nil { server = cfg.Server } formatter := output.NewFormatter(command, server) durationMs := int64(time.Since(start).Milliseconds()) resp := formatter.Success(data, durationMs) if cfg != nil && cfg.Output == "text" { printTextOutput(command, data) } else { output.PrintJSON(resp) } } func printTextOutput(command string, data interface{}) { switch v := data.(type) { case map[string]interface{}: for k, val := range v { fmt.Printf("%s: %v\n", k, val) } case []interface{}: fmt.Println("Items:") for i, item := range v { fmt.Printf(" [%d] %v\n", i, item) } default: fmt.Println(data) } } func printError(context string, err error) error { cfg := config.GetConfig() server := "http://localhost:8000" if cfg != nil { server = cfg.Server } formatter := output.NewFormatter("", server) durationMs := int64(0) code := "ERROR" if err != nil { code = "NETWORK_ERROR" } resp := formatter.Error(code, err.Error(), durationMs) if cfg != nil && cfg.Output == "text" { fmt.Fprintf(os.Stderr, "%s: %v\n", context, err) } else { output.PrintJSON(resp) } return err }