Files
claudia-docs-cli/cobra/commands.go
Claudia CLI Bot aca95d90f3 Initial MVP implementation of Claudia Docs CLI
- Auth commands: login, logout, status
- Document commands: create, list, get, update, delete
- Project commands: list, get
- Folder commands: list, create
- Tag commands: list, create, add
- Search command
- Reasoning save command
- JSON output format with --output text option
- Viper-based configuration management
- Cobra CLI framework
2026-03-31 01:25:15 +00:00

747 lines
18 KiB
Go

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
}