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
9 changes: 6 additions & 3 deletions cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@ var apiKeyFlag string

var rootCmd = &cobra.Command{
Use: "dune",
Short: "Dune CLI — interact with the Dune Analytics API",
Long: "A command-line interface for interacting with the Dune Analytics API.\n" +
"Manage queries, execute them, and retrieve results.",
Short: "Dune CLI — query, explore, and manage blockchain data on Dune Analytics",
Long: "A command-line interface for the Dune Analytics platform.\n\n" +
"Discover datasets across the Dune catalog, execute SQL queries (DuneSQL dialect),\n" +
"retrieve execution results, and manage your saved queries — all from the terminal.\n\n" +
"Authenticate with an API key via --api-key, the DUNE_API_KEY environment variable,\n" +
"or by running `dune auth`.",
PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
if cmd.Annotations["skipAuth"] == "true" {
return nil
Expand Down
2 changes: 1 addition & 1 deletion cmd/dataset/dataset.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import "github.com/spf13/cobra"
func NewDatasetCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "dataset",
Short: "Manage Dune datasets",
Short: "Discover and explore datasets across the Dune catalog",
}
cmd.AddCommand(newSearchCmd())
return cmd
Expand Down
29 changes: 17 additions & 12 deletions cmd/dataset/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,26 @@ import (
func newSearchCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "search",
Short: "Search for datasets across the Dune catalog",
Short: "Search for tables and datasets across the Dune catalog",
Long: "Natural-language table discovery across the Dune catalog. Use this command\n" +
"to find concrete table names for use in SQL queries.\n\n" +
"Filter by category (canonical for chain primitives, decoded for ABI-level\n" +
"events/calls, spell for curated datasets, community for user-contributed),\n" +
"by blockchain, schema, dataset type, or ownership scope.",
RunE: runSearch,
}

cmd.Flags().String("query", "", "search query text")
cmd.Flags().StringArray("categories", nil, "filter by category (canonical, decoded, spell, community)")
cmd.Flags().StringArray("blockchains", nil, "filter by blockchain")
cmd.Flags().StringArray("dataset-types", nil, "filter by dataset type")
cmd.Flags().StringArray("schemas", nil, "filter by schema")
cmd.Flags().String("owner-scope", "", "ownership filter (all, me, team)")
cmd.Flags().Bool("include-private", false, "include private datasets")
cmd.Flags().Bool("include-schema", false, "include column schema in results")
cmd.Flags().Bool("include-metadata", false, "include metadata in results")
cmd.Flags().Int32("limit", 20, "maximum number of results")
cmd.Flags().Int32("offset", 0, "pagination offset")
cmd.Flags().String("query", "", "natural-language search intent or entity hints (e.g. 'uniswap v3 swaps'); use '*' to browse without keyword bias")
cmd.Flags().StringArray("categories", nil, "filter by table family: canonical (chain primitives), decoded (ABI-level events/calls), spell (curated datasets), community (user-contributed)")
cmd.Flags().StringArray("blockchains", nil, "chain scope to reduce ambiguity and improve ranking (e.g. ethereum, solana)")
cmd.Flags().StringArray("dataset-types", nil, "fine-grained dataset type filter: dune_table, decoded_table, spell, uploaded_table, transformation_table, transformation_view")
cmd.Flags().StringArray("schemas", nil, "schema/namespace constraint for high precision (e.g. dex, uniswap_v3_ethereum)")
cmd.Flags().String("owner-scope", "", "ownership filter: all, me, or team; does NOT automatically include private datasets")
cmd.Flags().Bool("include-private", false, "widen results to include private datasets visible to the authenticated user/team alongside public ones")
cmd.Flags().Bool("include-schema", false, "include column-level schema (name, type, nullable) for every result; useful when preparing SQL")
cmd.Flags().Bool("include-metadata", false, "include category-specific metadata (page_rank_score, description, abi_type, contract_name, project_name, etc.)")
cmd.Flags().Int32("limit", 20, "number of results per page; use 5-15 for quick checks, 20-50 for deeper exploration")
cmd.Flags().Int32("offset", 0, "pagination offset; use previous response pagination info for next page")
output.AddFormatFlag(cmd, "text")

return cmd
Expand Down
2 changes: 1 addition & 1 deletion cmd/docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import "github.com/spf13/cobra"
func NewDocsCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "docs",
Short: "Search and browse Dune documentation",
Short: "Search the Dune documentation for guides, API references, and examples",
Annotations: map[string]string{"skipAuth": "true"},
}
cmd.AddCommand(newSearchCmd())
Expand Down
10 changes: 6 additions & 4 deletions cmd/docs/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@ const defaultMCPEndpoint = "https://docs.dune.com/mcp"
func newSearchCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "search",
Short: "Search the Dune documentation",
Short: "Search the Dune documentation for guides, API references, and code examples",
Long: "Search across all Dune documentation pages including guides, API references,\n" +
"DuneSQL syntax, and code examples. Does not require authentication.",
Annotations: map[string]string{"skipAuth": "true"},
RunE: runSearch,
}

