lkr-dev-cli 0.0.51__tar.gz → 0.0.55__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.
- lkr_dev_cli-0.0.55/.agents/skills/lkr-code-mode/SKILL.md +331 -0
- lkr_dev_cli-0.0.55/.agents/skills/lkr-code-mode/scripts/examples.py +46 -0
- lkr_dev_cli-0.0.55/.agents/skills/lkr-code-mode/scripts/order_items_query.py +24 -0
- lkr_dev_cli-0.0.55/.agents/skills/lkr-code-mode/scripts/personal_folder_dashboards.py +26 -0
- lkr_dev_cli-0.0.55/.agents/skills/lkr-code-mode/scripts/readme.py +65 -0
- lkr_dev_cli-0.0.55/.agents/skills/lkr-code-mode/scripts/traverse_folders.py +27 -0
- lkr_dev_cli-0.0.55/.agents/skills/lkr-project-helper/SKILL.md +251 -0
- lkr_dev_cli-0.0.55/.agents/skills/lkr-project-helper/scripts/bq_view_lookml_generator.py +129 -0
- lkr_dev_cli-0.0.55/.agents/skills/lkr-project-helper/scripts/configure_model.py +52 -0
- lkr_dev_cli-0.0.55/.agents/skills/lkr-project-helper/scripts/create_custom_view.py +64 -0
- lkr_dev_cli-0.0.55/.agents/skills/lkr-project-helper/scripts/deploy_and_reset_workspace.py +86 -0
- lkr_dev_cli-0.0.55/.agents/skills/lkr-project-helper/scripts/ensure_project_and_model.py +95 -0
- lkr_dev_cli-0.0.55/.agents/skills/lkr-project-helper/scripts/generate_lookml.py +102 -0
- lkr_dev_cli-0.0.55/.agents/skills/lkr-project-helper/scripts/init_project.py +37 -0
- lkr_dev_cli-0.0.55/.agents/skills/lkr-project-helper/scripts/inspect_connections.py +74 -0
- lkr_dev_cli-0.0.55/.agents/skills/lkr-project-helper/scripts/migrate_dashboard_to_udd.py +42 -0
- lkr_dev_cli-0.0.55/.agents/skills/lkr-project-helper/scripts/upload_assets.py +46 -0
- lkr_dev_cli-0.0.55/.agents/skills/lkr-project-helper/scripts/validate_and_commit.py +62 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/PKG-INFO +21 -8
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/README.md +10 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/auth_service.py +8 -1
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/codemode/examples.py +22 -2
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/codemode/main.py +63 -28
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/codemode/readme.py +24 -1
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/codemode/swagger.json +3072 -2056
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/extended_sdk_methods/main.py +30 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/main.py +8 -3
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/tools/lookml.py +114 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/pyproject.toml +14 -7
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/tests/test_codemode.py +44 -1
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/tests/test_extended_sdk_methods.py +26 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/tests/test_lookml_tools.py +71 -0
- lkr_dev_cli-0.0.55/uv.lock +2138 -0
- lkr_dev_cli-0.0.51/uv.lock +0 -1912
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/.github/workflows/ci.yml +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/.github/workflows/release.yml +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/.github/workflows/website.yml +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/.gitignore +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/.python-version +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/.vscode/launch.json +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/.vscode/settings.json +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/Dockerfile +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/LICENSE +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/Makefile +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/cloudbuild.yaml +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/codemode.md +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/__init__.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/auth/__init__.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/auth/main.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/auth/oauth.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/classes.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/codemode/LOCAL.md +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/codemode/__init__.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/codemode/constant.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/codemode/download_swagger.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/codemode/help.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/codemode/type.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/constants.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/custom_types.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/exceptions.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/extended_sdk_methods/__init__.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/extended_sdk_methods/classes.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/logger.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/mcp/classes.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/mcp/main.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/mcp/utils.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/observability/classes.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/observability/embed_container.html +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/observability/main.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/observability/utils.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/tools/classes.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/tools/main.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr/tools/permission_deprecation.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr.md +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/lkr_dev_cli/__init__.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/tests/TESTING.md +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/tests/test_dependency_resolution.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/tests/test_deps.sh +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/tests/test_init_sdk.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/tests/test_oauth_account.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/tests/test_permission_deprecation.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/tests/test_version.py +0 -0
- {lkr_dev_cli-0.0.51 → lkr_dev_cli-0.0.55}/ty.toml +0 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lkr-code-mode
|
|
3
|
+
description: >
|
|
4
|
+
Execute Python code, scripts, or Looker SDK operations against Looker instances using `lkr code-mode sandbox` and the Monty sandbox.
|
|
5
|
+
Use when asked to run Python code against Looker, query explores, inspect LookML models/views, manage dashboards/folders, check user auth, or run Looker SDK recipes.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Looker Code Mode Agent Skill (`lkr code-mode`)
|
|
9
|
+
|
|
10
|
+
This skill teaches AI agents how to execute Python code and Looker SDK operations against active Looker instances non-interactively using `lkr code-mode sandbox`.
|
|
11
|
+
|
|
12
|
+
## Mental Model
|
|
13
|
+
|
|
14
|
+
Instead of exposing hundreds of separate tool definitions (which bloats context windows), `lkr code-mode sandbox` runs Python code inside the Monty sandbox with direct access to all Looker SDK methods as global functions.
|
|
15
|
+
|
|
16
|
+
- No SDK instantiation needed. Looker SDK methods are pre-bound as globals (such as `me()`, `folder(id)`, `run_inline_query()`, `all_dashboards()`) or accessible via `sdk.<method>()` (such as `sdk.me()`).
|
|
17
|
+
- No imports required. Do not write `import looker_sdk`.
|
|
18
|
+
- Return values are plain Python dictionaries and lists. Access properties with dictionary keys (`user["id"]`), not attribute dot notation (`user.id`).
|
|
19
|
+
- Always return the final result. Avoid `print()` statements so the output is cleanly structured JSON.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
Install into your agent workspace using `npx skills`:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx skills add lkrdev/cli --skill lkr-code-mode
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Command Discovery and CLI Help
|
|
30
|
+
|
|
31
|
+
Inspect CLI options, authentication subcommands, or sandbox parameters:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# General CLI help
|
|
35
|
+
uvx --from "lkr-dev-cli[codemode]" lkr-dev-cli --help
|
|
36
|
+
uv run lkr --help
|
|
37
|
+
|
|
38
|
+
# Authentication commands (login, logout, whoami, list)
|
|
39
|
+
uvx --from "lkr-dev-cli[codemode]" lkr-dev-cli auth --help
|
|
40
|
+
uv run lkr auth --help
|
|
41
|
+
|
|
42
|
+
# Code-mode subcommands (sandbox, run)
|
|
43
|
+
uvx --from "lkr-dev-cli[codemode]" lkr-dev-cli code-mode --help
|
|
44
|
+
uv run lkr code-mode --help
|
|
45
|
+
|
|
46
|
+
# Sandbox specific options (--code, --file, --var, --dev-mode)
|
|
47
|
+
uvx --from "lkr-dev-cli[codemode]" lkr-dev-cli code-mode sandbox --help
|
|
48
|
+
uv run lkr code-mode sandbox --help
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Command Execution Syntax
|
|
52
|
+
|
|
53
|
+
### Direct Inline Code Execution (`--code` / `-c`)
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# In local repository workspace:
|
|
57
|
+
uv run lkr code-mode sandbox --code="return me()"
|
|
58
|
+
|
|
59
|
+
# Using uvx:
|
|
60
|
+
uvx --from "lkr-dev-cli[codemode]" lkr-dev-cli code-mode sandbox --code="return me()"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Script File Execution (`--file` / `-f`)
|
|
64
|
+
|
|
65
|
+
Run standalone script files directly or execute pre-built scripts located in `scripts/`:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Execute pre-built helper scripts:
|
|
69
|
+
uv run lkr code-mode sandbox --file=./skills/lkr-code-mode/scripts/personal_folder_dashboards.py
|
|
70
|
+
uv run lkr code-mode sandbox --file=./skills/lkr-code-mode/scripts/order_items_query.py
|
|
71
|
+
uv run lkr code-mode sandbox --file=./skills/lkr-code-mode/scripts/traverse_folders.py
|
|
72
|
+
|
|
73
|
+
# Using uvx:
|
|
74
|
+
uvx --from "lkr-dev-cli[codemode]" lkr-dev-cli code-mode sandbox --file=./path/to/script.py
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Injected Variables (`--var` / `-v`)
|
|
78
|
+
|
|
79
|
+
Inject string variables into the sandbox as top-level Python identifiers:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
uv run lkr code-mode sandbox --file=./script.py --var project=my_project --var folder_id=123
|
|
83
|
+
uvx --from "lkr-dev-cli[codemode]" lkr-dev-cli code-mode sandbox --file=./script.py -v project=my_project -v folder_id=123
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Inside `script.py`, `project` and `folder_id` are directly accessible as global strings:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
# script.py
|
|
90
|
+
p = project
|
|
91
|
+
f = folder(folder_id)
|
|
92
|
+
return {"project": p, "folder_name": f.get("name")}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Development Mode (`--dev-mode`)
|
|
96
|
+
|
|
97
|
+
Run operations in Looker Development Mode:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
uv run lkr code-mode sandbox --dev-mode --code="return all_projects()"
|
|
101
|
+
uvx --from "lkr-dev-cli[codemode]" lkr-dev-cli code-mode sandbox --dev-mode --code="return all_projects()"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Authentication Workflow and Pre-Flight Checks
|
|
105
|
+
|
|
106
|
+
Perform this check once per conversation when establishing the Looker connection. Once verified or when an active session exists, subsequent sandbox commands reuse the session directly.
|
|
107
|
+
|
|
108
|
+
### Step 1: Prompt for Authentication Preference
|
|
109
|
+
|
|
110
|
+
Prompt the user to choose their preferred authentication method:
|
|
111
|
+
- OAuth (Interactive user session)
|
|
112
|
+
- API Key (`LOOKERSDK_*` environment variables)
|
|
113
|
+
|
|
114
|
+
### Step 2: Session Management and Verification
|
|
115
|
+
|
|
116
|
+
Check existing credentials and verify connectivity:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# List configured OAuth accounts
|
|
120
|
+
uvx --from "lkr-dev-cli[codemode]" lkr-dev-cli auth list
|
|
121
|
+
uv run lkr auth list
|
|
122
|
+
|
|
123
|
+
# Verify current authentication and active user
|
|
124
|
+
uvx --from "lkr-dev-cli[codemode]" lkr-dev-cli auth whoami
|
|
125
|
+
uv run lkr auth whoami
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Step 3: OAuth Pre-Flight Verification and Login Flow
|
|
129
|
+
|
|
130
|
+
If OAuth is selected and no active account is configured, perform this setup once:
|
|
131
|
+
|
|
132
|
+
1. Prompt the user for their Looker instance URL (e.g. `https://mycompany.looker.com`).
|
|
133
|
+
2. Run a pre-flight `GET` check against the instance auth endpoint before opening a browser:
|
|
134
|
+
```
|
|
135
|
+
https://<instance_url>/auth?client_id=lkr-cli&redirect_uri=YOUR_REDIRECT_URI&response_type=code&scope=api&state=YOUR_UNIQUE_STATE_STRING&code_challenge=CHALLENGE_STRING&code_challenge_method=S256
|
|
136
|
+
```
|
|
137
|
+
3. If the response HTML contains "The OAuth client was not found", inform the user that `lkr-cli` is not registered on their Looker instance and link them to the configuration guide at [https://www.lkr.dev/docs/tools/cli/#oauth2-prerequisites](https://www.lkr.dev/docs/tools/cli/#oauth2-prerequisites).
|
|
138
|
+
4. If the client is detected (the response returns "redirect_uri mismatch"), run the interactive login command:
|
|
139
|
+
```bash
|
|
140
|
+
uvx --from "lkr-dev-cli[codemode]" lkr-dev-cli auth login
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Step 4: Authentication Execution Options
|
|
144
|
+
|
|
145
|
+
Execute codemode directly via CLI (`lkr-dev-cli code-mode sandbox --code="..."`) instead of running an MCP server. Pass `--oauth-account` or `uvx --env-file=.env` so credentials stay out of version-controlled files:
|
|
146
|
+
|
|
147
|
+
#### Named OAuth Profiles (`--oauth-account`)
|
|
148
|
+
```bash
|
|
149
|
+
# Local workspace execution:
|
|
150
|
+
uv run lkr --oauth-account=abc code-mode sandbox --code="return me()"
|
|
151
|
+
|
|
152
|
+
# Using uvx:
|
|
153
|
+
uvx --from "lkr-dev-cli[codemode]" lkr-dev-cli --oauth-account=abc code-mode sandbox --code="return me()"
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
#### Custom Environment File (`--env-file`)
|
|
157
|
+
```bash
|
|
158
|
+
# Passed to uvx directly:
|
|
159
|
+
uvx --env-file .env --from "lkr-dev-cli[codemode]" lkr-dev-cli code-mode sandbox --code="return me()"
|
|
160
|
+
|
|
161
|
+
# Passed to lkr CLI:
|
|
162
|
+
uv run lkr --env-file=.env code-mode sandbox --code="return me()"
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
#### Force OAuth Flow (`--force-oauth`)
|
|
166
|
+
```bash
|
|
167
|
+
uv run lkr --force-oauth code-mode sandbox --code="return me()"
|
|
168
|
+
uvx --from "lkr-dev-cli[codemode]" lkr-dev-cli --force-oauth code-mode sandbox --code="return me()"
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
#### Standard Environment Variables
|
|
172
|
+
- `LOOKERSDK_BASE_URL`: Looker instance API URL (e.g. `https://your-instance.looker.com:19999` or `https://your-instance.looker.com`)
|
|
173
|
+
- `LOOKERSDK_CLIENT_ID`: API3 client ID
|
|
174
|
+
- `LOOKERSDK_CLIENT_SECRET`: API3 client secret
|
|
175
|
+
- `LOOKERSDK_VERIFY_SSL`: `true` or `false` (default: `true`)
|
|
176
|
+
|
|
177
|
+
## SDK Discovery Builtins
|
|
178
|
+
|
|
179
|
+
Use these built-in sandbox functions to explore available Looker SDK methods, schemas, and types:
|
|
180
|
+
|
|
181
|
+
| Function | Description | Example |
|
|
182
|
+
| :--- | :--- | :--- |
|
|
183
|
+
| `dir()` | Lists all available SDK methods | `return [m for m in dir() if 'dashboard' in m]` |
|
|
184
|
+
| `help(pattern)` | Search SDK methods matching pattern | `return help('dashboard')` |
|
|
185
|
+
| `lookup(method_name)` | Get full docstring and signature for a method | `return lookup('search_dashboards')` |
|
|
186
|
+
| `search_with_lookups(pattern)` | Get docstrings for all matching methods | `return search_with_lookups('board_item')` |
|
|
187
|
+
| `lookup_type(type_name)` | Inspect fields and types of a Looker model | `return lookup_type('Dashboard')` |
|
|
188
|
+
| `examples()` | Built-in SDK code recipes | `return examples()` |
|
|
189
|
+
| `readme()` | Full reference documentation | `return readme()` |
|
|
190
|
+
|
|
191
|
+
## Bundled Helper Scripts (`scripts/`)
|
|
192
|
+
|
|
193
|
+
Pre-packaged scripts are located in `scripts/`:
|
|
194
|
+
|
|
195
|
+
- [scripts/personal_folder_dashboards.py](./scripts/personal_folder_dashboards.py): Inspects `me()`, extracts `personal_folder_id`, and returns personal folder dashboards.
|
|
196
|
+
- [scripts/order_items_query.py](./scripts/order_items_query.py): Runs an inline query on `thelook` / `order_items` with `created_year` and `users.count` filtered by `status: Returned`.
|
|
197
|
+
- [scripts/traverse_folders.py](./scripts/traverse_folders.py): Recursively collects dashboards and looks across all subfolders.
|
|
198
|
+
- [scripts/readme.py](./scripts/readme.py): Reference documentation generator.
|
|
199
|
+
- [scripts/examples.py](./scripts/examples.py): Built-in code examples collection.
|
|
200
|
+
|
|
201
|
+
## Common Looker SDK Recipes
|
|
202
|
+
|
|
203
|
+
### Inspect User and Personal Folder Dashboards
|
|
204
|
+
|
|
205
|
+
Get the authenticated user, inspect their `personal_folder_id`, and retrieve all dashboards in their personal folder:
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
user = me()
|
|
209
|
+
if not user:
|
|
210
|
+
return {"error": "Unable to retrieve current user metadata."}
|
|
211
|
+
|
|
212
|
+
personal_folder_id = user.get("personal_folder_id")
|
|
213
|
+
if not personal_folder_id:
|
|
214
|
+
return {"error": "User does not have a personal folder configured."}
|
|
215
|
+
|
|
216
|
+
personal_folder = folder(personal_folder_id)
|
|
217
|
+
if not personal_folder:
|
|
218
|
+
return {"error": f"Unable to retrieve folder with ID {personal_folder_id}."}
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
"user_id": user.get("id"),
|
|
222
|
+
"user_email": user.get("email"),
|
|
223
|
+
"personal_folder_id": personal_folder_id,
|
|
224
|
+
"dashboards": personal_folder.get("dashboards", []),
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Recursively Traverse Folders for Dashboards and Looks
|
|
229
|
+
|
|
230
|
+
Traverse a personal folder and all nested subfolders to gather all dashboards and looks:
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
def get_all_items(folder_id):
|
|
234
|
+
if not folder_id:
|
|
235
|
+
return {"dashboards": [], "looks": []}
|
|
236
|
+
f = folder(folder_id)
|
|
237
|
+
if not f:
|
|
238
|
+
return {"dashboards": [], "looks": []}
|
|
239
|
+
items = {"dashboards": f.get("dashboards", []), "looks": f.get("looks", [])}
|
|
240
|
+
children = folder_children(folder_id) or []
|
|
241
|
+
for child in children:
|
|
242
|
+
child_items = get_all_items(child.get("id"))
|
|
243
|
+
items["dashboards"].extend(child_items["dashboards"])
|
|
244
|
+
items["looks"].extend(child_items["looks"])
|
|
245
|
+
return items
|
|
246
|
+
|
|
247
|
+
user_data = me()
|
|
248
|
+
if not user_data or not user_data.get("personal_folder_id"):
|
|
249
|
+
return {"dashboards": [], "looks": []}
|
|
250
|
+
return get_all_items(user_data["personal_folder_id"])
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Run an Inline Explore Query on `thelook` (`order_items`)
|
|
254
|
+
|
|
255
|
+
Execute an inline query on model `thelook`, explore `order_items`, with fields `order_items.created_year` and `users.count`, filtered by `order_items.status: Returned`:
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
return run_inline_query(
|
|
259
|
+
result_format="json",
|
|
260
|
+
body={
|
|
261
|
+
"model": "thelook",
|
|
262
|
+
"view": "order_items",
|
|
263
|
+
"fields": [
|
|
264
|
+
"order_items.created_year",
|
|
265
|
+
"users.count",
|
|
266
|
+
],
|
|
267
|
+
"filters": {
|
|
268
|
+
"order_items.status": "Returned",
|
|
269
|
+
},
|
|
270
|
+
"sorts": ["order_items.created_year desc"],
|
|
271
|
+
"limit": "500",
|
|
272
|
+
},
|
|
273
|
+
)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Inspect LookML Models and Explores
|
|
277
|
+
|
|
278
|
+
Inspect model configuration and extract available fields (dimensions and measures) from an explore:
|
|
279
|
+
|
|
280
|
+
```python
|
|
281
|
+
explore = lookml_model_explore("thelook", "order_items")
|
|
282
|
+
|
|
283
|
+
fields = explore.get("fields", {})
|
|
284
|
+
dimensions = [d["name"] for d in fields.get("dimensions", [])]
|
|
285
|
+
measures = [m["name"] for m in fields.get("measures", [])]
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
"model": "thelook",
|
|
289
|
+
"explore": "order_items",
|
|
290
|
+
"total_dimensions": len(dimensions),
|
|
291
|
+
"total_measures": len(measures),
|
|
292
|
+
"sample_dimensions": dimensions[:5],
|
|
293
|
+
"sample_measures": measures[:5],
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Manage LookML Projects and Git Branches
|
|
298
|
+
|
|
299
|
+
List all LookML projects and inspect git branch state:
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
projects = all_projects(fields="id,name,git_remote_url")
|
|
303
|
+
project_name = projects[0]["id"] if projects else None
|
|
304
|
+
|
|
305
|
+
branches = git_branches(project_name) if project_name else []
|
|
306
|
+
return {
|
|
307
|
+
"projects": projects,
|
|
308
|
+
"selected_project": project_name,
|
|
309
|
+
"branches": branches,
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Search Dashboards with Field Filtering
|
|
314
|
+
|
|
315
|
+
Always specify `fields` when querying collections on large instances to prevent timeouts:
|
|
316
|
+
|
|
317
|
+
```python
|
|
318
|
+
return search_dashboards(
|
|
319
|
+
title="Marketing%",
|
|
320
|
+
fields="id,title,folder,user_id,created_at",
|
|
321
|
+
limit=20,
|
|
322
|
+
)
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## Agent Guidelines for Non-Interactive Execution
|
|
326
|
+
|
|
327
|
+
- Run `me()` once early in a task to verify authentication before executing multiple operations.
|
|
328
|
+
- Responses are JSON primitives. Write `dash["title"]` instead of `dash.title`.
|
|
329
|
+
- Always filter returned attributes with `fields` (e.g. `fields="id,title"`) when querying large instances.
|
|
330
|
+
- Pass `--dev-mode` on the CLI when making changes against LookML files or personal dev branches.
|
|
331
|
+
- Always return structured objects (dicts, lists, strings). Do not use `print()` statements.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Built-in Looker Code Mode examples."""
|
|
2
|
+
|
|
3
|
+
EXAMPLES = [
|
|
4
|
+
[
|
|
5
|
+
"Find all dashboard-related methods",
|
|
6
|
+
"return [m for m in dir() if 'dashboard' in m.lower()]",
|
|
7
|
+
],
|
|
8
|
+
[
|
|
9
|
+
"Get the description of a specific method",
|
|
10
|
+
"return help('search_dashboards')",
|
|
11
|
+
],
|
|
12
|
+
[
|
|
13
|
+
"Get personal folder dashboards",
|
|
14
|
+
"""user = me()
|
|
15
|
+
personal_folder = folder(user["personal_folder_id"])
|
|
16
|
+
return personal_folder.get("dashboards", [])""",
|
|
17
|
+
],
|
|
18
|
+
[
|
|
19
|
+
"Run an inline query on thelook (order_items)",
|
|
20
|
+
"""return run_inline_query(
|
|
21
|
+
result_format="json",
|
|
22
|
+
body={
|
|
23
|
+
"model": "thelook",
|
|
24
|
+
"view": "order_items",
|
|
25
|
+
"fields": ["order_items.created_year", "users.count"],
|
|
26
|
+
"filters": {"order_items.status": "Returned"},
|
|
27
|
+
"sorts": ["order_items.created_year desc"],
|
|
28
|
+
"limit": "500"
|
|
29
|
+
}
|
|
30
|
+
)""",
|
|
31
|
+
],
|
|
32
|
+
[
|
|
33
|
+
"List personal dashboards & looks (recursive)",
|
|
34
|
+
"""def get_all_items(folder_id):
|
|
35
|
+
f = folder(folder_id)
|
|
36
|
+
items = {"dashboards": f.get("dashboards", []), "looks": f.get("looks", [])}
|
|
37
|
+
for child in folder_children(folder_id):
|
|
38
|
+
child_items = get_all_items(child["id"])
|
|
39
|
+
items["dashboards"].extend(child_items["dashboards"])
|
|
40
|
+
items["looks"].extend(child_items["looks"])
|
|
41
|
+
return items
|
|
42
|
+
|
|
43
|
+
me_data = me()
|
|
44
|
+
return get_all_items(me_data["personal_folder_id"])""",
|
|
45
|
+
],
|
|
46
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# ruff: noqa: F821
|
|
2
|
+
"""Run an inline query on thelook / order_items with status filter."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def main():
|
|
6
|
+
return run_inline_query( # ty: ignore[unresolved-reference]
|
|
7
|
+
result_format="json",
|
|
8
|
+
body={
|
|
9
|
+
"model": "thelook",
|
|
10
|
+
"view": "order_items",
|
|
11
|
+
"fields": [
|
|
12
|
+
"order_items.created_year",
|
|
13
|
+
"users.count",
|
|
14
|
+
],
|
|
15
|
+
"filters": {
|
|
16
|
+
"order_items.status": "Returned",
|
|
17
|
+
},
|
|
18
|
+
"sorts": ["order_items.created_year desc"],
|
|
19
|
+
"limit": "500",
|
|
20
|
+
},
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
main()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# ruff: noqa: F821
|
|
2
|
+
"""Get current user's personal folder ID and list all dashboards inside it."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def main():
|
|
6
|
+
user = me() # ty: ignore[unresolved-reference]
|
|
7
|
+
if not user:
|
|
8
|
+
return {"error": "Unable to retrieve current user metadata."}
|
|
9
|
+
|
|
10
|
+
personal_folder_id = user.get("personal_folder_id")
|
|
11
|
+
if not personal_folder_id:
|
|
12
|
+
return {"error": "User does not have a personal folder configured."}
|
|
13
|
+
|
|
14
|
+
personal_folder = folder(personal_folder_id) # ty: ignore[unresolved-reference]
|
|
15
|
+
if not personal_folder:
|
|
16
|
+
return {"error": f"Unable to retrieve folder with ID {personal_folder_id}."}
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
"user_id": user.get("id"),
|
|
20
|
+
"user_email": user.get("email"),
|
|
21
|
+
"personal_folder_id": personal_folder_id,
|
|
22
|
+
"dashboards": personal_folder.get("dashboards", []),
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
main()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Full reference documentation and instructions for Looker Code Mode."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_readme() -> str:
|
|
5
|
+
return """
|
|
6
|
+
Looker Code Mode - Instructions & Examples
|
|
7
|
+
|
|
8
|
+
RULES & HINTS:
|
|
9
|
+
1. **SDK Access**: All Looker SDK methods are available as global functions (e.g., `me()`) or via the `sdk` object (e.g., `sdk.me()`).
|
|
10
|
+
2. **Dict Access**: Return values are dictionaries, not objects. Use `user["id"]`, not `user.id`.
|
|
11
|
+
3. **Discovery**: Use `dir()`, `help('pattern')`, `lookup('method_name')`, `lookup_type('TypeName')`, and `examples()` to explore the SDK.
|
|
12
|
+
4. **No Imports**: Do not `import looker_sdk`.
|
|
13
|
+
5. **Output**: Return your results instead of using `print()`.
|
|
14
|
+
6. **Efficiency**: Always use the `fields` parameter (e.g., `all_dashboards(fields="id,title")`) when listing many objects to prevent timeouts.
|
|
15
|
+
7. **Nested Folders**: Use `folder_children(id)` to get sub-folders.
|
|
16
|
+
8. **Injected CLI Variables**: When using `sandbox` with `--var key=value` flags, those variables are directly available in your code as top-level string constants.
|
|
17
|
+
|
|
18
|
+
EXAMPLES:
|
|
19
|
+
|
|
20
|
+
Example 1: Find all dashboard-related methods
|
|
21
|
+
return help('dashboard')
|
|
22
|
+
|
|
23
|
+
Example 2: Get description of a specific method
|
|
24
|
+
return lookup('search_dashboards')
|
|
25
|
+
|
|
26
|
+
Example 3: Lookup a type definition
|
|
27
|
+
return lookup_type('User')
|
|
28
|
+
|
|
29
|
+
Example 4: Search and return full details for all matches
|
|
30
|
+
return search_with_lookups('board_item')
|
|
31
|
+
|
|
32
|
+
Example 5: Get personal folder dashboards
|
|
33
|
+
user = me()
|
|
34
|
+
personal_folder = folder(user["personal_folder_id"])
|
|
35
|
+
return personal_folder.get("dashboards", [])
|
|
36
|
+
|
|
37
|
+
Example 6: Run an inline query on thelook (order_items)
|
|
38
|
+
return run_inline_query(
|
|
39
|
+
result_format="json",
|
|
40
|
+
body={
|
|
41
|
+
"model": "thelook",
|
|
42
|
+
"view": "order_items",
|
|
43
|
+
"fields": ["order_items.created_year", "users.count"],
|
|
44
|
+
"filters": {"order_items.status": "Returned"},
|
|
45
|
+
"sorts": ["order_items.created_year desc"],
|
|
46
|
+
"limit": "500"
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
Example 7: List personal dashboards & looks (Recursive traversal)
|
|
51
|
+
def get_all_items(folder_id):
|
|
52
|
+
f = folder(folder_id)
|
|
53
|
+
items = {
|
|
54
|
+
"dashboards": f.get("dashboards", []),
|
|
55
|
+
"looks": f.get("looks", [])
|
|
56
|
+
}
|
|
57
|
+
for child in folder_children(folder_id):
|
|
58
|
+
child_items = get_all_items(child["id"])
|
|
59
|
+
items["dashboards"].extend(child_items["dashboards"])
|
|
60
|
+
items["looks"].extend(child_items["looks"])
|
|
61
|
+
return items
|
|
62
|
+
|
|
63
|
+
me_data = me()
|
|
64
|
+
return get_all_items(me_data["personal_folder_id"])
|
|
65
|
+
"""
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# ruff: noqa: F821
|
|
2
|
+
"""Recursively traverse folders starting from personal folder to gather all dashboards and looks."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_all_items(folder_id):
|
|
6
|
+
if not folder_id:
|
|
7
|
+
return {"dashboards": [], "looks": []}
|
|
8
|
+
f = folder(folder_id) # ty: ignore[unresolved-reference]
|
|
9
|
+
if not f:
|
|
10
|
+
return {"dashboards": [], "looks": []}
|
|
11
|
+
items = {"dashboards": f.get("dashboards", []), "looks": f.get("looks", [])}
|
|
12
|
+
children = folder_children(folder_id) or [] # ty: ignore[unresolved-reference]
|
|
13
|
+
for child in children:
|
|
14
|
+
child_items = get_all_items(child.get("id"))
|
|
15
|
+
items["dashboards"].extend(child_items["dashboards"])
|
|
16
|
+
items["looks"].extend(child_items["looks"])
|
|
17
|
+
return items
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def main():
|
|
21
|
+
user_data = me() # ty: ignore[unresolved-reference]
|
|
22
|
+
if not user_data or not user_data.get("personal_folder_id"):
|
|
23
|
+
return {"dashboards": [], "looks": []}
|
|
24
|
+
return get_all_items(user_data["personal_folder_id"])
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
main()
|