pigeondrop 1.5.0 → 1.7.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/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # What's new
2
2
 
3
+ ## v1.7.0 — 4 November 2026
4
+
5
+ - Changed: **Pigeonhole is now called Pigeondrop.** The page, the installers and the instructions use the new name. Existing installs keep working, and the installers keep updating them where they are.
6
+ - Changed: **The `pigeonhole` command is gone.** Run `pigeondrop` instead.
7
+
8
+ ## v1.6.1 — 2 November 2026
9
+
10
+ - Changed: **The page heading reads Pigeonhole.** It is no longer shown in capitals.
11
+
12
+ ## v1.6.0 — 2 November 2026
13
+
14
+ - New: **Filter the file list by name.** Type in the box above the list to show only the files and folders whose names contain what you typed.
15
+
16
+ ## v1.5.1 — 16 October 2026
17
+
18
+ - Changed: **Pigeonhole's code now lives at github.com/cventour/pigeondrop.** Old links and install commands still work.
19
+
3
20
  ## v1.5.0 — 16 October 2026
4
21
 
5
22
  - New: **Run it with npx.** `npx pigeondrop` starts Pigeonhole in the folder you are in, with nothing to install. Its files and settings stay in that folder.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
- <img src="icon.svg" width="88" alt="Pigeonhole icon: a wall of arched pigeonholes, one holding a letter">
1
+ <img src="icon.svg" width="88" alt="Pigeondrop icon: a wall of arched pigeonholes, one holding a letter">
2
2
 
3
- # Pigeonhole
3
+ # Pigeondrop
4
4
 
5
- A file pigeonhole you can stand up in a minute. Put files in, let people take
5
+ A file drop you can stand up in a minute. Put files in, let people take
6
6
  them out, and take back whatever they leave you.
7
7
 
8
8
  Useful when a group needs a shared drop: distributing builds, installers or
@@ -61,7 +61,7 @@ Also WSL, and anything else with bash.
61
61
  ## Install it
62
62
 
63
63
  ```bash
64
- curl -fsSL https://raw.githubusercontent.com/cventour/pigeonhole/main/install.sh -o install.sh
64
+ curl -fsSL https://raw.githubusercontent.com/cventour/pigeondrop/main/install.sh -o install.sh
65
65
  ```
66
66
 
67
67
  Read it — it is about a hundred lines and does nothing surprising. Then:
@@ -70,14 +70,14 @@ Read it — it is about a hundred lines and does nothing surprising. Then:
70
70
  bash install.sh
71
71
  ```
72
72
 
73
- It installs to `~/pigeonhole`, checks your Node version, and starts the
73
+ It installs to `~/pigeondrop`, checks your Node version, and starts the
74
74
  server. Open http://localhost:3001.
75
75
 
76
76
  Options:
77
77
 
78
78
  | Option | Meaning |
79
79
  |---|---|
80
- | `--dir` | Where to install. Default `~/pigeonhole`. |
80
+ | `--dir` | Where to install. Default `~/pigeondrop`. |
81
81
  | `--root` | The directory to serve. Default `<install dir>/files`. |
82
82
  | `--port` | Default `3001`. |
83
83
  | `--host` | Bind address. Default `127.0.0.1`. |
@@ -97,8 +97,8 @@ Run the script again later and it updates the copy it installed.
97
97
  The script saves typing, not much else. The manual version is three lines:
98
98
 
99
99
  ```bash
100
- git clone https://github.com/cventour/pigeonhole.git
101
- cd pigeonhole
100
+ git clone https://github.com/cventour/pigeondrop.git
101
+ cd pigeondrop
102
102
  node server.js
103
103
  ```
104
104
 
@@ -150,25 +150,25 @@ Others open `http://<that-address>:3001`. Read
150
150
  The shell that started it owns it. To survive a closed terminal:
151
151
 
152
152
  ```bash
153
- nohup env REPO_ROOT=/srv/files node server.js > pigeonhole.log 2>&1 &
153
+ nohup env REPO_ROOT=/srv/files node server.js > pigeondrop.log 2>&1 &
154
154
  ```
155
155
 
156
- **Linux, with systemd.** Save as `/etc/systemd/system/pigeonhole.service`:
156
+ **Linux, with systemd.** Save as `/etc/systemd/system/pigeondrop.service`:
157
157
 
