# acequia-put: One-Keystroke WebDAV Upload from VSCode ## Problem When working inside a single domain folder, the VSCode WebDAV extension could upload the current file with a keybinding. It read `webdav.json` from the workspace root to find credentials and the remote URL. After switching to the top-level `sites/` workspace (30+ domain folders), the extension broke. It looked for `webdav.json` at `sites/`, not inside the domain subfolder. Editing a file and publishing it went from two keystrokes to a manual curl command or asking Claude. ## Solution A Node.js script that resolves the domain and credentials from the file path, plus a VSCode task and keybinding. **Ctrl+Shift+U** uploads the current file. No context switch, no typing! ## How It Works ``` file path → extract domain folder (first dir under sites/) → compute remote path (everything after the domain folder) → look up auth (.site file or .credentials/manifest.json) → curl PUT → print status code ``` The script takes about 1 second. Most of that is network latency. Credential resolution is instant (local file reads). ## Files ### `.vscode/webdav-put.js` The upload script. Zero npm dependencies, uses only Node.js built-ins (`fs`, `path`, `child_process`). **Auth resolution order:** 1. Check `{domain}/.site` for `auth: token` + `credential:` field. If found, read the token from `.credentials/{credential}.txt` and use Bearer auth on port 443. 2. Check `.credentials/manifest.json` for the domain name. If `type: basic`, use netrc auth with the host and port from the manifest entry. 3. Fallback: use the domain folder name as the host, port 2078, netrc auth. **Output:** - Green `201` or `204` on success, with the host and remote path - Red status code on failure, with curl stderr ### `.vscode/tasks.json` VSCode task definition that runs the script with `${file}` as the argument. ```json { "label": "WebDAV PUT", "type": "shell", "command": "node", "args": ["${workspaceFolder}/.vscode/webdav-put.js", "${file}"], "presentation": { "reveal": "silent", "panel": "shared", "showReuseMessage": false, "clear": true } } ``` `reveal: silent` keeps the editor focused. The result appears in the terminal panel without stealing your cursor. ### VSCode keybinding Added to `~\AppData\Roaming\Code\User\keybindings.json`: ```json { "key": "ctrl+shift+u", "command": "workbench.action.tasks.runTask", "args": "WebDAV PUT" } ``` ## Auth Mapping | Domain folder | Auth method | Host | Port | |---|---|---|---| | waldo.webdav.acequia.io | Bearer token | waldo.webdav.acequia.io | 443 | | stigmergic.webdav.acequia.io | Bearer token | stigmergic.webdav.acequia.io | 443 | | gsd.acequia.io | Bearer token | gsd.webdav.acequia.io | 443 | | simtable.acequia.io | Bearer token | simtable.acequia.io | 443 | | guerin.acequia.io | netrc (basic) | guerin.acequia.io | 2078 | | All other HostGo domains | netrc (basic) | {domain} | 2078 | Token-auth sites are identified by `.site` files containing `auth: token`. All others fall through to netrc. ## Discovery: Nephele Port Change During testing, we confirmed that all nephele WebDAV servers (waldo, stigmergic, gsd, simtable) now respond on port 443 (standard HTTPS). The previous port 3334 still works on some servers but 443 is now the standard. Updated all `.site` files accordingly. ## Discovery: PROPFIND Depth Header While testing waldo, PROPFIND requests without a `Depth` header appeared to hang. The cause: PROPFIND defaults to `Depth: infinity` when no header is sent, which recursively enumerates every file on the server. On a large file tree, this takes so long it looks like a timeout. **Rule:** Always include `Depth: 0` (single resource) or `Depth: 1` (resource + immediate children) on PROPFIND. Never omit it. This was added to: - `sites/.ai/remote-access.md` - `sites/CLAUDE.md` - Claude persistent memory ## Windows Toast Notifications With `reveal: silent`, the terminal result is easy to miss. The script triggers a Windows balloon notification after every upload so you get visual confirmation without losing editor focus. **How it works:** After curl completes, the script spawns a PowerShell one-liner that uses `System.Windows.Forms.NotifyIcon` to show a balloon tip near the system tray. No external modules needed; `System.Windows.Forms` is built into .NET on every Windows machine. **On success:** A balloon titled "WebDAV PUT" appears for ~3 seconds showing `201 guerin.acequia.io/notes/file.md` with an info icon. **On failure:** A balloon titled "WebDAV PUT FAILED" appears with the error status code and an error icon. The notification adds about 3.5 seconds to the script (the `Start-Sleep` that keeps the NotifyIcon alive long enough to display). The upload itself finishes instantly; the sleep only holds the PowerShell process, not Node. The terminal output prints immediately regardless. **Key code:** ```js const psScript = `Add-Type -AssemblyName System.Windows.Forms;` + `$n = New-Object System.Windows.Forms.NotifyIcon;` + `$n.Icon = [System.Drawing.SystemIcons]::${icon};` + `$n.Visible = $true;` + `$n.ShowBalloonTip(3000, '${title}', '${msg}', '${icon}');` + `Start-Sleep -Milliseconds 3500;` + `$n.Dispose()`; spawnSync('powershell', ['-Command', psScript], { stdio: 'ignore' }); ``` **Why `spawnSync` and not `spawn`?** The VSCode task process needs to stay alive until the notification displays. If the Node process exits immediately, PowerShell gets killed and the balloon never shows. Using `spawnSync` blocks for 3.5 seconds, which is fine because the upload is already done. **Why not the modern Windows Toast API?** The `Windows.UI.Notifications` API requires an app identity (AppUserModelID) and is more complex to set up from a script. The legacy `NotifyIcon.ShowBalloonTip` works everywhere with zero setup. ## Adding New Sites The script automatically picks up new sites. To add one: 1. Create the domain folder under `sites/` 2. For token auth: create a `.site` file with `auth: token` and `credential: name`, plus `.credentials/name.txt` 3. For basic auth: add the domain to `.credentials/manifest.json` (run `refresh.ps1`) and `_netrc` No changes to the script needed. ## Date 2026-02-20