lql-cli 0.2.0__tar.gz → 0.3.0__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 (34) hide show
  1. {lql_cli-0.2.0 → lql_cli-0.3.0}/.claude/settings.local.json +8 -1
  2. {lql_cli-0.2.0 → lql_cli-0.3.0}/.gitignore +2 -0
  3. {lql_cli-0.2.0 → lql_cli-0.3.0}/PKG-INFO +48 -3
  4. {lql_cli-0.2.0 → lql_cli-0.3.0}/README.md +46 -2
  5. lql_cli-0.3.0/examples/agent-traces.jsonl +20 -0
  6. lql_cli-0.3.0/package-lock.json +6 -0
  7. {lql_cli-0.2.0 → lql_cli-0.3.0}/pyproject.toml +2 -1
  8. lql_cli-0.3.0/src/lql/__init__.py +1 -0
  9. {lql_cli-0.2.0 → lql_cli-0.3.0}/src/lql/cli.py +4 -11
  10. {lql_cli-0.2.0 → lql_cli-0.3.0}/src/lql/commands/instructions.py +22 -0
  11. lql_cli-0.3.0/src/lql/commands/preview.py +653 -0
  12. lql_cli-0.3.0/src/lql/commands/tui.py +514 -0
  13. lql_cli-0.3.0/src/lql/commands/update.py +82 -0
  14. {lql_cli-0.2.0 → lql_cli-0.3.0}/uv.lock +142 -2
  15. lql_cli-0.2.0/src/lql/__init__.py +0 -1
  16. {lql_cli-0.2.0 → lql_cli-0.3.0}/src/lql/_opts.py +0 -0
  17. {lql_cli-0.2.0 → lql_cli-0.3.0}/src/lql/api.py +0 -0
  18. {lql_cli-0.2.0 → lql_cli-0.3.0}/src/lql/commands/__init__.py +0 -0
  19. {lql_cli-0.2.0 → lql_cli-0.3.0}/src/lql/commands/annotations.py +0 -0
  20. {lql_cli-0.2.0 → lql_cli-0.3.0}/src/lql/commands/auth.py +0 -0
  21. {lql_cli-0.2.0 → lql_cli-0.3.0}/src/lql/commands/buckets.py +0 -0
  22. {lql_cli-0.2.0 → lql_cli-0.3.0}/src/lql/commands/datasets.py +0 -0
  23. {lql_cli-0.2.0 → lql_cli-0.3.0}/src/lql/commands/edits.py +0 -0
  24. {lql_cli-0.2.0 → lql_cli-0.3.0}/src/lql/commands/evals.py +0 -0
  25. {lql_cli-0.2.0 → lql_cli-0.3.0}/src/lql/commands/highlights.py +0 -0
  26. {lql_cli-0.2.0 → lql_cli-0.3.0}/src/lql/commands/issues.py +0 -0
  27. {lql_cli-0.2.0 → lql_cli-0.3.0}/src/lql/commands/reports.py +0 -0
  28. {lql_cli-0.2.0 → lql_cli-0.3.0}/src/lql/commands/skills.py +0 -0
  29. {lql_cli-0.2.0 → lql_cli-0.3.0}/src/lql/commands/spec.py +0 -0
  30. {lql_cli-0.2.0 → lql_cli-0.3.0}/src/lql/commands/workspaces.py +0 -0
  31. {lql_cli-0.2.0 → lql_cli-0.3.0}/src/lql/config.py +0 -0
  32. {lql_cli-0.2.0 → lql_cli-0.3.0}/src/lql/output.py +0 -0
  33. {lql_cli-0.2.0 → lql_cli-0.3.0}/src/lql/sessions.py +0 -0
  34. {lql_cli-0.2.0 → lql_cli-0.3.0}/src/lql/util.py +0 -0
@@ -56,7 +56,14 @@
56
56
  "Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\)['info']; print\\('name:', d['name']\\); print\\('version:', d['version']\\); print\\('summary:', d.get\\('summary'\\)\\); print\\('author:', d.get\\('author'\\)\\); print\\('home:', d.get\\('home_page'\\) or d.get\\('project_url'\\)\\); print\\('uploaded releases:', len\\(json.load\\(open\\('/dev/stdin'\\)\\) if False else []\\) \\)\")",