158
158
  ```ini
159
159
  [Unit]
160
- Description=Pigeonhole
160
+ Description=Pigeondrop
161
161
  After=network.target
162
162
 
163
163
  [Service]
164
164
  Type=simple
165
- User=pigeonhole
166
- WorkingDirectory=/opt/pigeonhole
167
- ExecStart=/usr/bin/node /opt/pigeonhole/server.js
165
+ User=pigeondrop
166
+ WorkingDirectory=/opt/pigeondrop
167
+ ExecStart=/usr/bin/node /opt/pigeondrop/server.js
168
168
  Environment=REPO_ROOT=/srv/files
169
169
  # A place the service may write its settings, which ProtectSystem=strict
170
- # would otherwise refuse. Pigeonhole finds it by itself: /var/lib/pigeonhole.
171
- StateDirectory=pigeonhole
170
+ # would otherwise refuse. Pigeondrop finds it by itself: /var/lib/pigeondrop.
171
+ StateDirectory=pigeondrop
172
172
  # Quote any value containing a space: systemd splits unquoted ones.
173
173
  Environment="REPO_TITLE=Team Drop"
174
174
  Restart=on-failure
@@ -183,15 +183,15 @@ WantedBy=multi-user.target
183
183
  ```
184
184
 
185
185
  ```bash
186
- sudo systemctl enable --now pigeonhole
186
+ sudo systemctl enable --now pigeondrop
187
187
  ```
188
188
 
189
189
  ```bash
190
- systemctl status pigeonhole
190
+ systemctl status pigeondrop
191
191
  ```
192
192
 
193
193
  **macOS, with launchd.** Save as
194
- `~/Library/LaunchAgents/com.pigeonhole.plist`, with your own paths — launchd
194
+ `~/Library/LaunchAgents/com.pigeondrop.plist`, with your own paths — launchd
195
195
  does not expand `~`, and needs the full path to node:
196
196
 
197
197
  ```xml
@@ -199,11 +199,11 @@ does not expand `~`, and needs the full path to node:
199
199
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
200
200
  <plist version="1.0">
201
201
  <dict>
202
- <key>Label</key> <string>com.pigeonhole</string>
202
+ <key>Label</key> <string>com.pigeondrop</string>
203
203
  <key>ProgramArguments</key>
204
204
  <array>
205
205
  <string>/usr/local/bin/node</string>
206
- <string>/Users/you/pigeonhole/server.js</string>
206
+ <string>/Users/you/pigeondrop/server.js</string>
207
207
  </array>
208
208
  <key>EnvironmentVariables</key>
209
209
  <dict>
@@ -212,7 +212,7 @@ does not expand `~`, and needs the full path to node:
212
212
  </dict>
213
213
  <key>RunAtLoad</key> <true/>
214
214
  <key>KeepAlive</key> <true/>
215
- <key>StandardErrorPath</key> <string>/tmp/pigeonhole.log</string>
215
+ <key>StandardErrorPath</key> <string>/tmp/pigeondrop.log</string>
216
216
  </dict>
217
217
  </plist>
218
218
  ```
@@ -220,17 +220,17 @@ does not expand `~`, and needs the full path to node:
220
220
  `which node` gives the path to use. Then load it:
221
221
 
222
222
  ```bash
223
- launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.pigeonhole.plist
223
+ launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.pigeondrop.plist
224
224
  ```
225
225
 
226
226
  ```bash
227
- launchctl print gui/$(id -u)/com.pigeonhole
227
+ launchctl print gui/$(id -u)/com.pigeondrop
228
228
  ```
229
229
 
230
230
  To stop it for good:
231
231
 
232
232
  ```bash
233
- launchctl bootout gui/$(id -u)/com.pigeonhole
233
+ launchctl bootout gui/$(id -u)/com.pigeondrop
234
234
  ```
235
235
 
236
236
  ---
@@ -243,7 +243,7 @@ PowerShell 7.
243
243
  ## Install it
244
244
 
245
245
  ```powershell
246
- irm https://raw.githubusercontent.com/cventour/pigeonhole/main/install.ps1 -OutFile install.ps1
246
+ irm https://raw.githubusercontent.com/cventour/pigeondrop/main/install.ps1 -OutFile install.ps1
247
247
  ```
