Sloth-ninja brings Clio matters into the chat

This fork turns a legal AI workspace into a more practical companion for firms already running work through Clio.

integrationworkflow

Each person can connect their own Clio account, so access follows their existing permissions instead of relying on one shared firm login. The connection checks who has signed in, handles rate limits, and shows its status clearly.

It also adds chat-based access to Clio's practice-management data:

  • Matter search, including filtering by status and moving through large result sets.
  • Separate support for Clio Manage and Clio Grow, covering core practice management and client-intake work.
  • An account card that makes the connection visible in the product rather than burying it in settings.

The team has documented real-user permission checks, which is a useful signal. The catch is operational: firms still need live Clio credentials, user consent, and a plan for API rate limits before this becomes useful day to day.

So what Clio-based firms should care because this could put matter context where lawyers are already asking questions, without handing every user the same access.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?

Commits in this thread

25 commits from Sloth-ninja/JessicaOSS, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
2670934d Clio pivot: research + decision record sloth-ninja 2026-08-03 ↗ GitHub
commit body
Owner decision 03/08: Quill/Unity cancelled; Clio (Manage + Grow, EU) is
the first practice-management connector. Full two-API research in
docs/research/2026-08-03-clio.md; CLAUDE.md status updated; BUILD_LOG
entry. Docs only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
46cf57e9 Merge pull request #59 from Sloth-ninja/clio-pivot-research Ezana.H 2026-08-03 ↗ GitHub
Clio pivot: research + decision record
af85d350 Clio connector PR 1: user_clio_connections migration sloth-ninja 2026-08-03 ↗ GitHub
commit body
Owner-authorised 03/08/2026 (allowlist entry added by the owner).
Additive table: per-user encrypted OAuth tokens for Clio Manage + Grow
(one row per user per product, AES-256-GCM triplets per the
user_mcp_oauth_tokens precedent, rotation-safe design note), RLS
enabled, browser-role grants revoked. schema.sql mirrored.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
f13adcaf Merge pull request #62 from Sloth-ninja/clio-connections-migration Ezana.H 2026-08-03 ↗ GitHub
Clio connector PR 1: user_clio_connections migration
4ccb7835 Clio connector PR 2: backend (lib/clio seam, routes, chat tools) sloth-ninja 2026-08-03 ↗ GitHub
commit body
Self-contained lib/clio/ seam plus /clio + /clio-grow routes, chat-tool
wiring, and profile serialization for the Clio Manage + Grow connectors.
No migration/.env/schema.sql/LICENSE touched.

- config: region host maps (EU live), creds, redirect-URI derivation,
  pinned Manage X-API-VERSION (4.0.11, env-overridable).
- connections: user_clio_connections CRUD over shared apiKeyCrypto;
  42P01/42703-tolerant; atomic refresh-rotation persistence.
- oauth: start/callback both products; one-time in-process state; PKCE
  S256 for Grow; who_am_i on connect; best-effort deauthorize.
- client: per-user authed fetch; proactive + 401 single refresh/retry;
  Grow rotating refresh persisted before use; 429 Retry-After + rate
  buckets (Manage 50/min, Grow 3/s app-wide); fixed error mapping.
- manage/grow chat tools registered through the companiesHouse seam,
  gated on the product being connected; writes confirmed via prompt;
  save-document enforces ensureDocAccess + 25 MB cap via presigned upload.
- routes: oauth start/callback (callback = exact registered path),
  /status (both products + cheap Manage count), MFA-gated disconnect.
- chat + projectChat gate Clio tools on listConnectedProducts; profile
  exposes clioConnections {manage, grow}.

Verified: tsc clean; vitest 584/584 (513 baseline + 71 new); prettier
clean on new files. Built on the 03/08 spike/write-probe verified facts
(EU hosts, seconds semantics, 3-step upload, rotation, rate limits).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2ebab293 Clio PR2 correction: Grow who_am_i is live-verified - wire it on connect sloth-ninja 2026-08-03 ↗ GitHub
commit body
The 03/08 spike exercised the Grow API with a real token: GET /users/who_am_i,
GET /matters, GET /inbox_leads, GET /users all returned 200. Earlier flag
wrongly called Grow endpoints "unverified".

