Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,14 @@ web3datacli [command]

### Available Commands

| Command | Description |
|--------------|----------------------------------------|
| Command | Description |
| ------------ | --------------------------------------- |
| `arweave` | 🕸️ Interact with Arweave |
| `encryption` | 🔐 Manage data encryption and decryption |
| `ipfs` | 📤 Interact with IPFS |
| `version` | Show the CLI version |
| `completion` | Generate autocompletion for your shell |
| `help` | Help about any command |
| `ipfs` | 📤 Interact with IPFS |
| `version` | Show the CLI version |
| `completion` | Generate autocompletion for your shell |
| `help` | Help about any command |

---

Expand All @@ -61,8 +62,8 @@ web3datacli [command]
Enable autocompletion for your shell (e.g., bash, zsh):

```bash
web3datacli completion bash > /etc/bash_completion.d/web3datacli
source /etc/bash_completion.d/web3datacli
web3datacli completion zsh > ~/.zsh_completions/_web3datacli
source ~/.zsh_completions/_web3datacli
```

---
Expand All @@ -75,4 +76,4 @@ MIT License © 2025 F.CORDIER

## 🤝 Contributions

Feel free to open issues or submit PRs — contributions are welcome!
Feel free to open issues or submit PRs — contributions are welcome!
142 changes: 142 additions & 0 deletions cmd/arweave/arweave.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package arweave

import (
"bytes"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"path/filepath"

"github.com/spf13/cobra"
)

var (
apiBaseURL string
)

var ArweaveCmd = &cobra.Command{
Use: "arweave",
Short: "🕸️ Interact with Arweave",
}

func init() {
ArweaveCmd.AddCommand(arweaveUploadCommand())
ArweaveCmd.AddCommand(arweaveDownloadCommand())
}

func arweaveUploadCommand() *cobra.Command {
var filePath string

cmd := &cobra.Command{
Use: "upload",
Short: "Upload a file to Arweave through a relay API",
RunE: func(cmd *cobra.Command, args []string) error {
if filePath == "" {
return fmt.Errorf("❌ you must provide a file path with --file")
}

file, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("cannot open file: %w", err)
}
defer file.Close()

var buf bytes.Buffer
writer := multipart.NewWriter(&buf)
part, err := writer.CreateFormFile("file", filepath.Base(filePath))
if err != nil {
return fmt.Errorf("cannot create form file: %w", err)
}

if _, errCopy := io.Copy(part, file); errCopy != nil {
return fmt.Errorf("failed to copy file data: %w", errCopy)
}

if errClose := writer.Close(); errClose != nil {
return fmt.Errorf("failed to close multipart writer: %w", errClose)
}

resp, err := http.Post(apiBaseURL+"/upload", writer.FormDataContentType(), &buf)
if err != nil {
return fmt.Errorf("upload request failed: %w", err)
}
defer resp.Body.Close()

body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("arweave upload failed: %s", string(body))
}

var res struct {
ArweaveID string `json:"arweaveId"`
URL string `json:"url"`
}
if err := json.Unmarshal(body, &res); err != nil {
return fmt.Errorf("invalid response: %w", err)
}

fmt.Println("✅ Upload successful")
fmt.Printf("🆔 Arweave ID: %s\n🔗 URL: %s\n", res.ArweaveID, res.URL)
return nil
},
}

cmd.Flags().StringVarP(&filePath, "file", "f", "", "Path to the local file to upload")
cmd.Flags().StringVarP(&apiBaseURL, "api", "a", "http://localhost:3000", "Base URL of the Arweave relay API (without /upload)")

return cmd
}

func arweaveDownloadCommand() *cobra.Command {
var id string
var output string

cmd := &cobra.Command{
Use: "download",
Short: "Download a file from Arweave using its transaction ID",
RunE: func(cmd *cobra.Command, args []string) error {
if id == "" {
return fmt.Errorf("❌ you must provide an Arweave transaction ID with --id")
}
if output == "" {
output = id
}

url := fmt.Sprintf("https://arweave.net/%s", id)

fmt.Printf("🔗 Downloading from: %s\n📁 Saving to: %s\n", url, output)
// #nosec G107 -- ID is validated and used to build a known domain URL
resp, err := http.Get(url)
if err != nil {
return fmt.Errorf("failed to download from Arweave: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("arweave download failed: %s", string(body))
}

outFile, err := os.Create(output)
if err != nil {
return fmt.Errorf("cannot create output file: %w", err)
}
defer outFile.Close()

if _, err := io.Copy(outFile, resp.Body); err != nil {
return fmt.Errorf("error saving file: %w", err)
}

fmt.Println("✅ Download complete")
return nil
},
}

cmd.Flags().StringVarP(&id, "id", "i", "", "Arweave transaction ID to download (required)")
cmd.Flags().StringVarP(&output, "out", "o", "", "Output file path (optional)")

return cmd
}
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"github.com/thewhitewizard/web3data-cli/cmd/arweave"
"github.com/thewhitewizard/web3data-cli/cmd/encryption"
"github.com/thewhitewizard/web3data-cli/cmd/ipfs"
"github.com/thewhitewizard/web3data-cli/cmd/version"
Expand All @@ -20,5 +21,6 @@ func Execute() {
func init() {
rootCmd.AddCommand(encryption.EncryptionCmd)
rootCmd.AddCommand(ipfs.IPFSCmd)
rootCmd.AddCommand(arweave.ArweaveCmd)
rootCmd.AddCommand(version.VersionCmd)
}