Files
kb/client/cmd/export.go
T
steve b04823e67b Store original documents for download after ingestion
Persist uploaded files to {data_dir}/documents/{content_hash}{ext} after
successful ingestion. Add GET /documents/{id}/file endpoint for retrieval,
delete stored files on document deletion, and add `kb export` client command.
Includes schema migration, tests, and spec updates.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 15:16:27 +00:00

75 lines
1.6 KiB
Go

package cmd
import (
"fmt"
"io"
"mime"
"os"
"path/filepath"
"github.com/kb-search/kb/internal/api"
"github.com/spf13/cobra"
)
var exportCmd = &cobra.Command{
Use: "export <id>",
Short: "Download original document file",
Args: cobra.ExactArgs(1),
RunE: runExport,
}
func init() {
exportCmd.Flags().StringP("output", "o", "", "output file path (default: original filename to current directory)")
rootCmd.AddCommand(exportCmd)
}
func runExport(cmd *cobra.Command, args []string) error {
client := api.NewClient()
resp, err := client.Get("/api/v1/documents/" + args[0] + "/file")
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if err := api.CheckError(resp); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
defer resp.Body.Close()
outPath, _ := cmd.Flags().GetString("output")
if outPath == "" {
// Try to get filename from Content-Disposition header
cd := resp.Header.Get("Content-Disposition")
if cd != "" {
_, params, err := mime.ParseMediaType(cd)
if err == nil && params["filename"] != "" {
outPath = params["filename"]
}
}
if outPath == "" {
outPath = "document-" + args[0]
}
}
if outPath == "-" {
_, err := io.Copy(os.Stdout, resp.Body)
return err
}
outPath = filepath.Clean(outPath)
f, err := os.Create(outPath)
if err != nil {
return fmt.Errorf("failed to create output file: %w", err)
}
defer f.Close()
n, err := io.Copy(f, resp.Body)
if err != nil {
return fmt.Errorf("failed to write file: %w", err)
}
fmt.Fprintf(os.Stderr, "Saved %s (%d bytes)\n", outPath, n)
return nil
}