From 2f431ef8dbaa2cc922d7acf2f13ccbe18907b45d Mon Sep 17 00:00:00 2001 From: cemheren Date: Tue, 16 Jun 2026 01:05:02 -0700 Subject: [PATCH 1/4] chore(skill): log shell-completions run 2026-06-16 --- .claude/skills/grow-quicksheet/log.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.claude/skills/grow-quicksheet/log.md b/.claude/skills/grow-quicksheet/log.md index 7b845528..def10759 100644 --- a/.claude/skills/grow-quicksheet/log.md +++ b/.claude/skills/grow-quicksheet/log.md @@ -1,3 +1,11 @@ +## 2026-06-16 (feat: shell completions for bash/zsh/fish) + +- Stars: 0 (Δ 0). +- Action: Rewrote shell completion scripts (bash, zsh, fish) on existing PR #393 branch. Covers all CLI flags + .csv file completion. Added docs/shell-completions.md with install instructions. +- Bucket: E (issue fix — #372). +- Outcome: PR #393 updated (force-pushed commit d92d1d5). Closes #372. +- Follow-up: #319 (--delimiter flag) or #320 (stdin pipe) are next smallest qualifying issues with existing PRs. No new unaddressed issues remain. + ## 2026-05-19 (no-op #31 — #139 still open, hold) - Stars: 0 (Δ 0). PR #139 still open, no other action items. Hold. From 5e06c3e1473abb45773d435d23752416406bb52f Mon Sep 17 00:00:00 2001 From: cemheren Date: Tue, 16 Jun 2026 04:05:31 -0700 Subject: [PATCH 2/4] chore(skill): log stdin-pipe run 2026-06-16 --- .claude/skills/grow-quicksheet/log.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.claude/skills/grow-quicksheet/log.md b/.claude/skills/grow-quicksheet/log.md index def10759..d8fde6d8 100644 --- a/.claude/skills/grow-quicksheet/log.md +++ b/.claude/skills/grow-quicksheet/log.md @@ -1,3 +1,11 @@ +## 2026-06-16 (feat: stdin pipe support) + +- Stars: 0 (Δ 0). +- Action: Implemented `-` as file argument to read CSV from stdin in headless export modes. Reads stdin to temp file, processes, cleans up. Added error for non-headless use. Updated --help. +- Bucket: E (issue fix — #320). +- Outcome: PR #396 updated (commit 48f4104). Closes #320. +- Follow-up: #318 (NO_COLOR) or #158 (usage pattern/tabs) are next open issues. + ## 2026-06-16 (feat: shell completions for bash/zsh/fish) - Stars: 0 (Δ 0). From 9a8182961336cd0fc1ee9d8270418d0b39f6787a Mon Sep 17 00:00:00 2001 From: cemheren Date: Tue, 16 Jun 2026 09:07:30 -0700 Subject: [PATCH 3/4] chore(skill): log accountant landing page run 2026-06-16 --- .claude/skills/grow-quicksheet/log.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.claude/skills/grow-quicksheet/log.md b/.claude/skills/grow-quicksheet/log.md index d8fde6d8..6858f100 100644 --- a/.claude/skills/grow-quicksheet/log.md +++ b/.claude/skills/grow-quicksheet/log.md @@ -1,3 +1,11 @@ +## 2026-06-16 (docs: accountant landing page) + +- Stars: 0 (Δ 0). +- Action: Created `docs/for-accountants.md` landing page + `examples/accountant-dashboard.csv` starter sheet. Targets freelance accountants/bookkeepers with 1099, mileage, depreciation, margin, salestax, payroll extensions. Added README cross-link. +- Bucket: A (audience landing page). +- Outcome: PR #431. +- Follow-up: None queued — all backlog landing pages now have PRs (homelab merged, traders/sre/students on main, dms/writers/artists/accountants as open PRs). + ## 2026-06-16 (feat: stdin pipe support) - Stars: 0 (Δ 0). From 8966b6a40bf9a98993d562134ed6b178519b7031 Mon Sep 17 00:00:00 2001 From: cemheren Date: Tue, 16 Jun 2026 12:09:41 -0700 Subject: [PATCH 4/4] docs: add quicksheet-case-ext cross-link in README and tour --- .../extensions/quicksheet-case-ext/.gitignore | 3 + .../quicksheet-case-ext/CaseExtension.csproj | 10 ++ .../extensions/quicksheet-case-ext/LICENSE | 21 +++ .../extensions/quicksheet-case-ext/Program.cs | 166 ++++++++++++++++++ .../extensions/quicksheet-case-ext/README.md | 60 +++++++ .../quicksheet-extension.json | 8 + README.md | 1 + docs/tour.md | 1 + 8 files changed, 270 insertions(+) create mode 100644 .claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/.gitignore create mode 100644 .claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/CaseExtension.csproj create mode 100644 .claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/LICENSE create mode 100644 .claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/Program.cs create mode 100644 .claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/README.md create mode 100644 .claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/quicksheet-extension.json diff --git a/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/.gitignore b/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/.gitignore new file mode 100644 index 00000000..ac8d21bd --- /dev/null +++ b/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/.gitignore @@ -0,0 +1,3 @@ +bin/ +obj/ +*.user diff --git a/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/CaseExtension.csproj b/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/CaseExtension.csproj new file mode 100644 index 00000000..4f69373d --- /dev/null +++ b/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/CaseExtension.csproj @@ -0,0 +1,10 @@ + + + Exe + net9.0 + enable + enable + QuickSheetCase + CaseExtension + + diff --git a/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/LICENSE b/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/LICENSE new file mode 100644 index 00000000..dbaa22be --- /dev/null +++ b/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 cemheren + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/Program.cs b/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/Program.cs new file mode 100644 index 00000000..6b7f6fb8 --- /dev/null +++ b/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/Program.cs @@ -0,0 +1,166 @@ +using System.Net.Http.Headers; +using System.Text.Json; + +// QuickSheet case extension — US case law lookup via CourtListener's free API. +// +// Cell usage: +// case: Roe v Wade +// case: 410 U.S. 113 +// case: "unreasonable search" fourth amendment +// +// Output: up to gridRows results. Columns: case name, citation, court, year, snippet. +// API: https://www.courtlistener.com/api/rest/v4/search/?type=o&q= +// No auth required for basic search (rate-limited to ~100 req/hr unauthenticated). + +var http = new HttpClient(); +http.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("QuickSheetCaseExt", "1.0")); +http.Timeout = TimeSpan.FromSeconds(10); + +Console.OutputEncoding = System.Text.Encoding.UTF8; +Console.WriteLine(JsonSerializer.Serialize(new +{ + type = "register", + prefix = "case", + name = "Case Law", + version = "1.0.0", +})); +Console.Out.Flush(); + +string? line; +while ((line = Console.ReadLine()) != null) +{ + if (string.IsNullOrWhiteSpace(line)) continue; + try + { + using var doc = JsonDocument.Parse(line); + if (!doc.RootElement.TryGetProperty("type", out var t)) continue; + if (t.GetString() != "activate") continue; + HandleActivate(doc.RootElement); + } + catch { /* ignore malformed input */ } +} + +void HandleActivate(JsonElement root) +{ + string id = root.TryGetProperty("id", out var idEl) ? idEl.GetString() ?? "" : ""; + int gridRows = root.TryGetProperty("gridRows", out var gr) ? gr.GetInt32() : 5; + + string query = ""; + if (root.TryGetProperty("params", out var p) && p.ValueKind == JsonValueKind.Array) + { + var en = p.EnumerateArray(); + if (en.MoveNext()) query = en.Current.GetString() ?? ""; + } + query = query.Trim(); + + if (string.IsNullOrEmpty(query)) + { + Emit(id, new[] { new Cell(0, 0, "case: ") }); + return; + } + + var results = Search(query, gridRows); + if (results.Count == 0) + { + Emit(id, new[] { new Cell(0, 0, "No results"), new Cell(0, 1, query) }); + return; + } + + var cells = new List(); + for (int r = 0; r < results.Count && r < gridRows; r++) + { + var c = results[r]; + cells.Add(new Cell(r, 0, c.caseName)); + cells.Add(new Cell(r, 1, c.citation)); + cells.Add(new Cell(r, 2, c.court)); + cells.Add(new Cell(r, 3, c.year)); + cells.Add(new Cell(r, 4, c.snippet)); + } + Emit(id, cells); +} + +List Search(string query, int limit) +{ + var results = new List(); + try + { + string encoded = Uri.EscapeDataString(query); + string url = $"https://www.courtlistener.com/api/rest/v4/search/?type=o&q={encoded}&page_size={Math.Min(limit, 20)}"; + var response = http.GetAsync(url).GetAwaiter().GetResult(); + if (!response.IsSuccessStatusCode) return results; + + string json = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); + using var doc = JsonDocument.Parse(json); + + if (!doc.RootElement.TryGetProperty("results", out var arr)) return results; + + foreach (var item in arr.EnumerateArray()) + { + string caseName = item.TryGetProperty("caseName", out var cn) + ? cn.GetString() ?? "" : ""; + string citation = item.TryGetProperty("citation", out var cit) + ? FirstCitation(cit) : ""; + string court = item.TryGetProperty("court_citation_string", out var ct) + ? ct.GetString() ?? "" : ""; + string year = item.TryGetProperty("dateFiled", out var df) + ? ExtractYear(df.GetString()) : ""; + string snippet = item.TryGetProperty("snippet", out var sn) + ? StripHtml(sn.GetString() ?? "") : ""; + + if (caseName.Length == 0 && citation.Length == 0) continue; + results.Add(new CaseResult( + Truncate(caseName, 40), + Truncate(citation, 25), + Truncate(court, 15), + year, + Truncate(snippet, 60) + )); + } + } + catch { /* network error — return empty */ } + return results; +} + +static string FirstCitation(JsonElement el) +{ + if (el.ValueKind == JsonValueKind.Array) + { + foreach (var c in el.EnumerateArray()) + { + string? s = c.GetString(); + if (!string.IsNullOrEmpty(s)) return s; + } + } + if (el.ValueKind == JsonValueKind.String) return el.GetString() ?? ""; + return ""; +} + +static string ExtractYear(string? date) +{ + if (date == null || date.Length < 4) return ""; + return date[..4]; +} + +static string StripHtml(string s) +{ + // Remove , , , , and other common HTML tags from snippets + var clean = System.Text.RegularExpressions.Regex.Replace(s, "<[^>]+>", ""); + clean = clean.Replace("&", "&").Replace("<", "<").Replace(">", ">"); + clean = clean.Replace(""", "\"").Replace("'", "'"); + clean = clean.Replace("\n", " ").Replace("\r", ""); + // Collapse whitespace + clean = System.Text.RegularExpressions.Regex.Replace(clean, @"\s+", " ").Trim(); + return clean; +} + +static string Truncate(string s, int max) => + s.Length <= max ? s : s[..(max - 1)] + "…"; + +void Emit(string id, IEnumerable cells) +{ + Console.WriteLine(JsonSerializer.Serialize(new { type = "write", id, cells })); + Console.Out.Flush(); +} + +record Cell(int r, int c, string v); +record CaseResult(string caseName, string citation, string court, string year, string snippet); diff --git a/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/README.md b/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/README.md new file mode 100644 index 00000000..0d2da93d --- /dev/null +++ b/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/README.md @@ -0,0 +1,60 @@ +# quicksheet-case-ext + +A QuickSheet extension that looks up US case law citations and keywords via +[CourtListener](https://www.courtlistener.com/)'s free public API. + +``` +ext: github:cemheren/quicksheet-case-ext +case: Roe v Wade +case: 410 U.S. 113 +case: "unreasonable search" fourth amendment +``` + +## What you get + +Up to 5 results per query (configurable via grid rows). Each row shows: + +| Case Name | Citation | Court | Year | Snippet | +|------------------------------|----------------|-------|------|--------------------------------------------------| +| Roe v. Wade | 410 U.S. 113 | SCOTUS| 1973 | right of privacy...broad enough to encompass... | +| Planned Parenthood v. Casey | 505 U.S. 833 | SCOTUS| 1992 | undue burden standard...liberty of the woman... | + +## Features + +- **Citation search**: type a standard reporter citation (`410 U.S. 113`, `347 U.S. 483`) for a direct hit. +- **Keyword search**: natural language queries across millions of opinions. +- **No API key required**: CourtListener's search endpoint is free and open (rate-limited to ~100 req/hr). +- **HTML-stripped snippets**: result excerpts are cleaned of markup for readable cell display. + +## Use cases + +- Law students reviewing cases for class prep — keep citations on your desktop. +- Paralegals quickly verifying citation accuracy without leaving their workspace. +- Anyone who wants an ambient "legal research sticky note" on their wallpaper. + +## Build + +```bash +dotnet build CaseExtension.csproj +``` + +.NET 9, MIT, zero NuGet dependencies. + +## Protocol + +Standard QuickSheet JSON-lines protocol over stdin/stdout: + +1. On startup, emit `{"type":"register","prefix":"case","name":"Case Law","version":"1.0.0"}`. +2. On each `{"type":"activate","id":"...","params":[""],"gridRows":N}`, search CourtListener and reply with `{"type":"write","id":"...","cells":[...]}`. + +See [docs/extension-protocol.md](https://github.com/cemheren/QuickSheet/blob/main/docs/extension-protocol.md) in the main repo. + +## API + +Uses CourtListener REST API v4: `https://www.courtlistener.com/api/rest/v4/search/?type=o&q=` + +CourtListener is operated by [Free Law Project](https://free.law/), a 501(c)(3) non-profit. The API is free for reasonable use. If you plan heavy usage, consider [registering for a token](https://www.courtlistener.com/sign-in/). + +## License + +MIT. diff --git a/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/quicksheet-extension.json b/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/quicksheet-extension.json new file mode 100644 index 00000000..2bdbeb8f --- /dev/null +++ b/.claude/skills/grow-quicksheet/drafts/extensions/quicksheet-case-ext/quicksheet-extension.json @@ -0,0 +1,8 @@ +{ + "name": "case", + "version": "1.0.0", + "prefix": "case", + "description": "US case law lookup — search citations or keywords via CourtListener's free API", + "entry": "dotnet run --project CaseExtension.csproj", + "minProtocolVersion": 1 +} diff --git a/README.md b/README.md index 29fa4076..574a7171 100644 --- a/README.md +++ b/README.md @@ -231,6 +231,7 @@ tls: github.com, 1, 4 | `caniuse:` | Browser compat lookup via caniuse-db — `caniuse: css-grid` · `caniuse: flexbox` · `caniuse: webassembly`. Shows support across Chrome/Firefox/Safari/Edge + usage %. No API key. | `ext: github:Deskworks/quicksheet-caniuse` | | `gem:` | Ruby gem lookup via rubygems.org — version, total downloads, authors, license, homepage, description. `gem: rails` (detail) · `gem: search json parser` (top 5). No API key. | `ext: github:Deskworks/quicksheet-rubygems` | | `hackage:` | Haskell package lookup from Hackage — `hackage: aeson` (version, synopsis, author, category, license, homepage) · `hackage: search json` (top results). No API key. | `ext: github:Deskworks/quicksheet-hackage` | +| `case:` | US case law lookup — search by citation or keywords via CourtListener. `case: Roe v Wade` · `case: 410 U.S. 113` · `case: "fourth amendment" search`. No API key. | `ext: github:cemheren/quicksheet-case-ext` | See [docs/extensions.md](docs/extensions.md) for the full directory with descriptions and protocol docs. diff --git a/docs/tour.md b/docs/tour.md index d680219b..1f6d8bb9 100644 --- a/docs/tour.md +++ b/docs/tour.md @@ -84,6 +84,7 @@ Currently public: - [`quicksheet-qtr`](https://github.com/Deskworks/quicksheet-qtr) — IRS quarterly tax deadline countdown. - [`quicksheet-fx`](https://github.com/Deskworks/quicksheet-fx) — live currency conversion (200+ currencies, ECB rates). - [`quicksheet-rate`](https://github.com/Deskworks/quicksheet-rate) — freelance hourly rate calculator (taxes, benefits, billable time). +- [`quicksheet-case-ext`](https://github.com/cemheren/quicksheet-case-ext) — US case law lookup via CourtListener (citation or keyword search). [Write your own](../README.md#build-your-own) in whatever language you want — the protocol is two message types.