package cmd import ( "encoding/json" "fmt" "os" "github.com/aisim/kb-cli/internal/draft" "github.com/aisim/kb-cli/internal/index" "github.com/spf13/cobra" ) var draftCmd = &cobra.Command{ Use: "draft", Short: "草稿管理", Long: `# draft - 草稿管理 kb-cli draft create --type <类型> --title <标题> --content-file <文件> [--source <来源>] [--force] # 创建草稿(AI 提取 tags + 生成合并指示) kb-cli draft list [--json] # 列出待审阅区的所有活跃草稿 kb-cli draft convert [目录名] # 转换老格式单文件草稿到新格式(NNN-标题/draft.md + merge.md)`, } var draftCreateCmd = &cobra.Command{ Use: "create", Short: "创建草稿", Long: `# draft create - 创建草稿 kb-cli draft create --type <类型> --title <标题> --content-file <文件> [--source <来源>] [--force] # 创建草稿,自动提取 tags 并生成合并指示`, RunE: runDraftCreate, } var draftListCmd = &cobra.Command{ Use: "list", Short: "列出所有活跃草稿", Long: `# draft list - 列出所有活跃草稿 kb-cli draft list [--json] # 列出待审阅区的所有草稿(目录结构 + 老格式单文件)`, RunE: runDraftList, } var draftConvertCmd = &cobra.Command{ Use: "convert [目录名]", Short: "转换老格式草稿到新格式", Long: `# draft convert - 转换老格式草稿到新格式 kb-cli draft convert # 扫描所有待审阅子目录,转换老格式单文件 kb-cli draft convert TAPD提取 # 只转换指定子目录`, RunE: runDraftConvert, } var tagsCmd = &cobra.Command{ Use: "tags", Short: "标签管理", Long: `kb-cli tags rebuild # 批量重建现有文档的 tags`, } var tagsRebuildCmd = &cobra.Command{ Use: "rebuild", Short: "批量重建 tags", Long: `# tags rebuild - 批量重建 tags kb-cli tags rebuild [--vault <路径>] [--limit <数量>] [--dry-run] # 使用 LLM 重新提取现有文档的 tags`, RunE: runTagsRebuild, } var ( draftType string draftTitle string draftContentFile string draftSource string draftForce bool draftJSON bool tagsLimit int tagsDryRun bool convertTarget string ) func init() { // draft create 命令 draftCreateCmd.Flags().StringVar(&draftType, "type", "", "草稿类型(售后/产品/运营/行业/TAPD)") draftCreateCmd.Flags().StringVar(&draftTitle, "title", "", "草稿标题") draftCreateCmd.Flags().StringVar(&draftContentFile, "content-file", "", "草稿内容文件路径") draftCreateCmd.Flags().StringVar(&draftSource, "source", "", "来源标识") draftCreateCmd.Flags().BoolVar(&draftForce, "force", false, "强制创建(跳过重复检查)") draftCreateCmd.MarkFlagRequired("type") draftCreateCmd.MarkFlagRequired("title") draftCreateCmd.MarkFlagRequired("content-file") // draft list 命令 draftListCmd.Flags().BoolVar(&draftJSON, "json", false, "JSON 格式输出") // draft convert 命令(无额外参数,target 从 args 读取) draftCmd.AddCommand(draftCreateCmd) draftCmd.AddCommand(draftListCmd) draftCmd.AddCommand(draftConvertCmd) // tags rebuild 命令 tagsRebuildCmd.Flags().IntVar(&tagsLimit, "limit", 0, "限制处理文档数量(0=全部)") tagsRebuildCmd.Flags().BoolVar(&tagsDryRun, "dry-run", false, "仅预览,不实际更新") tagsCmd.AddCommand(tagsRebuildCmd) rootCmd.AddCommand(draftCmd) rootCmd.AddCommand(tagsCmd) } func runDraftCreate(cmd *cobra.Command, args []string) error { // 读取内容文件 content, err := os.ReadFile(draftContentFile) if err != nil { return fmt.Errorf("读取内容文件失败: %w", err) } if len(content) == 0 { return fmt.Errorf("内容文件为空") } // 打开索引 store, err := index.Open(dbPath) if err != nil { return fmt.Errorf("打开索引失败: %w", err) } defer store.Close() // 创建草稿录入器 intake := draft.NewIntake(vaultPath, store) // 创建草稿 if err := intake.CreateDraft(draftType, draftTitle, string(content), draftSource, draftForce); err != nil { return fmt.Errorf("创建草稿失败: %w", err) } return nil } func runTagsRebuild(cmd *cobra.Command, args []string) error { // 打开索引 store, err := index.Open(dbPath) if err != nil { return fmt.Errorf("打开索引失败: %w", err) } defer store.Close() // 创建草稿录入器 intake := draft.NewIntake(vaultPath, store) // 重建 tags if err := intake.RebuildTags(tagsLimit, tagsDryRun); err != nil { return fmt.Errorf("重建 tags 失败: %w", err) } return nil } // runDraftList 列出所有活跃草稿 func runDraftList(cmd *cobra.Command, args []string) error { // 创建草稿录入器 intake := draft.NewIntake(vaultPath, nil) // 列出草稿 drafts, err := intake.ListDrafts() if err != nil { return fmt.Errorf("列出草稿失败: %w", err) } if draftJSON { // JSON 输出 output := struct { Drafts []draft.DraftInfo `json:"drafts"` Total int `json:"total"` }{ Drafts: drafts, Total: len(drafts), } encoder := json.NewEncoder(os.Stdout) encoder.SetIndent("", " ") return encoder.Encode(output) } // 表格输出 if len(drafts) == 0 { fmt.Println("没有活跃草稿") return nil } fmt.Printf("找到 %d 个活跃草稿:\n\n", len(drafts)) for _, d := range drafts { mergeStatus := "❌" if d.HasMergeHint { mergeStatus = "✅" } fmt.Printf("• %s\n", d.Title) fmt.Printf(" 路径: %s\n", d.Filepath) fmt.Printf(" 类型: %s | 状态: %s | 合并指示: %s\n", d.Type, d.Status, mergeStatus) fmt.Printf(" 创建: %s\n\n", d.Created) } return nil } // runDraftConvert 转换老格式草稿到新格式 func runDraftConvert(cmd *cobra.Command, args []string) error { // 可选参数:指定子目录 target := "" if len(args) > 0 { target = args[0] } // 创建草稿录入器 intake := draft.NewIntake(vaultPath, nil) // 转换草稿 converted, err := intake.ConvertOldDrafts(target) if err != nil { return fmt.Errorf("转换草稿失败: %w", err) } fmt.Printf("\n✅ 成功转换 %d 个草稿\n", converted) return nil }