tableau-cli 0.1.0__tar.gz → 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. tableau_cli-0.1.1/.claude/settings.local.json +18 -0
  2. tableau_cli-0.1.1/.claude/skills/tableau-cli +1 -0
  3. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/PKG-INFO +44 -8
  4. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/README.md +42 -6
  5. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/pyproject.toml +2 -2
  6. tableau_cli-0.1.1/skills/SKILL.md +51 -0
  7. tableau_cli-0.1.1/skills/references/cli.md +282 -0
  8. tableau_cli-0.1.1/skills/references/installation.md +46 -0
  9. tableau_cli-0.1.1/skills-lock.json +10 -0
  10. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/src/tableau_cli/cli.py +1 -1
  11. tableau_cli-0.1.1/src/tableau_cli/commands/convert_cmd.py +62 -0
  12. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/src/tableau_cli/commands/datasources_cmd.py +27 -6
  13. tableau_cli-0.1.0/src/tableau_cli/commands/convert_cmd.py → tableau_cli-0.1.1/src/tableau_cli/utils/convert.py +23 -59
  14. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/.gitignore +0 -0
  15. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/LICENSE +0 -0
  16. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/NOTICE +0 -0
  17. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/src/tableau_cli/__init__.py +0 -0
  18. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/src/tableau_cli/api/__init__.py +0 -0
  19. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/src/tableau_cli/api/client.py +0 -0
  20. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/src/tableau_cli/auth/__init__.py +0 -0
  21. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/src/tableau_cli/auth/with_auth.py +0 -0
  22. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/src/tableau_cli/commands/__init__.py +0 -0
  23. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/src/tableau_cli/commands/config_cmd.py +0 -0
  24. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/src/tableau_cli/commands/search_cmd.py +0 -0
  25. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/src/tableau_cli/commands/views_cmd.py +0 -0
  26. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/src/tableau_cli/commands/workbooks_cmd.py +0 -0
  27. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/src/tableau_cli/config/__init__.py +0 -0
  28. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/src/tableau_cli/config/store.py +0 -0
  29. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/src/tableau_cli/config/types.py +0 -0
  30. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/src/tableau_cli/errors/__init__.py +0 -0
  31. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/src/tableau_cli/errors/cli_error.py +0 -0
  32. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/src/tableau_cli/errors/vds_error_handler.py +0 -0
  33. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/src/tableau_cli/output/__init__.py +0 -0
  34. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/src/tableau_cli/output/format.py +0 -0
  35. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/src/tableau_cli/output/json_output.py +0 -0
  36. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/src/tableau_cli/output/table_output.py +0 -0
  37. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/src/tableau_cli/utils/__init__.py +0 -0
  38. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/src/tableau_cli/utils/datasource_metadata_utils.py +0 -0
  39. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/src/tableau_cli/utils/paginate.py +0 -0
  40. {tableau_cli-0.1.0 → tableau_cli-0.1.1}/src/tableau_cli/utils/search_content_utils.py +0 -0
@@ -0,0 +1,18 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(python:*)",
5
+ "Bash(echo \"EXIT: $?\")",
6
+ "Bash(tableau-cli datasources:*)",
7
+ "Bash(tableau-cli convert:*)",
8
+ "Bash(ruff check:*)",
9
+ "Bash(git add:*)",
10
+ "Bash(git commit:*)",
11
+ "Bash(git push:*)",
12
+ "Bash(pip index:*)",
13
+ "Bash(pip install:*)",
14
+ "Bash(bash -l -c \"history\")",
15
+ "Bash(twine --version)"
16
+ ]
17
+ }
18
+ }
@@ -0,0 +1 @@
1
+ ../../.agents/skills/tableau-cli
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tableau-cli
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: CLI for Tableau Server/Cloud, designed for AI agent integration
5
- Project-URL: Repository, https://github.com/didiwang/tableau-cli
5
+ Project-URL: Repository, https://github.com/i-richardwang/tableau-cli
6
6
  Author: Richard Wang
7
7
  License-Expression: Apache-2.0
8
8
  License-File: LICENSE
@@ -34,7 +34,11 @@ A command-line interface for Tableau Server / Tableau Cloud, designed for AI age
34
34
 
35
35
  ## Why CLI over MCP?
36
36
 