- oauth: fetchClioIdentity now hits /users/who_am_i for BOTH products on
  connect (Grow parsed defensively for shape drift - data/user/top-level,
  name or first+last); best-effort, connect never fails on a who_am_i hiccup.
- growTools header + BUILD_LOG flags: /matters, /inbox_leads, /users,
  /users/who_am_i are LIVE-VERIFIED; only the matter-note read/write endpoints
  and /contacts remain research-based.
- tests: +2 (Grow identity stored on connect; connect survives who_am_i
  failure). vitest 586/586.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2d6b71cb Clio PR2 review fixes: wire proactive rate-limit backoff + deterministic tests sloth-ninja 2026-08-03 ↗ GitHub
commit body
Independent review (#63): security paths passed; two should-fixes + three nits.

SHOULD-FIX 1 (chosen: WIRE, not delete) - proactive backoff was exported and
tested but never called. Now clioRequest records a per-bucket backoff when a
response reports X-RateLimit-Remaining 0 + a Reset, and the next call for that
bucket waits until reset (capped 5s) or fails fast with the fixed rate-limit
message. Added parseRateLimitResetMs (epoch/delta tolerant) + 3 tests
(reset parsing, over-cap fail-fast without a second request, within-cap
wait-then-proceed under fake timers).

SHOULD-FIX 2 - deterministic CI: the scrypt-heavy Clio suites (connections,
client, oauth, manageTools, growTools) now raise testTimeout to 20s with the
#61-style explanatory comment, so real scryptSync under 43 parallel files can't
flake the 5s default.

NITS: BUILD_LOG test counts → 589/589 (76 new); "presigned PUT/download" →
"presigned PUT never carries the bearer (no document download is implemented)";
added a line that migration 20260803_01 was already run in production 03/08 so
the connector activates on deploy (code stays 42P01-tolerant regardless).

Verified: tsc clean; prettier clean; full suite run TWICE back-to-back, both
589/589 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3f7a50e2 Merge pull request #63 from Sloth-ninja/clio-connector-backend Ezana.H 2026-08-03 ↗ GitHub
Clio connector PR 2: backend (lib/clio seam, routes, chat tools)
8886caec Clio connector PR 3: frontend practice-management card sloth-ninja 2026-08-03 ↗ GitHub
commit body
Adds the "Practice management - Clio" card at the top of Account ›
Connectors (above the MCP gallery), per the approved mock-up screen 1.

- ClioConnectorCard.tsx: self-contained card - Manage/Grow rows, state
  pills (green Connected-as / neutral Not-connected), Connect (OAuth
  popup) + MFA-gated Disconnect (confirm dialogue, optimistic + rollback),
  loading skeleton, LoadErrorRow-style retry, not-configured honest line.
- mikeApi.ts: getClioStatus / startClioConnect / disconnectClio + types;
  clioConnections added to UserProfile.
- connectors/page.tsx: render the card above the gallery.

Connect uses popup-location return-polling keyed off the backend's
redirect callback (routes/clio.ts redirects, it does not postMessage), so
no change to the connectors page. Frontend only; backend untouched
(tsc clean, vitest 589/589). UK English; aria-labels on actions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cb5c7a77 Clio PR 3 review fixes: unconfigured pill, drop unused field, BUILD_LOG sloth-ninja 2026-08-03 ↗ GitHub
commit body
- ClioConnectorCard: an unconfigured product row (other product
  configured) now shows a "Not available" pill with title/aria hint
  "Not configured on this deployment." instead of "Not connected".
- mikeApi: remove the unread/unpopulated clioConnections field from
  UserProfile (nit).
- BUILD_LOG: reword deviation 2 - Clio card reachability depends on the
  firm's live memberMcpConnectors value (hidden for all org users incl.
  admins when OFF); exemption is an open owner decision for the deploy
  gate. Drop the stale clioConnections mention.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