cmd.Flags().String("query", "", "search query text (required)")
cmd.Flags().String("query", "", "search query text, e.g. 'DuneSQL date functions' or 'API authentication' (required)")
_ = cmd.MarkFlagRequired("query")
cmd.Flags().Bool("api-reference-only", false, "prioritize API reference pages")
cmd.Flags().Bool("code-only", false, "prioritize pages with code examples")
cmd.Flags().Bool("api-reference-only", false, "prioritize API reference pages over conceptual guides")
cmd.Flags().Bool("code-only", false, "prioritize pages with executable examples and code snippets")
cmd.Flags().String("mcp-endpoint", defaultMCPEndpoint, "MCP server endpoint URL")
_ = cmd.Flags().MarkHidden("mcp-endpoint")
output.AddFormatFlag(cmd, "text")
Expand Down
2 changes: 1 addition & 1 deletion cmd/execution/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import "github.com/spf13/cobra"
func NewExecutionCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "execution",
Short: "Manage query executions",
Short: "Retrieve and inspect query execution results",
}
cmd.AddCommand(newResultsCmd())
return cmd
Expand Down
54 changes: 49 additions & 5 deletions cmd/execution/results.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,38 @@ package execution

import (
"fmt"
"time"

"github.com/duneanalytics/cli/cmdutil"
"github.com/duneanalytics/cli/output"
"github.com/duneanalytics/duneapi-client-go/dune"
"github.com/duneanalytics/duneapi-client-go/models"
"github.com/spf13/cobra"
)

// PollInterval controls the polling interval when waiting for execution results.
var PollInterval = 2 * time.Second

func newResultsCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "results <execution-id>",
Short: "Fetch results of a query execution",
Args: cobra.ExactArgs(1),
RunE: runResults,
Short: "Get execution results for a query execution by execution ID",
Long: "Retrieve the results of a query execution. By default, waits for the execution\n" +
"to complete (up to the timeout) before returning results.\n\n" +
"Behavior:\n" +
" 1. Checks the current execution status\n" +
" 2. If still running: polls until complete or timeout is reached\n" +
" 3. If completed: returns the result data\n" +
" 4. If failed/cancelled: returns the error details\n\n" +
"Use --no-wait to return the current state immediately without polling.",
Args: cobra.ExactArgs(1),
RunE: runResults,
}

cmd.Flags().Int("limit", 0, "maximum number of rows to return (0 = all)")
cmd.Flags().Int("offset", 0, "number of rows to skip")
cmd.Flags().Int("limit", 0, "maximum number of result rows to return (0 = all)")
cmd.Flags().Int("offset", 0, "number of rows to skip before returning results, used for pagination")
cmd.Flags().Bool("no-wait", false, "return the current execution state immediately without waiting for completion")
cmd.Flags().Int("timeout", 300, "maximum seconds to wait for the execution to complete before timing out")
output.AddFormatFlag(cmd, "text")

return cmd
Expand All @@ -29,6 +44,7 @@ func runResults(cmd *cobra.Command, args []string) error {

limit, _ := cmd.Flags().GetInt("limit")
offset, _ := cmd.Flags().GetInt("offset")
noWait, _ := cmd.Flags().GetBool("no-wait")

if limit < 0 {
return fmt.Errorf("limit must be non-negative, got %d", limit)
Expand All @@ -46,11 +62,39 @@ func runResults(cmd *cobra.Command, args []string) error {
}

client := cmdutil.ClientFromCmd(cmd)

if noWait {
resp, err := client.QueryResultsV2(executionID, opts)
if err != nil {
return err
}
return handleResultsResponse(cmd, executionID, resp)
}

timeout, _ := cmd.Flags().GetInt("timeout")
intervalSec := int(PollInterval.Seconds())
maxRetries := timeout
if intervalSec > 0 {
maxRetries = timeout / intervalSec
}
if maxRetries < 1 {
maxRetries = 1
}

exec := dune.NewExecution(client, executionID)
if _, err := exec.WaitGetResults(PollInterval, maxRetries); err != nil {
return err
}

// Fetch final results with any limit/offset options.
resp, err := client.QueryResultsV2(executionID, opts)
if err != nil {
return err
}
return handleResultsResponse(cmd, executionID, resp)
}

func handleResultsResponse(cmd *cobra.Command, executionID string, resp *models.ResultsResponse) error {
switch resp.State {
case "QUERY_STATE_COMPLETED":
return output.DisplayResults(cmd, resp)
Expand Down
Loading