Skip to content

Manage email clips from the CLI - #271

Merged
robzolkos merged 5 commits into
mainfrom
clips-cli
Aug 22, 2026
Merged

Manage email clips from the CLI#271
robzolkos merged 5 commits into
mainfrom
clips-cli

Conversation

@robzolkos

@robzolkos robzolkos commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

Give people and coding agents a terminal-native way to save an exact passage from a known email entry, retain its source thread context, and remove it later. This is intentionally CLI-only; selecting text from rendered mail in the TUI remains separate interaction work.

Review readiness: ✅ Yes
Risk: 🟡 Medium — clip creation now reads and validates the source message before mutating; it adds no schema, dependency, or TUI changes.
Decision: Source-backed validation preserves the exact value; --account selects clip-list presentation while HEY derives create/delete ownership identity-wide.

✅ Change — clips can be listed, created from an entry, and deleted entirely from the CLI

Before

Person or coding agent finds an important email passage
├── save it as a HEY clip
│   └── ❌ leave the terminal and use HEY web
└── inspect saved clips and their source context
    └── ❌ no command surface

After

Person or coding agent finds an important email passage
├── hey clip create <entry-id> --content <text>
│   ├── ✅ reject text absent from the source entry      ← CHANGED
│   └── ✅ save the exact supplied text unchanged         ← CHANGED
├── hey clips
│   └── ✅ read clip, entry, and source-thread context  ← CHANGED
└── hey clip delete <clip-id>
    └── ✅ remove the saved clip  ← CHANGED

hey clip create first reads the entry through Messages().Get, derives source-backed message text (including embedded inbound bodies), and accepts browser-style whitespace differences without changing what it sends to HEY. Explicit hidden content and fabricated HTML boundaries are rejected; malformed or excessively deep HTML fails closed; passage/source size limits bound parser and storage work; HEY web remains authoritative for stylesheet-driven visibility.

hey clips preserves the SDK’s newest-first order and returns the complete typed clip shape in JSON, including source topic ID, name, and URL. Human and Markdown output sanitize untrusted content and topic names.

✅ Evidence — lifecycle, source context, formats, and cleanup pass against Haystack
  • ✅ Strict real-server smoke coverage composes a disposable source thread, rejects unrelated text without creating a clip, creates a source-backed clip, verifies its exact plain-text rendering and source thread in the browser, deletes it, and cleans up the source thread.
  • ✅ Unit coverage exercises every list format, empty results, write failures, terminal safety, selected-account scoping, exact content preservation, source-read failures, size limits, and rejection of unrelated, hidden, script, summary-only, case-changed, markup-only, boundary-spliced, and unparseable text. Matching cases cover inline rich text, entities, NBSP/CRLF/U+2028 whitespace, list selections, computed display, embedded inbound bodies, and literal HTML-shaped passages.
  • Real Haystack CLI recording — observe create, count, typed source context, scripted ID reuse, delete, and the count returning to its original value.
GOWORK=off make check
PASS

GOWORK=off make race-test
PASS — all internal packages

GOWORK=off make coverage
PASS — 81.4% repository coverage (70.8% floor)

cd tests/smoke
HEY_SMOKE_STRICT=1 GOWORK=off go test -run '^TestClip' -v -count=1
PASS — lifecycle, browser verification, formats, and validation

GOWORK=off ~/.local/share/mise/installs/go/1.26.5/bin/govulncheck ./...
PASS — no called vulnerabilities

~/.local/share/mise/installs/go/1.26.2/bin/gitleaks detect --source . --verbose --redact
PASS — no leaks

The full strict smoke suite was also exercised. Both clip tests passed; unrelated environment/fixture gates failed for attachment sending, cookie-backed auth token, absence of a seen Imbox posting, and dev-build version checks.

✅ Scope — CLI clip lifecycle only, using the released SDK

Included:

  • hey clips with styled, JSON, quiet, jq, Markdown, IDs, and count output.
  • hey clip create <entry-id> --content <text>, with a source-message read and source-backed passage validation, and hey clip delete <clip-id>.
  • Linked-account query propagation, terminal-safe presentation, breadcrumbs, README/help, command-surface snapshot, unit tests, and strict real-server smoke coverage. Haystack treats clip create/delete as identity-wide mutations even when an account filter is present; this is called out for review below.

Deferred intentionally:

  • TUI clipping, which needs a reliable interaction for selecting exact text from rendered email.
  • Clip editing, which HEY does not expose.
  • Pagination flags: the released SDK’s clip service returns HEY’s newest page and does not expose its page cursor.
➖ Delivery — no migration, configuration, dependency, or deployment ordering

The released HEY SDK v0.12.0 already provides Messages().Get plus Clips().List, Create, and Delete. There are no schema changes, feature flags, new runtime dependencies, backfills, or migrations. Existing clips appear immediately; rollback is a normal revert and does not alter saved data.

⚠️ Review decision — focus on command shape and the first-page boundary

Please confirm:

  1. Account semantics are explicit: listing follows --account; HEY assigns creation to the source entry’s account and resolves deletion by identity-owned clip ID across linked accounts. This is identity-wide ownership, not account isolation.
  2. Source-backed validation is the right integrity boundary: absent text is rejected, browser-style whitespace differences are accepted, and the original supplied text is stored unchanged for HEY web. HEY web remains authoritative for stylesheet-driven visibility.
  3. hey clip create <entry-id> --content <text> is the right explicit contract for scripts and agents that already obtained an entry ID from hey threads --json.
  4. Matching the released SDK’s newest-page list is sufficient here; cursor-aware clip pagination would require an SDK addition and is disclosed rather than recreated in the CLI.

Deletion is permanent for the clip but leaves its source entry and thread unchanged.

✅ Review path — command contract, behavioral proof, then public surface
  1. internal/cmd/clip.go — CLI hierarchy, output, source-message validation, exact-value preservation, source context, and mutation contract.
  2. internal/htmlutil/htmlutil.go and internal/htmlutil/htmlutil_test.go — source-backed message text, HTML/visibility boundaries, embedded inbound bodies, fail-closed parsing, size bounds, and edge-case proof.
  3. internal/cmd/clip_test.go and tests/smoke/clips_test.go — unit and real-server acceptance proof.
  4. internal/cmd/root.go, internal/cmd/help.go, internal/cmd/help_test.go, README.md, and .surface — registration, documentation, help contract, and discoverability.

Origin and supporting links: Basecamp card · separate completed snippets card · HEY SDK v0.12.0

@robzolkos
robzolkos requested a review from a team as a code owner August 22, 2026 08:15
Copilot AI balanced review requested due to automatic review settings August 22, 2026 08:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds CLI support for listing, creating, and deleting email clips with source-thread context and multiple output formats.

Changes:

  • Adds hey clips and hey clip create/delete.
  • Adds unit and real-server smoke coverage.
  • Updates help, documentation, and command surface.

Tip

If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
.surface Records the new command surface.
README.md Documents clip commands and formats.
internal/cmd/clip.go Implements clip listing and mutations.
internal/cmd/clip_test.go Tests command behavior and validation.
internal/cmd/help.go Adds clips to curated help.
internal/cmd/help_test.go Updates help expectations.
internal/cmd/root.go Registers the commands.
tests/smoke/clips_test.go Tests the lifecycle against HEY.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread README.md Outdated
Comment thread internal/cmd/clip.go
@robzolkos
robzolkos merged commit c0ff847 into main Aug 22, 2026
23 checks passed
@robzolkos
robzolkos deleted the clips-cli branch August 22, 2026 09:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants