Skip to content

Commit d3205e8

Browse files
authored
Merge dev → main: native installers + browser MCP off by default (#79)
## Summary Promotes the installer + safety changes from \`dev\` to \`main\`. What lands: - **PR #78** \`feat(install): native installers (.pkg + .exe)\` — proper macOS \`.pkg\` builder + Windows Inno Setup \`.exe\` with real icons. Replaces the curl-pipe scripts as the recommended path for non-technical users. - **PR #77** \`feat(mcp): disable Playwright MCP by default\` — browser tools become opt-in. Main agent and browser-pilot crew don't see \`browser_*\` tools until the user enables Playwright in MCP settings. - **PR #75** + follow-up fixes \`1cf856f\` and \`f61032a\` — the original curl-pipe scripts plus the two patches I pushed during testing (osascript GUI password prompt for non-TTY sudo; Unicode ellipses replaced with ASCII so \`set -u\` doesn't choke). After merge, the README's \`raw.githubusercontent.com/.../main/scripts/install.sh\` URL resolves and the legacy curl-pipe path keeps working alongside the new native installers. Future cleanup (deleting the curl-pipe scripts since the native installers are the recommended path) can be a follow-up commit. ## Test plan - [ ] After merge, build the macOS \`.pkg\` from \`installer/macos/build-pkg.sh\`, run on a clean Mac, confirm app installs and dashboard opens - [ ] Run \`installer/windows/Output/DaemoraSetup.exe\` on a clean Windows box, confirm same - [ ] Confirm browser tools are absent from main agent's tool list on a fresh install (PR #77 default) - [ ] Verify \`https://raw.githubusercontent.com/CodeAndCanvasLabs/Daemora/main/scripts/install.sh\` resolves
2 parents 5369cd4 + 2d00d8e commit d3205e8

29 files changed

Lines changed: 1579 additions & 35 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# ── Dependencies ─────────────────────────────────────────────────────────────
22
node_modules/
3+
.pnpm-store/
34

45
# ── Environment - NEVER commit secrets ───────────────────────────────────────
56
.env

README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,22 @@ Then message your bot. That's it.
121121

122122
## Installation
123123

124-
### npm (recommended)
124+
### Download the installer (recommended — no terminal needed)
125+
126+
Native installers handle everything: Node.js, the daemora package, app icons, Desktop / Start Menu shortcuts, and the launcher. Click the icon → browser opens to the dashboard. First-run walks through the setup wizard.
127+
128+
| Platform | Download | What you get |
129+
|---|---|---|
130+
| **macOS** (12+, Apple Silicon or Intel) | [`Daemora.pkg`](https://github.com/CodeAndCanvasLabs/Daemora/releases/latest) | Standard install wizard. `Daemora.app` and `Stop Daemora.app` land in `/Applications`. Desktop alias. Spotlight + Launchpad indexed. |
131+
| **Windows** (10+) | [`DaemoraSetup.exe`](https://github.com/CodeAndCanvasLabs/Daemora/releases/latest) | Inno Setup wizard. Start Menu + Desktop shortcuts. |
132+
133+
Double-click the installer → walk through the wizard → enter your password (it writes to `/Applications` on macOS or `Program Files` on Windows). Then double-click the **Daemora** icon. Browser opens at `http://localhost:8081`. The setup wizard inside the app collects your model API key and configures channels.
134+
135+
Closing the browser **does not** stop daemora — the daemon is detached and survives logout. Use the **Stop Daemora** shortcut to actually shut it down.
136+
137+
> Building the installer locally (e.g. for an unreleased commit)? See [`installer/README.md`](./installer/README.md) and the per-platform `BUILD.md` files.
138+
139+
### npm (developers)
125140

126141
```bash
127142
npm install -g daemora
File renamed without changes.

installer/README.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Daemora installers
2+
3+
One-click installers for non-technical users. Two platforms, same UX:
4+
5+
```
6+
Download installer → Run it → Click "Daemora" on Desktop → Web UI opens
7+
```
8+
9+
No terminal. No `npm install -g`. No `daemora setup`. The web UI handles
10+
all configuration on first run.
11+
12+
## Layout
13+
14+
```
15+
installer/
16+
├── windows/ Inno Setup project -> DaemoraSetup.exe
17+
│ ├── daemora.iss
18+
│ ├── launcher.ps1
19+
│ ├── assets/ (drop daemora.ico here)
20+
│ └── BUILD.md
21+
└── macos/ pkgbuild project -> Daemora.pkg
22+
├── build-pkg.sh
23+
├── distribution.xml
24+
├── scripts/
25+
│ ├── preinstall
26+
│ └── postinstall
27+
├── app-template/
28+
│ ├── Daemora.app/
29+
│ └── Stop Daemora.app/
30+
├── resources/ (welcome.html, license.html, etc.)
31+
└── BUILD.md
32+
```
33+
34+
## What the installer does
35+
36+
1. **Detects Node.js 22+** — installs it if missing.
37+
- Windows: `winget install OpenJS.NodeJS.LTS`, fallback to MSI download.
38+
- macOS: Homebrew (auto-installs brew if missing) + `brew install node@22`.
39+
2. **`npm install -g daemora`**.
40+
3. **Drops a launcher + shortcuts**:
41+
- Windows: `launcher.ps1` in install dir, shortcuts on Start Menu + Desktop.
42+
- macOS: `Daemora.app` and `Stop Daemora.app` in /Applications, alias on Desktop.
43+
44+
## Lifecycle
45+
46+
- **Start** (1-click): double-click the Daemora icon. If the daemon isn't
47+
running, it spawns hidden (no Terminal/PowerShell window), waits for
48+
`/health`, then opens the browser. If already running, just opens
49+
the browser.
50+
- **Closing the browser does NOT stop Daemora.** The daemon is detached
51+
and survives browser close, launcher exit, and even user logout.
52+
- **Stop**: separate "Stop Daemora" shortcut/app. Reads the PID file
53+
in the data dir and kills the process tree. Falls back to killing
54+
whatever's bound to port 8081 if the PID file is stale.
55+
56+
## Data dir
57+
58+
Daemora resolves its data dir to the OS-standard app dir automatically
59+
(see [src/config/env.ts](../src/config/env.ts)):
60+
61+
- Windows: `%APPDATA%\Daemora\` (= `C:\Users\<user>\AppData\Roaming\Daemora`)
62+
- macOS: `~/Library/Application Support/Daemora/`
63+
64+
The launcher writes `daemora.pid` and `daemora.log` to the same dir, so
65+
all per-user state lives in one place. Uninstalling the app does not
66+
delete the data dir — user state survives reinstalls.
67+
68+
## Build
69+
70+
See [`windows/BUILD.md`](windows/BUILD.md) and [`macos/BUILD.md`](macos/BUILD.md).
71+
72+
The Windows installer is built with Inno Setup on a Windows machine.
73+
The macOS installer needs `pkgbuild` / `productbuild`, which are
74+
macOS-only. CI: GitHub Actions has both `windows-latest` and
75+
`macos-latest` runners that can produce both artifacts in one workflow.
76+
77+
## Code signing
78+
79+
Both platforms warn loudly on unsigned installers. Strongly recommended
80+
for non-technical users — tells the OS "this came from us, run it".
81+
82+
| Platform | Cert | Cost | What you get |
83+
|---|---|---|---|
84+
| Windows | EV code-signing | $200–400/yr | No SmartScreen warning at all |
85+
| Windows | Standard OV | $80–200/yr | Warning clears once enough users install it (reputation) |
86+
| macOS | Developer ID + notarization | $99/yr | No Gatekeeper warning; "Open" works on first try |
87+
88+
Without signing, expect the user to click through one OS warning on
89+
their first install. After install, every subsequent launch is silent.
90+
91+
## Release process
92+
93+
1. Bump version in [`package.json`](../package.json).
94+
2. Bump `AppVersion` in [`windows/daemora.iss`](windows/daemora.iss) and
95+
`version` in [`macos/distribution.xml`](macos/distribution.xml).
96+
3. `npm publish` (so `npm install -g daemora` resolves the new version).
97+
4. Build both installers (CI or locally on each OS).
98+
5. Sign + notarize.
99+
6. Upload `DaemoraSetup.exe` and `Daemora.pkg` to GitHub Releases.
100+
7. Update README download links.

installer/macos/.metadata_never_index

Whitespace-only changes.

installer/macos/BUILD.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Building the macOS installer
2+
3+
Must be built on macOS 12+. Linux/Windows can't produce a notarized
4+
`.pkg` because `pkgbuild` and `productbuild` are macOS-only tools.
5+
6+
## Prerequisites
7+
8+
1. **Xcode CLI tools**: `xcode-select --install`
9+
2. (For signing/notarization, optional but strongly recommended for
10+
non-technical end users) **Apple Developer ID** ($99/yr).
11+
3. `daemora.icns` is checked into both `.app` Resources dirs, generated
12+
from [`ui/public/favicon.svg`](../../ui/public/favicon.svg). If the
13+
SVG changes, regenerate with `pnpm run icons:installer`.
14+
15+
## Build (unsigned, for local testing)
16+
17+
```bash
18+
chmod +x installer/macos/build-pkg.sh
19+
installer/macos/build-pkg.sh
20+
```
21+
22+
Output: `installer/macos/dist/Daemora.pkg`.
23+
24+
To test locally: double-click the `.pkg`. Gatekeeper will block it
25+
("cannot be opened because it is from an unidentified developer").
26+
Workaround: System Settings → Privacy & Security → "Open Anyway".
27+
28+
## Build, sign, and notarize (for public release)
29+
30+
```bash
31+
# 1. Build
32+
installer/macos/build-pkg.sh
33+
34+
# 2. Sign
35+
productsign --sign "Developer ID Installer: Your Name (TEAMID)" \
36+
installer/macos/dist/Daemora.pkg \
37+
installer/macos/dist/Daemora-signed.pkg
38+
39+
# 3. Notarize (one-time setup: store credentials in keychain)
40+
xcrun notarytool store-credentials NOTARY \
41+
--apple-id you@example.com \
42+
--team-id TEAMID \
43+
--password app-specific-password
44+
45+
xcrun notarytool submit installer/macos/dist/Daemora-signed.pkg \
46+
--keychain-profile NOTARY --wait
47+
48+
# 4. Staple the notarization ticket so Gatekeeper passes offline too
49+
xcrun stapler staple installer/macos/dist/Daemora-signed.pkg
50+
```
51+
52+
Without notarization: every user has to right-click → Open the first
53+
time. With notarization: double-click works on any Mac, no warnings.
54+
55+
## What the user sees
56+
57+
1. Download `Daemora.pkg` from GitHub Releases, double-click.
58+
2. Standard macOS install wizard: Continue → Continue → Install (asks
59+
for the user's password — required to write to /Applications).
60+
3. Two `.app` bundles land in /Applications: **Daemora** and **Stop
61+
Daemora**. A **Daemora** alias appears on the Desktop.
62+
4. Daemora is also dropped into the user's Applications list (Spotlight,
63+
Launchpad).
64+
65+
## 1-click launch
66+
67+
Double-click **Daemora** on the Desktop (or in /Applications). No
68+
Terminal window. Browser opens to http://localhost:8081 once the server
69+
is ready. The daemon survives browser close, app exit, and user logout.
70+
71+
To stop: double-click **Stop Daemora.app** in /Applications.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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>CFBundleDevelopmentRegion</key>
6+
<string>en</string>
7+
<key>CFBundleExecutable</key>
8+
<string>Daemora</string>
9+
<key>CFBundleIconFile</key>
10+
<string>daemora.icns</string>
11+
<key>CFBundleIdentifier</key>
12+
<string>com.codeandcanvaslabs.daemora</string>
13+
<key>CFBundleInfoDictionaryVersion</key>
14+
<string>6.0</string>
15+
<key>CFBundleName</key>
16+
<string>Daemora</string>
17+
<key>CFBundleDisplayName</key>
18+
<string>Daemora</string>
19+
<key>CFBundlePackageType</key>
20+
<string>APPL</string>
21+
<key>CFBundleShortVersionString</key>
22+
<string>1.0.0</string>
23+
<key>CFBundleVersion</key>
24+
<string>1</string>
25+
<key>LSApplicationCategoryType</key>
26+
<string>public.app-category.productivity</string>
27+
<key>LSMinimumSystemVersion</key>
28+
<string>12.0</string>
29+
<key>LSUIElement</key>
30+
<false/>
31+
<key>NSHighResolutionCapable</key>
32+
<true/>
33+
</dict>
34+
</plist>
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#!/bin/bash
2+
#
3+
# Daemora.app launcher — what runs when the user double-clicks the icon.
4+
#
5+
# 1-click flow:
6+
# - if daemon already running -> open browser, exit
7+
# - else -> start daemon detached, wait for /health, open browser, exit
8+
#
9+
# No Terminal window. No interactive prompts. The daemon survives this
10+
# script exiting (nohup + disown), browser close, and user logout.
11+
12+
set -u
13+
14+
PORT=8081
15+
URL="http://localhost:$PORT"
16+
DATA_DIR="$HOME/Library/Application Support/Daemora"
17+
PID_FILE="$DATA_DIR/daemora.pid"
18+
LOG_FILE="$DATA_DIR/daemora.log"
19+
20+
mkdir -p "$DATA_DIR"
21+
22+
# Make sure /opt/homebrew/bin (Apple Silicon) and /usr/local/bin (Intel)
23+
# are on PATH — Finder-launched apps inherit a sparse PATH that often
24+
# misses Homebrew, so daemora won't resolve without this.
25+
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
26+
27+
show_error() {
28+
osascript <<EOF
29+
display alert "Daemora failed to start" message "$1" as critical
30+
EOF
31+
}
32+
33+
is_health_ok() {
34+
curl -sf --max-time 2 "$URL/health" >/dev/null 2>&1
35+
}
36+
37+
is_daemon_running() {
38+
if is_health_ok; then return 0; fi
39+
[ -f "$PID_FILE" ] || return 1
40+
local pid
41+
pid=$(cat "$PID_FILE" 2>/dev/null)
42+
[ -n "$pid" ] || return 1
43+
kill -0 "$pid" 2>/dev/null
44+
}
45+
46+
start_daemon() {
47+
if ! command -v daemora >/dev/null 2>&1; then
48+
show_error "The 'daemora' command is not on PATH. Re-run the Daemora installer to fix this."
49+
return 1
50+
fi
51+
# Suppress daemora's own browser-open so we can poll /health first
52+
# and only open the page once the server is actually ready.
53+
DAEMORA_NO_OPEN=1 nohup daemora start >>"$LOG_FILE" 2>&1 &
54+
echo $! > "$PID_FILE"
55+
disown 2>/dev/null || true
56+
57+
# Poll for up to 60s — first boot loads skills, embeddings, MCP, etc.
58+
for _ in $(seq 1 60); do
59+
if is_health_ok; then return 0; fi
60+
sleep 1
61+
done
62+
return 1
63+
}
64+
65+
if ! is_daemon_running; then
66+
if ! start_daemon; then
67+
show_error "Daemora did not respond within 60 seconds. Check the log at:\n$LOG_FILE"
68+
exit 1
69+
fi
70+
fi
71+
72+
open "$URL"
73+
exit 0
Binary file not shown.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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>CFBundleDevelopmentRegion</key>
6+
<string>en</string>
7+
<key>CFBundleExecutable</key>
8+
<string>Stop Daemora</string>
9+
<key>CFBundleIconFile</key>
10+
<string>daemora.icns</string>
11+
<key>CFBundleIdentifier</key>
12+
<string>com.codeandcanvaslabs.daemora.stop</string>
13+
<key>CFBundleInfoDictionaryVersion</key>
14+
<string>6.0</string>
15+
<key>CFBundleName</key>
16+
<string>Stop Daemora</string>
17+
<key>CFBundleDisplayName</key>
18+
<string>Stop Daemora</string>
19+
<key>CFBundlePackageType</key>
20+
<string>APPL</string>
21+
<key>CFBundleShortVersionString</key>
22+
<string>1.0.0</string>
23+
<key>CFBundleVersion</key>
24+
<string>1</string>
25+
<key>LSApplicationCategoryType</key>
26+
<string>public.app-category.utilities</string>
27+
<key>LSMinimumSystemVersion</key>
28+
<string>12.0</string>
29+
<key>LSUIElement</key>
30+
<true/>
31+
<key>NSHighResolutionCapable</key>
32+
<true/>
33+
</dict>
34+
</plist>

0 commit comments

Comments
 (0)