clippa 1.0.0

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ivan Dovhosheia
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.
package/README.md ADDED
@@ -0,0 +1,105 @@
1
+ <p align="center">
2
+ <img src="docs/assets/app-icon.png" width="96" height="96" alt="Clippa icon">
3
+ </p>
4
+
5
+ <h1 align="center">Clippa</h1>
6
+
7
+ <p align="center">
8
+ A small, private macOS clipboard app built around one shortcut: press Command-Shift-W, choose a clip, paste.
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://vaniawl.github.io/Clippa/">Website</a>
13
+ ·
14
+ <a href="https://github.com/Vaniawl/Clippa/releases">Releases</a>
15
+ ·
16
+ <a href="#privacy">Privacy</a>
17
+ </p>
18
+
19
+ <p align="center">
20
+ <img src="docs/assets/screenshot-panel.svg" alt="Clippa clipboard panel" width="900">
21
+ </p>
22
+
23
+ ## Install Clippa
24
+
25
+ Install the current GitHub build:
26
+
27
+ ```bash
28
+ npx github:Vaniawl/Clippa
29
+ ```
30
+
31
+ After npm publish, the public install command will be:
32
+
33
+ ```bash
34
+ npx clippa
35
+ ```
36
+
37
+ After it opens, grant Accessibility access when macOS asks. Clippa needs that permission only to paste the selected item into the app where your cursor is already active.
38
+
39
+ Useful installer options:
40
+
41
+ ```bash
42
+ npx github:Vaniawl/Clippa -- --no-open
43
+ npx github:Vaniawl/Clippa -- --install-dir ~/Applications
44
+ ```
45
+
46
+ You can also download `Clippa.app.zip` from the latest GitHub release, unzip it, move `Clippa.app` to `/Applications`, and open it.
47
+
48
+ ## How It Works
49
+
50
+ - Runs quietly in the background as a menu-bar app.
51
+ - Press `Command-Shift-W` while your cursor is in a text field.
52
+ - Use `Up` / `Down` to choose a clipboard item.
53
+ - Press `Enter` or click an item to paste it.
54
+ - Press `Esc` or click outside the panel to close it.
55
+ - Stores recent text, links, images, and file references locally on your Mac.
56
+
57
+ ## Privacy
58
+
59
+ Clippa does not upload clipboard contents, does not use analytics, and does not require an account. Clipboard history is stored only on your Mac and encrypted locally with a per-user AES-GCM key file in Application Support. Clippa does not read or write Keychain items at startup, so locally signed reinstall builds do not trigger Keychain password prompts.
60
+
61
+ Accessibility permission is only used to confirm there is an editable field under the cursor and to paste the selected clip into that frontmost app.
62
+
63
+ ## Requirements
64
+
65
+ - macOS 26.0 or newer
66
+ - Xcode 26.6 or newer, only if you want to build from source
67
+
68
+ ## Build From Source
69
+
70
+ ```bash
71
+ git clone https://github.com/Vaniawl/Clippa.git
72
+ cd Clippa
73
+ xcodebuild -project Clippa.xcodeproj -scheme Clippa -destination 'platform=macOS' test
74
+ SMOKE_LAUNCH=1 ./scripts/release.sh
75
+ ```
76
+
77
+ The packaged app is written to:
78
+
79
+ ```bash
80
+ outputs/Clippa.app.zip
81
+ ```
82
+
83
+ ## npm Publish
84
+
85
+ The npm installer package is prepared as `clippa`. Publishing to npm requires an authenticated npm session:
86
+
87
+ ```bash
88
+ npm adduser
89
+ npm publish --access public
90
+ ```
91
+
92
+ ## Update From Git
93
+
94
+ For an existing checkout:
95
+
96
+ ```bash
97
+ git pull origin main
98
+ ```
99
+
100
+ ## Production Notes
101
+
102
+ - Bundle identifier: `com.ivandovhosheia.Clippa`
103
+ - Version: `1.0.0`
104
+ - Release builds use hardened runtime.
105
+ - Recent history is limited to 100 active items and stored only on this Mac.
@@ -0,0 +1,201 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { execFileSync } = require("node:child_process");
4
+ const fs = require("node:fs");
5
+ const os = require("node:os");
6
+ const path = require("node:path");
7
+ const https = require("node:https");
8
+
9
+ const appName = "Clippa";
10
+ const bundleIdentifier = "com.ivandovhosheia.Clippa";
11
+ const localZipPath = path.resolve(__dirname, "../outputs/Clippa.app.zip");
12
+ const packageJson = require("../package.json");
13
+ const defaultZipUrl = "https://github.com/Vaniawl/Clippa/releases/download/v1.0.0/Clippa.app.zip";
14
+ const zipUrl = process.env.CLIPPA_ZIP_URL || defaultZipUrl;
15
+
16
+ function usage() {
17
+ console.log(`Install Clippa for macOS.
18
+
19
+ Usage:
20
+ npx github:Vaniawl/Clippa
21
+ npx clippa
22
+
23
+ Options:
24
+ --install-dir <path> Copy Clippa.app into a custom applications folder.
25
+ --no-open Install without opening Clippa afterwards.
26
+ --version Show installer version.
27
+ --help Show this help.
28
+
29
+ The installer copies Clippa.app to /Applications when possible, otherwise to ~/Applications.
30
+ Press Command-Shift-W in a text field to open Clippa.
31
+ `);
32
+ }
33
+
34
+ function run(command, args, options = {}) {
35
+ execFileSync(command, args, { stdio: "inherit", ...options });
36
+ }
37
+
38
+ function parseArgs(argv) {
39
+ const options = {
40
+ help: false,
41
+ version: false,
42
+ noOpen: false,
43
+ installDir: null
44
+ };
45
+
46
+ for (let index = 0; index < argv.length; index += 1) {
47
+ const arg = argv[index];
48
+ switch (arg) {
49
+ case "--help":
50
+ case "-h":
51
+ options.help = true;
52
+ break;
53
+ case "--version":
54
+ case "-v":
55
+ options.version = true;
56
+ break;
57
+ case "--no-open":
58
+ options.noOpen = true;
59
+ break;
60
+ case "--install-dir":
61
+ index += 1;
62
+ if (!argv[index]) {
63
+ throw new Error("--install-dir requires a path.");
64
+ }
65
+ options.installDir = expandHome(argv[index]);
66
+ break;
67
+ default:
68
+ throw new Error(`Unknown option: ${arg}`);
69
+ }
70
+ }
71
+
72
+ return options;
73
+ }
74
+
75
+ function expandHome(value) {
76
+ if (value === "~") {
77
+ return os.homedir();
78
+ }
79
+ if (value.startsWith("~/")) {
80
+ return path.join(os.homedir(), value.slice(2));
81
+ }
82
+ return path.resolve(value);
83
+ }
84
+
85
+ function download(url, destination) {
86
+ return new Promise((resolve, reject) => {
87
+ const request = https.get(url, response => {
88
+ if ([301, 302, 303, 307, 308].includes(response.statusCode)) {
89
+ response.resume();
90
+ download(response.headers.location, destination).then(resolve, reject);
91
+ return;
92
+ }
93
+
94
+ if (response.statusCode !== 200) {
95
+ response.resume();
96
+ reject(new Error(`Download failed with HTTP ${response.statusCode}`));
97
+ return;
98
+ }
99
+
100
+ const file = fs.createWriteStream(destination);
101
+ response.pipe(file);
102
+ file.on("finish", () => file.close(resolve));
103
+ file.on("error", reject);
104
+ });
105
+
106
+ request.on("error", reject);
107
+ });
108
+ }
109
+
110
+ function copyApp(source, destination) {
111
+ if (fs.existsSync(destination)) {
112
+ fs.rmSync(destination, { recursive: true, force: true });
113
+ }
114
+ run("/usr/bin/ditto", [source, destination]);
115
+ }
116
+
117
+ function quitRunningApp() {
118
+ try {
119
+ run("/usr/bin/osascript", ["-e", `tell application id "${bundleIdentifier}" to quit`], {
120
+ stdio: "ignore",
121
+ timeout: 3000
122
+ });
123
+ } catch {
124
+ try {
125
+ run("/usr/bin/killall", [appName], { stdio: "ignore", timeout: 3000 });
126
+ } catch {
127
+ // The app may not be running yet.
128
+ }
129
+ }
130
+ }
131
+
132
+ async function main() {
133
+ const options = parseArgs(process.argv.slice(2));
134
+ if (options.help) {
135
+ usage();
136
+ return;
137
+ }
138
+ if (options.version) {
139
+ console.log(packageJson.version);
140
+ return;
141
+ }
142
+
143
+ if (process.platform !== "darwin") {
144
+ console.error("Clippa is a macOS app. Run this installer on macOS.");
145
+ process.exit(1);
146
+ }
147
+
148
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "clippa-install-"));
149
+ const zipPath = path.join(tempDir, "Clippa.app.zip");
150
+ const extractDir = path.join(tempDir, "extract");
151
+
152
+ try {
153
+ fs.mkdirSync(extractDir, { recursive: true });
154
+
155
+ if (fs.existsSync(localZipPath)) {
156
+ fs.copyFileSync(localZipPath, zipPath);
157
+ } else {
158
+ console.log(`Downloading Clippa from ${zipUrl}`);
159
+ await download(zipUrl, zipPath);
160
+ }
161
+
162
+ run("/usr/bin/ditto", ["-x", "-k", zipPath, extractDir]);
163
+
164
+ const appPath = path.join(extractDir, `${appName}.app`);
165
+ if (!fs.existsSync(appPath)) {
166
+ throw new Error(`Archive did not contain ${appName}.app`);
167
+ }
168
+
169
+ quitRunningApp();
170
+
171
+ let installDir = options.installDir || "/Applications";
172
+ let installPath = path.join(installDir, `${appName}.app`);
173
+ if (options.installDir) {
174
+ fs.mkdirSync(installDir, { recursive: true });
175
+ copyApp(appPath, installPath);
176
+ } else {
177
+ try {
178
+ copyApp(appPath, installPath);
179
+ } catch (error) {
180
+ installDir = path.join(os.homedir(), "Applications");
181
+ fs.mkdirSync(installDir, { recursive: true });
182
+ installPath = path.join(installDir, `${appName}.app`);
183
+ copyApp(appPath, installPath);
184
+ }
185
+ }
186
+
187
+ console.log(`Installed ${appName} to ${installPath}`);
188
+ if (!options.noOpen) {
189
+ console.log("Opening Clippa...");
190
+ run("/usr/bin/open", [installPath]);
191
+ console.log("Press Command-Shift-W in a text field to open Clippa. Grant Accessibility access if macOS asks.");
192
+ }
193
+ } finally {
194
+ fs.rmSync(tempDir, { recursive: true, force: true });
195
+ }
196
+ }
197
+
198
+ main().catch(error => {
199
+ console.error(error.message);
200
+ process.exit(1);
201
+ });
Binary file
@@ -0,0 +1,39 @@
1
+ <svg width="1440" height="900" viewBox="0 0 1440 900" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="1440" height="900" fill="#111414"/>
3
+ <rect x="340" y="164" width="760" height="520" rx="30" fill="#181C1D"/>
4
+ <rect x="340.5" y="164.5" width="759" height="519" rx="29.5" stroke="#3A4242"/>
5
+ <filter id="shadow" x="300" y="124" width="840" height="600" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
6
+ <feDropShadow dx="0" dy="26" stdDeviation="28" flood-color="#000000" flood-opacity=".34"/>
7
+ </filter>
8
+ <g filter="url(#shadow)">
9
+ <rect x="340" y="164" width="760" height="520" rx="30" fill="#181C1D"/>
10
+ <rect x="340.5" y="164.5" width="759" height="519" rx="29.5" stroke="#3A4242"/>
11
+ </g>
12
+ <circle cx="388" cy="221" r="24" fill="#0A84FF"/>
13
+ <path d="M379 230l29-29a10 10 0 0 1 14 14l-32 32a17 17 0 0 1-24-24l31-31" stroke="#06131F" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
14
+ <text x="430" y="215" fill="#F4F7F5" font-family="-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif" font-size="25" font-weight="760">Clippa</text>
15
+ <text x="430" y="244" fill="#9BA6A2" font-family="-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif" font-size="16" font-weight="620">4 Items</text>
16
+ <line x1="380" y1="282" x2="1060" y2="282" stroke="#303838"/>
17
+ <rect x="380" y="314" width="680" height="72" rx="14" fill="#12365B"/>
18
+ <rect x="380.5" y="314.5" width="679" height="71" rx="13.5" stroke="#0A84FF" stroke-opacity=".62"/>
19
+ <rect x="402" y="330" width="42" height="42" rx="10" fill="#20364F"/>
20
+ <path d="M414 361h20M414 351h25M414 341h18" stroke="#B6C2C9" stroke-width="4" stroke-linecap="round"/>
21
+ <text x="464" y="346" fill="#F5F8F6" font-family="-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif" font-size="21" font-weight="720">Release checklist</text>
22
+ <text x="464" y="371" fill="#B1BEC8" font-family="-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif" font-size="15" font-weight="610">Text / Notes</text>
23
+ <rect x="380" y="398" width="680" height="72" rx="14" fill="#202526"/>
24
+ <rect x="402" y="414" width="42" height="42" rx="10" fill="#143C38"/>
25
+ <path d="M413 447l14-17 10 12 6-7 13 17h-43v-5z" fill="#64D2C8"/>
26
+ <text x="464" y="430" fill="#F5F8F6" font-family="-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif" font-size="21" font-weight="720">Clipboard image</text>
27
+ <text x="464" y="455" fill="#98A4A0" font-family="-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif" font-size="15" font-weight="610">1280 x 720 / 482 KB / Preview</text>
28
+ <rect x="380" y="482" width="680" height="72" rx="14" fill="#202526"/>
29
+ <rect x="402" y="498" width="42" height="42" rx="10" fill="#223B58"/>
30
+ <path d="M413 522c7-9 13-9 20 0s13 9 20 0" stroke="#8EC5FF" stroke-width="5" stroke-linecap="round"/>
31
+ <text x="464" y="514" fill="#F5F8F6" font-family="-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif" font-size="21" font-weight="720">https://example.com/research</text>
32
+ <text x="464" y="539" fill="#98A4A0" font-family="-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif" font-size="15" font-weight="610">URL / Safari</text>
33
+ <rect x="380" y="566" width="680" height="72" rx="14" fill="#202526"/>
34
+ <rect x="402" y="582" width="42" height="42" rx="10" fill="#3A3324"/>
35
+ <path d="M414 595h18l8 8v20h-26v-28z" stroke="#FFD479" stroke-width="4" stroke-linejoin="round"/>
36
+ <text x="464" y="598" fill="#F5F8F6" font-family="-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif" font-size="21" font-weight="720">invoice.pdf</text>
37
+ <text x="464" y="623" fill="#98A4A0" font-family="-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif" font-size="15" font-weight="610">File / Finder</text>
38
+ <text x="380" y="724" fill="#75817D" font-family="-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif" font-size="18" font-weight="650">Command-Shift-W opens Clippa. Enter or click pastes the selected item.</text>
39
+ </svg>
Binary file
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "clippa",
3
+ "version": "1.0.0",
4
+ "description": "Installer for Clippa, a private macOS paste panel.",
5
+ "bin": {
6
+ "install-clippa": "bin/install-clippa.js",
7
+ "clippa": "bin/install-clippa.js",
8
+ "Clippa": "bin/install-clippa.js"
9
+ },
10
+ "scripts": {
11
+ "release": "./scripts/release.sh",
12
+ "test": "xcodebuild -project Clippa.xcodeproj -scheme Clippa -destination 'platform=macOS' test"
13
+ },
14
+ "files": [
15
+ "bin",
16
+ "docs/assets",
17
+ "outputs/Clippa.app.zip",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/Vaniawl/Clippa.git"
24
+ },
25
+ "homepage": "https://vaniawl.github.io/Clippa/",
26
+ "bugs": {
27
+ "url": "https://github.com/Vaniawl/Clippa/issues"
28
+ },
29
+ "keywords": [
30
+ "macos",
31
+ "clipboard",
32
+ "swiftui",
33
+ "productivity"
34
+ ],
35
+ "license": "MIT"
36
+ }