@deltaroboticsinc/protoboard 0.1.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,19 @@
1
+ Copyright (c) 2026 Delta Robotics Inc. All rights reserved.
2
+
3
+ This software is the proprietary property of Delta Robotics Inc.
4
+ Use, copying, modification, distribution, sublicensing, or any other
5
+ exploitation of this software, in whole or in part, is prohibited
6
+ without the prior written permission of Delta Robotics Inc.
7
+
8
+ This software is published to the npm registry solely to make
9
+ installation convenient for Delta Robotics employees and explicitly
10
+ authorized collaborators. Public availability on the registry does
11
+ not constitute a grant of any rights under this notice.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
14
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
15
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
16
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
17
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
18
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
19
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # protoboard-desktop
2
+
3
+ Downloadable, self-hostable build of Protoboard for desktop or sandbox use.
4
+
5
+ Until the full desktop build lands, this repo ships **`@deltaroboticsinc/protoboard`** — a one-command installer that registers the `.protoboard` file type with its logo and a double-click handler on Windows, macOS, and Linux.
6
+
7
+ ## Quickstart
8
+
9
+ ```sh
10
+ npx @deltaroboticsinc/protoboard install
11
+ ```
12
+
13
+ That's it. Save a `.protoboard` file anywhere (Desktop, Downloads, wherever) and it will show the Protoboard mark. Double-clicking opens the board in your default browser at `https://alpha.protoboard.xyz/open?boardId=...`.
14
+
15
+ To remove:
16
+
17
+ ```sh
18
+ npx @deltaroboticsinc/protoboard uninstall
19
+ ```
20
+
21
+ No admin/root required. All registration is per-user.
22
+
23
+ ## How it works
24
+
25
+ - **Windows** — writes per-user keys under `HKCU:\Software\Classes`; copies `ProtoboardFile.ico` and a launcher `Open-Protoboard.ps1` into `%LOCALAPPDATA%\Protoboard\`. Double-click runs the launcher, which reads `board.metadata.id` from the JSON and opens the browser.
26
+ - **macOS** — installs a tiny `Protoboard.app` bundle into `~/Applications/` declaring the `xyz.protoboard.file` UTI; `iconutil` assembles `.icns` from the bundled PNGs; LaunchServices picks it up via `lsregister`. If `duti` is installed, also sets as the default handler.
27
+ - **Linux** — installs an XDG MIME definition, a `.desktop` entry, and mimetype icons via `xdg-icon-resource`. `xdg-mime default` makes it the default handler.
28
+
29
+ The `.protoboard` format itself doesn't change — it's still plain JSON with a custom extension.
30
+
31
+ ## For maintainers
32
+
33
+ ```sh
34
+ pnpm install
35
+ pnpm run generate-icons # rebuilds .ico + PNGs from assets/PB Logo N-200.svg
36
+ ```
37
+
38
+ Generated icons (`assets/ProtoboardFile.ico`, `assets/icons/*.png`) are checked into the repo so install-time has no native build step. `.icns` is assembled on the user's mac at install time via Apple's `iconutil`, so it doesn't need to be checked in.
39
+
40
+ ### Publishing
41
+
42
+ Tag a version and push:
43
+
44
+ ```sh
45
+ git tag v0.1.0 && git push origin v0.1.0
46
+ ```
47
+
48
+ The `Publish to npm` workflow uses the repo's `NPM_TOKEN` secret to publish to `@deltaroboticsinc/protoboard` on npmjs.com. First-time setup:
49
+
50
+ 1. NPM org `deltaroboticsinc` already exists.
51
+ 2. Generate an automation token at npmjs.com and add it as `NPM_TOKEN` in this repo's GitHub secrets.
52
+
53
+ ### Repo layout
54
+
55
+ ```
56
+ assets/ # SVG source + generated icons (committed)
57
+ installers/
58
+ windows/ # install.ps1, uninstall.ps1, Open-Protoboard.ps1
59
+ macos/ # install.sh, uninstall.sh, Protoboard.app/
60
+ linux/ # install.sh, uninstall.sh, .xml, .desktop, launcher
61
+ scripts/generate-icons.mjs
62
+ cli.js # the published bin: detects OS and dispatches
63
+ ```
64
+
65
+ ## Troubleshooting
66
+
67
+ - **Windows: icon doesn't update in Explorer.** Sign out/in, or run `ie4uinit.exe -show`. The installer triggers `SHChangeNotify` but Explorer occasionally caches aggressively.
68
+ - **macOS: Finder still shows the JSON icon.** Right-click any `.protoboard` file → Get Info → Open with → Protoboard → Change All. Or install `duti` (`brew install duti`) and re-run the installer.
69
+ - **Linux: handler doesn't open.** Confirm `~/.local/bin` is on `PATH`, or check `xdg-mime query default application/x-protoboard+json` returns `protoboard-open.desktop`.
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="500" zoomAndPan="magnify" viewBox="0 0 375 374.999991" height="500" preserveAspectRatio="xMidYMid meet" version="1.0"><defs><g/><clipPath id="9012e781b9"><rect x="0" width="303" y="0" height="375"/></clipPath></defs><g transform="matrix(1, 0, 0, 1, 36, 0)"><g clip-path="url(#9012e781b9)"><g fill="#e5e5e5" fill-opacity="1"><g transform="translate(0.238764, 349.173976)"><g><path d="M 93.671875 -137.90625 L 204.75 -249 C 206 -250.25 206.625 -251.816406 206.625 -253.703125 C 206.625 -255.585938 206 -257.15625 204.75 -258.40625 L 180.75 -281.9375 C 177.601562 -285.082031 173.679688 -286.65625 168.984375 -286.65625 L 132.734375 -286.65625 C 128.023438 -286.65625 124.101562 -285.082031 120.96875 -281.9375 L 93.671875 -254.640625 C 90.523438 -251.503906 88.953125 -247.582031 88.953125 -242.875 L 88.953125 -140.265625 C 88.953125 -137.753906 89.582031 -136.5 90.84375 -136.5 C 91.78125 -136.5 92.722656 -136.96875 93.671875 -137.90625 Z M 132.734375 -42.828125 L 168.984375 -42.828125 C 173.679688 -42.828125 177.601562 -44.398438 180.75 -47.546875 L 208.046875 -74.84375 C 211.179688 -77.976562 212.75 -81.898438 212.75 -86.609375 L 212.75 -189.21875 C 212.75 -191.726562 212.125 -192.984375 210.875 -192.984375 C 209.925781 -192.984375 208.984375 -192.507812 208.046875 -191.5625 L 96.96875 -80.484375 C 95.394531 -78.921875 94.609375 -77.351562 94.609375 -75.78125 C 94.609375 -74.207031 95.394531 -72.640625 96.96875 -71.078125 L 120.96875 -47.546875 C 124.101562 -44.398438 128.023438 -42.828125 132.734375 -42.828125 Z M 52.25 -273.9375 L 101.671875 -322.890625 C 105.429688 -327.285156 110.453125 -329.484375 116.734375 -329.484375 L 184.984375 -329.484375 C 190.941406 -329.484375 195.960938 -327.441406 200.046875 -323.359375 L 249 -273.9375 C 253.382812 -270.175781 255.578125 -265.15625 255.578125 -258.875 L 255.578125 -70.609375 C 255.578125 -64.640625 253.382812 -59.460938 249 -55.078125 L 200.046875 -6.125 C 195.960938 -2.039062 190.941406 0 184.984375 0 L 116.734375 0 C 110.453125 0 105.429688 -2.195312 101.671875 -6.59375 L 52.25 -55.546875 C 48.164062 -59.617188 46.125 -64.640625 46.125 -70.609375 L 46.125 -258.875 C 46.125 -264.84375 48.164062 -269.863281 52.25 -273.9375 Z M 52.25 -273.9375 "/></g></g></g></g></g></svg>
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/cli.js ADDED
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ const { spawn } = require('node:child_process');
3
+ const path = require('node:path');
4
+ const fs = require('node:fs');
5
+
6
+ const VALID = new Set(['install', 'uninstall']);
7
+ const HELP = `@deltaroboticsinc/protoboard
8
+
9
+ Register the .protoboard file type with its logo and a double-click
10
+ handler that opens the board in your default browser.
11
+
12
+ Usage:
13
+ npx @deltaroboticsinc/protoboard <command>
14
+
15
+ Commands:
16
+ install Register .protoboard icon and open handler for the current user.
17
+ uninstall Remove the registration.
18
+ --help Show this message.
19
+ --version Print the package version.
20
+
21
+ Per-user install; no admin/root required.`;
22
+
23
+ function pkgVersion() {
24
+ return require('./package.json').version;
25
+ }
26
+
27
+ function pickInstaller(cmd) {
28
+ const root = __dirname;
29
+ if (process.platform === 'win32') {
30
+ const script = path.join(root, 'installers', 'windows', cmd === 'install' ? 'install.ps1' : 'uninstall.ps1');
31
+ return {
32
+ exe: 'powershell.exe',
33
+ args: ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', script],
34
+ };
35
+ }
36
+ if (process.platform === 'darwin') {
37
+ const script = path.join(root, 'installers', 'macos', cmd === 'install' ? 'install.sh' : 'uninstall.sh');
38
+ return { exe: 'bash', args: [script] };
39
+ }
40
+ if (process.platform === 'linux') {
41
+ const script = path.join(root, 'installers', 'linux', cmd === 'install' ? 'install.sh' : 'uninstall.sh');
42
+ return { exe: 'bash', args: [script] };
43
+ }
44
+ return null;
45
+ }
46
+
47
+ function main() {
48
+ const arg = process.argv[2];
49
+ if (!arg || arg === '--help' || arg === '-h' || arg === 'help') {
50
+ console.log(HELP);
51
+ process.exit(0);
52
+ }
53
+ if (arg === '--version' || arg === '-v') {
54
+ console.log(pkgVersion());
55
+ process.exit(0);
56
+ }
57
+ if (!VALID.has(arg)) {
58
+ console.error(`Unknown command: ${arg}\n\n${HELP}`);
59
+ process.exit(2);
60
+ }
61
+ const picked = pickInstaller(arg);
62
+ if (!picked) {
63
+ console.error(`Unsupported platform: ${process.platform}. Supported: win32, darwin, linux.`);
64
+ process.exit(2);
65
+ }
66
+ if (!fs.existsSync(picked.args[picked.args.length - 1])) {
67
+ console.error(`Installer script missing: ${picked.args[picked.args.length - 1]}`);
68
+ process.exit(2);
69
+ }
70
+ const child = spawn(picked.exe, picked.args, { stdio: 'inherit' });
71
+ child.on('exit', (code) => process.exit(code ?? 1));
72
+ child.on('error', (err) => {
73
+ console.error(`Failed to launch installer: ${err.message}`);
74
+ process.exit(1);
75
+ });
76
+ }
77
+
78
+ main();
@@ -0,0 +1,53 @@
1
+ #!/bin/bash
2
+ # Register the .protoboard file type for the current user on Linux.
3
+ # Per-user XDG paths; no sudo. Uses shared-mime-info + .desktop + xdg-icon-resource.
4
+
5
+ set -euo pipefail
6
+
7
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ repo_root="$(cd "$script_dir/../.." && pwd)"
9
+ icons_src="$repo_root/assets/icons"
10
+
11
+ if [[ ! -f "$icons_src/256.png" ]]; then
12
+ echo "Missing PNGs in $icons_src. Run 'pnpm run generate-icons' first." >&2
13
+ exit 1
14
+ fi
15
+
16
+ mime_dir="$HOME/.local/share/mime/packages"
17
+ apps_dir="$HOME/.local/share/applications"
18
+ bin_dir="$HOME/.local/bin"
19
+
20
+ mkdir -p "$mime_dir" "$apps_dir" "$bin_dir"
21
+
22
+ # 1. MIME definition (links .protoboard glob to application/x-protoboard+json).
23
+ cp "$script_dir/protoboard.xml" "$mime_dir/protoboard.xml"
24
+
25
+ # 2. .desktop entry pointing at protoboard-open.
26
+ cp "$script_dir/protoboard-open.desktop" "$apps_dir/protoboard-open.desktop"
27
+
28
+ # 3. Launcher script.
29
+ cp "$script_dir/protoboard-open" "$bin_dir/protoboard-open"
30
+ chmod +x "$bin_dir/protoboard-open"
31
+
32
+ # 4. Install mimetype icons at each size.
33
+ for size in 16 32 48 64 128 256 512; do
34
+ png="$icons_src/$size.png"
35
+ if [[ -f "$png" ]]; then
36
+ xdg-icon-resource install --noupdate --context mimetypes --size "$size" "$png" application-x-protoboard+json
37
+ fi
38
+ done
39
+ xdg-icon-resource forceupdate
40
+
41
+ # 5. Refresh MIME and desktop databases.
42
+ update-mime-database "$HOME/.local/share/mime"
43
+ update-desktop-database "$apps_dir" 2>/dev/null || true
44
+ xdg-mime default protoboard-open.desktop application/x-protoboard+json
45
+
46
+ case ":$PATH:" in
47
+ *":$bin_dir:"*) ;;
48
+ *) echo "Note: $bin_dir is not on PATH. The .desktop entry uses an absolute path so the handler will still fire, but you may want to add it for terminal use." ;;
49
+ esac
50
+
51
+ echo "Protoboard file association installed."
52
+ echo " Handler: $bin_dir/protoboard-open"
53
+ echo " MIME: application/x-protoboard+json (glob *.protoboard)"
@@ -0,0 +1,37 @@
1
+ #!/bin/bash
2
+ # Launcher invoked when a .protoboard file is opened on Linux.
3
+ # Builds https://alpha.protoboard.xyz/board/<userId>/<boardId> when both
4
+ # ids are present; otherwise falls back to https://alpha.protoboard.xyz/.
5
+
6
+ set -u
7
+
8
+ APP_ROOT="https://alpha.protoboard.xyz"
9
+
10
+ file="${1:-}"
11
+ if [[ -z "$file" || ! -f "$file" ]]; then
12
+ xdg-open "$APP_ROOT" >/dev/null 2>&1 &
13
+ exit 0
14
+ fi
15
+
16
+ read -r board_id user_id <<EOF
17
+ $(python3 - "$file" <<'PY' 2>/dev/null || true
18
+ import json, sys
19
+ try:
20
+ with open(sys.argv[1], "r", encoding="utf-8") as fh:
21
+ d = json.load(fh)
22
+ board = d.get("board") or {}
23
+ md = board.get("metadata") or {}
24
+ bid = md.get("id") or ""
25
+ uid = md.get("userId") or board.get("userId") or ""
26
+ print(bid, uid)
27
+ except Exception:
28
+ print("", "")
29
+ PY
30
+ )
31
+ EOF
32
+
33
+ if [[ -n "${board_id:-}" && -n "${user_id:-}" ]]; then
34
+ xdg-open "$APP_ROOT/board/$user_id/$board_id" >/dev/null 2>&1 &
35
+ else
36
+ xdg-open "$APP_ROOT" >/dev/null 2>&1 &
37
+ fi
@@ -0,0 +1,10 @@
1
+ [Desktop Entry]
2
+ Type=Application
3
+ Name=Protoboard
4
+ Comment=Open Protoboard files in the browser
5
+ Exec=protoboard-open %f
6
+ Terminal=false
7
+ NoDisplay=true
8
+ MimeType=application/x-protoboard+json;
9
+ Icon=application-x-protoboard+json
10
+ Categories=Development;Engineering;
@@ -0,0 +1,9 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
3
+ <mime-type type="application/x-protoboard+json">
4
+ <comment>Protoboard File</comment>
5
+ <sub-class-of type="application/json"/>
6
+ <glob pattern="*.protoboard"/>
7
+ <icon name="application-x-protoboard+json"/>
8
+ </mime-type>
9
+ </mime-info>
@@ -0,0 +1,22 @@
1
+ #!/bin/bash
2
+ # Remove the .protoboard registration for the current user on Linux.
3
+
4
+ set -u
5
+
6
+ mime_file="$HOME/.local/share/mime/packages/protoboard.xml"
7
+ desktop_file="$HOME/.local/share/applications/protoboard-open.desktop"
8
+ bin_file="$HOME/.local/bin/protoboard-open"
9
+
10
+ [[ -f "$mime_file" ]] && rm -f "$mime_file"
11
+ [[ -f "$desktop_file" ]] && rm -f "$desktop_file"
12
+ [[ -f "$bin_file" ]] && rm -f "$bin_file"
13
+
14
+ for size in 16 32 48 64 128 256 512; do
15
+ xdg-icon-resource uninstall --noupdate --context mimetypes --size "$size" application-x-protoboard+json 2>/dev/null || true
16
+ done
17
+ xdg-icon-resource forceupdate 2>/dev/null || true
18
+
19
+ update-mime-database "$HOME/.local/share/mime" 2>/dev/null || true
20
+ update-desktop-database "$HOME/.local/share/applications" 2>/dev/null || true
21
+
22
+ echo "Protoboard file association removed."
@@ -0,0 +1,72 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>CFBundleIdentifier</key>
6
+ <string>xyz.protoboard.opener</string>
7
+ <key>CFBundleName</key>
8
+ <string>Protoboard</string>
9
+ <key>CFBundleDisplayName</key>
10
+ <string>Protoboard</string>
11
+ <key>CFBundleVersion</key>
12
+ <string>0.1.0</string>
13
+ <key>CFBundleShortVersionString</key>
14
+ <string>0.1.0</string>
15
+ <key>CFBundlePackageType</key>
16
+ <string>APPL</string>
17
+ <key>CFBundleSignature</key>
18
+ <string>????</string>
19
+ <key>CFBundleExecutable</key>
20
+ <string>protoboard-open</string>
21
+ <key>CFBundleIconFile</key>
22
+ <string>ProtoboardFile</string>
23
+ <key>LSMinimumSystemVersion</key>
24
+ <string>10.13</string>
25
+ <key>LSUIElement</key>
26
+ <true/>
27
+ <key>CFBundleDocumentTypes</key>
28
+ <array>
29
+ <dict>
30
+ <key>CFBundleTypeName</key>
31
+ <string>Protoboard File</string>
32
+ <key>CFBundleTypeRole</key>
33
+ <string>Viewer</string>
34
+ <key>CFBundleTypeIconFile</key>
35
+ <string>ProtoboardFile</string>
36
+ <key>LSItemContentTypes</key>
37
+ <array>
38
+ <string>xyz.protoboard.file</string>
39
+ </array>
40
+ <key>LSHandlerRank</key>
41
+ <string>Owner</string>
42
+ </dict>
43
+ </array>
44
+ <key>UTExportedTypeDeclarations</key>
45
+ <array>
46
+ <dict>
47
+ <key>UTTypeIdentifier</key>
48
+ <string>xyz.protoboard.file</string>
49
+ <key>UTTypeDescription</key>
50
+ <string>Protoboard File</string>
51
+ <key>UTTypeConformsTo</key>
52
+ <array>
53
+ <string>public.json</string>
54
+ <string>public.data</string>
55
+ </array>
56
+ <key>UTTypeIconFile</key>
57
+ <string>ProtoboardFile</string>
58
+ <key>UTTypeTagSpecification</key>
59
+ <dict>
60
+ <key>public.filename-extension</key>
61
+ <array>
62
+ <string>protoboard</string>
63
+ </array>
64
+ <key>public.mime-type</key>
65
+ <array>
66
+ <string>application/x-protoboard+json</string>
67
+ </array>
68
+ </dict>
69
+ </dict>
70
+ </array>
71
+ </dict>
72
+ </plist>
@@ -0,0 +1,37 @@
1
+ #!/bin/bash
2
+ # Launcher invoked by LaunchServices when a .protoboard file is opened.
3
+ # Builds https://alpha.protoboard.xyz/board/<userId>/<boardId> when both
4
+ # ids are present; otherwise falls back to https://alpha.protoboard.xyz/.
5
+
6
+ set -u
7
+
8
+ APP_ROOT="https://alpha.protoboard.xyz"
9
+
10
+ file="${1:-}"
11
+ if [[ -z "$file" || ! -f "$file" ]]; then
12
+ open "$APP_ROOT"
13
+ exit 0
14
+ fi
15
+
16
+ read -r board_id user_id <<EOF
17
+ $(python3 - "$file" <<'PY' 2>/dev/null || true
18
+ import json, sys
19
+ try:
20
+ with open(sys.argv[1], "r", encoding="utf-8") as fh:
21
+ d = json.load(fh)
22
+ board = d.get("board") or {}
23
+ md = board.get("metadata") or {}
24
+ bid = md.get("id") or ""
25
+ uid = md.get("userId") or board.get("userId") or ""
26
+ print(bid, uid)
27
+ except Exception:
28
+ print("", "")
29
+ PY
30
+ )
31
+ EOF
32
+
33
+ if [[ -n "${board_id:-}" && -n "${user_id:-}" ]]; then
34
+ open "$APP_ROOT/board/$user_id/$board_id"
35
+ else
36
+ open "$APP_ROOT"
37
+ fi
@@ -0,0 +1,69 @@
1
+ #!/bin/bash
2
+ # Install the Protoboard.app bundle into ~/Applications and register the
3
+ # .protoboard UTI + icon with LaunchServices. Per-user; no sudo.
4
+ #
5
+ # The .icns icon is assembled here (not pre-built) using Apple's native
6
+ # iconutil, so the repo doesn't need to ship a binary .icns blob.
7
+
8
+ set -euo pipefail
9
+
10
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+ repo_root="$(cd "$script_dir/../.." && pwd)"
12
+ src_app="$script_dir/Protoboard.app"
13
+ dst_app="$HOME/Applications/Protoboard.app"
14
+
15
+ if [[ ! -d "$src_app" ]]; then
16
+ echo "Missing app bundle: $src_app" >&2
17
+ exit 1
18
+ fi
19
+
20
+ # 1. Build ProtoboardFile.icns from the PNGs in assets/icons/.
21
+ icons_src="$repo_root/assets/icons"
22
+ if [[ ! -f "$icons_src/512.png" ]]; then
23
+ echo "Missing PNGs in $icons_src. Run 'pnpm run generate-icons' first." >&2
24
+ exit 1
25
+ fi
26
+ tmp_iconset="$(mktemp -d)/ProtoboardFile.iconset"
27
+ mkdir -p "$tmp_iconset"
28
+ cp "$icons_src/16.png" "$tmp_iconset/icon_16x16.png"
29
+ cp "$icons_src/32.png" "$tmp_iconset/icon_16x16@2x.png"
30
+ cp "$icons_src/32.png" "$tmp_iconset/icon_32x32.png"
31
+ cp "$icons_src/64.png" "$tmp_iconset/icon_32x32@2x.png"
32
+ cp "$icons_src/128.png" "$tmp_iconset/icon_128x128.png"
33
+ cp "$icons_src/256.png" "$tmp_iconset/icon_128x128@2x.png"
34
+ cp "$icons_src/256.png" "$tmp_iconset/icon_256x256.png"
35
+ cp "$icons_src/512.png" "$tmp_iconset/icon_256x256@2x.png"
36
+ cp "$icons_src/512.png" "$tmp_iconset/icon_512x512.png"
37
+ if [[ -f "$icons_src/1024.png" ]]; then
38
+ cp "$icons_src/1024.png" "$tmp_iconset/icon_512x512@2x.png"
39
+ fi
40
+ icns_out="$src_app/Contents/Resources/ProtoboardFile.icns"
41
+ mkdir -p "$(dirname "$icns_out")"
42
+ iconutil -c icns "$tmp_iconset" -o "$icns_out"
43
+ rm -rf "$(dirname "$tmp_iconset")"
44
+
45
+ # 2. Make the launcher executable and copy the bundle into ~/Applications.
46
+ chmod +x "$src_app/Contents/MacOS/protoboard-open"
47
+ mkdir -p "$HOME/Applications"
48
+ rm -rf "$dst_app"
49
+ cp -R "$src_app" "$dst_app"
50
+
51
+ # 3. Register with LaunchServices so the UTI and icon are picked up.
52
+ lsregister="/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister"
53
+ "$lsregister" -f "$dst_app"
54
+
55
+ # 4. Try to set as default handler. duti is optional.
56
+ if command -v duti >/dev/null 2>&1; then
57
+ duti -s xyz.protoboard.file all || true
58
+ fi
59
+
60
+ # 5. Nudge Finder to refresh icon cache.
61
+ killall Finder 2>/dev/null || true
62
+
63
+ echo "Protoboard file association installed."
64
+ echo " App: $dst_app"
65
+ echo " UTI: xyz.protoboard.file"
66
+ echo
67
+ echo "If Finder still shows the default JSON icon: right-click any .protoboard file"
68
+ echo "→ Get Info → Open with → Protoboard → Change All. Or install 'duti' (brew install duti)"
69
+ echo "and re-run this installer."
@@ -0,0 +1,15 @@
1
+ #!/bin/bash
2
+ # Remove the Protoboard.app bundle and unregister the UTI.
3
+
4
+ set -u
5
+
6
+ dst_app="$HOME/Applications/Protoboard.app"
7
+ lsregister="/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister"
8
+
9
+ if [[ -d "$dst_app" ]]; then
10
+ "$lsregister" -u "$dst_app" 2>/dev/null || true
11
+ rm -rf "$dst_app"
12
+ fi
13
+
14
+ killall Finder 2>/dev/null || true
15
+ echo "Protoboard file association removed."
@@ -0,0 +1,38 @@
1
+ # Launcher invoked by Explorer when a .protoboard file is double-clicked.
2
+ # Builds https://alpha.protoboard.xyz/board/<userId>/<boardId> when both
3
+ # ids are present; otherwise falls back to https://alpha.protoboard.xyz/.
4
+
5
+ param([Parameter(Mandatory = $true)][string]$Path)
6
+
7
+ $AppRoot = 'https://alpha.protoboard.xyz'
8
+
9
+ function Open-Url([string]$Url) {
10
+ Start-Process $Url | Out-Null
11
+ }
12
+
13
+ try {
14
+ if (-not (Test-Path -LiteralPath $Path)) {
15
+ Open-Url $AppRoot
16
+ return
17
+ }
18
+ $json = Get-Content -LiteralPath $Path -Raw -Encoding UTF8 | ConvertFrom-Json
19
+
20
+ $boardId = $null
21
+ $userId = $null
22
+ if ($json.board) {
23
+ if ($json.board.metadata) {
24
+ $boardId = $json.board.metadata.id
25
+ if (-not $userId) { $userId = $json.board.metadata.userId }
26
+ }
27
+ if (-not $userId) { $userId = $json.board.userId }
28
+ }
29
+
30
+ if ($boardId -and $userId) {
31
+ Open-Url "$AppRoot/board/$userId/$boardId"
32
+ } else {
33
+ Open-Url $AppRoot
34
+ }
35
+ }
36
+ catch {
37
+ Open-Url $AppRoot
38
+ }
@@ -0,0 +1,85 @@
1
+ # Register the .protoboard file type for the current user.
2
+ # - Per-user HKCU registry (no admin required)
3
+ # - Icon: ProtoboardFile.ico copied to %LOCALAPPDATA%\Protoboard\
4
+ # - Open handler: Open-Protoboard.ps1 reads board id from the JSON
5
+ # and launches https://alpha.protoboard.xyz/open?boardId=...
6
+
7
+ $ErrorActionPreference = 'Stop'
8
+
9
+ $installDir = Join-Path $env:LOCALAPPDATA 'Protoboard'
10
+ $scriptDir = $PSScriptRoot
11
+ $repoRoot = Resolve-Path (Join-Path $scriptDir '..\..')
12
+ $icoSource = Join-Path $repoRoot 'assets\ProtoboardFile.ico'
13
+ $launcherSource = Join-Path $scriptDir 'Open-Protoboard.ps1'
14
+
15
+ if (-not (Test-Path $icoSource)) { throw "Missing icon: $icoSource. Run 'pnpm run generate-icons' first." }
16
+ if (-not (Test-Path $launcherSource)) { throw "Missing launcher: $launcherSource" }
17
+
18
+ New-Item -ItemType Directory -Path $installDir -Force | Out-Null
19
+ Copy-Item $icoSource (Join-Path $installDir 'ProtoboardFile.ico') -Force
20
+ Copy-Item $launcherSource (Join-Path $installDir 'Open-Protoboard.ps1') -Force
21
+
22
+ $icoPath = Join-Path $installDir 'ProtoboardFile.ico'
23
+ $launcherPath = Join-Path $installDir 'Open-Protoboard.ps1'
24
+
25
+ $extKey = 'HKCU:\Software\Classes\.protoboard'
26
+ $progKey = 'HKCU:\Software\Classes\Protoboard.File'
27
+ $iconKey = Join-Path $progKey 'DefaultIcon'
28
+ $shellKey = Join-Path $progKey 'shell\open\command'
29
+
30
+ New-Item -Path $extKey -Force | Out-Null
31
+ New-ItemProperty -Path $extKey -Name '(default)' -Value 'Protoboard.File' -PropertyType String -Force | Out-Null
32
+ New-ItemProperty -Path $extKey -Name 'Content Type' -Value 'application/x-protoboard+json' -PropertyType String -Force | Out-Null
33
+ New-ItemProperty -Path $extKey -Name 'PerceivedType' -Value 'text' -PropertyType String -Force | Out-Null
34
+
35
+ New-Item -Path $progKey -Force | Out-Null
36
+ New-ItemProperty -Path $progKey -Name '(default)' -Value 'Protoboard File' -PropertyType String -Force | Out-Null
37
+ New-ItemProperty -Path $progKey -Name 'FriendlyTypeName' -Value 'Protoboard File' -PropertyType String -Force | Out-Null
38
+
39
+ New-Item -Path $iconKey -Force | Out-Null
40
+ New-ItemProperty -Path $iconKey -Name '(default)' -Value "`"$icoPath`",0" -PropertyType String -Force | Out-Null
41
+
42
+ New-Item -Path $shellKey -Force | Out-Null
43
+ $openCmd = "powershell.exe -NoProfile -ExecutionPolicy Bypass -File `"$launcherPath`" `"%1`""
44
+ New-ItemProperty -Path $shellKey -Name '(default)' -Value $openCmd -PropertyType String -Force | Out-Null
45
+
46
+ # If the user has ever right-clicked → Open with on a .protoboard file,
47
+ # Windows wrote a UserChoice override under FileExts that hides our icon.
48
+ # Clear it so DefaultIcon wins. (Re-set as Open-with is one-click to recover.)
49
+ $fileExts = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.protoboard'
50
+ if (Test-Path $fileExts) {
51
+ Remove-Item -Path $fileExts -Recurse -Force -ErrorAction SilentlyContinue
52
+ }
53
+
54
+ # Wipe the per-user icon cache so Explorer re-reads DefaultIcon.
55
+ $explorerWasRunning = $false
56
+ $explorer = Get-Process explorer -ErrorAction SilentlyContinue
57
+ if ($explorer) {
58
+ $explorerWasRunning = $true
59
+ Stop-Process -Name explorer -Force -ErrorAction SilentlyContinue
60
+ Start-Sleep -Milliseconds 600
61
+ }
62
+ $cacheDir = Join-Path $env:LOCALAPPDATA 'Microsoft\Windows\Explorer'
63
+ if (Test-Path $cacheDir) {
64
+ Get-ChildItem -Path $cacheDir -Filter 'iconcache_*.db' -Force -ErrorAction SilentlyContinue |
65
+ Remove-Item -Force -ErrorAction SilentlyContinue
66
+ Get-ChildItem -Path $cacheDir -Filter 'thumbcache_*.db' -Force -ErrorAction SilentlyContinue |
67
+ Remove-Item -Force -ErrorAction SilentlyContinue
68
+ }
69
+ $legacyCache = Join-Path $env:LOCALAPPDATA 'IconCache.db'
70
+ if (Test-Path $legacyCache) { Remove-Item -Path $legacyCache -Force -ErrorAction SilentlyContinue }
71
+
72
+ if ($explorerWasRunning) { Start-Process explorer.exe | Out-Null }
73
+
74
+ # Force Explorer to pick up the new icon/handler immediately.
75
+ Add-Type -Namespace Win32 -Name Shell32 -MemberDefinition @'
76
+ [System.Runtime.InteropServices.DllImport("shell32.dll")]
77
+ public static extern void SHChangeNotify(int wEventId, int uFlags, System.IntPtr dwItem1, System.IntPtr dwItem2);
78
+ '@ | Out-Null
79
+ [Win32.Shell32]::SHChangeNotify(0x08000000, 0, [System.IntPtr]::Zero, [System.IntPtr]::Zero)
80
+
81
+ Write-Host "Protoboard file association installed." -ForegroundColor Green
82
+ Write-Host " Icon: $icoPath"
83
+ Write-Host " Handler: $launcherPath"
84
+ Write-Host ""
85
+ Write-Host "Save a .protoboard file anywhere to see the icon. Double-click opens the board in your browser."
@@ -0,0 +1,19 @@
1
+ # Remove the .protoboard file-type registration for the current user.
2
+
3
+ $ErrorActionPreference = 'Continue'
4
+
5
+ $installDir = Join-Path $env:LOCALAPPDATA 'Protoboard'
6
+ $extKey = 'HKCU:\Software\Classes\.protoboard'
7
+ $progKey = 'HKCU:\Software\Classes\Protoboard.File'
8
+
9
+ if (Test-Path $extKey) { Remove-Item -Path $extKey -Recurse -Force }
10
+ if (Test-Path $progKey) { Remove-Item -Path $progKey -Recurse -Force }
11
+ if (Test-Path $installDir) { Remove-Item -Path $installDir -Recurse -Force }
12
+
13
+ Add-Type -Namespace Win32 -Name Shell32 -MemberDefinition @'
14
+ [System.Runtime.InteropServices.DllImport("shell32.dll")]
15
+ public static extern void SHChangeNotify(int wEventId, int uFlags, System.IntPtr dwItem1, System.IntPtr dwItem2);
16
+ '@ | Out-Null
17
+ [Win32.Shell32]::SHChangeNotify(0x08000000, 0, [System.IntPtr]::Zero, [System.IntPtr]::Zero)
18
+
19
+ Write-Host "Protoboard file association removed." -ForegroundColor Green
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@deltaroboticsinc/protoboard",
3
+ "version": "0.1.0",
4
+ "description": "Register the .protoboard file type with its logo and a double-click handler across Windows, macOS, and Linux.",
5
+ "license": "UNLICENSED",
6
+ "author": "Delta Robotics Inc",
7
+ "homepage": "https://github.com/Delta-Robotics-Inc/protoboard-desktop",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/Delta-Robotics-Inc/protoboard-desktop.git"
11
+ },
12
+ "bin": {
13
+ "protoboard": "./cli.js"
14
+ },
15
+ "main": "cli.js",
16
+ "files": [
17
+ "cli.js",
18
+ "assets/",
19
+ "installers/"
20
+ ],
21
+ "devDependencies": {
22
+ "png-to-ico": "^2.1.8",
23
+ "sharp": "^0.34.1"
24
+ },
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
28
+ "os": [
29
+ "darwin",
30
+ "linux",
31
+ "win32"
32
+ ],
33
+ "keywords": [
34
+ "protoboard",
35
+ "file-association",
36
+ "file-icon",
37
+ "windows",
38
+ "macos",
39
+ "linux"
40
+ ],
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "scripts": {
45
+ "generate-icons": "node scripts/generate-icons.mjs"
46
+ }
47
+ }