248
248
 
249
249
  Read it — it is about a hundred lines and does nothing surprising. Then:
@@ -255,14 +255,14 @@ powershell -ExecutionPolicy Bypass -File .\install.ps1
255
255
  Windows blocks downloaded scripts by default, which is why that is longer than
256
256
  `.\install.ps1`. It applies to this one run and changes nothing on the machine.
257
257
 
258
- It installs to `$HOME\pigeonhole`, checks your Node version, and starts the
258
+ It installs to `$HOME\pigeondrop`, checks your Node version, and starts the
259
259
  server. Open http://localhost:3001.
260
260
 
261
261
  Options:
262
262
 
263
263
  | Option | Meaning |
264
264
  |---|---|
265
- | `-Dir` | Where to install. Default `$HOME\pigeonhole`. |
265
+ | `-Dir` | Where to install. Default `$HOME\pigeondrop`. |
266
266
  | `-Root` | The directory to serve. Default `<install dir>\files`. |
267
267
  | `-Port` | Default `3001`. |
268
268
  | `-Bind` | Bind address. Default `127.0.0.1`. |
@@ -285,8 +285,8 @@ Run the script again later and it updates the copy it installed.
285
285
  The script saves typing, not much else. The manual version is three lines:
286
286
 
287
287
  ```powershell
288
- git clone https://github.com/cventour/pigeonhole.git
289
- cd pigeonhole
288
+ git clone https://github.com/cventour/pigeondrop.git
289
+ cd pigeondrop
290
290
  node server.js
291
291
  ```
292
292
 
@@ -294,9 +294,9 @@ Windows does not ship with git, so on a fresh machine that first line fails.
294
294
  Take the zip instead — this needs nothing that is not already there:
295
295
 
296
296
  ```powershell
297
- irm https://github.com/cventour/pigeonhole/archive/refs/heads/main.zip -OutFile pigeonhole.zip
298
- Expand-Archive pigeonhole.zip -DestinationPath .
299
- cd pigeonhole-main
297
+ irm https://github.com/cventour/pigeondrop/archive/refs/heads/main.zip -OutFile pigeondrop.zip
298
+ Expand-Archive pigeondrop.zip -DestinationPath .
299
+ cd pigeondrop-main
300
300
  node server.js
301
301
  ```
302
302
 
@@ -340,7 +340,7 @@ Windows Firewall will block the port even once it is bound, and the prompt is
340
340
  easy to miss. Allow it from an **administrator** PowerShell:
341
341
 
342
342
  ```powershell
343
- New-NetFirewallRule -DisplayName "Pigeonhole" -Direction Inbound -Protocol TCP -LocalPort 3001 -Action Allow
343
+ New-NetFirewallRule -DisplayName "Pigeondrop" -Direction Inbound -Protocol TCP -LocalPort 3001 -Action Allow
344
344
  ```
345
345
 
346
346
  Others open `http://<that-address>:3001`. Read
@@ -349,7 +349,7 @@ Others open `http://<that-address>:3001`. Read
349
349
  To close it again afterwards:
350
350
 
351
351
  ```powershell
352
- Remove-NetFirewallRule -DisplayName "Pigeonhole"
352
+ Remove-NetFirewallRule -DisplayName "Pigeondrop"
353
353
  ```
354
354
 
355
355
  ## Keep it running
@@ -358,7 +358,7 @@ The window that started it owns it. To put it in the background for this
358
358
  session:
359
359
 
360
360
  ```powershell
361
- $p = Start-Process node -ArgumentList "server.js" -WorkingDirectory "$HOME\pigeonhole" -WindowStyle Hidden -PassThru
361
+ $p = Start-Process node -ArgumentList "server.js" -WorkingDirectory "$HOME\pigeondrop" -WindowStyle Hidden -PassThru
362
362
  ```
363
363
 
364
364
  `-PassThru` hands back the process, so you can stop it again:
@@ -372,7 +372,7 @@ PowerShell. Install it somewhere outside your profile first, since the task
372
372
  runs as SYSTEM:
373
373
 