b9289056 Merge pull request #64 from Sloth-ninja/clio-connector-frontend Ezana.H 2026-08-03 ↗ GitHub
Clio connector PR 3: frontend practice-management card
e2573b2e Exempt Clio card from the memberMcpConnectors policy sloth-ninja 2026-08-03 ↗ GitHub
commit body
Owner decision (03/08): the "Members may add custom connectors" policy
governs custom MCP servers only; the Practice management - Clio card must
be reachable for all org users regardless.

- connectors/page.tsx: the personalConnectorsBlocked early-return now
  renders <ClioConnectorCard /> above the neutral FirmManagedCard (which
  stays for the MCP gallery); neutral copy narrowed to "Custom connectors".
- account/layout.tsx: Connectors tab always visible (drop the
  personalConnectorsBlocked filter branch + its now-unused import).
- BUILD_LOG: new entry (layout decision (a) recorded); the #64 open owner
  decision marked TAKEN (exempt).

Frontend only; backend untouched. tsc clean; eslint clean on changed files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3684498b BUILD_LOG: restore the orphaned PR 3 heading, normalise entry separator sloth-ninja 2026-08-03 ↗ GitHub
commit body
The exemption entry's heading had displaced #64's "Clio connector PR 3:
frontend" heading, orphaning its Scope block. Reinstate the PR 3 heading
above that block and normalise to a single `---` + blank line between the
two entries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
86986c91 Merge pull request #65 from Sloth-ninja/clio-policy-exemption Ezana.H 2026-08-03 ↗ GitHub
Exempt Clio card from the memberMcpConnectors policy
dd00a829 Clio train: composed-range fix wave sloth-ninja 2026-08-03 ↗ GitHub
commit body
Address the six findings from the composed-range multi-lens review of the
Clio connector train (PRs 2/3 + policy-exemption), plus one stale-comment nit.