37
- MCP (Model Context Protocol) servers continuously occupy agent context. A CLI tool follows a simpler call-execute-exit pattern — the agent invokes a command, reads the JSON output, and moves on. This project provides the same capabilities as the [tableau-mcp](https://github.com/anthropics/tableau-mcp) server with behavioral alignment in data transformations, error handling, and output structures.
37
+ MCP (Model Context Protocol) servers continuously occupy agent context — every tool description is loaded into the system prompt, even when only one command is needed. A CLI follows a simpler call-execute-exit pattern: the agent invokes a command, reads the JSON output, and moves on.
38
+
39
+ The companion Claude Code skill under `skills/` extends the same idea to command knowledge — the full reference is loaded only when the agent is about to execute a command, not kept in context upfront. See [Use with Claude Code](#use-with-claude-code) below.
40
+
41
+ This project provides the same capabilities as the [tableau-mcp](https://github.com/tableau/tableau-mcp) server, with behavioral alignment in data transformations, error handling, and output structures.
38
42
 
39
43
  ## Quick Start
40
44
 
@@ -47,7 +51,7 @@ MCP (Model Context Protocol) servers continuously occupy agent context. A CLI to
47
51
  ### Install
48
52
 
49
53
  ```bash
50
- git clone <repo-url>
54
+ git clone https://github.com/i-richardwang/tableau-cli.git
51
55
  cd tableau-cli
52
56
  pip install -e .
53
57
 
@@ -79,6 +83,29 @@ Environment variables take precedence over the config file. `siteName` defaults
79
83
  tableau-cli config show
80
84
  ```
81
85
 
86
+ ## Use with Claude Code
87
+
88
+ This repository ships with a Claude Code skill under `skills/`. Once loaded, it gives an agent enough context to route a user's request to the right command — without putting the full CLI reference into the system prompt.
89
+
90
+ ```
91
+ skills/
92
+ ├── SKILL.md # Intent routing + environment check
93
+ └── references/
94
+ ├── cli.md # Full command reference (loaded before executing)
95
+ └── installation.md # Install + auth setup (loaded if not configured)
96
+ ```
97
+
98
+ Once Claude Code has loaded the skill, a typical interaction looks like:
99
+
100
+ 1. The agent runs `tableau-cli --help` to verify the CLI is installed and configured. If not, it loads `references/installation.md` and stops until setup is complete.
101
+ 2. The agent maps the user's intent to a subcommand using the Intent Routing table in `SKILL.md` (e.g., "find datasources with Sales in the name" → `ds list --filter "name:has:Sales"`).
102
+ 3. Before constructing the actual command, the agent loads `references/cli.md` — the skill explicitly forbids guessing flags from memory, so the reference is the source of truth for syntax.
103
+ 4. The agent runs the command and parses the structured JSON output, including the `hint` field on errors.
104
+
105
+ Common workflows (e.g., `ds download --to parquet → load with Polars/Pandas`) are pre-defined under Intent Routing in `SKILL.md`, so the agent doesn't have to reason about chaining from scratch.
106
+
107
+ You can of course use `tableau-cli` directly from a shell without the skill — the skill is only needed when you want an agent to drive the tool.
108
+
82
109
  ## Commands
83
110
 
84
111
  ### Search
@@ -101,6 +128,10 @@ tableau-cli ds list --filter "name:has:Sales" --limit 50
101
128
  # Download datasource file (.tdsx)
102
129
  tableau-cli datasources download <datasourceId> -o ./data/
103
130
 
131
+ # Download and convert to Parquet or CSV in one step (requires tableau-cli[convert])
132
+ tableau-cli ds download <datasourceId> -o ./data/ --to parquet
133
+ tableau-cli ds download <datasourceId> -o ./data/ --to csv
134
+
104
135
  # Get field metadata (VizQL Data Service + Metadata API enrichment)
105
136
  tableau-cli datasources metadata <luid>
106
137
 
@@ -126,7 +157,9 @@ tableau-cli views image <viewId> --width 1200 --height 800 --img-format SVG -o d
126
157
 
127
158
  ### Convert
128
159
 
129
- Convert Tableau TDSX/HYPER files to Parquet or CSV. Requires `pip install tableau-cli[convert]`.
160
+ Convert local TDSX/HYPER files to Parquet or CSV. Requires `pip install tableau-cli[convert]`.
161
+
162
+ For most use cases, `ds download --to parquet` (or `--to csv`) is simpler — it downloads and converts in one step. The `convert` command is useful when you already have a `.tdsx` or `.hyper` file on disk.
130
163
 
131
164
  ```bash
132
165
  # Convert TDSX to Parquet (default)
@@ -176,9 +209,12 @@ tableau-cli ds list --format table
176
209
  Commands that save files (`ds download`, `views image -o`, `convert`) output a JSON object with the file path to stdout, enabling agents to chain operations:
177
210
 
178
211
  ```bash
179
- # Download and convert pipeline
180
- tableau-cli ds download <id> -o ./data/ # → {"filePath": ".../data.tdsx"}
181
- tableau-cli convert ./data/data.tdsx -o ./data/ # → {"filePath": ".../data.parquet"}
212
+ # Download and convert in one step
213
+ tableau-cli ds download <id> -o ./data/ --to parquet # → {"filePath": ".../data.parquet"}
214
+
215
+ # Or as separate steps (useful for keeping the original .tdsx)
216
+ tableau-cli ds download <id> -o ./data/ # → {"filePath": ".../data.tdsx"}
217
+ tableau-cli convert ./data/data.tdsx -o ./data/ # → {"filePath": ".../data.parquet"}
182
218
  ```
183
219
 
184
220
  ### Error Output
@@ -4,7 +4,11 @@ A command-line interface for Tableau Server / Tableau Cloud, designed for AI age
4
4
 
5
5
  ## Why CLI over MCP?
6
6
 
7
- MCP (Model Context Protocol) servers continuously occupy agent context. A CLI tool follows a simpler call-execute-exit pattern — the agent invokes a command, reads the JSON output, and moves on. This project provides the same capabilities as the [tableau-mcp](https://github.com/anthropics/tableau-mcp) server with behavioral alignment in data transformations, error handling, and output structures.
7
+ MCP (Model Context Protocol) servers continuously occupy agent context — every tool description is loaded into the system prompt, even when only one command is needed. A CLI follows a simpler call-execute-exit pattern: the agent invokes a command, reads the JSON output, and moves on.
8
+
9
+ The companion Claude Code skill under `skills/` extends the same idea to command knowledge — the full reference is loaded only when the agent is about to execute a command, not kept in context upfront. See [Use with Claude Code](#use-with-claude-code) below.
10
+
11
+ This project provides the same capabilities as the [tableau-mcp](https://github.com/tableau/tableau-mcp) server, with behavioral alignment in data transformations, error handling, and output structures.
8
12
 
9
13
  ## Quick Start
10
14
 
@@ -17,7 +21,7 @@ MCP (Model Context Protocol) servers continuously occupy agent context. A CLI to
17
21
  ### Install
18
22
 
19
23
  ```bash
20
- git clone <repo-url>
24
+ git clone https://github.com/i-richardwang/tableau-cli.git
21
25
  cd tableau-cli
22
26
  pip install -e .
23
27
 
@@ -49,6 +53,29 @@ Environment variables take precedence over the config file. `siteName` defaults
49
53
  tableau-cli config show
50
54
  ```
51
55
 
56
+ ## Use with Claude Code
57
+
58
+ This repository ships with a Claude Code skill under `skills/`. Once loaded, it gives an agent enough context to route a user's request to the right command — without putting the full CLI reference into the system prompt.
59
+
60
+ ```
61
+ skills/
62
+ ├── SKILL.md # Intent routing + environment check
63
+ └── references/
64
+ ├── cli.md # Full command reference (loaded before executing)
65
+ └── installation.md # Install + auth setup (loaded if not configured)
66
+ ```
67
+
68
+ Once Claude Code has loaded the skill, a typical interaction looks like:
69
+
70
+ 1. The agent runs `tableau-cli --help` to verify the CLI is installed and configured. If not, it loads `references/installation.md` and stops until setup is complete.
71
+ 2. The agent maps the user's intent to a subcommand using the Intent Routing table in `SKILL.md` (e.g., "find datasources with Sales in the name" → `ds list --filter "name:has:Sales"`).
72
+ 3. Before constructing the actual command, the agent loads `references/cli.md` — the skill explicitly forbids guessing flags from memory, so the reference is the source of truth for syntax.
73
+ 4. The agent runs the command and parses the structured JSON output, including the `hint` field on errors.
74
+
75
+ Common workflows (e.g., `ds download --to parquet → load with Polars/Pandas`) are pre-defined under Intent Routing in `SKILL.md`, so the agent doesn't have to reason about chaining from scratch.
76
+
77
+ You can of course use `tableau-cli` directly from a shell without the skill — the skill is only needed when you want an agent to drive the tool.
78
+
52
79
  ## Commands
53
80
 
54
81
  ### Search
@@ -71,6 +98,10 @@ tableau-cli ds list --filter "name:has:Sales" --limit 50
71
98
  # Download datasource file (.tdsx)
72
99
  tableau-cli datasources download <datasourceId> -o ./data/
73
100
 
101
+ # Download and convert to Parquet or CSV in one step (requires tableau-cli[convert])
102
+ tableau-cli ds download <datasourceId> -o ./data/ --to parquet
103
+ tableau-cli ds download <datasourceId> -o ./data/ --to csv
104
+
74
105
  # Get field metadata (VizQL Data Service + Metadata API enrichment)
75
106
  tableau-cli datasources metadata <luid>
76
107
 
@@ -96,7 +127,9 @@ tableau-cli views image <viewId> --width 1200 --height 800 --img-format SVG -o d
96
127
 
97
128
  ### Convert
98
129
 
99
- Convert Tableau TDSX/HYPER files to Parquet or CSV. Requires `pip install tableau-cli[convert]`.
130
+ Convert local TDSX/HYPER files to Parquet or CSV. Requires `pip install tableau-cli[convert]`.
131
+
132
+ For most use cases, `ds download --to parquet` (or `--to csv`) is simpler — it downloads and converts in one step. The `convert` command is useful when you already have a `.tdsx` or `.hyper` file on disk.
100
133
 
101
134
  ```bash
102
135
  # Convert TDSX to Parquet (default)
@@ -146,9 +179,12 @@ tableau-cli ds list --format table
146
179
  Commands that save files (`ds download`, `views image -o`, `convert`) output a JSON object with the file path to stdout, enabling agents to chain operations:
147
180
 
148
181
  ```bash
149
- # Download and convert pipeline
150
- tableau-cli ds download <id> -o ./data/ # → {"filePath": ".../data.tdsx"}
151
- tableau-cli convert ./data/data.tdsx -o ./data/ # → {"filePath": ".../data.parquet"}
182
+ # Download and convert in one step
183
+ tableau-cli ds download <id> -o ./data/ --to parquet # → {"filePath": ".../data.parquet"}
184
+
185
+ # Or as separate steps (useful for keeping the original .tdsx)
186
+ tableau-cli ds download <id> -o ./data/ # → {"filePath": ".../data.tdsx"}
187
+ tableau-cli convert ./data/data.tdsx -o ./data/ # → {"filePath": ".../data.parquet"}
152
188
  ```
153
189
 
154
190
  ### Error Output
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "tableau-cli"
7
- version = "0.1.0"
7
+ version = "0.1.1"
8
8
  description = "CLI for Tableau Server/Cloud, designed for AI agent integration"
9
9
  license = "Apache-2.0"
10
10
  requires-python = ">=3.10"
@@ -30,7 +30,7 @@ dependencies = [
30
30
  ]
31
31
 
32
32
  [project.urls]
33
- Repository = "https://github.com/didiwang/tableau-cli"
33
+ Repository = "https://github.com/i-richardwang/tableau-cli"
34
34
 
35
35
  [project.optional-dependencies]
36
36
  convert = [
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: tableau-cli
3
+ description: "Interact with Tableau Server / Cloud via the tableau-cli command: search content, query datasources, download files, export view images and data, convert TDSX/HYPER to Parquet/CSV. Use when the user wants to find, download, query, or export any Tableau content."
4
+ ---
5
+
6
+ # Tableau CLI
7
+
8
+ Interact with Tableau Server / Cloud via the `tableau-cli` command.
9
+
10
+ ## References
11
+
12
+ Load these files when needed — do not load all of them upfront:
13
+
14
+ - [references/cli.md](references/cli.md) — full CLI command reference. Load before running any `tableau-cli` command or looking up flags and syntax.
15
+ - [references/installation.md](references/installation.md) — installation and authentication setup. Load if `tableau-cli` is not installed or not configured.
16
+
17
+ ## Environment Check
18
+
19
+ Before running any command, verify the CLI is available:
20
+
21
+ ```bash
22
+ tableau-cli --help
23
+ ```
24
+
25
+ If `tableau-cli` is not installed or the API key is missing, load [references/installation.md](references/installation.md) and stop until setup is complete.
26
+
27
+ ## CLI Reference
28
+
29
+ Before running any `tableau-cli` command, load [references/cli.md](references/cli.md) as the source of truth for exact flags, subcommands, and examples. Do not guess command syntax from memory.
30
+
31
+ ## Intent Routing
32
+
33
+ The following requests can be handled directly with CLI commands. Load [references/cli.md](references/cli.md) and execute:
34
+
35
+ - Search content across types — `search`
36
+ - List/filter datasources, views, or workbooks — `ds list` / `views list` / `wb list`
37
+ - Download a datasource file — `ds download` (supports `--to parquet` / `--to csv` for direct conversion)
38
+ - Inspect datasource field metadata — `ds metadata`
39
+ - Query data from a datasource — `ds query`
40
+ - Export view data as CSV — `views data`
41
+ - Export view as image — `views image`
42
+ - View workbook details and its views — `wb get`
43
+ - Convert TDSX/HYPER to Parquet/CSV — `convert`
44
+
45
+ Common combined workflows:
46
+
47
+ **Download datasource for local analysis**: `ds list` → `ds download --to parquet` → load with Polars/Pandas
48
+
49
+ **Quick data extraction (no file download)**: `ds metadata` → `ds query` (requires VizQL Data Service; fall back to download + convert if unavailable)
50
+
51
+ **Export dashboard screenshot**: `views list` → `views image`
@@ -0,0 +1,282 @@
1
+ ---
2
+ name: tableau-cli-reference
3
+ description: Full CLI command reference for tableau-cli. Load before running any command as the authoritative source for flags and syntax.
4
+ ---
5
+
6
+ # CLI Command Reference
7
+
8
+ All commands output JSON (indent=2) by default. List commands support `--format table` for human-readable output.
9
+
10
+ Errors are also structured JSON:
11
+ ```json
12
+ {
13
+ "isError": true,
14
+ "errorType": "<type>",
15
+ "message": "<description>",
16
+ "hint": "<suggested action>"
17
+ }
18
+ ```
19
+
20
+ ---
21
+
22
+ ## search
23
+
24
+ Search across all content types on the site.
25
+
26
+ ```bash
27
+ tableau-cli search "keyword"
28
+ tableau-cli search "keyword" --type workbook,view,datasource
29
+ tableau-cli search "keyword" --limit 10 --order-by hitsTotal:desc
30
+ tableau-cli search --format table
31
+ ```
32
+
33
+ | Option | Type | Default | Description |
34
+ |--------|------|---------|-------------|
35
+ | `terms` | string (positional) | none | Search terms, optional |
36
+ | `--type` | string | none | Comma-separated content types (workbook, view, datasource, etc.) |
37
+ | `--limit` | int | 100 | Max results to return |
38
+ | `--order-by` | string | none | Sort method (e.g., `hitsTotal:desc`) |
39
+ | `--format` | json / table | json | Output format |
40
+
41
+ View results include `parentWorkbookName` (parent workbook) and `totalViewCount` (total views).
42
+
43
+ ---
44
+
45
+ ## datasources (alias: ds)
46
+
47
+ ### ds list
48
+
49
+ ```bash
50
+ tableau-cli ds list
51
+ tableau-cli ds list --filter "name:has:keyword" --limit 50 --format table
52
+ ```
53
+
54
+ | Option | Type | Default | Description |
55
+ |--------|------|---------|-------------|
56
+ | `--filter` | string | none | Server-side filter (see filter syntax below) |
57
+ | `--page-size` | int | none | Items per page |
58
+ | `--limit` | int | none | Max total results |
59
+ | `--format` | json / table | json | Output format |
60
+
61
+ Automatically paginates until all results are fetched or limit is reached.
62
+
63
+ ### ds download
64
+
65
+ ```bash
66
+ tableau-cli ds download <datasource-id> -o ./output/
67
+ tableau-cli ds download <datasource-id> -o ./output/ --to parquet
68
+ tableau-cli ds download <datasource-id> -o ./output/ --to csv
69
+ ```
70
+
71
+ | Option | Type | Default | Description |
72
+ |--------|------|---------|-------------|
73
+ | `datasource_id` | string (positional) | required | Datasource LUID |
74
+ | `-o, --output` | path | `.` | Output path (directory or filename) |
75
+ | `--to` | tdsx / parquet / csv | tdsx | Output format; parquet/csv automatically converts after download |
76
+
77
+ Output: `{"filePath": "<absolute path>"}`
78
+
79
+ When `--to parquet` or `--to csv` is specified, the datasource is downloaded, converted in a temporary directory, and only the final file is written to the output path. Requires `tableau-cli[convert]` extras.
80
+
81
+ ### ds metadata
82
+
83
+ View datasource field metadata. Requires VizQL Data Service.
84
+
85
+ ```bash
86
+ tableau-cli ds metadata <luid>
87
+ tableau-cli ds metadata <luid> --format table
88
+ ```
89
+
90
+ | Option | Type | Default | Description |
91
+ |--------|------|---------|-------------|
92
+ | `luid` | string (positional) | required | Datasource LUID |
93
+ | `--format` | json / table | json | Output format |
94
+
95
+ Returns:
96
+ - `datasourceDescription` — datasource description
97
+ - `fieldGroups[].fields[]` — field list (name, dataType, role, formula, description, etc.)
98
+ - `parameters[]` — parameter list (name, parameterType, dataType, value, etc.)
99
+
100
+ Attempts to enrich via GraphQL Metadata API; falls back to base VizQL metadata if unavailable.
101
+
102
+ ### ds query
103
+
104
+ Query datasource data directly. Requires VizQL Data Service.
105
+
106
+ ```bash
107
+ tableau-cli ds query <luid> --query '{"fields": [{"fieldCaption": "Category"}, {"fieldCaption": "Sales"}]}'
108
+ tableau-cli ds query <luid> --query '{"fields": [...]}' --limit 100 --format table
109
+ ```
110
+
111
+ | Option | Type | Default | Description |
112
+ |--------|------|---------|-------------|
113
+ | `luid` | string (positional) | required | Datasource LUID |
114
+ | `--query` | JSON string | required | Query definition (fields, filters, etc.) |
115
+ | `--limit` | int | none | Row limit (client-side truncation) |
116
+ | `--format` | json / table | json | Output format |
117
+
118
+ `--query` must be valid JSON; otherwise reports `invalid-input` error. `--limit` truncates client-side, not server-side.
119
+
120
+ ---
121
+
122
+ ## views
123
+
124
+ Views include worksheets (Sheets), dashboards, and stories.
125
+
126
+ ### views list
127
+
128
+ ```bash
129
+ tableau-cli views list
130
+ tableau-cli views list --filter "name:has:keyword" --format table
131
+ ```
132
+
133
+ | Option | Type | Default | Description |
134
+ |--------|------|---------|-------------|
135
+ | `--filter` | string | none | Server-side filter |
136
+ | `--page-size` | int | none | Items per page |
137
+ | `--limit` | int | none | Max total results |
138
+ | `--format` | json / table | json | Output format |
139
+
140
+ Results include `workbookId` to trace back to the parent workbook.
141
+
142
+ ### views data
143
+
144
+ Export view data as CSV.
145
+
146
+ ```bash
147
+ tableau-cli views data <view-id>
148
+ ```
149
+
150
+ | Option | Type | Description |
151
+ |--------|------|-------------|
152
+ | `view_id` | string (positional) | View LUID |
153
+
154
+ Output: CSV text written directly to stdout (not JSON).
155
+
156
+ ### views image
157
+
158
+ Export view as image.
159
+
160
+ ```bash
161
+ tableau-cli views image <view-id> -o output.png
162
+ tableau-cli views image <view-id> --width 1200 --height 800 -o output.png
163
+ tableau-cli views image <view-id> --img-format SVG -o output.svg
164
+ ```
165
+
166
+ | Option | Type | Default | Description |
167
+ |--------|------|---------|-------------|
168
+ | `view_id` | string (positional) | required | View LUID |
169
+ | `--width` | int | none | Image width in pixels |
170
+ | `--height` | int | none | Image height in pixels |
171
+ | `--img-format` | PNG / SVG | PNG | Image format |
172
+ | `-o, --output` | path | none | Output file path |
173
+
174
+ With `-o`: outputs `{"filePath": "<absolute path>"}`. Without `-o`: base64-encoded image to stdout.
175
+
176
+ ---
177
+
178
+ ## workbooks (alias: wb)
179
+
180
+ ### wb list
181
+
182
+ ```bash
183
+ tableau-cli wb list
184
+ tableau-cli wb list --filter "name:has:keyword" --format table
185
+ ```
186
+
187
+ | Option | Type | Default | Description |
188
+ |--------|------|---------|-------------|
189
+ | `--filter` | string | none | Server-side filter |
190
+ | `--page-size` | int | none | Items per page |
191
+ | `--limit` | int | none | Max total results |
192
+ | `--format` | json / table | json | Output format |
193
+
194
+ ### wb get
195
+
196
+ View workbook details, including all views with usage statistics.
197
+
198
+ ```bash
199
+ tableau-cli wb get <workbook-id>
200
+ tableau-cli wb get <workbook-id> --format table
201
+ ```
202
+
203
+ | Option | Type | Default | Description |
204
+ |--------|------|---------|-------------|
205
+ | `workbook_id` | string (positional) | required | Workbook LUID |
206
+ | `--format` | json / table | json | Output format |
207
+
208
+ ---
209
+
210
+ ## convert
211
+
212
+ Local file conversion: TDSX/HYPER → Parquet/CSV. Requires `tableau-cli[convert]`.
213
+
214
+ ```bash
215
+ tableau-cli convert data.tdsx
216
+ tableau-cli convert data.tdsx -o ./output/
217
+ tableau-cli convert data.tdsx --to csv -o ./output/
218
+ tableau-cli convert extract.hyper -o ./output/result.parquet
219
+ ```
220
+
221
+ | Option | Type | Default | Description |
222
+ |--------|------|---------|-------------|
223
+ | `input_path` | path (positional) | required | Input file (.tdsx or .hyper) |
224
+ | `--to` | parquet / csv | parquet | Output format |
225
+ | `-o, --output` | path | none | Output path (directory or filename); defaults to input file directory |
226
+
227
+ Output: `{"filePath": "<absolute path>"}`
228
+
229
+ Reports `missing-dependencies` error if convert extras are not installed, with hint to run `pip install "tableau-cli[convert]"`.
230
+
231
+ ---
232
+
233
+ ## config
234
+
235
+ ### config show
236
+
237
+ ```bash
238
+ tableau-cli config show
239
+ ```
240
+
241
+ Outputs current configuration as JSON. PAT value is masked as `"****"`.
242
+
243
+ ### config set
244
+
245
+ ```bash
246
+ tableau-cli config set --server https://... --site-name ... --pat-name ... --pat-value ...
247
+ ```
248
+
249
+ All options are optional; only updates the specified fields. Saves to `~/.tableau-cli.json`.
250
+
251
+ ---
252
+
253
+ ## Filter Syntax
254
+
255
+ The `--filter` option uses `field:operator:value` format:
256
+
257
+ | Operator | Meaning | Example |
258
+ |----------|---------|---------|
259
+ | `eq` | Exact match | `name:eq:Superstore` |
260
+ | `has` | Contains | `name:has:Sales` |
261
+ | `in` | Multi-value match | `name:in:[A,B,C]` |
262
+
263
+ ---
264
+
265
+ ## Intent → Command Quick Reference
266
+
267
+ | User wants to… | Command |
268
+ |-----------------|---------|
269
+ | Search content (any type) | `search "keyword"` |
270
+ | Find datasources | `ds list --filter "name:has:..."` |
271
+ | Download a datasource file | `ds download <id> -o dir/` |
272
+ | Download datasource as Parquet | `ds download <id> -o dir/ --to parquet` |
273
+ | Download datasource as CSV | `ds download <id> -o dir/ --to csv` |
274
+ | Inspect datasource fields | `ds metadata <luid>` |
275
+ | Query data from a datasource | `ds query <luid> --query '{...}'` |
276
+ | Find a dashboard/view | `views list --filter "name:has:..."` |
277
+ | Export view data | `views data <view-id>` |
278
+ | Export view screenshot | `views image <view-id> -o file.png` |
279
+ | Find workbooks | `wb list --filter "name:has:..."` |
280
+ | See views in a workbook | `wb get <workbook-id>` |
281
+ | Convert local TDSX to Parquet | `convert file.tdsx -o dir/` |
282
+ | Download and convert for analysis | `ds download <id> --to parquet` |
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: tableau-cli-installation
3
+ description: Installation and authentication setup for tableau-cli. Load when the CLI is not installed or not configured.
4
+ ---
5
+
6
+ ## Installation
7
+
8
+ ```bash
9
+ pip install "tableau-cli[convert]"
10
+ ```
11
+
12
+ Without `[convert]`, only core features are available (search, datasources, views, workbooks) — the `convert` command will not work.
13
+
14
+ ## Authentication
15
+
16
+ tableau-cli uses Personal Access Token (PAT) authentication.
17
+
18
+ ### Option 1: CLI config (saved to ~/.tableau-cli.json)
19
+
20
+ ```bash
21
+ tableau-cli config set \
22
+ --server https://your-tableau-server.com \
23
+ --site-name YourSite \
24
+ --pat-name your-token-name \
25
+ --pat-value your-token-value
26
+ ```
27
+
28
+ ### Option 2: Environment variables
29
+
30
+ ```bash
31
+ export SERVER=https://your-tableau-server.com
32
+ export SITE_NAME=YourSite
33
+ export PAT_NAME=your-token-name
34
+ export PAT_VALUE=your-token-value
35
+ ```
36
+
37
+ Environment variables take precedence over the config file. `SITE_NAME` defaults to `""` (the default site) if not set.
38
+
39
+ ## Verification
40
+
41
+ ```bash
42
+ tableau-cli config show
43
+ tableau-cli ds list --limit 1
44
+ ```
45
+
46
+ `config show` displays the current configuration (PAT value is masked). If `ds list` returns data, authentication is working.
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 1,
3
+ "skills": {
4
+ "tableau-cli": {
5
+ "source": "i-richardwang/tableau-cli",
6
+ "sourceType": "github",
7
+ "computedHash": "26defe3fe68adfac902da3328240d33528db5fe9757f88f7e3a8bdc90744c6d9"
8
+ }
9
+ }
10
+ }
@@ -95,7 +95,7 @@ class TableauCli(click.Group):
95
95
 
96
96
 
97
97
  @click.group(cls=TableauCli)
98
- @click.version_option("0.1.0")
98
+ @click.version_option("0.1.1")
99
99
  def cli():
100
100
  """CLI tool for interacting with Tableau Server/Cloud."""
101
101
 
@@ -0,0 +1,62 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import sys
5
+ from pathlib import Path
6
+ from tempfile import TemporaryDirectory
7
+
8
+ import click
9
+
10
+ from ..errors.cli_error import CliError
11
+ from ..output.format import output
12
+ from ..utils.convert import (
13
+ SUPPORTED_FORMATS,
14
+ check_convert_deps,
15
+ extract_hyper_from_tdsx,
16
+ read_hyper,
17
+ write_df,
18
+ )
19
+
20
+
21
+ @click.command("convert")
22
+ @click.argument("input_path", type=click.Path(exists=True))
23
+ @click.option(
24
+ "--to", "to_fmt", default="parquet", type=click.Choice(SUPPORTED_FORMATS), help="Output format (default: parquet)"
25
+ )
26
+ @click.option("-o", "--output", "output_path", default=None, help="Output file or directory (default: same as input)")
27
+ def convert_command(input_path, to_fmt, output_path):
28
+ """Convert TDSX/HYPER files to Parquet or CSV format."""
29
+ check_convert_deps()
30
+
31
+ input_p = Path(input_path)
32
+ suffix = input_p.suffix.lower()
33
+
34
+ if suffix not in (".tdsx", ".hyper"):
35
+ raise CliError(
36
+ error_type="invalid-input",
37
+ message=f"Unsupported file type: {suffix}",
38
+ hint="Supported formats: .tdsx, .hyper",
39
+ )
40
+
41
+ # Resolve output path
42
+ ext = f".{to_fmt}"
43
+ if output_path is None:
44
+ out_p = input_p.with_suffix(ext)
45
+ elif os.path.isdir(output_path):
46
+ out_p = Path(output_path) / f"{input_p.stem}{ext}"
47
+ else:
48
+ out_p = Path(output_path)
49
+
50
+ # Read hyper data
51
+ if suffix == ".hyper":
52
+ df = read_hyper(input_p)
53
+ else:
54
+ with TemporaryDirectory() as td:
55
+ hyper_path = extract_hyper_from_tdsx(input_p, Path(td))
56
+ df = read_hyper(hyper_path)
57
+
58
+ write_df(df, out_p, to_fmt)
59
+
60
+ abs_path = os.path.abspath(out_p)
61
+ sys.stderr.write(f"Converted to {abs_path}\n")
62
+ output({"filePath": abs_path}, "json")
@@ -51,19 +51,40 @@ def datasources_list(filter_, page_size, limit, fmt):
51
51
  @click.option(
52
52
  "-o", "--output", "output_path", default=".", help="Output file path or directory (default: current directory)"
53
53
  )
54
- def datasources_download(datasource_id, output_path):
55
- """Download a datasource file (.tdsx)."""
54
+ @click.option(
55
+ "--to",
56
+ "to_fmt",
57
+ default="tdsx",
58
+ type=click.Choice(["tdsx", "parquet", "csv"]),
59
+ help="Output format (default: tdsx)",
60
+ )
61
+ def datasources_download(datasource_id, output_path, to_fmt):
62
+ """Download a datasource file, optionally converting to Parquet or CSV."""
63
+ from pathlib import Path
64
+
56
65
  config = resolve_config()
57
66
 
67
+ if to_fmt != "tdsx":
68
+ from ..utils.convert import check_convert_deps
69
+
70
+ check_convert_deps()
71
+
58
72
  data, filename = with_auth(
59
73
  config,
60
74
  lambda api: api.download_datasource(datasource_id=datasource_id, site_id=api.site_id),
61
75
  )
62
76
 
63
- file_path = os.path.join(output_path, filename) if os.path.isdir(output_path) else output_path
64
-
65
- with open(file_path, "wb") as f:
66
- f.write(data)
77
+ if to_fmt == "tdsx":
78
+ file_path = os.path.join(output_path, filename) if os.path.isdir(output_path) else output_path
79
+ with open(file_path, "wb") as f:
80
+ f.write(data)
81
+ else:
82
+ from ..utils.convert import convert_tdsx_bytes
83
+
84
+ stem = Path(filename).stem
85
+ out_name = f"{stem}.{to_fmt}"
86
+ file_path = os.path.join(output_path, out_name) if os.path.isdir(output_path) else output_path
87
+ convert_tdsx_bytes(data, Path(file_path), to_fmt)
67
88
 
68
89
  abs_path = os.path.abspath(file_path)
69
90
  sys.stderr.write(f"Downloaded to {abs_path}\n")
@@ -1,16 +1,13 @@
1
+ """Shared helpers for converting TDSX / HYPER files to Parquet or CSV."""
2
+
1
3
  from __future__ import annotations
2
4
 
3
- import os
4
- import sys
5
5
  from pathlib import Path
6
6
  from tempfile import TemporaryDirectory
7
7
  from typing import TYPE_CHECKING
8
8
  from zipfile import ZipFile
9
9
 
10
- import click
11
-
12
10
  from ..errors.cli_error import CliError
13
- from ..output.format import output
14
11
 
15
12
  if TYPE_CHECKING:
16
13
  import polars as pl
@@ -19,7 +16,15 @@ CONVERT_DEPS = ("pantab", "polars", "pyarrow")
19
16
  SUPPORTED_FORMATS = ("parquet", "csv")
20
17
 
21
18
 
22
- def _check_convert_deps() -> None:
19
+ def _is_importable(name: str) -> bool:
20
+ try:
21
+ __import__(name)
22
+ return True
23
+ except ImportError:
24
+ return False
25
+
26
+
27
+ def check_convert_deps() -> None:
23
28
  """Check that optional convert dependencies are installed."""
24
29
  missing = [pkg for pkg in CONVERT_DEPS if not _is_importable(pkg)]
25
30
  if missing:
@@ -30,15 +35,7 @@ def _check_convert_deps() -> None:
30
35
  )
31
36
 
32
37
 
33
- def _is_importable(name: str) -> bool:
34
- try:
35
- __import__(name)
36
- return True
37
- except ImportError:
38
- return False
39
-
40
-
41
- def _extract_hyper_from_tdsx(tdsx_path: Path, target_dir: Path) -> Path:
38
+ def extract_hyper_from_tdsx(tdsx_path: Path, target_dir: Path) -> Path:
42
39
  """Extract the .hyper file from a TDSX archive."""
43
40
  with ZipFile(tdsx_path) as zf:
44
41
  members = [m for m in zf.namelist() if m.endswith(".hyper")]
@@ -63,7 +60,7 @@ def _extract_hyper_from_tdsx(tdsx_path: Path, target_dir: Path) -> Path:
63
60
  return extracted
64
61
 
65
62
 
66
- def _read_hyper(hyper_path: Path) -> pl.DataFrame:
63
+ def read_hyper(hyper_path: Path) -> pl.DataFrame:
67
64
  """Read .hyper file and return as Polars DataFrame."""
68
65
  import pantab
69
66
  import polars as pl
@@ -83,7 +80,7 @@ def _read_hyper(hyper_path: Path) -> pl.DataFrame:
83
80
  return pl.from_pandas(next(iter(frames.values())))
84
81
 
85
82
 
86
- def _write_df(df: pl.DataFrame, output_path: Path, to: str) -> None:
83
+ def write_df(df: pl.DataFrame, output_path: Path, to: str) -> None:
87
84
  """Write DataFrame to the specified format."""
88
85
  if to == "parquet":
89
86
  df.write_parquet(output_path)
@@ -91,45 +88,12 @@ def _write_df(df: pl.DataFrame, output_path: Path, to: str) -> None:
91
88
  df.write_csv(output_path)
92
89
 
93
90
 
94
- @click.command("convert")
95
- @click.argument("input_path", type=click.Path(exists=True))
96
- @click.option(
97
- "--to", "to_fmt", default="parquet", type=click.Choice(SUPPORTED_FORMATS), help="Output format (default: parquet)"
98
- )
99
- @click.option("-o", "--output", "output_path", default=None, help="Output file or directory (default: same as input)")
100
- def convert_command(input_path, to_fmt, output_path):
101
- """Convert TDSX/HYPER files to Parquet or CSV format."""
102
- _check_convert_deps()
103
-
104
- input_p = Path(input_path)
105
- suffix = input_p.suffix.lower()
106
-
107
- if suffix not in (".tdsx", ".hyper"):
108
- raise CliError(
109
- error_type="invalid-input",
110
- message=f"Unsupported file type: {suffix}",
111
- hint="Supported formats: .tdsx, .hyper",
112
- )
113
-
114
- # Resolve output path
115
- ext = f".{to_fmt}"
116
- if output_path is None:
117
- out_p = input_p.with_suffix(ext)
118
- elif os.path.isdir(output_path):
119
- out_p = Path(output_path) / f"{input_p.stem}{ext}"
120
- else:
121
- out_p = Path(output_path)
122
-
123
- # Read hyper data
124
- if suffix == ".hyper":
125
- df = _read_hyper(input_p)
126
- else:
127
- with TemporaryDirectory() as td:
128
- hyper_path = _extract_hyper_from_tdsx(input_p, Path(td))
129
- df = _read_hyper(hyper_path)
130
-
131
- _write_df(df, out_p, to_fmt)
132
-
133
- abs_path = os.path.abspath(out_p)
134
- sys.stderr.write(f"Converted to {abs_path}\n")
135
- output({"filePath": abs_path}, "json")
91
+ def convert_tdsx_bytes(data: bytes, output_path: Path, to_fmt: str) -> None:
92
+ """Convert raw TDSX bytes directly to parquet/csv without persisting the intermediate file."""
93
+ with TemporaryDirectory() as td:
94
+ tmp_dir = Path(td)
95
+ tdsx_path = tmp_dir / "datasource.tdsx"
96
+ tdsx_path.write_bytes(data)
97
+ hyper_path = extract_hyper_from_tdsx(tdsx_path, tmp_dir)
98
+ df = read_hyper(hyper_path)
99
+ write_df(df, output_path, to_fmt)
File without changes
File without changes
File without changes