374
374
  ```powershell
375
- powershell -ExecutionPolicy Bypass -File .\install.ps1 -Dir C:\pigeonhole -NoStart
375
+ powershell -ExecutionPolicy Bypass -File .\install.ps1 -Dir C:\pigeondrop -NoStart
376
376
  ```
377
377
 
378
378
  Set every setting you need machine-wide, because SYSTEM does not see your
@@ -384,9 +384,9 @@ account's variables:
384
384
  ```
385
385
 
386
386
  ```powershell
387
- $action = New-ScheduledTaskAction -Execute (Get-Command node).Source -Argument "server.js" -WorkingDirectory "C:\pigeonhole"
387
+ $action = New-ScheduledTaskAction -Execute (Get-Command node).Source -Argument "server.js" -WorkingDirectory "C:\pigeondrop"
388
388
  $trigger = New-ScheduledTaskTrigger -AtStartup
389
- Register-ScheduledTask -TaskName Pigeonhole -Action $action -Trigger $trigger -User SYSTEM -RunLevel Highest
389
+ Register-ScheduledTask -TaskName Pigeondrop -Action $action -Trigger $trigger -User SYSTEM -RunLevel Highest
390
390
  ```
391
391
 
392
392
  `(Get-Command node).Source` resolves the full path to node.exe, because SYSTEM
@@ -395,11 +395,11 @@ does not necessarily share your `PATH`.
395
395
  Start it now rather than waiting for a reboot:
396
396
 
397
397
  ```powershell
398
- Start-ScheduledTask -TaskName Pigeonhole
398
+ Start-ScheduledTask -TaskName Pigeondrop
399
399
  ```
400
400
 
401
401
  ```powershell
402
- Get-ScheduledTask -TaskName Pigeonhole | Get-ScheduledTaskInfo
402
+ Get-ScheduledTask -TaskName Pigeondrop | Get-ScheduledTaskInfo
403
403
  ```
404
404
 
405
405
  A `LastTaskResult` of `267009` is not an error — it is `0x41301`, "the task is
@@ -408,11 +408,11 @@ currently running", which is what you want to see.
408
408
  Stop it, or remove it entirely:
409
409
 
410
410
  ```powershell
411
- Stop-ScheduledTask -TaskName Pigeonhole
411
+ Stop-ScheduledTask -TaskName Pigeondrop
412
412
  ```
413
413
 
414
414
  ```powershell