57
57
  "Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\)['info']; print\\('name:',d['name']\\); print\\('version:',d['version']\\); print\\('summary:',d.get\\('summary'\\)\\); print\\('author:',d.get\\('author'\\)\\)\")",
58
58
  "Bash(uvx --quiet twine check dist/*)",
59
- "Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\('name:', d['info']['name']\\); print\\('version:', d['info']['version']\\); print\\('files:', [f['filename'] for f in d['urls']]\\)\")"
59
+ "Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\('name:', d['info']['name']\\); print\\('version:', d['info']['version']\\); print\\('files:', [f['filename'] for f in d['urls']]\\)\")",
60
+ "Bash(rm -f ~/.config/lql/publish.env)",
61
+ "Read(//Users/jonathanbuchanan/.config/lql/**)",
62
+ "Bash(rmdir ~/.config/lql)",
63
+ "Bash(git tag *)",
64
+ "Bash(git show *)",
65
+ "Bash(git ls-remote *)",
66
+ "Bash(awk 'NR>=235{print NR\": \"$0}' README.md)"
60
67
  ]
61
68
  }
62
69
  }
@@ -13,3 +13,5 @@ venv/
13
13
 
14
14
  # local config
15
15
  ~/.lql/
16
+
17
+ .env
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lql-cli
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: lql — CLI for the Liquid DataViewer platform
5
5
  Project-URL: Homepage, https://github.com/Liquid4All/lql
6
6
  Author: Liquid AI
@@ -9,6 +9,7 @@ Requires-Python: >=3.9
9
9
  Requires-Dist: httpx>=0.27
10
10
  Requires-Dist: huggingface-hub>=0.24
11
11
  Requires-Dist: rich>=13.0
12
+ Requires-Dist: textual>=0.50
12
13
  Requires-Dist: typer>=0.12
13
14
  Description-Content-Type: text/markdown
14
15
 
@@ -16,6 +17,8 @@ Description-Content-Type: text/markdown
16
17
 
