lkr-dev-cli 0.0.47__tar.gz → 0.0.53__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 (82) hide show
  1. lkr_dev_cli-0.0.53/.agents/skills/lkr-code-mode/SKILL.md +331 -0
  2. lkr_dev_cli-0.0.53/.agents/skills/lkr-code-mode/scripts/examples.py +46 -0
  3. lkr_dev_cli-0.0.53/.agents/skills/lkr-code-mode/scripts/order_items_query.py +24 -0
  4. lkr_dev_cli-0.0.53/.agents/skills/lkr-code-mode/scripts/personal_folder_dashboards.py +26 -0
  5. lkr_dev_cli-0.0.53/.agents/skills/lkr-code-mode/scripts/readme.py +65 -0
  6. lkr_dev_cli-0.0.53/.agents/skills/lkr-code-mode/scripts/traverse_folders.py +27 -0
  7. lkr_dev_cli-0.0.53/.agents/skills/lkr-project-helper/SKILL.md +251 -0
  8. lkr_dev_cli-0.0.53/.agents/skills/lkr-project-helper/scripts/bq_view_lookml_generator.py +129 -0
  9. lkr_dev_cli-0.0.53/.agents/skills/lkr-project-helper/scripts/configure_model.py +52 -0
  10. lkr_dev_cli-0.0.53/.agents/skills/lkr-project-helper/scripts/create_custom_view.py +64 -0
  11. lkr_dev_cli-0.0.53/.agents/skills/lkr-project-helper/scripts/deploy_and_reset_workspace.py +86 -0
  12. lkr_dev_cli-0.0.53/.agents/skills/lkr-project-helper/scripts/ensure_project_and_model.py +95 -0
  13. lkr_dev_cli-0.0.53/.agents/skills/lkr-project-helper/scripts/generate_lookml.py +102 -0
  14. lkr_dev_cli-0.0.53/.agents/skills/lkr-project-helper/scripts/init_project.py +37 -0
  15. lkr_dev_cli-0.0.53/.agents/skills/lkr-project-helper/scripts/inspect_connections.py +74 -0
  16. lkr_dev_cli-0.0.53/.agents/skills/lkr-project-helper/scripts/migrate_dashboard_to_udd.py +42 -0
  17. lkr_dev_cli-0.0.53/.agents/skills/lkr-project-helper/scripts/upload_assets.py +46 -0
  18. lkr_dev_cli-0.0.53/.agents/skills/lkr-project-helper/scripts/validate_and_commit.py +62 -0
  19. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/PKG-INFO +12 -2
  20. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/README.md +10 -0
  21. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/codemode.md +3 -0
  22. {lkr_dev_cli-0.0.47/lkr_dev_cli → lkr_dev_cli-0.0.53/lkr}/__init__.py +3 -3
  23. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/auth/main.py +7 -6
  24. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/auth/oauth.py +10 -13
  25. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/auth_service.py +48 -37
  26. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/classes.py +2 -2
  27. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/codemode/download_swagger.py +3 -2
  28. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/codemode/examples.py +22 -2
  29. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/codemode/help.py +11 -10
  30. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/codemode/main.py +64 -13
  31. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/codemode/readme.py +25 -1
  32. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/codemode/type.py +4 -2
  33. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/custom_types.py +2 -2
  34. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/extended_sdk_methods/classes.py +21 -21
  35. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/extended_sdk_methods/main.py +62 -32
  36. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/logger.py +1 -0
  37. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/main.py +3 -3
  38. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/mcp/classes.py +2 -2
  39. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/mcp/main.py +37 -38
  40. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/mcp/utils.py +6 -5
  41. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/observability/classes.py +12 -11
  42. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/observability/main.py +30 -32
  43. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/observability/utils.py +8 -7
  44. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/tools/classes.py +24 -24
  45. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/tools/lookml.py +134 -20
  46. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/tools/main.py +10 -10
  47. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/tools/permission_deprecation.py +14 -12
  48. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr.md +22 -0
  49. {lkr_dev_cli-0.0.47/lkr → lkr_dev_cli-0.0.53/lkr_dev_cli}/__init__.py +3 -3
  50. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/pyproject.toml +1 -1
  51. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/tests/test_codemode.py +52 -7
  52. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/tests/test_dependency_resolution.py +21 -7
  53. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/tests/test_extended_sdk_methods.py +28 -1
  54. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/tests/test_init_sdk.py +11 -8
  55. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/tests/test_lookml_tools.py +72 -1
  56. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/tests/test_oauth_account.py +9 -3
  57. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/tests/test_permission_deprecation.py +4 -1
  58. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/tests/test_version.py +1 -0
  59. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/uv.lock +542 -505
  60. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/.github/workflows/ci.yml +0 -0
  61. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/.github/workflows/release.yml +0 -0
  62. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/.github/workflows/website.yml +0 -0
  63. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/.gitignore +0 -0
  64. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/.python-version +0 -0
  65. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/.vscode/launch.json +0 -0
  66. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/.vscode/settings.json +0 -0
  67. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/Dockerfile +0 -0
  68. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/LICENSE +0 -0
  69. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/Makefile +0 -0
  70. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/cloudbuild.yaml +0 -0
  71. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/auth/__init__.py +0 -0
  72. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/codemode/LOCAL.md +0 -0
  73. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/codemode/__init__.py +0 -0
  74. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/codemode/constant.py +0 -0
  75. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/codemode/swagger.json +0 -0
  76. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/constants.py +0 -0
  77. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/exceptions.py +0 -0
  78. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/extended_sdk_methods/__init__.py +7 -7
  79. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/lkr/observability/embed_container.html +0 -0
  80. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/tests/TESTING.md +0 -0
  81. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/tests/test_deps.sh +0 -0
  82. {lkr_dev_cli-0.0.47 → lkr_dev_cli-0.0.53}/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()