alvin-bot 5.0.0 → 5.1.1
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 +73 -0
- package/README.md +98 -14
- package/bin/cli.js +95 -9
- package/dist/handlers/commands.js +26 -0
- package/dist/services/permissions-wizard.js +291 -0
- package/dist/services/sudo.js +66 -6
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,79 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Alvin Bot are documented here.
|
|
4
4
|
|
|
5
|
+
## [5.1.1] — 2026-05-13
|
|
6
|
+
|
|
7
|
+
### Audit baseline cleanup — 16 → 6 vulnerabilities via safe fixes
|
|
8
|
+
|
|
9
|
+
Ran `npm audit fix` (no `--force`) on the lockfile. Cleared 10 of 16 findings: the protobufjs top-level (axios pollution, basic-ftp CRLF, fast-uri path traversal, xmldom DoS, hono jsx/cache, ip-address XSS, postcss XSS, follow-redirects header leak, and the protobufjs vuln at the package root — but **not** the nested copy inside `libsignal-node`). No source-code changes; build + privacy clean; vitest passes 542/543 (one pre-existing flaky port-binding test, unrelated to this change).
|
|
10
|
+
|
|
11
|
+
### Remaining 6 — documented as known/deferred baseline
|
|
12
|
+
|
|
13
|
+
These are tracked, not blocking, with an honest paper trail in `.github/workflows/security-audit.yml`:
|
|
14
|
+
|
|
15
|
+
| Sev | Package | Why deferred |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| critical | `protobufjs@6.8.8` (nested) | Pinned by `@whiskeysockets/libsignal-node` (custom git fork). Forcing an `npm override` to newer protobufjs would touch Signal-Protocol parsing → high breakage risk. Awaits whiskeysockets upstream. **Only reachable with `WHATSAPP_ENABLED=true`** — dead code for the typical user. |
|
|
18
|
+
| high | `electron <=39.8.4` | devDependency only — affects the DMG-build path, not the npm-CLI runtime users. Major bump 35→42 = breaking, scheduled separately when DMG release is next prepared. |
|
|
19
|
+
| moderate × 4 | derived | `@anthropic-ai/sdk` + `claude-agent-sdk` await Anthropic minor; `baileys` + `libsignal-node` carry the protobufjs upstream lag. |
|
|
20
|
+
|
|
21
|
+
### CI audit workflow — documents the baseline
|
|
22
|
+
|
|
23
|
+
`.github/workflows/security-audit.yml` keeps `continue-on-error: true` on the audit step (PRs not blocked on the documented baseline), but the workflow header now spells out exactly what's tracked, why each is deferred, and what would unblock removal of the soft-fail flag.
|
|
24
|
+
|
|
25
|
+
### Stale Dependabot PRs closed
|
|
26
|
+
|
|
27
|
+
The 8 open Dependabot PRs (#10–#17) were opened against the pre-audit-fix lockfile state. Closed all with a redirect comment; Dependabot will reconsider against the cleaned-up baseline at its next scheduled Monday run. Closing them avoids tedious merge-conflict resolution on each.
|
|
28
|
+
|
|
29
|
+
### What didn't change
|
|
30
|
+
|
|
31
|
+
- No source code edits — pure dependency-tree pruning
|
|
32
|
+
- No npm audit fix `--force` — every applied bump is within existing semver ranges
|
|
33
|
+
- Bot runtime behavior identical to 5.1.0; verified on .75 (Pre-Flight green, all permissions detection works, bot online via launchd)
|
|
34
|
+
|
|
35
|
+
## [5.1.0] — 2026-05-13
|
|
36
|
+
|
|
37
|
+
### Permissions Wizard — guided one-and-done macOS setup
|
|
38
|
+
|
|
39
|
+
macOS' TCC framework architecturally **refuses** to let any app grant Full Disk Access / Automation / Accessibility programmatically — only the user can flip those switches in System Settings. There is no API, no AppleScript trick, no sudo bypass for a normal Node CLI.
|
|
40
|
+
|
|
41
|
+
What we can do — and what 5.1.0 does — is make the toggling experience painless:
|
|
42
|
+
|
|
43
|
+
1. **Detect** every permission's current state (sudo + FDA + Automation + Accessibility)
|
|
44
|
+
2. For each missing one: **open the exact right Settings pane**
|
|
45
|
+
3. **Poll for the toggle** every 2 s, up to 60 s per permission
|
|
46
|
+
4. **Verify** and move to the next
|
|
47
|
+
5. End with a clear summary of granted / skipped / still-missing
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
alvin-bot permissions status # quick state-of-the-world
|
|
51
|
+
alvin-bot permissions wizard # interactive guided setup
|
|
52
|
+
alvin-bot permissions open <id> # open one Settings pane
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The wizard also bundles **sudo-password storage** (Keychain on macOS, encrypted file on Linux) as the first step, so users get a single upfront onboarding flow instead of separate prompts later. Run-once intent: no more piecemeal permission requests at runtime.
|
|
56
|
+
|
|
57
|
+
### New detections
|
|
58
|
+
|
|
59
|
+
- **Automation (Apple Events)** — probes via `osascript -e 'tell application "System Events" to ...'`, catches error code 1743 / "Not authorized" to distinguish denied vs granted. Used by Apple Mail, Apple Notes, Calendar skills.
|
|
60
|
+
- **Accessibility** — now distinguishes "cliclick not installed" from "permission denied", so the wizard suggests the actually-correct fix (install via brew vs. toggle in Settings).
|
|
61
|
+
|
|
62
|
+
### Doctor integration
|
|
63
|
+
|
|
64
|
+
`alvin-bot doctor` now uses the same wizard service for its macOS-permissions section — shows all 4 permissions instead of just FDA, points to the wizard for any missing ones.
|
|
65
|
+
|
|
66
|
+
### Telegram `/setup` integration
|
|
67
|
+
|
|
68
|
+
`/setup` keyboard gets a new **🛡️ Permissions Wizard (Mac)** button — surfaces the current 4-permission status and the CLI / WebUI commands to actually run the wizard. Mobile UX is intentionally read-only: the wizard needs to drive System Settings panes on the host, which only the local CLI/WebUI can do.
|
|
69
|
+
|
|
70
|
+
### README — comprehensive CLI section refresh
|
|
71
|
+
|
|
72
|
+
Documented commands added: `tools`, `provider`, `permissions`, `browser`, `status`. Plus the full env-var opt-out table for Self-Preservation Phase 1 + 2 (`ALVIN_DISABLE_*`, `ALVIN_DEADMAN_THRESHOLD_SEC`, etc.). The README CLI section was missing six commands shipped in 4.23 — 5.0; now matches reality.
|
|
73
|
+
|
|
74
|
+
### Honest about what we CAN'T do
|
|
75
|
+
|
|
76
|
+
The README and wizard are explicit about this: macOS TCC permissions cannot be granted by an app. The wizard opens panes and verifies after; the user toggles. No tricks, no entitlement bypass attempts.
|
|
77
|
+
|
|
5
78
|
## [5.0.0] — 2026-05-13
|
|
6
79
|
|
|
7
80
|
### Self-Preservation Phase 2 — the bot now reasons about its own failures
|
package/README.md
CHANGED
|
@@ -527,21 +527,105 @@ Drop your own `<name>/SKILL.md` into `~/.alvin-bot/skills/` for hot-reload. List
|
|
|
527
527
|
|
|
528
528
|
## 🛠️ CLI
|
|
529
529
|
|
|
530
|
+
### Core lifecycle
|
|
531
|
+
|
|
532
|
+
```bash
|
|
533
|
+
alvin-bot setup # Interactive setup wizard (Telegram + AI provider + tools)
|
|
534
|
+
alvin-bot start # Start the bot in background (launchd on macOS, pm2 elsewhere)
|
|
535
|
+
alvin-bot start -f # Start in foreground (for debugging)
|
|
536
|
+
alvin-bot stop # Stop the running bot
|
|
537
|
+
alvin-bot status # Show version + LaunchAgent / pm2 state (offline)
|
|
538
|
+
alvin-bot doctor # Health check — config, provider, memory, permissions
|
|
539
|
+
alvin-bot update # Pull latest from npm (or git if running from source)
|
|
540
|
+
alvin-bot version # Show version
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### Interactive chat
|
|
544
|
+
|
|
545
|
+
```bash
|
|
546
|
+
alvin-bot tui # Terminal chat UI with streaming + ANSI colors ✨
|
|
547
|
+
alvin-bot chat # Alias for tui
|
|
548
|
+
alvin-bot tui --lang de # Force German UI
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### AI provider management (since 4.24.0)
|
|
552
|
+
|
|
553
|
+
Switch between Claude SDK / Codex CLI / Groq / Gemini / OpenAI / OpenRouter / NVIDIA NIM / offline Gemma 4 without re-running the full setup wizard. The switch command runs the same install + auth flow the wizard uses (CLI install + OAuth login for `claude-sdk` / `codex-cli`, API-key prompt + live validation for the rest), then does a byte-preserving merge of `~/.alvin-bot/.env` — the previous provider's API key is **parked, not deleted**, so rollback is one un-comment away.
|
|
554
|
+
|
|
555
|
+
```bash
|
|
556
|
+
alvin-bot provider list # Show all providers + per-provider install/key status
|
|
557
|
+
alvin-bot provider show # Detailed info on the currently configured provider
|
|
558
|
+
alvin-bot provider switch <key> # Switch (interactive setup + .env merge + bot restart)
|
|
559
|
+
alvin-bot provider doctor # Validate current provider's auth against its API
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
`<key>` accepts canonical slugs **or** short aliases: `claude`, `codex`, `gemini`, `nvidia`, `gpt`, `gemma`.
|
|
563
|
+
|
|
564
|
+
### Optional tools — install / update (since 4.23.0)
|
|
565
|
+
|
|
566
|
+
A curated set of universally useful CLIs that unlock specific skills. Bootstrap tools (`yt-dlp`, `ffmpeg`, and `wacli` if WhatsApp is enabled) are auto-installed/updated by `setup` and `update`; the rest you opt into through the menu.
|
|
567
|
+
|
|
568
|
+
```bash
|
|
569
|
+
alvin-bot tools list # Show installed / missing optional tools
|
|
570
|
+
alvin-bot tools install # Interactive menu — pick which to install
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
### macOS permissions wizard (since 5.1.0)
|
|
574
|
+
|
|
575
|
+
macOS' TCC framework refuses to let any app grant Full Disk Access / Automation / Accessibility programmatically — only the user can flip those switches. The wizard makes the toggling experience painless: it detects every permission's current state, opens the **exact right Settings pane** for each missing one, waits for you to toggle (polling every 2 s for up to 60 s per permission), verifies, and moves on. Bundles sudo-password storage in the same upfront flow.
|
|
576
|
+
|
|
577
|
+
```bash
|
|
578
|
+
alvin-bot permissions status # Quick status: all 4 permissions + current state
|
|
579
|
+
alvin-bot permissions wizard # Interactive guided setup, one-and-done
|
|
580
|
+
alvin-bot permissions open <id> # Open one Settings pane (full-disk-access / automation / accessibility)
|
|
581
|
+
alvin-bot perms # alias for permissions
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### LaunchAgent (macOS only)
|
|
585
|
+
|
|
586
|
+
```bash
|
|
587
|
+
alvin-bot launchd install # Write ~/Library/LaunchAgents/com.alvinbot.app.plist + load
|
|
588
|
+
# (Also installs the dead-man-switch companion plist since 4.26.0)
|
|
589
|
+
alvin-bot launchd status # Show PID + recent stdout/stderr from the LaunchAgent
|
|
590
|
+
alvin-bot launchd uninstall # Unload + remove both plists
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
### Browser automation (bot-managed Chromium)
|
|
594
|
+
|
|
595
|
+
```bash
|
|
596
|
+
alvin-bot browser start # Launch Chromium with CDP, persistent profile
|
|
597
|
+
alvin-bot browser start headful # Same, visible (for login flows)
|
|
598
|
+
alvin-bot browser goto <url> # Open URL, return JSON metadata
|
|
599
|
+
alvin-bot browser shot <url> [file] # Screenshot → ~/.alvin-bot/browser/screenshots/
|
|
600
|
+
alvin-bot browser eval <url> "<js>" # Run JS in page context
|
|
601
|
+
alvin-bot browser tabs # List open tabs
|
|
602
|
+
alvin-bot browser status # PID + CDP endpoint
|
|
603
|
+
alvin-bot browser stop # Quit Chromium
|
|
604
|
+
alvin-bot browser doctor # Diagnose Chromium / Playwright setup
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
### Maintenance & introspection
|
|
608
|
+
|
|
609
|
+
```bash
|
|
610
|
+
alvin-bot audit # Security health check — permissions, secrets, config
|
|
611
|
+
alvin-bot search "<query>" # Search assets, memories, and skills index
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
### Environment-variable opt-outs (Self-Preservation features since 4.26.0 / 5.0.0)
|
|
615
|
+
|
|
616
|
+
Granular opt-out for the resilience subsystems — everything is enabled by default:
|
|
617
|
+
|
|
530
618
|
```bash
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
alvin-bot launchd uninstall # macOS only: remove LaunchAgent
|
|
542
|
-
alvin-bot audit # Security health check
|
|
543
|
-
alvin-bot search # Search assets/memories/skills
|
|
544
|
-
alvin-bot version # Show version
|
|
619
|
+
ALVIN_DISABLE_SELF_PRESERVATION=true # Kill ALL Phase-1 + Phase-2 features below
|
|
620
|
+
ALVIN_DISABLE_PREFLIGHT=true # Skip startup sanity check (Telegram, provider, SQLite, disk)
|
|
621
|
+
ALVIN_DISABLE_CRITICAL_NOTIFY=true # Skip cross-channel alerts (Telegram + macOS notif + file flag)
|
|
622
|
+
ALVIN_DISABLE_DEAD_MAN=true # Skip the zombie-detection heartbeat writer
|
|
623
|
+
ALVIN_DISABLE_AUTO_DIAGNOSTIC=true # Skip forensic-bundle writing on crash
|
|
624
|
+
ALVIN_DISABLE_SELF_DIAGNOSIS=true # Skip AI analysis of forensic bundles at startup
|
|
625
|
+
ALVIN_DISABLE_TRENDS=true # Skip daily trend snapshots + AI anomaly detection
|
|
626
|
+
ALVIN_DEADMAN_THRESHOLD_SEC=600 # Dead-man's-switch staleness threshold (default 10 min)
|
|
627
|
+
ALVIN_TRENDS_INTERVAL_HOURS=24 # Trend-snapshot cadence (default 24 h)
|
|
628
|
+
ALVIN_TRENDS_AI_AFTER_DAYS=7 # Days of history before AI anomaly detection kicks in
|
|
545
629
|
```
|
|
546
630
|
|
|
547
631
|
---
|
package/bin/cli.js
CHANGED
|
@@ -2538,17 +2538,32 @@ async function doctor() {
|
|
|
2538
2538
|
}
|
|
2539
2539
|
|
|
2540
2540
|
// ── macOS permissions (TCC) ────────────────────────────────────────────
|
|
2541
|
+
// 5.1.0 — superseded by the permissions-wizard service, which checks
|
|
2542
|
+
// all four (sudo + FDA + Automation + Accessibility) and gives the
|
|
2543
|
+
// user a single command to fix anything missing. The legacy FDA-only
|
|
2544
|
+
// block stays as a fast-path summary for users who haven't yet run
|
|
2545
|
+
// the wizard.
|
|
2541
2546
|
if (process.platform === "darwin") {
|
|
2542
2547
|
console.log("\n macOS permissions:");
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2548
|
+
try {
|
|
2549
|
+
const { readPermissionsSnapshot, formatPermissionsSnapshot } = await import("../dist/services/permissions-wizard.js");
|
|
2550
|
+
const snaps = await readPermissionsSnapshot();
|
|
2551
|
+
console.log(formatPermissionsSnapshot(snaps));
|
|
2552
|
+
const missing = snaps.filter(s => s.state === "missing" || s.state === "tool-missing").length;
|
|
2553
|
+
if (missing > 0) {
|
|
2554
|
+
console.log(`\n ${missing} permission${missing === 1 ? "" : "s"} need attention. Run:`);
|
|
2555
|
+
console.log(" alvin-bot permissions wizard");
|
|
2556
|
+
}
|
|
2557
|
+
} catch (err) {
|
|
2558
|
+
// Fall back to the legacy FDA-only check if the wizard module
|
|
2559
|
+
// is unavailable for any reason.
|
|
2560
|
+
const fda = checkMacosFullDiskAccess(process.execPath);
|
|
2561
|
+
if (fda.hasFDA) {
|
|
2562
|
+
console.log(` ✅ Full Disk Access — granted to ${fda.realNodePath}`);
|
|
2563
|
+
} else {
|
|
2564
|
+
console.log(` ⚠️ Full Disk Access NOT granted to node (${fda.realNodePath})`);
|
|
2565
|
+
console.log(` To fix: open "${fda.paneUrl}" → add ${fda.realNodePath}`);
|
|
2566
|
+
}
|
|
2552
2567
|
}
|
|
2553
2568
|
}
|
|
2554
2569
|
|
|
@@ -3102,6 +3117,76 @@ switch (cmd) {
|
|
|
3102
3117
|
}
|
|
3103
3118
|
break;
|
|
3104
3119
|
}
|
|
3120
|
+
case "permissions":
|
|
3121
|
+
case "perms": {
|
|
3122
|
+
const sub = (process.argv[3] || "status").toLowerCase();
|
|
3123
|
+
(async () => {
|
|
3124
|
+
const { readPermissionsSnapshot, formatPermissionsSnapshot, runPermissionsWizard, PERMISSIONS } = await import("../dist/services/permissions-wizard.js");
|
|
3125
|
+
if (sub === "status" || sub === "ls" || sub === "list") {
|
|
3126
|
+
const snaps = await readPermissionsSnapshot();
|
|
3127
|
+
console.log("\n🛡️ Permissions status\n");
|
|
3128
|
+
console.log(formatPermissionsSnapshot(snaps));
|
|
3129
|
+
const missing = snaps.filter(s => s.state === "missing" || s.state === "tool-missing").length;
|
|
3130
|
+
console.log("");
|
|
3131
|
+
if (missing > 0) {
|
|
3132
|
+
console.log(` ${missing} missing — run: alvin-bot permissions wizard`);
|
|
3133
|
+
} else {
|
|
3134
|
+
console.log(" All applicable permissions granted.");
|
|
3135
|
+
}
|
|
3136
|
+
console.log("");
|
|
3137
|
+
} else if (sub === "wizard" || sub === "grant" || sub === "setup") {
|
|
3138
|
+
const yes = ["y","yes","j","ja"];
|
|
3139
|
+
await runPermissionsWizard({
|
|
3140
|
+
print: (t) => console.log(t),
|
|
3141
|
+
confirm: async (q) => {
|
|
3142
|
+
const a = (await ask(`${q} (y/n): `)).trim().toLowerCase();
|
|
3143
|
+
return yes.includes(a) || a === "";
|
|
3144
|
+
},
|
|
3145
|
+
promptPassword: async (q) => {
|
|
3146
|
+
// Best-effort: hide password if process.stdin is TTY (we can
|
|
3147
|
+
// toggle echo). Otherwise plain read — acceptable fallback.
|
|
3148
|
+
try {
|
|
3149
|
+
if (process.stdin.isTTY) {
|
|
3150
|
+
process.stdin.setRawMode?.(true);
|
|
3151
|
+
}
|
|
3152
|
+
} catch {}
|
|
3153
|
+
const a = await ask(q);
|
|
3154
|
+
try {
|
|
3155
|
+
process.stdin.setRawMode?.(false);
|
|
3156
|
+
} catch {}
|
|
3157
|
+
return a.trim();
|
|
3158
|
+
},
|
|
3159
|
+
});
|
|
3160
|
+
} else if (sub === "open") {
|
|
3161
|
+
const id = process.argv[4];
|
|
3162
|
+
const perm = PERMISSIONS.find(p => p.id === id);
|
|
3163
|
+
if (!perm || !perm.openPane) {
|
|
3164
|
+
console.log(`Unknown or non-openable permission: ${id}`);
|
|
3165
|
+
console.log("Available: " + PERMISSIONS.filter(p => p.openPane).map(p => p.id).join(", "));
|
|
3166
|
+
} else {
|
|
3167
|
+
const ok = perm.openPane();
|
|
3168
|
+
console.log(ok ? `Opened System Settings → ${perm.name}` : "Could not open Settings.");
|
|
3169
|
+
}
|
|
3170
|
+
} else {
|
|
3171
|
+
console.log("Usage: alvin-bot permissions <status|wizard|open <id>>");
|
|
3172
|
+
console.log("");
|
|
3173
|
+
console.log(" status (default) — List all permissions + current state");
|
|
3174
|
+
console.log(" wizard — Interactive guided setup: walks through each missing");
|
|
3175
|
+
console.log(" permission, opens the right Settings pane, waits for");
|
|
3176
|
+
console.log(" you to toggle, verifies. macOS-only TCC bits + sudo.");
|
|
3177
|
+
console.log(" open <id> — Open one specific Settings pane");
|
|
3178
|
+
console.log(" <id>: full-disk-access | automation | accessibility");
|
|
3179
|
+
console.log("");
|
|
3180
|
+
console.log("Note: macOS does NOT allow apps to grant TCC permissions programmatically.");
|
|
3181
|
+
console.log("The wizard opens the right Settings panes; YOU flip the switches; the wizard");
|
|
3182
|
+
console.log("verifies each one is actually granted before moving on.");
|
|
3183
|
+
}
|
|
3184
|
+
})().then(() => closeRL()).catch((err) => {
|
|
3185
|
+
console.error(err);
|
|
3186
|
+
closeRL();
|
|
3187
|
+
});
|
|
3188
|
+
break;
|
|
3189
|
+
}
|
|
3105
3190
|
case "provider": {
|
|
3106
3191
|
const sub = (process.argv[3] || "list").toLowerCase();
|
|
3107
3192
|
const arg = process.argv[4];
|
|
@@ -3494,6 +3579,7 @@ ${t("cli.commands")}
|
|
|
3494
3579
|
search Search your assets, memories, and skills
|
|
3495
3580
|
tools List / install optional capability tools (ffmpeg, pandoc, …)
|
|
3496
3581
|
provider List / switch AI provider (claude, codex, groq, gemini, …)
|
|
3582
|
+
permissions Status / wizard for macOS TCC + sudo permissions (alias: perms)
|
|
3497
3583
|
browser Manage bot-owned Chromium (start/stop/goto/shot/doctor)
|
|
3498
3584
|
update ${t("cli.updateDesc")}
|
|
3499
3585
|
start ${t("cli.startDesc")} (background via PM2)
|
|
@@ -1698,6 +1698,7 @@ export function registerCommands(bot) {
|
|
|
1698
1698
|
.text("🔑 Manage API Keys", "setup:keys").row()
|
|
1699
1699
|
.text("📱 Platforms", "setup:platforms").row()
|
|
1700
1700
|
.text("🔐 Sudo / Admin Access", "setup:sudo").row()
|
|
1701
|
+
.text("🛡️ Permissions Wizard (Mac)", "setup:permissions").row()
|
|
1701
1702
|
.text("🔧 Open Web Dashboard", "setup:web").row();
|
|
1702
1703
|
await ctx.reply(`⚙️ *Alvin Bot Setup*\n\n` +
|
|
1703
1704
|
`*Active Model:* ${activeInfo.name}\n` +
|
|
@@ -1854,6 +1855,31 @@ export function registerCommands(bot) {
|
|
|
1854
1855
|
`_The password is securely stored in ${status.storageMethod}._`, { parse_mode: "Markdown" });
|
|
1855
1856
|
break;
|
|
1856
1857
|
}
|
|
1858
|
+
case "permissions": {
|
|
1859
|
+
// 5.1.0 — Permissions Wizard summary. Mobile UX is intentionally
|
|
1860
|
+
// read-only: the wizard itself needs to open System Settings panes
|
|
1861
|
+
// on the host machine, which only the CLI/WebUI can drive. Here
|
|
1862
|
+
// we surface status + tell the user how to run the actual wizard.
|
|
1863
|
+
try {
|
|
1864
|
+
const { readPermissionsSnapshot } = await import("../services/permissions-wizard.js");
|
|
1865
|
+
const snaps = await readPermissionsSnapshot();
|
|
1866
|
+
const icons = { granted: "✅", missing: "❌", "tool-missing": "⚠️", "n/a": "·" };
|
|
1867
|
+
const lines = snaps.map(s => `${icons[s.state]} *${s.permission.name}* — ${s.state}${s.detail ? " _(" + s.detail + ")_" : ""}`);
|
|
1868
|
+
const missing = snaps.filter(s => s.state === "missing" || s.state === "tool-missing").length;
|
|
1869
|
+
const summary = missing === 0
|
|
1870
|
+
? "*All applicable permissions granted.*"
|
|
1871
|
+
: `*${missing} permission${missing === 1 ? "" : "s"} need attention.*`;
|
|
1872
|
+
await ctx.editMessageText(`🛡️ *Permissions Status*\n\n${lines.join("\n")}\n\n${summary}\n\n` +
|
|
1873
|
+
`_macOS doesn't let any app grant TCC permissions on its own — you toggle the switch, the wizard verifies._\n\n` +
|
|
1874
|
+
`*To run the guided wizard:*\n` +
|
|
1875
|
+
`• CLI: \`alvin-bot permissions wizard\` (opens each Settings pane, waits, verifies)\n` +
|
|
1876
|
+
`• WebUI: http://localhost:${process.env.WEB_PORT || 3100} → Settings`, { parse_mode: "Markdown" });
|
|
1877
|
+
}
|
|
1878
|
+
catch (err) {
|
|
1879
|
+
await ctx.editMessageText("🛡️ *Permissions Status*\n\n_Could not load wizard module — try `alvin-bot permissions status` in a terminal._", { parse_mode: "Markdown" });
|
|
1880
|
+
}
|
|
1881
|
+
break;
|
|
1882
|
+
}
|
|
1857
1883
|
case "web": {
|
|
1858
1884
|
await ctx.editMessageText(`🌐 *Web Dashboard*\n\n` +
|
|
1859
1885
|
`URL: \`http://localhost:${process.env.WEB_PORT || 3100}\`\n\n` +
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permissions Wizard (5.1.0).
|
|
3
|
+
*
|
|
4
|
+
* Reality check up front: on macOS, TCC (Transparency, Consent, Control)
|
|
5
|
+
* permissions are architecturally NOT grantable by an app — no API, no
|
|
6
|
+
* AppleScript, no sudo trick, no signed-entitlement bypass for a normal
|
|
7
|
+
* Node CLI. The user has to toggle the switches in System Settings.
|
|
8
|
+
*
|
|
9
|
+
* What this wizard DOES do — make the toggling experience painless:
|
|
10
|
+
*
|
|
11
|
+
* 1. Detect every permission's current state
|
|
12
|
+
* 2. For each missing one: open the EXACT right Settings pane
|
|
13
|
+
* 3. Wait for the user to toggle (poll detect every 2 s)
|
|
14
|
+
* 4. Verify and move to the next
|
|
15
|
+
* 5. End with a clear summary of granted / skipped / still-missing
|
|
16
|
+
*
|
|
17
|
+
* Covered permissions (macOS):
|
|
18
|
+
* - sudo password (not TCC; stored in Keychain so brew/apt commands
|
|
19
|
+
* don't re-prompt — separate from TCC but bundled here for
|
|
20
|
+
* "one upfront onboarding" UX)
|
|
21
|
+
* - Full Disk Access (TCC — for protected dirs like ~/Library/Mail,
|
|
22
|
+
* ~/Documents when needed by skills/codex)
|
|
23
|
+
* - Automation (TCC — for osascript driving Apple Mail/Notes/Calendar)
|
|
24
|
+
* - Accessibility (TCC — for cliclick mouse/keyboard automation)
|
|
25
|
+
*
|
|
26
|
+
* On Linux: only sudo applies. The wizard reports a no-op for TCC steps.
|
|
27
|
+
*
|
|
28
|
+
* Opt-out: set ALVIN_DISABLE_SELF_PRESERVATION=true to silence everything,
|
|
29
|
+
* but this wizard never runs unless explicitly invoked — no startup hook.
|
|
30
|
+
*/
|
|
31
|
+
import { execSync } from "child_process";
|
|
32
|
+
import os from "os";
|
|
33
|
+
import { getSudoStatus, openSystemSettings, storePassword, verifyPassword, } from "./sudo.js";
|
|
34
|
+
const PLATFORM = os.platform();
|
|
35
|
+
export const PERMISSIONS = [
|
|
36
|
+
{
|
|
37
|
+
id: "sudo",
|
|
38
|
+
name: "Sudo / Admin Access",
|
|
39
|
+
why: "Lets Alvin run admin commands (brew install, apt-get, etc.) without re-prompting every time.",
|
|
40
|
+
platforms: ["darwin", "linux"],
|
|
41
|
+
detect: async (status) => {
|
|
42
|
+
const s = status ?? (await getSudoStatus());
|
|
43
|
+
if (!s.configured)
|
|
44
|
+
return { state: "missing", detail: "password not stored" };
|
|
45
|
+
if (!s.verified)
|
|
46
|
+
return { state: "missing", detail: "stored but doesn't authenticate" };
|
|
47
|
+
return { state: "granted" };
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "full-disk-access",
|
|
52
|
+
name: "Full Disk Access",
|
|
53
|
+
why: "Lets `node` (and anything it spawns — codex, file-skills, document tools) read protected directories like ~/Library/Mail, ~/Documents when skills need them.",
|
|
54
|
+
platforms: ["darwin"],
|
|
55
|
+
detect: async (status) => {
|
|
56
|
+
const s = status ?? (await getSudoStatus());
|
|
57
|
+
if (PLATFORM !== "darwin")
|
|
58
|
+
return { state: "n/a" };
|
|
59
|
+
return { state: s.permissions.fullDiskAccess ? "granted" : "missing" };
|
|
60
|
+
},
|
|
61
|
+
openPane: () => openSystemSettings("full-disk-access"),
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: "automation",
|
|
65
|
+
name: "Automation (Apple Events)",
|
|
66
|
+
why: "Lets osascript drive Apple Mail, Apple Notes, Calendar, Finder — used by the email-send, apple-notes, and several productivity skills.",
|
|
67
|
+
platforms: ["darwin"],
|
|
68
|
+
detect: async (status) => {
|
|
69
|
+
const s = status ?? (await getSudoStatus());
|
|
70
|
+
if (PLATFORM !== "darwin")
|
|
71
|
+
return { state: "n/a" };
|
|
72
|
+
const a = s.permissions.automation;
|
|
73
|
+
if (a === null || a === undefined)
|
|
74
|
+
return { state: "n/a" };
|
|
75
|
+
return { state: a ? "granted" : "missing" };
|
|
76
|
+
},
|
|
77
|
+
openPane: () => openSystemSettings("automation"),
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: "accessibility",
|
|
81
|
+
name: "Accessibility",
|
|
82
|
+
why: "Lets cliclick simulate mouse + keyboard input — used by some browser-automation and UI-testing skills.",
|
|
83
|
+
platforms: ["darwin"],
|
|
84
|
+
detect: async (status) => {
|
|
85
|
+
const s = status ?? (await getSudoStatus());
|
|
86
|
+
if (PLATFORM !== "darwin")
|
|
87
|
+
return { state: "n/a" };
|
|
88
|
+
if (s.accessibilityDetail === "cliclick-missing") {
|
|
89
|
+
return { state: "tool-missing", detail: "cliclick not installed (brew install cliclick)" };
|
|
90
|
+
}
|
|
91
|
+
return { state: s.permissions.accessibility ? "granted" : "missing" };
|
|
92
|
+
},
|
|
93
|
+
openPane: () => openSystemSettings("accessibility"),
|
|
94
|
+
prereq: "brew install cliclick",
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
/**
|
|
98
|
+
* Read-only — return the current state of every applicable permission.
|
|
99
|
+
* Used by `permissions status` and by the doctor command.
|
|
100
|
+
*/
|
|
101
|
+
export async function readPermissionsSnapshot() {
|
|
102
|
+
const status = await getSudoStatus();
|
|
103
|
+
const out = [];
|
|
104
|
+
for (const perm of PERMISSIONS) {
|
|
105
|
+
if (!perm.platforms.includes(PLATFORM)) {
|
|
106
|
+
out.push({ permission: perm, state: "n/a", detail: `not applicable on ${PLATFORM}` });
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const r = await perm.detect(status);
|
|
110
|
+
out.push({ permission: perm, ...r });
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Wait up to `timeoutMs` for a permission to flip from missing→granted.
|
|
116
|
+
* Polls detect() every 2 seconds. Used after openPane() to give the
|
|
117
|
+
* user time to toggle the switch.
|
|
118
|
+
*/
|
|
119
|
+
async function waitForGrant(perm, timeoutMs) {
|
|
120
|
+
const deadline = Date.now() + timeoutMs;
|
|
121
|
+
while (Date.now() < deadline) {
|
|
122
|
+
const r = await perm.detect();
|
|
123
|
+
if (r.state === "granted")
|
|
124
|
+
return true;
|
|
125
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
126
|
+
}
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Run the full guided permissions wizard. Sequential per permission.
|
|
131
|
+
*
|
|
132
|
+
* Behaviour per permission:
|
|
133
|
+
* - already granted → skip silently (one-liner)
|
|
134
|
+
* - tool-missing → tell user the prereq, offer to skip
|
|
135
|
+
* - missing (TCC) → open Settings pane, wait up to 60 s, verify
|
|
136
|
+
* - missing (sudo) → prompt for password, store in Keychain, verify
|
|
137
|
+
*
|
|
138
|
+
* The user can decline any step. Nothing is required for the bot to keep
|
|
139
|
+
* working — degraded skills just won't work until the relevant permission
|
|
140
|
+
* is granted.
|
|
141
|
+
*/
|
|
142
|
+
export async function runPermissionsWizard(cb) {
|
|
143
|
+
const result = { granted: [], skipped: [], stillMissing: [], toolMissing: [] };
|
|
144
|
+
cb.print("\n🛡️ Mac Permissions Wizard");
|
|
145
|
+
cb.print("─────────────────────────────────────────────────────────");
|
|
146
|
+
cb.print("macOS doesn't let any app grant TCC permissions on its own —");
|
|
147
|
+
cb.print("we'll walk you through each toggle. Decline any step you want.");
|
|
148
|
+
cb.print("");
|
|
149
|
+
// First read snapshot once so we don't re-probe constantly
|
|
150
|
+
const initial = await readPermissionsSnapshot();
|
|
151
|
+
cb.print("Current state:");
|
|
152
|
+
for (const snap of initial) {
|
|
153
|
+
const icon = snap.state === "granted" ? "✓" : snap.state === "n/a" ? "·" : "✗";
|
|
154
|
+
cb.print(` ${icon} ${snap.permission.name.padEnd(28)} ${snap.state}${snap.detail ? " (" + snap.detail + ")" : ""}`);
|
|
155
|
+
}
|
|
156
|
+
cb.print("");
|
|
157
|
+
// ── Sudo step (always first; not TCC, just Keychain) ────────────────
|
|
158
|
+
const sudoSnap = initial.find((s) => s.permission.id === "sudo");
|
|
159
|
+
if (sudoSnap && sudoSnap.state !== "granted" && sudoSnap.permission.platforms.includes(PLATFORM)) {
|
|
160
|
+
cb.print(`\n[1/4] ${sudoSnap.permission.name}`);
|
|
161
|
+
cb.print(` ${sudoSnap.permission.why}`);
|
|
162
|
+
cb.print(` Status: ${sudoSnap.state}${sudoSnap.detail ? " (" + sudoSnap.detail + ")" : ""}`);
|
|
163
|
+
const proceed = await cb.confirm(" Set up now?");
|
|
164
|
+
if (!proceed) {
|
|
165
|
+
result.skipped.push("sudo");
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
const pw = await cb.promptPassword(" Your macOS account password (stored in Keychain): ");
|
|
169
|
+
if (!pw) {
|
|
170
|
+
result.skipped.push("sudo");
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
const stored = storePassword(pw);
|
|
174
|
+
if (!stored.ok) {
|
|
175
|
+
cb.print(` ❌ Could not store: ${stored.error}`);
|
|
176
|
+
result.stillMissing.push("sudo");
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
const verify = await verifyPassword();
|
|
180
|
+
if (verify.ok) {
|
|
181
|
+
cb.print(` ✓ Stored in ${stored.method} and verified.`);
|
|
182
|
+
result.granted.push("sudo");
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
cb.print(` ❌ Wrong password — Keychain entry removed.`);
|
|
186
|
+
// storePassword stored it; verifyPassword found it wrong.
|
|
187
|
+
// We should revoke to avoid leaving a bogus pw lying around.
|
|
188
|
+
try {
|
|
189
|
+
const { revokePassword } = await import("./sudo.js");
|
|
190
|
+
revokePassword();
|
|
191
|
+
}
|
|
192
|
+
catch { }
|
|
193
|
+
result.stillMissing.push("sudo");
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
else if (sudoSnap?.state === "granted") {
|
|
200
|
+
cb.print(`[1/4] ${sudoSnap.permission.name} ✓ already configured`);
|
|
201
|
+
result.granted.push("sudo");
|
|
202
|
+
}
|
|
203
|
+
// ── TCC permissions (Mac only) ──────────────────────────────────────
|
|
204
|
+
if (PLATFORM !== "darwin") {
|
|
205
|
+
cb.print("\nNon-macOS platform — TCC permissions are macOS-only. Skipping.");
|
|
206
|
+
return result;
|
|
207
|
+
}
|
|
208
|
+
let i = 2;
|
|
209
|
+
for (const id of ["full-disk-access", "automation", "accessibility"]) {
|
|
210
|
+
const perm = PERMISSIONS.find((p) => p.id === id);
|
|
211
|
+
const snap = initial.find((s) => s.permission.id === id);
|
|
212
|
+
cb.print(`\n[${i}/4] ${perm.name}`);
|
|
213
|
+
cb.print(` ${perm.why}`);
|
|
214
|
+
cb.print(` Status: ${snap.state}${snap.detail ? " (" + snap.detail + ")" : ""}`);
|
|
215
|
+
i++;
|
|
216
|
+
if (snap.state === "granted") {
|
|
217
|
+
cb.print(` ✓ Already granted.`);
|
|
218
|
+
result.granted.push(id);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (snap.state === "tool-missing") {
|
|
222
|
+
cb.print(` ⚠ Prerequisite missing: ${perm.prereq || "(no install hint)"}`);
|
|
223
|
+
const install = await cb.confirm(" Install the prerequisite now?");
|
|
224
|
+
if (install && perm.prereq) {
|
|
225
|
+
try {
|
|
226
|
+
execSync(perm.prereq, { stdio: "inherit", timeout: 120_000 });
|
|
227
|
+
cb.print(` ✓ Installed.`);
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
cb.print(` ❌ Install failed.`);
|
|
231
|
+
result.toolMissing.push(id);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
result.toolMissing.push(id);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// Open the Settings pane and wait for toggle
|
|
241
|
+
if (!perm.openPane) {
|
|
242
|
+
cb.print(` (no automatic pane-open for this permission)`);
|
|
243
|
+
result.stillMissing.push(id);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const proceed = await cb.confirm(" Open System Settings and grant now?");
|
|
247
|
+
if (!proceed) {
|
|
248
|
+
result.skipped.push(id);
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
cb.print(` → Opening System Settings…`);
|
|
252
|
+
perm.openPane();
|
|
253
|
+
cb.print(` → Waiting up to 60 s for you to toggle the switch (polls every 2 s)…`);
|
|
254
|
+
cb.print(` → For Full Disk Access: add the actual node binary —`);
|
|
255
|
+
cb.print(` ${getNodeBinaryHint()}`);
|
|
256
|
+
const granted = await waitForGrant(perm, 60_000);
|
|
257
|
+
if (granted) {
|
|
258
|
+
cb.print(` ✓ Granted.`);
|
|
259
|
+
result.granted.push(id);
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
cb.print(` ⏱ Timeout — you can run the wizard again any time.`);
|
|
263
|
+
result.stillMissing.push(id);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
function getNodeBinaryHint() {
|
|
269
|
+
try {
|
|
270
|
+
const path = execSync(`readlink -f "$(command -v node)"`, { encoding: "utf-8", timeout: 1000 }).trim();
|
|
271
|
+
return path || "(could not resolve node binary)";
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return "(run: readlink -f \"$(command -v node)\")";
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Format a snapshot list as a compact human-readable block.
|
|
279
|
+
* Used by both `permissions status` and the doctor command.
|
|
280
|
+
*/
|
|
281
|
+
export function formatPermissionsSnapshot(snaps) {
|
|
282
|
+
const lines = [];
|
|
283
|
+
for (const snap of snaps) {
|
|
284
|
+
const icon = snap.state === "granted" ? "✓" :
|
|
285
|
+
snap.state === "tool-missing" ? "⚠" :
|
|
286
|
+
snap.state === "n/a" ? "·" : "✗";
|
|
287
|
+
lines.push(` ${icon} ${snap.permission.name.padEnd(28)} ${snap.state}` +
|
|
288
|
+
(snap.detail ? ` — ${snap.detail}` : ""));
|
|
289
|
+
}
|
|
290
|
+
return lines.join("\n");
|
|
291
|
+
}
|
package/dist/services/sudo.js
CHANGED
|
@@ -218,7 +218,47 @@ export function openSystemSettings(pane) {
|
|
|
218
218
|
}
|
|
219
219
|
}
|
|
220
220
|
/**
|
|
221
|
-
*
|
|
221
|
+
* Detect Automation permission on macOS (TCC).
|
|
222
|
+
*
|
|
223
|
+
* Method: try to send a benign AppleEvent to System Events. If TCC has
|
|
224
|
+
* NOT granted the calling binary (typically `node` under launchd)
|
|
225
|
+
* permission to control System Events, osascript fails with one of:
|
|
226
|
+
*
|
|
227
|
+
* - error 1743 ("Not authorized to send Apple events to ...")
|
|
228
|
+
* - "(-1743)" in stderr
|
|
229
|
+
* - "errAEEventNotPermitted"
|
|
230
|
+
*
|
|
231
|
+
* If TCC grants it, osascript returns a small integer (process count).
|
|
232
|
+
* Either way, we don't actually care about the count — only that the
|
|
233
|
+
* call succeeded.
|
|
234
|
+
*
|
|
235
|
+
* Returns:
|
|
236
|
+
* true — Automation granted
|
|
237
|
+
* false — Automation denied (or never asked, which appears identical)
|
|
238
|
+
* null — non-macOS (no TCC)
|
|
239
|
+
*/
|
|
240
|
+
function detectAutomation() {
|
|
241
|
+
if (PLATFORM !== "darwin")
|
|
242
|
+
return null;
|
|
243
|
+
try {
|
|
244
|
+
execSync(`osascript -e 'tell application "System Events" to count of processes' 2>&1`, { stdio: "pipe", timeout: 3000 });
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
catch (err) {
|
|
248
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
249
|
+
// Either way, treat as "not granted". The error path is fast; we
|
|
250
|
+
// don't want to wedge startup on this probe.
|
|
251
|
+
if (msg.includes("1743") || /[Nn]ot authoriz/i.test(msg) || /errAEEventNotPermitted/.test(msg)) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
// Other error (e.g. osascript itself missing — very unlikely on macOS)
|
|
255
|
+
// — treat as unknown but bias false to surface the issue.
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Get comprehensive sudo + permissions status. Used by both the
|
|
261
|
+
* `/sudo` command and the new permissions wizard.
|
|
222
262
|
*/
|
|
223
263
|
export async function getSudoStatus() {
|
|
224
264
|
const configured = retrievePassword() !== null;
|
|
@@ -230,15 +270,33 @@ export async function getSudoStatus() {
|
|
|
230
270
|
}
|
|
231
271
|
// Check macOS permissions
|
|
232
272
|
let accessibility = null;
|
|
273
|
+
let accessibilityDetail = "n/a";
|
|
233
274
|
let fullDiskAccess = null;
|
|
275
|
+
let automation = null;
|
|
234
276
|
if (PLATFORM === "darwin") {
|
|
277
|
+
// Accessibility — distinguish "denied" from "tool not installed".
|
|
278
|
+
// cliclick is the standard probe; if it's missing the test is
|
|
279
|
+
// inconclusive but we can tell the wizard exactly what to do.
|
|
280
|
+
let cliclickPresent = false;
|
|
235
281
|
try {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
accessibility = true;
|
|
282
|
+
execSync("command -v cliclick", { stdio: "pipe", timeout: 1000 });
|
|
283
|
+
cliclickPresent = true;
|
|
239
284
|
}
|
|
240
|
-
catch {
|
|
285
|
+
catch { }
|
|
286
|
+
if (!cliclickPresent) {
|
|
241
287
|
accessibility = false;
|
|
288
|
+
accessibilityDetail = "cliclick-missing";
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
try {
|
|
292
|
+
execSync("cliclick p:.", { stdio: "pipe", timeout: 3000 });
|
|
293
|
+
accessibility = true;
|
|
294
|
+
accessibilityDetail = "granted";
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
accessibility = false;
|
|
298
|
+
accessibilityDetail = "denied";
|
|
299
|
+
}
|
|
242
300
|
}
|
|
243
301
|
try {
|
|
244
302
|
// Check Full Disk Access (try to read a protected file)
|
|
@@ -248,6 +306,7 @@ export async function getSudoStatus() {
|
|
|
248
306
|
catch {
|
|
249
307
|
fullDiskAccess = false;
|
|
250
308
|
}
|
|
309
|
+
automation = detectAutomation();
|
|
251
310
|
}
|
|
252
311
|
return {
|
|
253
312
|
configured,
|
|
@@ -255,7 +314,8 @@ export async function getSudoStatus() {
|
|
|
255
314
|
verified,
|
|
256
315
|
platform: PLATFORM,
|
|
257
316
|
user,
|
|
258
|
-
permissions: { accessibility, fullDiskAccess },
|
|
317
|
+
permissions: { accessibility, fullDiskAccess, automation },
|
|
318
|
+
accessibilityDetail,
|
|
259
319
|
};
|
|
260
320
|
}
|
|
261
321
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "alvin-bot",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.1.1",
|
|
4
4
|
"description": "Alvin Bot — Your personal AI agent on Telegram, WhatsApp, Discord, Signal, and Web.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
"test:watch": "vitest",
|
|
17
17
|
"test:ui": "vitest --ui",
|
|
18
18
|
"privacy-check": "bash scripts/privacy-check.sh",
|
|
19
|
+
"audit": "npm audit --audit-level=high",
|
|
20
|
+
"audit:full": "npm audit",
|
|
19
21
|
"prepublishOnly": "bash scripts/privacy-check.sh",
|
|
20
22
|
"electron:compile": "tsc -p electron/tsconfig.json",
|
|
21
23
|
"electron:dev": "npm run electron:compile && electron .",
|