1. SAR completeness (#52 pattern): buildUserAccountExport gains a token-free
   clio_connections section via getClioConnectionMetadata (allowlist re-projection,
   42P01/42703-tolerant) - never any encrypted/token material.
2. Account deletion best-effort revokes each connected Clio grant
   (revokeAllClioGrants) before auth.admin.deleteUser; failures never block delete.
3. Connect-flow timeout re-checks /clio/status once before declaring failure,
   so a slow-but-successful connect self-heals; honest fixed message otherwise.
4. A refresh rejected as invalid_grant prunes the dead connection row so the
   stale connected pill self-heals; transient/network failures never prune.
5. Policy-OFF connectors page: page-level "Connectors" heading now sits above
   the Clio card (FirmManagedCard heading made optional).
6. BUILD_LOG: add the missing --- between the PR 3 and PR 2 entries.

Also correct client.test.ts's stale "5s cap" comment to 3s (MAX_RETRY_AFTER_MS).

Backend tsc clean, vitest 600/600 (589 baseline + 11 new). Frontend tsc clean,
ESLint clean on changed files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
b619847a Clio train close-out: status refresh sloth-ninja 2026-08-03 ↗ GitHub
commit body
CLAUDE.md Current status: Clio connector v1 complete (#62-#66), both
sides deployed 03/08, migration run in production, pilot onboarding =
owner + two solicitors connect via the Practice management card.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
aca24961 Merge pull request #66 from Sloth-ninja/clio-train-fixes Ezana.H 2026-08-03 ↗ GitHub
Clio train: composed-range fix wave
df1ec415 Merge pull request #67 from Sloth-ninja/clio-closeout Ezana.H 2026-08-03 ↗ GitHub
Clio train close-out: status refresh
14d45833 Record pilot Clio per-user permissions verification (04/08) sloth-ninja 2026-08-04 ↗ GitHub
commit body
Lindsay's reconnect succeeded after the API_PUBLIC_URL fix and her
matters-visible count differs from the owner's as expected - the
owner-designated per-user confidentiality check for Clio v1 passes.
Docs-only: BUILD_LOG entry, HANDOVER §0.1 resolved, CLAUDE.md status.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
6aae01fa Address review: separator, scoping-claim precision, count/chronology fixes sloth-ninja 2026-08-04 ↗ GitHub
commit body
Review findings on #70: BUILD_LOG entry separator; soften the per-user
scoping claim to what was observed (count-level check only); 604 tests;
status-date bump; verification sentence moved after the incident it
depends on and scoped to the two provisioned accounts; HANDOVER #69;
UK 'solicitors'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
faaf5a6b Clio matter search: status filter, 100-result pages, honest pagination sloth-ninja 2026-08-04 ↗ GitHub
commit body
Pilot bug (04/08): "open Kyckr matters" made the model loop targeted
searches for minutes because clio_find_matter hardcoded limit 10 with no
filters or pagination. Now: page size 100 (Clio caps index actions at
200/request), validated open/pending/closed status filter passed through
as Clio's comma-separated status param, page_token continuation minted
from meta.paging.next and replayed against the API base, and a
{ matters, count, total_entries, has_more, next_page_token? } payload so
the model reports totals honestly instead of guessing. meta.records is
undocumented in reachable Clio docs, so it is parsed tolerantly (null
when absent). Schema + system prompt teach: status arg not query
keywords, report totals, fetch further pages only on request, never
brute-force loop.

Verified: tsc clean, 616/616 vitest (baseline 604 + 12 new), prettier
clean on changed files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
381d6fff Merge pull request #70 from Sloth-ninja/pilot-verification-aug4 Ezana.H 2026-08-04 ↗ GitHub
Record pilot Clio per-user permissions verification (04/08)
11373e51 Review fixes: opaque page_token cursor, raw-next has_more, FAQ citation sloth-ninja 2026-08-04 ↗ GitHub
commit body
Round-1 review blocker: the path-shaped page_token with a
startsWith("/matters.json") guard was traversal-bypassable because
buildUrl's new URL() normalises ".." segments (DURABLE_LESSONS
2026-07-28 class). Redesigned to an opaque cursor:
matterPageTokenFromNext now parses meta.paging.next, requires the exact
Manage API origin and exact <apiBase>/matters.json pathname, and
extracts only the page_token query-param value; the continuation
request is rebuilt from named parts (hardcoded path, our fields+limit,
repeated query/status per the schema) so a model-supplied string never
becomes a request path. has_more now derives from the raw presence of
meta.paging.next, never from cursor extraction success. Status-filter
citation pinned to docs.developers.clio.com/faq/ (case sensitivity
undocumented; lowercase literals enforced); at-least-one-argument rule
stated in the function description. Tests: traversal/suffix-path/
off-host/protocol-relative next URLs rejected, traversal-shaped
page_token asserted to never reach the path, continuation rebuild
asserted to carry fields+limit+cursor. 620/620 tests, tsc + prettier
clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ea89d17e Final review wave: filter-bound page token, host-spoof tests, length bound sloth-ninja 2026-08-04 ↗ GitHub
commit body
Bind the continuation token: next_page_token is now base64url JSON
{ c: cursor, q?, s? } carrying the query/status the page was actually
fetched with. On continuation the token is length-bounded (<=1024),
decoded (failures -> the fixed friendly error before any fetch), its
status re-validated, and the decoded filters used - model-supplied
query/status are ignored under page_token, so a model dropping optional
args on a follow-up can never silently fetch the unfiltered matter list.
Schema/prompt now say "pass page_token alone; filters are remembered".
Removed the uncited "next URL = original params + page_token" comment
claim. Added the two named 2026-07-28 host-spoof regression shapes
(suffix-domain, userinfo) to the matterPageTokenFromNext tests.

627/627 tests (23 new vs baseline), tsc + prettier clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
74646d1e Merge pull request #71 from Sloth-ninja/clio-matter-search-fix Ezana.H 2026-08-04 ↗ GitHub
Clio matter search: status filter, 100-result pages, honest pagination

Capture this thread into my fork

Download a single Markdown prompt that tells Claude how to port every commit above into your working tree — adapting paths and structure to match your repo. Run it via claude -p < capture-thread-999.md from inside the repo you want the changes in.

⬇ Download capture-thread-999.md