csvq-cli 0.1.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.
csvq_cli-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marcus
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.4
2
+ Name: csvq-cli
3
+ Version: 0.1.0
4
+ Summary: CSV/TSV query, filter, transform, and convert from the command line
5
+ Author-email: Marcus <marcus.builds.things@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/marcusbuildsthings-droid/csvq
8
+ Keywords: csv,tsv,query,filter,cli,data,tabular
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Utilities
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: click>=8.0
19
+ Dynamic: license-file
20
+
21
+ # csvq-cli
22
+
23
+ CSV/TSV query, filter, transform, and convert from the command line.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ pip install csvq-cli
29
+ ```
30
+
31
+ ## Commands
32
+
33
+ | Command | Description |
34
+ |---------|-------------|
35
+ | `csvq head` | Show headers and preview rows |
36
+ | `csvq count` | Count rows |
37
+ | `csvq select` | Select specific columns |
38
+ | `csvq filter` | Filter rows (==, !=, >, <, contains, matches, etc.) |
39
+ | `csvq sort` | Sort by column |
40
+ | `csvq unique` | Unique values with counts |
41
+ | `csvq stats` | Numeric stats (min, max, mean, median, sum) |
42
+ | `csvq grep` | Search rows matching regex |
43
+ | `csvq sample` | Random sample of rows |
44
+ | `csvq merge` | Merge multiple CSV files |
45
+ | `csvq to-json` | Convert to JSON |
46
+ | `csvq to-tsv` | Convert to TSV |
47
+ | `csvq to-md` | Convert to Markdown table |
48
+
49
+ ## Examples
50
+
51
+ ```bash
52
+ # Preview a file
53
+ csvq head data.csv
54
+
55
+ # Filter and convert
56
+ csvq filter data.csv status == active --json
57
+
58
+ # Stats on a column
59
+ csvq stats sales.csv revenue
60
+
61
+ # Grep across all columns
62
+ csvq grep data.csv "error|warning"
63
+
64
+ # Select columns and sort
65
+ csvq select data.csv name email | csvq sort - name
66
+
67
+ # Pipe from stdin
68
+ cat data.csv | csvq count
69
+ ```
70
+
71
+ All commands support `--json` output. Auto-detects delimiter (CSV, TSV, semicolon, pipe).
72
+
73
+ ## For AI Agents
74
+
75
+ See [SKILL.md](SKILL.md) for agent-optimized documentation.
76
+
77
+ ## License
78
+
79
+ MIT
@@ -0,0 +1,59 @@
1
+ # csvq-cli
2
+
3
+ CSV/TSV query, filter, transform, and convert from the command line.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install csvq-cli
9
+ ```
10
+
11
+ ## Commands
12
+
13
+ | Command | Description |
14
+ |---------|-------------|
15
+ | `csvq head` | Show headers and preview rows |
16
+ | `csvq count` | Count rows |
17
+ | `csvq select` | Select specific columns |
18
+ | `csvq filter` | Filter rows (==, !=, >, <, contains, matches, etc.) |
19
+ | `csvq sort` | Sort by column |
20
+ | `csvq unique` | Unique values with counts |
21
+ | `csvq stats` | Numeric stats (min, max, mean, median, sum) |
22
+ | `csvq grep` | Search rows matching regex |
23
+ | `csvq sample` | Random sample of rows |
24
+ | `csvq merge` | Merge multiple CSV files |
25
+ | `csvq to-json` | Convert to JSON |
26
+ | `csvq to-tsv` | Convert to TSV |
27
+ | `csvq to-md` | Convert to Markdown table |
28
+
29
+ ## Examples
30
+
31
+ ```bash
32
+ # Preview a file
33
+ csvq head data.csv
34
+
35
+ # Filter and convert
36
+ csvq filter data.csv status == active --json
37
+
38
+ # Stats on a column
39
+ csvq stats sales.csv revenue
40
+
41
+ # Grep across all columns
42
+ csvq grep data.csv "error|warning"
43
+
44
+ # Select columns and sort
45
+ csvq select data.csv name email | csvq sort - name
46
+
47
+ # Pipe from stdin
48
+ cat data.csv | csvq count
49
+ ```
50
+
51
+ All commands support `--json` output. Auto-detects delimiter (CSV, TSV, semicolon, pipe).
52
+
53
+ ## For AI Agents
54
+
55
+ See [SKILL.md](SKILL.md) for agent-optimized documentation.
56
+
57
+ ## License
58
+
59
+ MIT
@@ -0,0 +1,2 @@
1
+ """csvq-cli - CSV/TSV query, filter, transform, and convert."""
2
+ __version__ = "0.1.0"
@@ -0,0 +1,396 @@
1
+ """csvq-cli — CSV/TSV query, filter, transform, and convert."""
2
+ import csv
3
+ import io
4
+ import json
5
+ import sys
6
+ import re
7
+ import operator
8
+ from pathlib import Path
9
+ from collections import Counter
10
+
11
+ import click
12
+
13
+ # ── helpers ──────────────────────────────────────────────────────────
14
+
15
+ def _detect_dialect(path_or_text, is_file=True):
16
+ """Detect CSV dialect (delimiter, quoting)."""
17
+ if is_file:
18
+ with open(path_or_text, newline="") as f:
19
+ sample = f.read(8192)
20
+ else:
21
+ sample = path_or_text[:8192]
22
+ try:
23
+ dialect = csv.Sniffer().sniff(sample, delimiters=",\t;|")
24
+ return dialect
25
+ except csv.Error:
26
+ return csv.excel
27
+
28
+
29
+ def _read_rows(file, delimiter, has_header=True):
30
+ """Read CSV into (headers, rows). Returns (None, rows) if no header."""
31
+ if file == "-":
32
+ text = sys.stdin.read()
33
+ dialect = _detect_dialect(text, is_file=False)
34
+ else:
35
+ dialect = _detect_dialect(file, is_file=True)
36
+ with open(file) as f:
37
+ text = f.read()
38
+
39
+ if delimiter:
40
+ dialect.delimiter = delimiter
41
+
42
+ reader = csv.reader(io.StringIO(text), dialect)
43
+ all_rows = list(reader)
44
+ if not all_rows:
45
+ return ([], [])
46
+ if has_header:
47
+ return (all_rows[0], all_rows[1:])
48
+ return (None, all_rows)
49
+
50
+
51
+ def _output(headers, rows, fmt, file=None):
52
+ """Output rows in requested format."""
53
+ if fmt == "json":
54
+ if headers:
55
+ data = [dict(zip(headers, r)) for r in rows]
56
+ else:
57
+ data = rows
58
+ out = json.dumps(data, indent=2, ensure_ascii=False)
59
+ elif fmt == "tsv":
60
+ buf = io.StringIO()
61
+ w = csv.writer(buf, delimiter="\t", lineterminator="\n")
62
+ if headers:
63
+ w.writerow(headers)
64
+ w.writerows(rows)
65
+ out = buf.getvalue()
66
+ elif fmt == "markdown":
67
+ if not headers:
68
+ headers = [f"col{i}" for i in range(len(rows[0]))] if rows else []
69
+ lines = ["| " + " | ".join(headers) + " |"]
70
+ lines.append("| " + " | ".join(["---"] * len(headers)) + " |")
71
+ for r in rows:
72
+ lines.append("| " + " | ".join(r) + " |")
73
+ out = "\n".join(lines) + "\n"
74
+ else: # csv
75
+ buf = io.StringIO()
76
+ w = csv.writer(buf, lineterminator="\n")
77
+ if headers:
78
+ w.writerow(headers)
79
+ w.writerows(rows)
80
+ out = buf.getvalue()
81
+
82
+ if file:
83
+ Path(file).write_text(out)
84
+ click.echo(f"Written to {file}")
85
+ else:
86
+ click.echo(out, nl=False)
87
+
88
+
89
+ OPS = {
90
+ "==": operator.eq, "!=": operator.ne,
91
+ ">": operator.gt, ">=": operator.ge,
92
+ "<": operator.lt, "<=": operator.le,
93
+ "contains": lambda a, b: b in a,
94
+ "startswith": lambda a, b: a.startswith(b),
95
+ "endswith": lambda a, b: a.endswith(b),
96
+ "matches": lambda a, b: bool(re.search(b, a)),
97
+ }
98
+
99
+
100
+ def _coerce(val):
101
+ """Try numeric coercion for comparisons."""
102
+ try:
103
+ return float(val)
104
+ except (ValueError, TypeError):
105
+ return val
106
+
107
+
108
+ # ── CLI ──────────────────────────────────────────────────────────────
109
+
110
+ @click.group()
111
+ @click.version_option()
112
+ def cli():
113
+ """CSV/TSV query, filter, transform, and convert."""
114
+
115
+
116
+ @cli.command()
117
+ @click.argument("file", default="-")
118
+ @click.option("-d", "--delimiter", help="Force delimiter")
119
+ @click.option("--json", "fmt", flag_value="json", help="JSON output")
120
+ def head(file, delimiter, fmt):
121
+ """Show headers and first rows."""
122
+ headers, rows = _read_rows(file, delimiter)
123
+ preview = rows[:10]
124
+ if fmt == "json":
125
+ click.echo(json.dumps({"headers": headers, "rows": len(rows), "preview": [dict(zip(headers, r)) for r in preview]}, indent=2))
126
+ else:
127
+ if headers:
128
+ click.echo(f"Columns ({len(headers)}): {', '.join(headers)}")
129
+ click.echo(f"Rows: {len(rows)}")
130
+ click.echo()
131
+ for r in preview:
132
+ click.echo(" | ".join(r))
133
+
134
+
135
+ @cli.command()
136
+ @click.argument("file", default="-")
137
+ @click.option("-d", "--delimiter", help="Force delimiter")
138
+ @click.option("--json", "fmt", flag_value="json", help="JSON output")
139
+ def count(file, delimiter, fmt):
140
+ """Count rows."""
141
+ headers, rows = _read_rows(file, delimiter)
142
+ n = len(rows)
143
+ if fmt == "json":
144
+ click.echo(json.dumps({"count": n}))
145
+ else:
146
+ click.echo(n)
147
+
148
+
149
+ @cli.command()
150
+ @click.argument("file", default="-")
151
+ @click.argument("columns", nargs=-1, required=True)
152
+ @click.option("-d", "--delimiter", help="Force delimiter")
153
+ @click.option("-o", "--output", "outfile", help="Output file")
154
+ @click.option("--json", "fmt", flag_value="json", help="JSON output")
155
+ @click.option("--tsv", "fmt", flag_value="tsv", help="TSV output")
156
+ @click.option("--md", "fmt", flag_value="markdown", help="Markdown output")
157
+ def select(file, columns, delimiter, outfile, fmt):
158
+ """Select specific columns by name or index."""
159
+ headers, rows = _read_rows(file, delimiter)
160
+ if not headers:
161
+ click.echo("Error: no headers found", err=True)
162
+ sys.exit(1)
163
+
164
+ indices = []
165
+ out_headers = []
166
+ for c in columns:
167
+ if c.isdigit():
168
+ idx = int(c)
169
+ indices.append(idx)
170
+ out_headers.append(headers[idx] if idx < len(headers) else f"col{idx}")
171
+ elif c in headers:
172
+ indices.append(headers.index(c))
173
+ out_headers.append(c)
174
+ else:
175
+ click.echo(f"Error: column '{c}' not found. Available: {', '.join(headers)}", err=True)
176
+ sys.exit(1)
177
+
178
+ out_rows = [[r[i] if i < len(r) else "" for i in indices] for r in rows]
179
+ _output(out_headers, out_rows, fmt or "csv", outfile)
180
+
181
+
182
+ @cli.command()
183
+ @click.argument("file", default="-")
184
+ @click.argument("column")
185
+ @click.argument("op", type=click.Choice(list(OPS.keys())))
186
+ @click.argument("value")
187
+ @click.option("-d", "--delimiter", help="Force delimiter")
188
+ @click.option("-o", "--output", "outfile", help="Output file")
189
+ @click.option("--json", "fmt", flag_value="json", help="JSON output")
190
+ @click.option("--tsv", "fmt", flag_value="tsv", help="TSV output")
191
+ @click.option("--md", "fmt", flag_value="markdown", help="Markdown output")
192
+ def filter(file, column, op, value, delimiter, outfile, fmt):
193
+ """Filter rows where COLUMN OP VALUE."""
194
+ headers, rows = _read_rows(file, delimiter)
195
+ if column.isdigit():
196
+ idx = int(column)
197
+ elif column in headers:
198
+ idx = headers.index(column)
199
+ else:
200
+ click.echo(f"Error: column '{column}' not found", err=True)
201
+ sys.exit(1)
202
+
203
+ op_fn = OPS[op]
204
+ out = []
205
+ for r in rows:
206
+ cell = r[idx] if idx < len(r) else ""
207
+ a, b = _coerce(cell), _coerce(value)
208
+ try:
209
+ if op_fn(a, b):
210
+ out.append(r)
211
+ except TypeError:
212
+ if op_fn(str(a), str(b)):
213
+ out.append(r)
214
+ _output(headers, out, fmt or "csv", outfile)
215
+
216
+
217
+ @cli.command()
218
+ @click.argument("file", default="-")
219
+ @click.argument("column")
220
+ @click.option("--desc", is_flag=True, help="Descending order")
221
+ @click.option("-n", "--numeric", is_flag=True, help="Numeric sort")
222
+ @click.option("-d", "--delimiter", help="Force delimiter")
223
+ @click.option("-o", "--output", "outfile", help="Output file")
224
+ @click.option("--json", "fmt", flag_value="json", help="JSON output")
225
+ @click.option("--tsv", "fmt", flag_value="tsv", help="TSV output")
226
+ def sort(file, column, desc, numeric, delimiter, outfile, fmt):
227
+ """Sort rows by column."""
228
+ headers, rows = _read_rows(file, delimiter)
229
+ if column.isdigit():
230
+ idx = int(column)
231
+ elif column in headers:
232
+ idx = headers.index(column)
233
+ else:
234
+ click.echo(f"Error: column '{column}' not found", err=True)
235
+ sys.exit(1)
236
+
237
+ def key_fn(r):
238
+ v = r[idx] if idx < len(r) else ""
239
+ if numeric:
240
+ try:
241
+ return float(v)
242
+ except ValueError:
243
+ return float("inf")
244
+ return v
245
+
246
+ rows.sort(key=key_fn, reverse=desc)
247
+ _output(headers, rows, fmt or "csv", outfile)
248
+
249
+
250
+ @cli.command()
251
+ @click.argument("file", default="-")
252
+ @click.argument("column")
253
+ @click.option("-d", "--delimiter", help="Force delimiter")
254
+ @click.option("--json", "fmt", flag_value="json", help="JSON output")
255
+ def unique(file, column, delimiter, fmt):
256
+ """Show unique values in a column."""
257
+ headers, rows = _read_rows(file, delimiter)
258
+ if column.isdigit():
259
+ idx = int(column)
260
+ elif column in headers:
261
+ idx = headers.index(column)
262
+ else:
263
+ click.echo(f"Error: column '{column}' not found", err=True)
264
+ sys.exit(1)
265
+
266
+ counts = Counter(r[idx] if idx < len(r) else "" for r in rows)
267
+ if fmt == "json":
268
+ click.echo(json.dumps({"column": column, "unique": len(counts), "values": dict(counts.most_common())}, indent=2))
269
+ else:
270
+ for val, cnt in counts.most_common():
271
+ click.echo(f"{cnt:>6} {val}")
272
+
273
+
274
+ @cli.command()
275
+ @click.argument("file", default="-")
276
+ @click.argument("column")
277
+ @click.option("-d", "--delimiter", help="Force delimiter")
278
+ @click.option("--json", "fmt", flag_value="json", help="JSON output")
279
+ def stats(file, column, delimiter, fmt):
280
+ """Basic stats for a numeric column (min, max, mean, sum, count)."""
281
+ headers, rows = _read_rows(file, delimiter)
282
+ if column.isdigit():
283
+ idx = int(column)
284
+ elif column in headers:
285
+ idx = headers.index(column)
286
+ else:
287
+ click.echo(f"Error: column '{column}' not found", err=True)
288
+ sys.exit(1)
289
+
290
+ vals = []
291
+ for r in rows:
292
+ try:
293
+ vals.append(float(r[idx]))
294
+ except (ValueError, IndexError):
295
+ pass
296
+
297
+ if not vals:
298
+ click.echo("No numeric values found", err=True)
299
+ sys.exit(1)
300
+
301
+ result = {
302
+ "column": column,
303
+ "count": len(vals),
304
+ "min": min(vals),
305
+ "max": max(vals),
306
+ "sum": sum(vals),
307
+ "mean": round(sum(vals) / len(vals), 4),
308
+ }
309
+ vals_sorted = sorted(vals)
310
+ n = len(vals_sorted)
311
+ result["median"] = vals_sorted[n // 2] if n % 2 else (vals_sorted[n // 2 - 1] + vals_sorted[n // 2]) / 2
312
+
313
+ if fmt == "json":
314
+ click.echo(json.dumps(result, indent=2))
315
+ else:
316
+ for k, v in result.items():
317
+ click.echo(f"{k:>8}: {v}")
318
+
319
+
320
+ @cli.command("to-json")
321
+ @click.argument("file", default="-")
322
+ @click.option("-d", "--delimiter", help="Force delimiter")
323
+ @click.option("-o", "--output", "outfile", help="Output file")
324
+ def to_json(file, delimiter, outfile):
325
+ """Convert CSV to JSON."""
326
+ headers, rows = _read_rows(file, delimiter)
327
+ _output(headers, rows, "json", outfile)
328
+
329
+
330
+ @cli.command("to-tsv")
331
+ @click.argument("file", default="-")
332
+ @click.option("-d", "--delimiter", help="Force delimiter")
333
+ @click.option("-o", "--output", "outfile", help="Output file")
334
+ def to_tsv(file, delimiter, outfile):
335
+ """Convert CSV to TSV."""
336
+ headers, rows = _read_rows(file, delimiter)
337
+ _output(headers, rows, "tsv", outfile)
338
+
339
+
340
+ @cli.command("to-md")
341
+ @click.argument("file", default="-")
342
+ @click.option("-d", "--delimiter", help="Force delimiter")
343
+ @click.option("-o", "--output", "outfile", help="Output file")
344
+ def to_md(file, delimiter, outfile):
345
+ """Convert CSV to Markdown table."""
346
+ headers, rows = _read_rows(file, delimiter)
347
+ _output(headers, rows, "markdown", outfile)
348
+
349
+
350
+ @cli.command()
351
+ @click.argument("file", default="-")
352
+ @click.argument("pattern")
353
+ @click.option("-d", "--delimiter", help="Force delimiter")
354
+ @click.option("-o", "--output", "outfile", help="Output file")
355
+ @click.option("--json", "fmt", flag_value="json", help="JSON output")
356
+ def grep(file, pattern, delimiter, outfile, fmt):
357
+ """Search rows matching pattern (any column)."""
358
+ headers, rows = _read_rows(file, delimiter)
359
+ regex = re.compile(pattern, re.IGNORECASE)
360
+ matched = [r for r in rows if any(regex.search(cell) for cell in r)]
361
+ _output(headers, matched, fmt or "csv", outfile)
362
+
363
+
364
+ @cli.command()
365
+ @click.argument("files", nargs=-1, required=True)
366
+ @click.option("-d", "--delimiter", help="Force delimiter")
367
+ @click.option("-o", "--output", "outfile", help="Output file")
368
+ @click.option("--json", "fmt", flag_value="json", help="JSON output")
369
+ def merge(files, delimiter, outfile, fmt):
370
+ """Merge multiple CSV files (same columns)."""
371
+ all_headers = None
372
+ all_rows = []
373
+ for f in files:
374
+ h, r = _read_rows(f, delimiter)
375
+ if all_headers is None:
376
+ all_headers = h
377
+ all_rows.extend(r)
378
+ _output(all_headers, all_rows, fmt or "csv", outfile)
379
+
380
+
381
+ @cli.command()
382
+ @click.argument("file", default="-")
383
+ @click.option("-n", "--rows", "nrows", default=10, help="Number of rows")
384
+ @click.option("-d", "--delimiter", help="Force delimiter")
385
+ @click.option("-o", "--output", "outfile", help="Output file")
386
+ @click.option("--json", "fmt", flag_value="json", help="JSON output")
387
+ def sample(file, nrows, delimiter, outfile, fmt):
388
+ """Random sample of rows."""
389
+ import random
390
+ headers, rows = _read_rows(file, delimiter)
391
+ sampled = random.sample(rows, min(nrows, len(rows)))
392
+ _output(headers, sampled, fmt or "csv", outfile)
393
+
394
+
395
+ if __name__ == "__main__":
396
+ cli()
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.4
2
+ Name: csvq-cli
3
+ Version: 0.1.0
4
+ Summary: CSV/TSV query, filter, transform, and convert from the command line
5
+ Author-email: Marcus <marcus.builds.things@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/marcusbuildsthings-droid/csvq
8
+ Keywords: csv,tsv,query,filter,cli,data,tabular
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Utilities
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: click>=8.0
19
+ Dynamic: license-file
20
+
21
+ # csvq-cli
22
+
23
+ CSV/TSV query, filter, transform, and convert from the command line.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ pip install csvq-cli
29
+ ```
30
+
31
+ ## Commands
32
+
33
+ | Command | Description |
34
+ |---------|-------------|
35
+ | `csvq head` | Show headers and preview rows |
36
+ | `csvq count` | Count rows |
37
+ | `csvq select` | Select specific columns |
38
+ | `csvq filter` | Filter rows (==, !=, >, <, contains, matches, etc.) |
39
+ | `csvq sort` | Sort by column |
40
+ | `csvq unique` | Unique values with counts |
41
+ | `csvq stats` | Numeric stats (min, max, mean, median, sum) |
42
+ | `csvq grep` | Search rows matching regex |
43
+ | `csvq sample` | Random sample of rows |
44
+ | `csvq merge` | Merge multiple CSV files |
45
+ | `csvq to-json` | Convert to JSON |
46
+ | `csvq to-tsv` | Convert to TSV |
47
+ | `csvq to-md` | Convert to Markdown table |
48
+
49
+ ## Examples
50
+
51
+ ```bash
52
+ # Preview a file
53
+ csvq head data.csv
54
+
55
+ # Filter and convert
56
+ csvq filter data.csv status == active --json
57
+
58
+ # Stats on a column
59
+ csvq stats sales.csv revenue
60
+
61
+ # Grep across all columns
62
+ csvq grep data.csv "error|warning"
63
+
64
+ # Select columns and sort
65
+ csvq select data.csv name email | csvq sort - name
66
+
67
+ # Pipe from stdin
68
+ cat data.csv | csvq count
69
+ ```
70
+
71
+ All commands support `--json` output. Auto-detects delimiter (CSV, TSV, semicolon, pipe).
72
+
73
+ ## For AI Agents
74
+
75
+ See [SKILL.md](SKILL.md) for agent-optimized documentation.
76
+
77
+ ## License
78
+
79
+ MIT
@@ -0,0 +1,11 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ csvq/__init__.py
5
+ csvq/cli.py
6
+ csvq_cli.egg-info/PKG-INFO
7
+ csvq_cli.egg-info/SOURCES.txt
8
+ csvq_cli.egg-info/dependency_links.txt
9
+ csvq_cli.egg-info/entry_points.txt
10
+ csvq_cli.egg-info/requires.txt
11
+ csvq_cli.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ csvq = csvq.cli:cli
@@ -0,0 +1 @@
1
+ click>=8.0
@@ -0,0 +1 @@
1
+ csvq
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "csvq-cli"
7
+ version = "0.1.0"
8
+ description = "CSV/TSV query, filter, transform, and convert from the command line"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.9"
12
+ authors = [{name = "Marcus", email = "marcus.builds.things@gmail.com"}]
13
+ keywords = ["csv", "tsv", "query", "filter", "cli", "data", "tabular"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Environment :: Console",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Topic :: Utilities",
21
+ ]
22
+ dependencies = ["click>=8.0"]
23
+
24
+ [project.scripts]
25
+ csvq = "csvq.cli:cli"
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/marcusbuildsthings-droid/csvq"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+