17
18
  Scriptable CLI for the [Liquid DataViewer](https://dataviewer.liquid.ai) platform. Designed for both humans and AI agents (Claude Code, Codex, etc.) to automate datasets, eval analysis, spec docs, annotations, and more.
18
19
 
20
+ A Python package (Python ≥ 3.9), published on PyPI as **[`lql-cli`](https://pypi.org/project/lql-cli/)** — the installed command is `lql`.
21
+
19
22
  ## Quick start
20
23
 
21
24
  ```bash
@@ -56,7 +59,9 @@ cd lql
56
59
  uv tool install . # or: pip install -e .
57
60
  ```
58
61
 
59
- Update to the latest version with `uv tool upgrade lql-cli` (or `pipx upgrade lql-cli` / `pip install -U lql-cli`).
62
+ Update to the latest version with `lql update --run`, which detects how lql was
63
+ installed (uv tool / pipx / pip) and runs the matching upgrade. Run `lql update`
64
+ (no flag) to just print the command without executing it.
60
65
 
61
66
  ## Authentication
62
67
 
@@ -90,6 +95,7 @@ lql logout
90
95
  | `LQL_API_URL` | Override API base URL |
91
96
  | `LQL_HF_TOKEN` | HuggingFace token (required for `datasets upload`) |
92
97
  | `LQL_EVAL_WORKSPACE` | Default workspace for `eval list` |
98
+ | `LQL_ALLOW_INSECURE_API_URL` | Set to `1` to allow a plaintext `http://` API URL to a non-loopback host (off by default — the token is only sent over HTTPS or to localhost) |
93
99
 
94
100
  ## Command reference
95
101
 
@@ -134,6 +140,38 @@ lql datasets upload <file> --workspace <id> --name <repo-name> [--split <s>]
134
140
 
135
141
  `datasets upload` requires `LQL_HF_TOKEN`.
136
142
 
143
+ ### Preview
144
+
145
+ View dataset samples in the terminal — a Textual TUI with a chat-style layout
146
+ (user turns on the right, system/assistant/tool on the left). It renders the
147
+ same formats as the DataViewer web UI: OpenAI `{role, content}`, structured/
148
+ multimodal content (text/image/audio), ShareGPT `{from, value}`, native OpenAI
149
+ `tool_calls`, plus `<think>` reasoning blocks, `<|tool_call_start|>…<|tool_call_end|>`
150
+ / Python / XML / JSON tool calls, tool results, tool-definition tables, and code.
151
+
152
+ Works on a local `.jsonl`/`.json` file or a platform dataset ID. No browser, and
153
+ nothing to forward over SSH — it's just the terminal.
154
+
155
+ ```
156
+ lql preview <file.jsonl|file.json> Local file: each line/object is a row
157
+ lql preview <dataset-id> Platform dataset (fetched & paged lazily)
158
+ lql preview <src> -c <field> Force field(s) as conversations (repeatable)
159
+ lql preview <src> -n <N> Page size when paging a platform dataset
160
+ lql preview <src> --offset N Start at row index N
161
+ lql preview <src> --title "<title>" Title shown in the viewer header
162
+ ```
163
+
164
+ **Navigation** — two modes, toggle with `m` / `Tab`:
165
+
166
+ - **pager** (default): one sample at a time · `←/→` or `n`/`b` switch samples · `↑/↓`/`j`/`k`/PgUp-Dn scroll
167
+ - **scroll**: all samples in one buffer · `n`/`b` jump between samples · arrows scroll
168
+ - `q` quits
169
+
170
+ ```
171
+ lql preview examples/agent-traces.jsonl # 20-sample file of agent-trace/tool-use formats
172
+ lql preview <dataset-id> # browse a platform dataset, paged on demand
173
+ ```
174
+
137
175
  ### Evals (dataset analysis)
138
176
 
139
177
  Eval datasets are evaluation-run output — each row a sample with a model
@@ -281,6 +319,13 @@ lql instructions Print the full agent reference
281
319
  flags, examples, and workflows) in one read
282
320
  ```
283
321
 
322
+ ### Update
323
+
324
+ ```
325
+ lql update Detect install method (uv/pipx/pip), print upgrade cmd
326
+ lql update --run Detect and run the upgrade in place
327
+ ```
328
+
284
329
  ## Global flags
285
330
 
286
331
  All commands accept:
@@ -313,7 +358,7 @@ All commands accept:
313
358
  "default": {
314
359
  "token": "ak_live_...",
315
360
  "key_id": "uuid",
316
- "api_url": "https://api.dataviewer.liquid.ai"
361
+ "api_url": "https://liquid-anchor-api.fly.dev"
317
362
  }
318
363
  }
319
364
  }
@@ -2,6 +2,8 @@
2
2
 
3
3
  Scriptable CLI for the [Liquid DataViewer](https://dataviewer.liquid.ai) platform. Designed for both humans and AI agents (Claude Code, Codex, etc.) to automate datasets, eval analysis, spec docs, annotations, and more.
4
4
 
5
+ A Python package (Python ≥ 3.9), published on PyPI as **[`lql-cli`](https://pypi.org/project/lql-cli/)** — the installed command is `lql`.
6
+
5
7
  ## Quick start
6
8
 
7
9
  ```bash
@@ -42,7 +44,9 @@ cd lql
42
44
  uv tool install . # or: pip install -e .
43
45
  ```
44
46
 
45
- Update to the latest version with `uv tool upgrade lql-cli` (or `pipx upgrade lql-cli` / `pip install -U lql-cli`).
47
+ Update to the latest version with `lql update --run`, which detects how lql was
48
+ installed (uv tool / pipx / pip) and runs the matching upgrade. Run `lql update`
49
+ (no flag) to just print the command without executing it.
46
50
 
47
51
  ## Authentication
48
52
 
@@ -76,6 +80,7 @@ lql logout
76
80
  | `LQL_API_URL` | Override API base URL |
77
81
  | `LQL_HF_TOKEN` | HuggingFace token (required for `datasets upload`) |
78
82
  | `LQL_EVAL_WORKSPACE` | Default workspace for `eval list` |
83
+ | `LQL_ALLOW_INSECURE_API_URL` | Set to `1` to allow a plaintext `http://` API URL to a non-loopback host (off by default — the token is only sent over HTTPS or to localhost) |
79
84
 
80
85
  ## Command reference
81
86
 
@@ -120,6 +125,38 @@ lql datasets upload <file> --workspace <id> --name <repo-name> [--split <s>]
120
125
 
121
126
  `datasets upload` requires `LQL_HF_TOKEN`.
122
127
 
128
+ ### Preview
129
+
130
+ View dataset samples in the terminal — a Textual TUI with a chat-style layout
131
+ (user turns on the right, system/assistant/tool on the left). It renders the
132
+ same formats as the DataViewer web UI: OpenAI `{role, content}`, structured/
133
+ multimodal content (text/image/audio), ShareGPT `{from, value}`, native OpenAI
134
+ `tool_calls`, plus `<think>` reasoning blocks, `<|tool_call_start|>…<|tool_call_end|>`
135
+ / Python / XML / JSON tool calls, tool results, tool-definition tables, and code.
136
+
137
+ Works on a local `.jsonl`/`.json` file or a platform dataset ID. No browser, and
138
+ nothing to forward over SSH — it's just the terminal.
139
+
140
+ ```
141
+ lql preview <file.jsonl|file.json> Local file: each line/object is a row
142
+ lql preview <dataset-id> Platform dataset (fetched & paged lazily)
143
+ lql preview <src> -c <field> Force field(s) as conversations (repeatable)
144
+ lql preview <src> -n <N> Page size when paging a platform dataset
145
+ lql preview <src> --offset N Start at row index N
146
+ lql preview <src> --title "<title>" Title shown in the viewer header
147
+ ```
148
+
149
+ **Navigation** — two modes, toggle with `m` / `Tab`:
150
+
151
+ - **pager** (default): one sample at a time · `←/→` or `n`/`b` switch samples · `↑/↓`/`j`/`k`/PgUp-Dn scroll
152
+ - **scroll**: all samples in one buffer · `n`/`b` jump between samples · arrows scroll
153
+ - `q` quits
154
+
155
+ ```
156
+ lql preview examples/agent-traces.jsonl # 20-sample file of agent-trace/tool-use formats
157
+ lql preview <dataset-id> # browse a platform dataset, paged on demand
158
+ ```
159
+
123
160
  ### Evals (dataset analysis)
124
161
 
125
162
  Eval datasets are evaluation-run output — each row a sample with a model
@@ -267,6 +304,13 @@ lql instructions Print the full agent reference
267
304
  flags, examples, and workflows) in one read
