Skip to content

Commit 92ffb50

Browse files
authored
Timeout and help improvements (#15)
* Add timeout support * rich tool descriptions * use wait results from sdk
1 parent 44dabca commit 92ffb50

17 files changed

Lines changed: 272 additions & 77 deletions

File tree

cli/root.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@ var apiKeyFlag string
2424

2525
var rootCmd = &cobra.Command{
2626
Use: "dune",
27-
Short: "Dune CLI — interact with the Dune Analytics API",
28-
Long: "A command-line interface for interacting with the Dune Analytics API.\n" +
29-
"Manage queries, execute them, and retrieve results.",
27+
Short: "Dune CLI — query, explore, and manage blockchain data on Dune Analytics",
28+
Long: "A command-line interface for the Dune Analytics platform.\n\n" +
29+
"Discover datasets across the Dune catalog, execute SQL queries (DuneSQL dialect),\n" +
30+
"retrieve execution results, and manage your saved queries — all from the terminal.\n\n" +
31+
"Authenticate with an API key via --api-key, the DUNE_API_KEY environment variable,\n" +
32+
"or by running `dune auth`.",
3033
PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
3134
if cmd.Annotations["skipAuth"] == "true" {
3235
return nil

cmd/dataset/dataset.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import "github.com/spf13/cobra"
66
func NewDatasetCmd() *cobra.Command {
77
cmd := &cobra.Command{
88
Use: "dataset",
9-
Short: "Manage Dune datasets",
9+
Short: "Discover and explore datasets across the Dune catalog",
1010
}
1111
cmd.AddCommand(newSearchCmd())
1212
return cmd

cmd/dataset/search.go

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,26 @@ import (
1313
func newSearchCmd() *cobra.Command {
1414
cmd := &cobra.Command{
1515
Use: "search",
16-
Short: "Search for datasets across the Dune catalog",
16+
Short: "Search for tables and datasets across the Dune catalog",
17+
Long: "Natural-language table discovery across the Dune catalog. Use this command\n" +
18+
"to find concrete table names for use in SQL queries.\n\n" +
19+
"Filter by category (canonical for chain primitives, decoded for ABI-level\n" +
20+
"events/calls, spell for curated datasets, community for user-contributed),\n" +
21+
"by blockchain, schema, dataset type, or ownership scope.",
1722
RunE: runSearch,
1823
}
1924

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

3338
return cmd

cmd/docs/docs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import "github.com/spf13/cobra"
66
func NewDocsCmd() *cobra.Command {
77
cmd := &cobra.Command{
88
Use: "docs",
9-
Short: "Search and browse Dune documentation",
9+
Short: "Search the Dune documentation for guides, API references, and examples",
1010
Annotations: map[string]string{"skipAuth": "true"},
1111
}
1212
cmd.AddCommand(newSearchCmd())

cmd/docs/search.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,17 @@ const defaultMCPEndpoint = "https://docs.dune.com/mcp"
1414
func newSearchCmd() *cobra.Command {
1515
cmd := &cobra.Command{
1616
Use: "search",
17-
Short: "Search the Dune documentation",
17+
Short: "Search the Dune documentation for guides, API references, and code examples",
18+
Long: "Search across all Dune documentation pages including guides, API references,\n" +
19+
"DuneSQL syntax, and code examples. Does not require authentication.",
1820
Annotations: map[string]string{"skipAuth": "true"},
1921
RunE: runSearch,
2022
}
2123

22-
cmd.Flags().String("query", "", "search query text (required)")
24+
cmd.Flags().String("query", "", "search query text, e.g. 'DuneSQL date functions' or 'API authentication' (required)")
2325
_ = cmd.MarkFlagRequired("query")
24-
cmd.Flags().Bool("api-reference-only", false, "prioritize API reference pages")
25-
cmd.Flags().Bool("code-only", false, "prioritize pages with code examples")
26+
cmd.Flags().Bool("api-reference-only", false, "prioritize API reference pages over conceptual guides")
27+
cmd.Flags().Bool("code-only", false, "prioritize pages with executable examples and code snippets")
2628
cmd.Flags().String("mcp-endpoint", defaultMCPEndpoint, "MCP server endpoint URL")
2729
_ = cmd.Flags().MarkHidden("mcp-endpoint")
2830
output.AddFormatFlag(cmd, "text")

cmd/execution/execution.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import "github.com/spf13/cobra"
66
func NewExecutionCmd() *cobra.Command {
77
cmd := &cobra.Command{
88
Use: "execution",
9-
Short: "Manage query executions",
9+
Short: "Retrieve and inspect query execution results",
1010
}
1111
cmd.AddCommand(newResultsCmd())
1212
return cmd

cmd/execution/results.go

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,38 @@ package execution
22

33
import (
44
"fmt"
5+
"time"
56

67
"github.com/duneanalytics/cli/cmdutil"
78
"github.com/duneanalytics/cli/output"
9+
"github.com/duneanalytics/duneapi-client-go/dune"
810
"github.com/duneanalytics/duneapi-client-go/models"
911
"github.com/spf13/cobra"
1012
)
1113

14+
// PollInterval controls the polling interval when waiting for execution results.
15+
var PollInterval = 2 * time.Second
16+
1217
func newResultsCmd() *cobra.Command {
1318
cmd := &cobra.Command{
1419
Use: "results <execution-id>",
15-
Short: "Fetch results of a query execution",
16-
Args: cobra.ExactArgs(1),
17-
RunE: runResults,
20+
Short: "Get execution results for a query execution by execution ID",
21+
Long: "Retrieve the results of a query execution. By default, waits for the execution\n" +
22+
"to complete (up to the timeout) before returning results.\n\n" +
23+
"Behavior:\n" +
24+
" 1. Checks the current execution status\n" +
25+
" 2. If still running: polls until complete or timeout is reached\n" +
26+
" 3. If completed: returns the result data\n" +
27+
" 4. If failed/cancelled: returns the error details\n\n" +
28+
"Use --no-wait to return the current state immediately without polling.",
29+
Args: cobra.ExactArgs(1),
30+
RunE: runResults,
1831
}
1932

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

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

3045
limit, _ := cmd.Flags().GetInt("limit")
3146
offset, _ := cmd.Flags().GetInt("offset")
47+
noWait, _ := cmd.Flags().GetBool("no-wait")
3248

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

4864
client := cmdutil.ClientFromCmd(cmd)
65+
66+
if noWait {
67+
resp, err := client.QueryResultsV2(executionID, opts)
68+
if err != nil {
69+
return err
70+
}
71+
return handleResultsResponse(cmd, executionID, resp)
72+
}
73+
74+
timeout, _ := cmd.Flags().GetInt("timeout")
75+
intervalSec := int(PollInterval.Seconds())
76+
maxRetries := timeout
77+
if intervalSec > 0 {
78+
maxRetries = timeout / intervalSec
79+
}
80+
if maxRetries < 1 {
81+
maxRetries = 1
82+
}
83+
84+
exec := dune.NewExecution(client, executionID)
85+
if _, err := exec.WaitGetResults(PollInterval, maxRetries); err != nil {
86+
return err
87+
}
88+
89+
// Fetch final results with any limit/offset options.
4990
resp, err := client.QueryResultsV2(executionID, opts)
5091
if err != nil {
5192
return err
5293
}
94+
return handleResultsResponse(cmd, executionID, resp)
95+
}
5396

97+
func handleResultsResponse(cmd *cobra.Command, executionID string, resp *models.ResultsResponse) error {
5498
switch resp.State {
5599
case "QUERY_STATE_COMPLETED":
56100
return output.DisplayResults(cmd, resp)

0 commit comments

Comments
 (0)