Commit ba62721654
ba6272165474d294fe74fc0966638c329e972346
parent: 84a94244b1
Verified · cmc
cmc <hello@cleberg.net> · 2026-09-18 01:22 UTC
Clear the clipboard instead of restoring it
Layout: unified · split
Sources/keycask/Clipboard.swift
+4 −5
| @@ -4,7 +4,6 @@ import KeycaskCore |
| 4 | 4 | enum Clipboard { |
| 5 | 5 | struct Handoff: Codable, Equatable { |
| 6 | 6 | var secret: String |
| 7 | | var previous: String |
| 8 | 7 | } |
| 9 | 8 | |
| 10 | 9 | struct Tool: Equatable { |
| @@ -14,7 +13,7 @@ enum Clipboard { |
| 14 | 13 | |
| 15 | 14 | static let timeoutSeconds = 45 |
| 16 | 15 | |
| 17 | | static func shouldRestore(secret: String, current: String?) -> Bool { |
| 16 | static func shouldClear(secret: String, current: String?) -> Bool { |
| 18 | 17 | current == secret |
| 19 | 18 | } |
| 20 | 19 | |
| @@ -75,7 +74,7 @@ enum Clipboard { |
| 75 | 74 | |
| 76 | 75 | static func copyWithTimeout(_ secret: String, seconds: Int = timeoutSeconds) throws { |
| 77 | 76 | _ = try requireTool() |
| 78 | | let handoff = Handoff(secret: secret, previous: try read()) |
| 77 | let handoff = Handoff(secret: secret) |
| 79 | 78 | try write(secret) |
| 80 | 79 | let process = Process() |
| 81 | 80 | process.executableURL = Bundle.main.executableURL |
| @@ -103,8 +102,8 @@ enum Clipboard { |
| 103 | 102 | } |
| 104 | 103 | Thread.sleep(forTimeInterval: TimeInterval(seconds)) |
| 105 | 104 | let current = try? read() |
| 106 | | guard shouldRestore(secret: handoff.secret, current: current) else { return } |
| 107 | | try write(handoff.previous) |
| 105 | guard shouldClear(secret: handoff.secret, current: current) else { return } |
| 106 | try write("") |
| 108 | 107 | } |
| 109 | 108 | |
| 110 | 109 | private static func requireTool() throws -> Tool { |
Tests/KeycaskCLITests/ClipboardTests.swift
+5 −5
| @@ -4,14 +4,14 @@ import Testing |
| 4 | 4 | @testable import keycask |
| 5 | 5 | |
| 6 | 6 | @Suite struct ClipboardTests { |
| 7 | | @Test func restoresOnlyWhenClipboardStillHoldsTheSecret() { |
| 8 | | #expect(Clipboard.shouldRestore(secret: "s", current: "s")) |
| 9 | | #expect(!Clipboard.shouldRestore(secret: "s", current: "user pasted")) |
| 10 | | #expect(!Clipboard.shouldRestore(secret: "s", current: nil)) |
| 7 | @Test func clearsOnlyWhenClipboardStillHoldsTheSecret() { |
| 8 | #expect(Clipboard.shouldClear(secret: "s", current: "s")) |
| 9 | #expect(!Clipboard.shouldClear(secret: "s", current: "user pasted")) |
| 10 | #expect(!Clipboard.shouldClear(secret: "s", current: nil)) |
| 11 | 11 | } |
| 12 | 12 | |
| 13 | 13 | @Test func handoffRoundTrips() throws { |
| 14 | | let h = Clipboard.Handoff(secret: "s3cret", previous: "old") |
| 14 | let h = Clipboard.Handoff(secret: "s3cret") |
| 15 | 15 | let data = try JSONEncoder().encode(h) |
| 16 | 16 | #expect(try JSONDecoder().decode(Clipboard.Handoff.self, from: data) == h) |
| 17 | 17 | } |
docs/superpowers/specs/2026-09-17-keycask-design.md
+7 −6
| @@ -224,11 +224,12 @@ and a Windows body where they differ. |
| 224 | 224 | Linux; `clip.exe` to write and `powershell -command Get-Clipboard` to |
| 225 | 225 | read on Windows. No tool found is exit 1 with a message naming the |
| 226 | 226 | tools. `clip` spawns `keycask clipboard-daemon` detached with stdout |
| 227 | | and stderr to null, writes `{"secret": ..., "previous": ...}` to its |
| 228 | | stdin, and exits without waiting. `clip` writes the clipboard itself, |
| 229 | | then spawns the daemon. The daemon sleeps 45 seconds, reads the |
| 230 | | clipboard, and if it still equals the secret restores `previous` or |
| 231 | | clears when `previous` is empty. |
| 227 | and stderr to null and exits without waiting. `clip` writes the |
| 228 | clipboard itself, then spawns the daemon with `{"secret": ...}` on its |
| 229 | stdin. The daemon sleeps 45 seconds, reads the clipboard, and clears |
| 230 | it if it still equals the secret. It never restores earlier contents, |
| 231 | so a second `clip` inside the window cannot bring an earlier secret |
| 232 | back. |
| 232 | 233 | |
| 233 | 234 | ## Errors |
| 234 | 235 | |
| @@ -272,7 +273,7 @@ CLI, black box: |
| 272 | 273 | masking versus `--reveal`, `--field` raw output, the ambiguous-name |
| 273 | 274 | listing, `rm` without `--yes` and without a TTY, `edit` with no flags, |
| 274 | 275 | `--generate` with `--words`, and `init` on an existing vault. |
| 275 | | - The clipboard daemon's restore decision is unit-tested in process; |
| 276 | - The clipboard daemon's clear decision is unit-tested in process; |
| 276 | 277 | the tests do not touch the real clipboard. |
| 277 | 278 | |
| 278 | 279 | The CLI suite is the conformance suite. When the app exists, its |