268
305
  ```
269
306
 
307
+ ### Update
308
+
309
+ ```
310
+ lql update Detect install method (uv/pipx/pip), print upgrade cmd
311
+ lql update --run Detect and run the upgrade in place
312
+ ```
313
+
270
314
  ## Global flags
271
315
 
272
316
  All commands accept:
@@ -299,7 +343,7 @@ All commands accept:
299
343
  "default": {
300
344
  "token": "ak_live_...",
301
345
  "key_id": "uuid",
302
- "api_url": "https://api.dataviewer.liquid.ai"
346
+ "api_url": "https://liquid-anchor-api.fly.dev"
303
347
  }
304
348
  }
305
349
  }
@@ -0,0 +1,20 @@
1
+ {"id": "chat-001", "source": "openai-chat", "messages": [{"role": "system", "content": "You are a concise assistant."}, {"role": "user", "content": "What's the capital of France?"}, {"role": "assistant", "content": "The capital of France is **Paris**."}], "score": 1.0}
2
+ {"id": "bfcl-002", "tool_format": "Python", "messages": [{"role": "system", "content": "You are an expert function-calling assistant. Here is a list of functions in JSON format that you can invoke:\n[{\"name\": \"get_weather\", \"description\": \"Get current weather for a city.\", \"parameters\": {\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\",\"description\":\"City name\"},\"unit\":{\"type\":\"string\",\"description\":\"celsius or fahrenheit\"}},\"required\":[\"city\"]}}]"}, {"role": "user", "content": "What's the weather in Tokyo right now?"}, {"role": "assistant", "content": "<think>\nThe user wants current weather in Tokyo. I'll call get_weather with city=Tokyo.\n</think>\n<|tool_call_start|>[get_weather(city='Tokyo', unit='celsius')]<|tool_call_end|>"}, {"role": "tool", "content": "[{\"name\": \"get_weather\", \"result\": {\"city\": \"Tokyo\", \"temp_c\": 22, \"condition\": \"Partly cloudy\"}}]"}, {"role": "assistant", "content": "It's currently **22\u00b0C** and partly cloudy in Tokyo."}]}
3
+ {"id": "sharegpt-003", "conversations": [{"from": "system", "value": "You are a helpful coding tutor."}, {"from": "human", "value": "How do I reverse a list in Python?"}, {"from": "gpt", "value": "Use slicing: `my_list[::-1]` or `list(reversed(my_list))`."}]}
4
+ {"id": "reason-004", "messages": [{"role": "user", "content": "If a train travels 60 km in 45 minutes, what is its speed in km/h?"}, {"role": "assistant", "content": "<think>\n45 minutes = 0.75 hours. Speed = distance / time = 60 / 0.75 = 80.\n</think>\nThe train's speed is **80 km/h**."}]}
5
+ {"id": "openai-native-005", "messages": [{"role": "user", "content": "Book a table for 2 at 7pm."}, {"role": "assistant", "content": null, "tool_calls": [{"id": "call_1", "type": "function", "function": {"name": "book_table", "arguments": "{\"party_size\": 2, \"time\": \"19:00\"}"}}]}, {"role": "tool", "tool_call_id": "call_1", "name": "book_table", "content": "{\"status\": \"confirmed\", \"reservation_id\": \"R-8842\"}"}, {"role": "assistant", "content": "Done! Your table for 2 at 7:00pm is confirmed (reservation R-8842)."}]}
6
+ {"id": "parallel-006", "messages": [{"role": "user", "content": "Compare the weather in Paris and Berlin."}, {"role": "assistant", "content": "<|tool_call_start|>[get_weather(city='Paris'), get_weather(city='Berlin')]<|tool_call_end|>"}, {"role": "tool", "content": "[{\"name\": \"get_weather\", \"result\": {\"city\": \"Paris\", \"temp_c\": 18}}, {\"name\": \"get_weather\", \"result\": {\"city\": \"Berlin\", \"temp_c\": 15}}]"}, {"role": "assistant", "content": "Paris is 18\u00b0C and Berlin is 15\u00b0C \u2014 Paris is 3 degrees warmer."}]}
7
+ {"id": "toolresult-007", "messages": [{"role": "user", "content": "Search for papers on RLHF."}, {"role": "assistant", "content": "<|tool_call_start|>[search(query='RLHF papers', limit=2)]<|tool_call_end|>"}, {"role": "tool", "content": "[{\"name\": \"search\", \"result\": [{\"title\": \"Deep RL from Human Preferences\", \"year\": 2017}, {\"title\": \"InstructGPT\", \"year\": 2022}]}]"}]}
8
+ {"id": "xml-008", "messages": [{"role": "user", "content": "Convert 100 USD to EUR."}, {"role": "assistant", "content": "Let me convert that.\n<|tool_call_start|><function_call><name>convert_currency</name><arguments><amount>100</amount><from>USD</from><to>EUR</to></arguments></function_call><|tool_call_end|>"}, {"role": "tool", "content": "[{\"name\": \"convert_currency\", \"result\": {\"converted\": 92.4, \"rate\": 0.924}}]"}]}
9
+ {"id": "multimodal-009", "messages": [{"role": "user", "content": [{"type": "text", "text": "What's in this image?"}, {"type": "image_url", "image_url": {"url": "https://example.com/cat.jpg"}}]}, {"role": "assistant", "content": "It appears to be a cat sitting on a windowsill."}]}
10
+ {"id": "agenttrove-010", "agent": "terminus", "model": "gpt-4", "conversations": [{"role": "user", "content": "Solve the task. Respond as JSON with analysis, plan, commands."}, {"role": "assistant", "content": "{\n \"analysis\": \"Need to create a script that checks balanced parentheses.\",\n \"plan\": \"Write a Python script then test it.\",\n \"commands\": [{\"keystrokes\": \"python3 solve.py\\n\", \"duration\": 0.5}]\n}"}]}
11
+ {"id": "code-011", "messages": [{"role": "user", "content": "Write a Python function to compute factorial."}, {"role": "assistant", "content": "Here's a recursive implementation:\n\n```python\ndef factorial(n):\n if n <= 1:\n return 1\n return n * factorial(n - 1)\n```\n\nIt runs in O(n) time."}]}
12
+ {"id": "policy-012", "domain": "telecom", "messages": [{"role": "system", "content": "<instructions>\nYou are a customer service agent. Follow the policy.\n</instructions>\n<policy>\n# Telecom Policy\n- Verify identity before account changes.\n- Never disclose another customer's data.\n</policy>"}, {"role": "user", "content": "I want to change my plan."}, {"role": "assistant", "content": "I can help with that. First, can you verify your account by confirming your phone number?"}]}
13
+ {"id": "memory-013", "domain": "volunteering", "messages": [{"role": "user", "content": "Remember my shift is Saturday 9am-12pm."}, {"role": "assistant", "content": "<think>\nStore the shift schedule in archival memory.\n</think>\n<|tool_call_start|>[archival_memory_add(key='shift', value='Saturday 09:00-12:00')]<|tool_call_end|>"}, {"role": "tool", "content": "[{\"name\": \"archival_memory_add\", \"result\": {\"status\": \"success\", \"total_entries\": 1}}]"}, {"role": "assistant", "content": "Got it \u2014 I've saved your Saturday 9am-12pm shift."}]}
14
+ {"id": "tau2-014", "reward": 1.0, "domain": "telecom", "messages": [{"role": "assistant", "content": "Hi! How can I help you today?"}, {"role": "user", "content": "My mobile data is really slow."}, {"role": "assistant", "content": "<|tool_call_start|>[transfer_to_human_agents(summary='Customer reports slow mobile data.')]<|tool_call_end|>"}, {"role": "tool", "content": "Transfer successful"}, {"role": "assistant", "content": "I've transferred you to a human agent who can troubleshoot your connection."}]}
15
+ {"id": "websearch-015", "num_hops": 2, "messages": [{"role": "system", "content": "You are an expert at composing function calls to answer multi-hop questions."}, {"role": "user", "content": "What language is spoken in the birthplace of the inventor of the telephone?"}, {"role": "assistant", "content": "<think>\nFirst find the inventor of the telephone, then their birthplace, then the language.\n</think>\n<|tool_call_start|>[search_engine_query(keywords='inventor of the telephone birthplace')]<|tool_call_end|>"}, {"role": "tool", "content": "[{\"name\": \"search_engine_query\", \"result\": [{\"url\": \"https://en.wikipedia.org/wiki/Alexander_Graham_Bell\", \"title\": \"Alexander Graham Bell - born Edinburgh, Scotland\"}]}]"}, {"role": "assistant", "content": "Bell was born in Edinburgh, Scotland, where the primary language is **English** (and Scots/Scottish Gaelic)."}]}
16
+ {"id": "extras-016", "messages": [{"role": "user", "content": "Rate this answer.", "turn": 1}, {"role": "assistant", "content": "I'd rate it 4/5.", "weight": 1, "logprob": -0.21}]}
17
+ {"id": "meta-017", "model": "lfm-8b", "temperature": 0.7, "reward": 0.83, "tags": ["coding", "python"], "messages": [{"role": "user", "content": "Hello"}, {"role": "assistant", "content": "Hi there! How can I help?"}]}
18
+ [{"role": "user", "content": "Bare array conversation \u2014 no wrapper object."}, {"role": "assistant", "content": "Yep, this whole row is just a list of messages."}]
19
+ {"id": "tooldefs-019", "messages": [{"role": "system", "content": "You can use these tools:\n[{\"type\": \"function\", \"function\": {\"name\": \"send_email\", \"description\": \"Send an email to a recipient.\", \"parameters\": {\"type\": \"object\", \"properties\": {\"to\": {\"type\": \"string\", \"description\": \"recipient email\"}, \"subject\": {\"type\": \"string\", \"description\": \"email subject\"}, \"body\": {\"type\": \"string\", \"description\": \"email body\"}}, \"required\": [\"to\", \"body\"]}}}]"}, {"role": "user", "content": "Email alice@example.com saying hi."}, {"role": "assistant", "content": "<|tool_call_start|>[send_email(to='alice@example.com', subject='Hi', body='Hello Alice!')]<|tool_call_end|>"}]}
20
+ {"id": "markdown-020", "messages": [{"role": "user", "content": "Summarize the benefits of unit testing."}, {"role": "assistant", "content": "# Benefits of Unit Testing\n\nUnit testing offers several advantages:\n\n- **Catch bugs early** before they reach production\n- **Refactor safely** with a regression safety net\n- **Document behavior** \u2014 tests show how code is meant to be used\n\nUse `pytest` or `unittest` to get started. *Aim for fast, isolated tests.*"}]}
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "lql",
3
+ "lockfileVersion": 3,
4
+ "requires": true,
5
+ "packages": {}
6
+ }
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "lql-cli"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "lql — CLI for the Liquid DataViewer platform"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -15,6 +15,7 @@ dependencies = [
15
15
  "httpx>=0.27",
16
16
  "rich>=13.0",
17
17
  "huggingface-hub>=0.24",
18
+ "textual>=0.50",
18
19
  ]
19
20
 
20
21
  [project.scripts]
@@ -0,0 +1 @@
1
+ __version__ = "0.3.0"
@@ -15,7 +15,9 @@ from .commands import (
15
15
  highlights,
16
16
  instructions,
17
17
  issues,
18
+ preview,
18
19
  reports,
20
+ update as update_cmd,
19
21
  skills,
20
22
  spec,
21
23
  workspaces,
@@ -63,21 +65,12 @@ app.add_typer(reports.app, name="reports")
63
65
  app.add_typer(buckets.app, name="buckets")
64
66
 
65
67
  # Single commands
68
+ app.command("preview", help="View dataset samples (local file or dataset ID) in the terminal")(preview.preview)
66
69
  app.command("instructions", help="Print a full reference for agents and humans")(instructions.instructions)
70
+ app.command("update", help="Show how to update lql (or upgrade in place with --run)")(update_cmd.update)
67
71
  app.add_typer(skills.app, name="skills")
68
72
 
69
73
 
70
- @app.command("update")
71
- def update() -> None:
72
- """Show how to update lql to the latest version."""
73
- sys.stdout.write(
74
- "To update lql, use the tool you installed it with:\n"
75
- " uv tool upgrade lql # if installed via `uv tool install`\n"
76
- " pipx upgrade lql # if installed via pipx\n"
77
- " pip install --upgrade lql # if installed via pip\n"
78
- )
79
-
80
-
81
74
  def main() -> None:
82
75
  # Mirror the TS top-level catch: unexpected errors surface as the documented
83
76
  # machine-readable contract, not a traceback. Typer/Click exits (SystemExit)
@@ -71,6 +71,28 @@ A workspace is the top-level container for datasets, spec docs, and members.
71
71
  Upload a local file (uploads to HuggingFace liquid-ai org, then syncs):
72
72
  LQL_HF_TOKEN=<token> lql datasets upload <file.parquet> --workspace <id> --name <repo-name>
73
73
 
74
+ ## Preview
75
+
76
+ View dataset samples in the terminal (a Textual TUI) — no browser. Chat-style
77
+ layout (user right, system/assistant/tool left) with the same parsing as the
78
+ DataViewer web UI: OpenAI {role, content}, structured/multimodal content
79
+ ([{type,text}] / image / audio segments), ShareGPT {from, value}, native OpenAI
80
+ tool_calls, plus <think> reasoning blocks, <|tool_call_start|>…<|tool_call_end|>
81
+ / Python / XML / JSON tool calls, tool results, and tool-definition tables.
82
+ Non-conversation fields render as labeled metadata.
83
+
84
+ lql preview <file.jsonl|file.json> # local file: each line/object is a row
85
+ lql preview <dataset-id> # platform dataset, fetched & paged lazily
86
+ lql preview data.jsonl -c messages # force field(s) as conversations
87
+
88
+ Options: -c/--column (field(s) to treat as conversations; default auto-detect,
89
+ repeatable), -n/--limit (page size when paging a platform dataset), --offset
90
+ (start row index), --title, --profile, --api-url.
91
+
92
+ Navigation: two modes toggled with m/Tab — pager (one sample at a time; ←/→ or
93
+ n/b switch samples, ↑/↓/j/k scroll) and scroll (all samples; n/b jump between
94
+ them). q quits. Works over plain SSH with no browser or port-forward.
95
+
74
96
  ## Evals
75
97
 
76
98
  Eval datasets (evaluation-run output: each row a sample with a model 'response'