415
- Unregister-ScheduledTask -TaskName Pigeonhole -Confirm:$false
415
+ Unregister-ScheduledTask -TaskName Pigeondrop -Confirm:$false
416
416
  ```
417
417
 
418
418
  ---
@@ -426,7 +426,7 @@ All optional, all environment variables, identical on every platform.
426
426
  | `REPO_ROOT` | `./files`, in the working directory | The directory it manages. Created if missing. |
427
427
  | `HOST` | `127.0.0.1` | Bind address. Loopback by default, on purpose. |
428
428
  | `PORT` | `3001` | |
429
- | `REPO_TITLE` | `Pigeonhole` | Page title and heading |
429
+ | `REPO_TITLE` | `Pigeondrop` | Page title and heading |
430
430
  | `REPO_SUBTITLE` | `Drop files in. Take files out.` | Line under the heading |
431
431
  | `SETTINGS_FILE` | `$STATE_DIRECTORY/settings.json` under systemd, else `settings.json` beside `server.js` (in the working directory when run through npx or a global install) | Where changes made on the Settings page are kept. Mode 600; not in the managed directory. It must be somewhere the service can write. |
432
432
 
@@ -442,7 +442,7 @@ How you set them is the only thing that differs:
442
442
 
443
443
  ## Scan uploads with ICAP
444
444
 
445
- **Settings** in the toolbar points Pigeonhole at an ICAP server (MetaDefender
445
+ **Settings** in the toolbar points Pigeondrop at an ICAP server (MetaDefender
446
446
  ICAP Server, c-icap with ClamAV, anything that speaks ICAP `REQMOD`). Each
447
447
  upload is sent there once it has arrived, and appears in the listing only if
448
448
  the scanner passes it. A blocked file is deleted and the upload card shows the
@@ -463,7 +463,7 @@ with no licence. It uses the values in the form, so a server can be checked
463
463
  before saving.
464
464
 
465
465
  If the certificate is self-signed or issued by a private CA, either start
466
- Pigeonhole with `NODE_EXTRA_CA_CERTS` set to that CA's PEM file, or turn off
466
+ Pigeondrop with `NODE_EXTRA_CA_CERTS` set to that CA's PEM file, or turn off
467
467
  **Verify the certificate**. Use the name the certificate was issued for as
468
468
  Server.
469
469
 
@@ -479,7 +479,7 @@ means, are not.
479
479
  ## There is no authentication
480
480
 
481
481
  Read that again before you expose it. Anyone who can reach it can upload,
482
- rename and delete. Pigeonhole assumes it sits on a network you trust — a home
482
+ rename and delete. Pigeondrop assumes it sits on a network you trust — a home
483
483
  or office LAN, a VPN, a lab segment.
484
484
 
485
485
  That is a scope decision, not an oversight. Authentication done badly is worse
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "pigeondrop",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "description": "A small self-hosted file manager with no dependencies. Drag-and-drop upload with progress, browse, rename, delete, download files or folders as zip.",
5
5
  "type": "module",
6
6
  "main": "server.js",
7
- "bin": { "pigeondrop": "./server.js", "pigeonhole": "./server.js" },
7
+ "bin": { "pigeondrop": "./server.js" },
8
8
  "scripts": { "start": "node server.js" },
9
9
  "engines": { "node": ">=20" },
10
10
  "license": "MIT",
11
11
  "keywords": ["file-manager", "upload", "self-hosted", "zero-dependency", "http"],
12
- "repository": { "type": "git", "url": "git+https://github.com/cventour/pigeonhole.git" },
12
+ "repository": { "type": "git", "url": "git+https://github.com/cventour/pigeondrop.git" },
13
13
  "files": ["server.js", "public", "icon.svg", "README.md", "LICENSE", "CHANGELOG.md"]
14
14
  }
package/public/index.html CHANGED
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>Pigeonhole</title>
6
+ <title>Pigeondrop</title>
7
7
  <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'> <rect x='2' y='2' width='60' height='60' rx='13' fill='%232d3339'/> <g fill='%230f1113'> <path d='M8 28V17a7 7 0 0 1 14 0v11z'/> <path d='M25 28V17a7 7 0 0 1 14 0v11z'/> <path d='M8 51V40a7 7 0 0 1 14 0v11z'/> <path d='M25 51V40a7 7 0 0 1 14 0v11z'/> <path d='M42 51V40a7 7 0 0 1 14 0v11z'/> </g> <path d='M42 28V17a7 7 0 0 1 14 0v11z' fill='%23ff6a1f'/> <path d='M44.5 19.5h9v6.5h-9z' fill='%231b1e21'/> <path d='M44.5 19.5l4.5 3.5 4.5-3.5' fill='none' stroke='%23ff6a1f' stroke-width='1.3'/> <path d='M6 30h52M6 53h52' stroke='%23c8ced4' stroke-width='2.5' stroke-linecap='round'/> </svg>">
8
8
  <link rel="preconnect" href="https://fonts.googleapis.com">
9
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
@@ -45,7 +45,7 @@ body{margin:0;background:var(--panel);color:var(--ink);font-family:var(--sans);
45
45
 
46
46
  .wrap{max-width:1080px;margin:0 auto;padding:0 var(--gut)}
47
47
  header{padding:clamp(28px,5vw,48px) 0 20px}
48
- h1{font-family:var(--narrow);font-weight:700;margin:0;text-transform:uppercase;
48
+ h1{font-family:var(--narrow);font-weight:700;margin:0;
49
49
  font-size:clamp(1.6rem,4vw,2.5rem);line-height:1;letter-spacing:-.02em}
50
50
  .brand{display:flex;align-items:center;gap:14px}
51
51
  .brand svg{width:clamp(36px,6vw,48px);height:auto;flex:none}
@@ -103,6 +103,10 @@ button.act.primary:hover{background:var(--signal);color:#0d0f11;border-color:var
103
103
  .q-retry:hover{background:var(--signal);color:#0d0f11;border-color:var(--signal)}
104
104
 
105
105
  /* --- listing --- */
106
+ .search{display:block;width:100%;padding:10px 12px;margin:0 0 -1px;
107
+ font-family:var(--mono);font-size:.85rem;color:var(--ink);
108
+ background:var(--panel);border:1px solid var(--rule)}
109
+ .search:focus{outline:none;border-color:var(--signal)}
106
110
  .tablewrap{border:1px solid var(--rule);background:var(--panel-lo);overflow-x:auto}
107
111
  table{width:100%;border-collapse:collapse;min-width:520px}
108
112
  th{text-align:left;font-family:var(--mono);font-weight:500;font-size:.75rem;
@@ -240,7 +244,7 @@ footer a{color:var(--steel-dim)}
240
244
  <header>
241
245
  <div class="brand">
242
246
  <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"> <rect x="2" y="2" width="60" height="60" rx="13" fill="#2d3339"/> <g fill="#0f1113"> <path d="M8 28V17a7 7 0 0 1 14 0v11z"/> <path d="M25 28V17a7 7 0 0 1 14 0v11z"/> <path d="M8 51V40a7 7 0 0 1 14 0v11z"/> <path d="M25 51V40a7 7 0 0 1 14 0v11z"/> <path d="M42 51V40a7 7 0 0 1 14 0v11z"/> </g> <path d="M42 28V17a7 7 0 0 1 14 0v11z" fill="#ff6a1f"/> <path d="M44.5 19.5h9v6.5h-9z" fill="#1b1e21"/> <path d="M44.5 19.5l4.5 3.5 4.5-3.5" fill="none" stroke="#ff6a1f" stroke-width="1.3"/> <path d="M6 30h52M6 53h52" stroke="#c8ced4" stroke-width="2.5" stroke-linecap="round"/> </svg>
243
- <h1 id="title">Pigeonhole</h1>
247
+ <h1 id="title">Pigeondrop</h1>
244
248
  </div>
245
249
  <p class="sub" id="subtitle">Drop files in. Take files out.</p>
246
250
  </header>
@@ -262,6 +266,7 @@ footer a{color:var(--steel-dim)}
262
266
  <div class="queue" id="queue"></div>
263
267
  <p class="msg" id="msg" role="status"></p>
264
268
 
269
+ <input type="search" class="search" id="search" placeholder="Filter by name" aria-label="Filter files by name" autocomplete="off" spellcheck="false">
265
270
  <div class="tablewrap">
266
271
  <table>
267
272
  <thead><tr>
@@ -278,7 +283,7 @@ footer a{color:var(--steel-dim)}
278
283
  <span id="rootname"></span>
279
284
  <span id="scanstate"></span>
280
285
  <span>no authentication — trusted networks only</span>
281
- <span><a href="https://github.com/cventour/pigeonhole">pigeonhole</a></span>
286
+ <span><a href="https://github.com/cventour/pigeondrop">pigeondrop</a></span>
282
287
  </footer>
283
288
  </div>
284
289
 
@@ -462,7 +467,7 @@ function drawCrumbs() {
462
467
  $('drop-target').textContent = cwd;
463
468
  }
464
469
 
465
- async function go(dir) { cwd = dir || '/'; selected.clear(); await refresh(); }
470
+ async function go(dir) { cwd = dir || '/'; selected.clear(); $('search').value = ''; await refresh(); }
466
471
 
467
472
  // --- selection and download ------------------------------------------------
468
473
  // Paths of the ticked rows in this folder. A single file downloads as itself;
@@ -566,7 +571,8 @@ async function asJson(r) {
566
571
  // person scanning for a folder expect. Folders have no size, so under Size
567
572
  // they fall back to name order. Remembered per browser, not per folder.
568
573
  let sort = { key: 'name', dir: 1 };
569
- try { Object.assign(sort, JSON.parse(localStorage.getItem('pigeonhole.sort')) || {}); } catch {}
574
+ // Falls back to the key used before the rename to Pigeondrop.
575
+ try { Object.assign(sort, JSON.parse(localStorage.getItem('pigeondrop.sort') || localStorage.getItem('pigeonhole.sort')) || {}); } catch {}
570
576
  // A first click on Size or Modified wants the biggest or newest first.
571
577
  const FIRST_DIR = { name: 1, size: -1, mtime: -1 };
572
578
  let items = [];
@@ -590,11 +596,13 @@ for (const th of document.querySelectorAll('th[data-k]')) {
590
596
  th.querySelector('button').onclick = () => {
591
597
  const k = th.dataset.k;
592
598
  sort = k === sort.key ? { key: k, dir: -sort.dir } : { key: k, dir: FIRST_DIR[k] };
593
- try { localStorage.setItem('pigeonhole.sort', JSON.stringify(sort)); } catch {}
599
+ try { localStorage.setItem('pigeondrop.sort', JSON.stringify(sort)); } catch {}
594
600
  render();
595
601
  };
596
602
  }
597
603
 
604
+ $('search').oninput = () => render();
605
+
598
606
  async function refresh() {
599
607
  drawCrumbs();
600
608
  let data;
@@ -630,7 +638,15 @@ function render() {
630
638
  return;
631
639
  }
632
640
 
633
- for (const it of sorted(items)) {
641
+ const q = $('search').value.trim().toLowerCase();
642
+ const list = q ? items.filter(it => it.name.toLowerCase().includes(q)) : items;
643
+ if (q && !list.length) {
644
+ const tr = document.createElement('tr');
645
+ tr.innerHTML = '<td colspan="5" class="empty">No names match.</td>';
646
+ rows.append(tr);
647
+ }
648
+
649
+ for (const it of sorted(list)) {
634
650
  const full = (cwd === '/' ? '' : cwd) + '/' + it.name;
635
651
  const tr = document.createElement('tr');
636
652
  tr.innerHTML = `
package/server.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // Pigeonhole — a small self-hosted file manager.
2
+ // Pigeondrop — a small self-hosted file manager.
3
3
  //
4
4
  // Node standard library only. Uploads arrive as a raw PUT body rather than
5
5
  // multipart/form-data, which removes the only reason this would need a
@@ -40,7 +40,7 @@ const SETTINGS_FILE = path.resolve(process.env.SETTINGS_FILE
40
40
  || path.join(process.env.STATE_DIRECTORY?.split(':')[0] || CODE_DIR, 'settings.json'));
41
41
 
42
42
  // Branding is configuration, not code: one binary, any deployment.
43
- const TITLE = process.env.REPO_TITLE || 'Pigeonhole';
43
+ const TITLE = process.env.REPO_TITLE || 'Pigeondrop';
44
44
  const SUBTITLE = process.env.REPO_SUBTITLE || 'Drop files in. Take files out.';
45
45
 
46
46
  // --- path safety -----------------------------------------------------------
@@ -284,7 +284,7 @@ async function icapScan(source, name, cfg) {
284
284
  await once(sock, icapReady(cfg), { signal: ac.signal });
285
285
  const put = async (b) => { if (!sock.write(b)) await once(sock, 'drain', { signal: ac.signal }); };
286
286
 
287
- const http = `PUT /${encodeURIComponent(name)} HTTP/1.1\r\nHost: pigeonhole\r\n` +
287
+ const http = `PUT /${encodeURIComponent(name)} HTTP/1.1\r\nHost: pigeondrop\r\n` +
288
288
  `Content-Disposition: attachment; filename="${name.replace(/[^\x20-\x7E]|["\\]/g, '_')}"\r\n` +
289
289
  `Content-Type: application/octet-stream\r\nContent-Length: ${source.size}\r\n\r\n`;
290
290
  await put(`REQMOD icap://${cfg.host}:${cfg.port}/${cfg.service} ICAP/1.0\r\n` +
@@ -337,9 +337,9 @@ async function icapTest(cfg) {
337
337
  });
338
338
  if (options.error) return { ok: false, error: options.error };
339
339
 
340
- const sample = Buffer.from('pigeonhole connection test\n');
340
+ const sample = Buffer.from('pigeondrop connection test\n');
341
341
  const scan = await icapScan({ size: sample.length, stream: () => Readable.from([sample]) },
342
- 'pigeonhole-test.txt', { ...cfg, timeout: 20 });
342
+ 'pigeondrop-test.txt', { ...cfg, timeout: 20 });
343
343
  const ms = Date.now() - t0;
344
344
  if (scan.verdict === 'clean') return { ok: true, server: options.server, methods: options.methods, ms };
345
345
  return {