poke-gate 0.1.9 → 0.2.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.
Files changed (32) hide show
  1. package/.github/workflows/release.yml +53 -3
  2. package/Gate.app +0 -0
  3. package/README.md +48 -14
  4. package/bin/poke-gate.js +17 -0
  5. package/clients/Poke macOS Gate/Poke macOS Gate/AboutView.swift +7 -1
  6. package/clients/Poke macOS Gate/Poke macOS Gate/AccessibilityPermissionView.swift +58 -0
  7. package/clients/Poke macOS Gate/Poke macOS Gate/GateService.swift +389 -23
  8. package/clients/Poke macOS Gate/Poke macOS Gate/Info.plist +2 -0
  9. package/clients/Poke macOS Gate/Poke macOS Gate/LogsView.swift +1 -1
  10. package/clients/Poke macOS Gate/Poke macOS Gate/MacVisualStyle.swift +89 -0
  11. package/clients/Poke macOS Gate/Poke macOS Gate/PermissionRowView.swift +55 -0
  12. package/clients/Poke macOS Gate/Poke macOS Gate/Poke_macOS_GateApp.swift +234 -91
  13. package/clients/Poke macOS Gate/Poke macOS Gate/SettingsView.swift +125 -81
  14. package/clients/Poke macOS Gate/Poke macOS Gate/SetupView.swift +157 -0
  15. package/clients/Poke macOS Gate/Poke macOS Gate.xcodeproj/project.pbxproj +31 -11
  16. package/docs/cli.md +19 -0
  17. package/docs/getting-started.md +9 -6
  18. package/docs/index.md +23 -18
  19. package/docs/macos-app.md +39 -4
  20. package/docs/security.md +62 -18
  21. package/examples/agents/battery.30m.js +1 -1
  22. package/examples/agents/screentime.24h.js +5 -6
  23. package/macOS +0 -0
  24. package/package.json +3 -1
  25. package/src/agents.js +5 -8
  26. package/src/app.js +29 -5
  27. package/src/mcp-server.js +502 -27
  28. package/src/permission-service.js +128 -0
  29. package/test/mcp-server-access-policy.test.js +40 -0
  30. package/test/mcp-server-loop-guard.test.js +57 -0
  31. package/test/mcp-server-sandbox-command.test.js +18 -0
  32. package/test/permission-service.test.js +97 -0
@@ -0,0 +1,157 @@
1
+ import SwiftUI
2
+
3
+ struct SetupView: View {
4
+ @ObservedObject var service: GateService
5
+ @State private var selectedMode: GateService.PermissionMode
6
+ @State private var step: Step = .accessMode
7
+
8
+ enum Step { case accessMode, permissions }
9
+
10
+ init(service: GateService) {
11
+ self.service = service
12
+ _selectedMode = State(initialValue: service.permissionMode)
13
+ }
14
+
15
+ var body: some View {
16
+ VStack(alignment: .leading, spacing: 0) {
17
+ stepIndicator
18
+ .padding(.horizontal, 16)
19
+ .padding(.top, 16)
20
+ .padding(.bottom, 12)
21
+
22
+ Divider()
23
+
24
+ switch step {
25
+ case .accessMode: accessModeStep
26
+ case .permissions: permissionsStep
27
+ }
28
+ }
29
+ .frame(width: 380)
30
+ .onAppear { service.startPermissionPolling() }
31
+ .onDisappear { service.stopPermissionPolling() }
32
+ }
33
+
34
+ private var stepIndicator: some View {
35
+ HStack(spacing: 6) {
36
+ ForEach(Array(Step.allCases.enumerated()), id: \.offset) { index, s in
37
+ HStack(spacing: 4) {
38
+ Circle()
39
+ .fill(s == step ? MacVisualStyle.chipActiveFill : (isCompleted(s) ? Color.accentColor.opacity(0.5) : MacVisualStyle.chipInactiveFill))
40
+ .frame(width: 6, height: 6)
41
+ Text(s.label)
42
+ .font(.caption2)
43
+ .foregroundStyle(s == step ? .primary : .secondary)
44
+ }
45
+ if index < Step.allCases.count - 1 {
46
+ Rectangle()
47
+ .fill(MacVisualStyle.progressTrackColor)
48
+ .frame(height: 1)
49
+ .frame(maxWidth: .infinity)
50
+ }
51
+ }
52
+ }
53
+ }
54
+
55
+ private var accessModeStep: some View {
56
+ VStack(alignment: .leading, spacing: 16) {
57
+ VStack(alignment: .leading, spacing: 4) {
58
+ Text("Choose access mode")
59
+ .font(.headline)
60
+ Text("Controls which tools Poke can use on this machine.")
61
+ .font(.caption)
62
+ .foregroundStyle(.secondary)
63
+ }
64
+
65
+ VStack(spacing: 8) {
66
+ ForEach(GateService.PermissionMode.allCases) { mode in
67
+ modeRow(mode)
68
+ }
69
+ }
70
+
71
+ HStack {
72
+ Spacer()
73
+ Button("Continue") { step = .permissions }
74
+ .keyboardShortcut(.defaultAction)
75
+ }
76
+ }
77
+ .padding(16)
78
+ }
79
+
80
+ private var permissionsStep: some View {
81
+ VStack(alignment: .leading, spacing: 16) {
82
+ VStack(alignment: .leading, spacing: 4) {
83
+ Text("Grant permissions")
84
+ .font(.headline)
85
+ Text("These allow Poke to control your Mac. You can grant them now or later from the menu bar.")
86
+ .font(.caption)
87
+ .foregroundStyle(.secondary)
88
+ }
89
+
90
+ VStack(spacing: 10) {
91
+ ForEach(GateService.SystemPermission.allCases) { permission in
92
+ let status = service.systemPermissionStatuses.first(where: { $0.permission == permission })
93
+ PermissionRowView(
94
+ permission: permission,
95
+ isGranted: status?.isGranted ?? false,
96
+ onGrant: { service.openSystemPermission(permission) }
97
+ )
98
+ }
99
+ }
100
+
101
+ HStack {
102
+ Button("Back") { step = .accessMode }
103
+ .buttonStyle(.plain)
104
+ .foregroundStyle(.secondary)
105
+
106
+ Spacer()
107
+
108
+ Button("Finish Setup") {
109
+ service.completeFirstRunSetup(selectedMode: selectedMode, requestPermissions: false)
110
+ }
111
+ .keyboardShortcut(.defaultAction)
112
+ }
113
+ }
114
+ .padding(16)
115
+ }
116
+
117
+ private func modeRow(_ mode: GateService.PermissionMode) -> some View {
118
+ Button { selectedMode = mode } label: {
119
+ HStack(alignment: .top, spacing: 10) {
120
+ Image(systemName: selectedMode == mode ? "checkmark.circle.fill" : "circle")
121
+ .foregroundStyle(selectedMode == mode ? Color.accentColor : Color.secondary)
122
+
123
+ VStack(alignment: .leading, spacing: 2) {
124
+ Text(mode.title)
125
+ .font(.subheadline)
126
+ .foregroundStyle(.primary)
127
+ Text(mode.subtitle)
128
+ .font(.caption)
129
+ .foregroundStyle(.secondary)
130
+ }
131
+
132
+ Spacer()
133
+ }
134
+ .padding(10)
135
+ .macPanelStyle(selectedMode == mode ? .selected : .neutral)
136
+ }
137
+ .buttonStyle(.plain)
138
+ }
139
+
140
+ private func isCompleted(_ s: Step) -> Bool {
141
+ switch s {
142
+ case .accessMode: return step == .permissions
143
+ case .permissions: return false
144
+ }
145
+ }
146
+ }
147
+
148
+ extension SetupView.Step: CaseIterable {
149
+ static var allCases: [SetupView.Step] { [.accessMode, .permissions] }
150
+
151
+ var label: String {
152
+ switch self {
153
+ case .accessMode: return "Access Mode"
154
+ case .permissions: return "Permissions"
155
+ }
156
+ }
157
+ }
@@ -10,9 +10,22 @@
10
10
  442DE4462F71DCD9009BF9EF /* Poke macOS Gate.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Poke macOS Gate.app"; sourceTree = BUILT_PRODUCTS_DIR; };
11
11
  /* End PBXFileReference section */
12
12
 
13
+ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
14
+ 114D3ACF2F74597400C9F8B9 /* Exceptions for "Poke macOS Gate" folder in "Poke macOS Gate" target */ = {
15
+ isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
16
+ membershipExceptions = (
17
+ Info.plist,
18
+ );
19
+ target = 442DE4452F71DCD9009BF9EF /* Poke macOS Gate */;
20
+ };
21
+ /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
22
+
13
23
  /* Begin PBXFileSystemSynchronizedRootGroup section */
14
24
  442DE4482F71DCD9009BF9EF /* Poke macOS Gate */ = {
15
25
  isa = PBXFileSystemSynchronizedRootGroup;
26
+ exceptions = (
27
+ 114D3ACF2F74597400C9F8B9 /* Exceptions for "Poke macOS Gate" folder in "Poke macOS Gate" target */,
28
+ );
16
29
  path = "Poke macOS Gate";
17
30
  sourceTree = "<group>";
18
31
  };
@@ -78,7 +91,7 @@
78
91
  attributes = {
79
92
  BuildIndependentTargetsInParallel = 1;
80
93
  LastSwiftUpdateCheck = 2620;
81
- LastUpgradeCheck = 2620;
94
+ LastUpgradeCheck = 2630;
82
95
  TargetAttributes = {
83
96
  442DE4452F71DCD9009BF9EF = {
84
97
  CreatedOnToolsVersion = 26.2;
@@ -159,8 +172,9 @@
159
172
  CLANG_WARN_UNREACHABLE_CODE = YES;
160
173
  CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
161
174
  COPY_PHASE_STRIP = NO;
175
+ DEAD_CODE_STRIPPING = YES;
162
176
  DEBUG_INFORMATION_FORMAT = dwarf;
163
- DEVELOPMENT_TEAM = RJA7656U34;
177
+ DEVELOPMENT_TEAM = "";
164
178
  ENABLE_STRICT_OBJC_MSGSEND = YES;
165
179
  ENABLE_TESTABILITY = YES;
166
180
  ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -179,11 +193,12 @@
179
193
  GCC_WARN_UNUSED_FUNCTION = YES;
180
194
  GCC_WARN_UNUSED_VARIABLE = YES;
181
195
  LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
182
- MACOSX_DEPLOYMENT_TARGET = 26.2;
196
+ MACOSX_DEPLOYMENT_TARGET = 15.0;
183
197
  MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
184
198
  MTL_FAST_MATH = YES;
185
199
  ONLY_ACTIVE_ARCH = YES;
186
200
  SDKROOT = macosx;
201
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
187
202
  SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
188
203
  SWIFT_OPTIMIZATION_LEVEL = "-Onone";
189
204
  };
@@ -223,8 +238,9 @@
223
238
  CLANG_WARN_UNREACHABLE_CODE = YES;
224
239
  CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
225
240
  COPY_PHASE_STRIP = NO;
241
+ DEAD_CODE_STRIPPING = YES;
226
242
  DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
227
- DEVELOPMENT_TEAM = RJA7656U34;
243
+ DEVELOPMENT_TEAM = "";
228
244
  ENABLE_NS_ASSERTIONS = NO;
229
245
  ENABLE_STRICT_OBJC_MSGSEND = YES;
230
246
  ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -237,10 +253,11 @@
237
253
  GCC_WARN_UNUSED_FUNCTION = YES;
238
254
  GCC_WARN_UNUSED_VARIABLE = YES;
239
255
  LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
240
- MACOSX_DEPLOYMENT_TARGET = 26.2;
256
+ MACOSX_DEPLOYMENT_TARGET = 15.0;
241
257
  MTL_ENABLE_DEBUG_INFO = NO;
242
258
  MTL_FAST_MATH = YES;
243
259
  SDKROOT = macosx;
260
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
244
261
  SWIFT_COMPILATION_MODE = wholemodule;
245
262
  };
246
263
  name = Release;
@@ -250,10 +267,12 @@
250
267
  buildSettings = {
251
268
  ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
252
269
  ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
270
+ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
253
271
  CODE_SIGN_STYLE = Automatic;
254
272
  COMBINE_HIDPI_IMAGES = YES;
255
273
  CURRENT_PROJECT_VERSION = 1;
256
- DEVELOPMENT_TEAM = RJA7656U34;
274
+ DEAD_CODE_STRIPPING = YES;
275
+ DEVELOPMENT_TEAM = "";
257
276
  ENABLE_APP_SANDBOX = NO;
258
277
  ENABLE_HARDENED_RUNTIME = NO;
259
278
  ENABLE_PREVIEWS = YES;
@@ -266,8 +285,8 @@
266
285
  "$(inherited)",
267
286
  "@executable_path/../Frameworks",
268
287
  );
269
- MACOSX_DEPLOYMENT_TARGET = 26.0;
270
- MARKETING_VERSION = 0.1.7;
288
+ MACOSX_DEPLOYMENT_TARGET = 15.0;
289
+ MARKETING_VERSION = 0.2.0;
271
290
  PRODUCT_BUNDLE_IDENTIFIER = "dev.fka.Poke-macOS-Gate";
272
291
  PRODUCT_NAME = "$(TARGET_NAME)";
273
292
  REGISTER_APP_GROUPS = YES;
@@ -285,10 +304,11 @@
285
304
  buildSettings = {
286
305
  ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
287
306
  ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
307
+ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
288
308
  CODE_SIGN_STYLE = Automatic;
289
309
  COMBINE_HIDPI_IMAGES = YES;
290
310
  CURRENT_PROJECT_VERSION = 1;
291
- DEVELOPMENT_TEAM = RJA7656U34;
311
+ DEAD_CODE_STRIPPING = YES;
292
312
  ENABLE_APP_SANDBOX = NO;
293
313
  ENABLE_HARDENED_RUNTIME = NO;
294
314
  ENABLE_PREVIEWS = YES;
@@ -301,8 +321,8 @@
301
321
  "$(inherited)",
302
322
  "@executable_path/../Frameworks",
303
323
  );
304
- MACOSX_DEPLOYMENT_TARGET = 26.0;
305
- MARKETING_VERSION = 0.1.7;
324
+ MACOSX_DEPLOYMENT_TARGET = 15.0;
325
+ MARKETING_VERSION = 0.2.0;
306
326
  PRODUCT_BUNDLE_IDENTIFIER = "dev.fka.Poke-macOS-Gate";
307
327
  PRODUCT_NAME = "$(TARGET_NAME)";
308
328
  REGISTER_APP_GROUPS = YES;
package/docs/cli.md CHANGED
@@ -8,6 +8,23 @@ npx poke-gate
8
8
 
9
9
  Starts the MCP server, connects the tunnel, and begins the agent scheduler. On first run, if you're not signed in, opens Poke OAuth in your browser.
10
10
 
11
+ ### Access mode
12
+
13
+ ```bash
14
+ npx poke-gate --mode limited
15
+ npx poke-gate --mode sandbox
16
+ ```
17
+
18
+ Controls which tools your Poke agent can use. Defaults to `full` if not specified.
19
+
20
+ | Mode | Description |
21
+ |------|-------------|
22
+ | `full` | All tools available, subject to chat approval for risky actions |
23
+ | `limited` | Safe tools and a curated set of read-only commands only |
24
+ | `sandbox` | Broader command support, but writes are restricted by macOS `sandbox-exec` policies |
25
+
26
+ You can also set the mode via the `POKE_GATE_PERMISSION_MODE` environment variable. The `--mode` flag takes precedence.
27
+
11
28
  ### Verbose mode
12
29
 
13
30
  ```bash
@@ -92,6 +109,8 @@ Fetching agent "beeper" from GitHub...
92
109
 
93
110
  | Variable | Description |
94
111
  |----------|-------------|
112
+ | `POKE_GATE_PERMISSION_MODE` | Access mode: `full` (default), `limited`, or `sandbox` |
113
+ | `POKE_GATE_HMAC_SECRET` | Fixed HMAC secret for approval tokens (random per session by default) |
95
114
  | `POKE_API_KEY` | Override the API key (skips OAuth) |
96
115
  | `POKE_API` | Override the Poke API base URL |
97
116
  | `POKE_FRONTEND` | Override the Poke frontend URL |
@@ -28,14 +28,17 @@ If you don't need the macOS app:
28
28
  npx poke-gate
29
29
  ```
30
30
 
31
- ## Sign in
31
+ Poke Gate needs **Accessibility** permission on your Mac to automate keyboard/mouse and take screenshots.
32
32
 
33
+ ### 1. Sign in
33
34
  Poke Gate uses Poke OAuth to authenticate. On first launch:
34
35
 
35
- 1. Open Poke Gate from your menu bar
36
- 2. The app starts and connects automatically
37
- 3. If you're not signed in, a browser window opens for Poke OAuth
38
- 4. After signing in, the connection is established
36
+ 1. Open Poke Gate from your menu bar.
37
+ 2. The **Setup View** will appear to guide you through:
38
+ - Selecting an access mode (Full, Limited, or Sandbox)
39
+ - Granting the required macOS Accessibility permissions
40
+ 3. If you're not signed in, a browser window opens for Poke OAuth.
41
+ 4. After signing in, the connection is established.
39
42
 
40
43
  You can also sign in manually:
41
44
 
@@ -47,7 +50,7 @@ npx poke login
47
50
 
48
51
  Once connected, you'll see a green dot in the menu bar. The popover shows:
49
52
 
50
- > ● Connected to your Poke, [your name]
53
+ > ● Connected to your Poke, your name
51
54
 
52
55
  Now open iMessage or Telegram and message your Poke:
53
56
 
package/docs/index.md CHANGED
@@ -17,24 +17,29 @@ hero:
17
17
  link: https://github.com/f/poke-gate
18
18
 
19
19
  features:
20
- - icon: 🖥️
21
- title: Full Shell Access
22
- details: Run any terminal command on your machine — ls, git, brew, python, curl, and more.
23
- - icon: 📁
24
- title: File Operations
25
- details: Read, write, and list files and directories. Your Poke agent sees your filesystem.
26
- - icon: 📸
27
- title: Screenshots
28
- details: Capture your screen remotely. Poke can see what you see.
29
- - icon: 🤖
30
- title: Agents
31
- details: Scheduled scripts that run in the background — automate message digests, backups, health checks.
32
- - icon: 🌴
33
- title: macOS Menu Bar App
34
- details: Native SwiftUI app that lives in your menu bar. Auto-connects, auto-restarts, shows real-time status.
35
- - icon:
36
- title: MCP Tunnel
37
- details: Secure WebSocket tunnel powered by the Poke SDK. Only your authenticated agent can reach your machine.
20
+ - icon: 🖥️
21
+ title: Full Shell Access
22
+ details: Run any terminal command on your machine — ls, git, brew, python,
23
+ curl, and more.
24
+ - icon: 📁
25
+ title: File Operations
26
+ details: Read, write, and list files and directories. Your Poke agent sees
27
+ your filesystem.
28
+ - icon: 📸
29
+ title: Screenshots
30
+ details: Capture your screen remotely. Poke can see what you see.
31
+ - icon: 🤖
32
+ title: Agents
33
+ details: Scheduled scripts that run in the background — automate message
34
+ digests, backups, health checks.
35
+ - icon: 🌴
36
+ title: macOS Menu Bar App
37
+ details: Native SwiftUI app that lives in your menu bar. Auto-connects,
38
+ auto-restarts, shows real-time status.
39
+ - icon: ⚡
40
+ title: MCP Tunnel
41
+ details: Secure WebSocket tunnel powered by the Poke SDK. Only your
42
+ authenticated agent can reach your machine.
38
43
  ---
39
44
 
40
45
  ## Quick install
package/docs/macos-app.md CHANGED
@@ -1,16 +1,26 @@
1
1
  # macOS App
2
2
 
3
- Poke Gate includes a native SwiftUI menu bar app for macOS.
3
+ Poke Gate includes a native SwiftUI menu bar app for macOS 15+ (Sequoia).
4
+
5
+ ## First-run setup
6
+
7
+ On first launch, a **Setup View** guides you through two steps:
8
+
9
+ 1. **Choose access mode** — select Full, Limited, or Sandbox (see [Access modes](#access-modes) below)
10
+ 2. **Grant permissions** — the app checks for Accessibility permission and walks you through enabling it in System Settings
11
+
12
+ The setup view only appears once. You can change the access mode anytime from Settings.
4
13
 
5
14
  ## Menu bar
6
15
 
7
16
  The app runs in the menu bar only — no Dock icon. Click the door icon to see the popover:
8
17
 
9
18
  - **Status** — green dot when connected, yellow when connecting, red on error
10
- - **Personalized** — shows "Connected to your Poke, [name]"
19
+ - **Personalized** — shows "Connected to your Poke, name"
11
20
  - **Recent activity** — last few log entries
12
21
  - **Action buttons** — Logs, Agents, Settings, Restart/Start, Quit
13
- - **About** — version, GitHub link
22
+ - **About** — dynamic version pulled from the app bundle (no hardcoded strings)
23
+ - **Access mode chip** — shows the current mode with quick-switch buttons
14
24
 
15
25
  ### Status icons
16
26
 
@@ -20,6 +30,28 @@ The app runs in the menu bar only — no Dock icon. Click the door icon to see t
20
30
  | 🚪 (closed) | Stopped or connecting |
21
31
  | ⚠️ | Error |
22
32
 
33
+ ## Access modes
34
+
35
+ The macOS app lets you choose an access mode from Settings or the popover. Changing the mode restarts poke-gate automatically.
36
+
37
+ | Mode | What it allows |
38
+ |------|---------------|
39
+ | **Full System Access** | All tools available, subject to chat approval for risky actions |
40
+ | **Limited Permissions** | Safe tools and curated command families only (`ls`, `cat`, `grep`, `curl`, etc.) |
41
+ | **Run in Sandbox** | Broader command support, but writes restricted by macOS `sandbox-exec` to `~/Downloads` and `/tmp` |
42
+
43
+ When **Full** mode is selected, the app shows an Accessibility permission prompt — this permission is required for keyboard/mouse automation and AppleScript tasks.
44
+
45
+ ## Accessibility permission
46
+
47
+ The app uses an **Accessibility-first** permission model. Instead of requesting Full Disk Access, the app checks for Accessibility permission using the native `AXIsProcessTrusted()` API.
48
+
49
+ - A dedicated **AccessibilityPermissionView** shows the current status with a button to open System Settings
50
+ - Permission state refreshes automatically whenever the app regains focus
51
+ - The view updates live — no need to restart the app after granting permission
52
+
53
+ When Full mode is active, this view appears in both the Settings window and the popover to ensure you don't miss it.
54
+
23
55
  ## Settings
24
56
 
25
57
  Open Settings from the popover. The settings window shows:
@@ -27,6 +59,8 @@ Open Settings from the popover. The settings window shows:
27
59
  - **Authentication status** — whether you're signed in via Poke OAuth
28
60
  - **Sign in button** — runs `npx poke login` and opens a browser window
29
61
  - **Connection status** — current state with a Reconnect button
62
+ - **Access mode** — radio buttons for Full, Limited, and Sandbox with descriptions
63
+ - **Accessibility status** — permission check with a direct link to System Settings (in Full mode)
30
64
 
31
65
  ## Logs
32
66
 
@@ -34,6 +68,7 @@ The Logs window shows real-time activity:
34
68
 
35
69
  - Tool calls are highlighted
36
70
  - Errors appear in red
71
+ - Sandbox status shown for each command (`sandbox=os` or `sandbox=none`)
37
72
  - Copy all logs to clipboard
38
73
  - Clear logs
39
74
 
@@ -58,7 +93,7 @@ The app connects automatically on launch if you've previously signed in. If the
58
93
 
59
94
  ## Building from source
60
95
 
61
- Requires macOS 15+ and Xcode 26+.
96
+ Requires macOS 15+ and Xcode 16+.
62
97
 
63
98
  ```bash
64
99
  git clone https://github.com/f/poke-gate.git
package/docs/security.md CHANGED
@@ -1,34 +1,78 @@
1
1
  # Security
2
2
 
3
3
  ::: danger Full shell access
4
- Poke Gate grants **full shell access** to your Poke agent. Understand the implications before running it.
4
+ In **full** mode, Poke Gate grants **full shell access** to your Poke agent. Understand the implications before running it — or choose a more restrictive mode.
5
5
  :::
6
6
 
7
- ## What the agent can do
7
+ ## Access modes
8
8
 
9
- When Poke Gate is running, your Poke agent can:
9
+ Poke Gate supports three access modes that control what tools your agent can use:
10
10
 
11
- - **Run any command** with your user's permissions (`run_command`)
12
- - **Read any file** your user can access (`read_file`, `read_image`)
13
- - **Write any file** your user can access (`write_file`)
14
- - **List any directory** (`list_directory`)
15
- - **Take screenshots** of your screen (`take_screenshot`)
16
- - **See system info** — hostname, memory, uptime (`system_info`)
11
+ ### Full (default)
12
+
13
+ All tools are available. Risky actions (`run_command`, `write_file`, `take_screenshot`) require **chat approval** — the agent must ask you in chat before executing, and you approve with a signed token.
14
+
15
+ ### Limited
16
+
17
+ Only safe, read-only tools are available:
18
+
19
+ - `read_file`, `read_image`, `list_directory`, `system_info`, `network_speed` work normally
20
+ - `run_command` is restricted to a curated allowlist: `ls`, `pwd`, `cat`, `grep`, `find`, `head`, `tail`, `wc`, `sed`, `awk`, `curl`, `jq`, `diff`, and others
21
+ - `write_file` and `take_screenshot` are **disabled**
22
+ - Dangerous patterns (`sudo`, `rm -rf`, etc.) are always blocked
23
+
24
+ ### Sandbox
25
+
26
+ Broader command support than Limited, plus commands like `brew`, `node`, `python`, `ffmpeg`, `mkdir`, `cp`, `mv`:
27
+
28
+ - `run_command` uses macOS `sandbox-exec` to restrict file writes to `~/Downloads` and `/tmp`
29
+ - `write_file` and `take_screenshot` are **disabled**
30
+ - Dangerous patterns are always blocked
31
+
32
+ ### Setting the mode
33
+
34
+ **CLI:**
35
+
36
+ ```bash
37
+ npx poke-gate --mode limited
38
+ npx poke-gate --mode sandbox
39
+ ```
40
+
41
+ **Environment variable:**
42
+
43
+ ```bash
44
+ POKE_GATE_PERMISSION_MODE=sandbox npx poke-gate
45
+ ```
46
+
47
+ **macOS app:** Open Settings and select the access mode. The app restarts automatically when you change it.
48
+
49
+ ## Tool approval flow
50
+
51
+ In **full** mode, risky tools (`run_command`, `write_file`, `take_screenshot`) use an HMAC-signed approval flow:
52
+
53
+ 1. The agent calls the tool — Poke Gate returns `AWAITING_APPROVAL` with a signed token
54
+ 2. The agent asks you in chat to approve
55
+ 3. You approve — the agent re-calls the tool with the approval token
56
+ 4. Optionally, you can `remember_in_session` (same command) or `remember_all_risky` (all risky tools for the session)
17
57
 
18
58
  ## What protects you
19
59
 
20
- - **Authentication** — only your Poke agent (authenticated via your Poke OAuth session) can reach the tunnel. No one else can send tool calls.
21
- - **Tunnel isolation** — the MCP server only listens on `127.0.0.1` (localhost). It's not exposed to the network. The tunnel is the only way to reach it.
22
- - **No persistent access** — when you quit Poke Gate (Ctrl-C or Quit from menu bar), the tunnel closes and the connection is deleted. Your machine is no longer reachable.
23
- - **Connection cleanup** — old connections are deleted before new ones are created, preventing stale tunnels.
60
+ - **Authentication** — only your Poke agent (authenticated via Poke OAuth) can reach the tunnel
61
+ - **Tunnel isolation** — the MCP server only listens on `127.0.0.1` (localhost), not exposed to the network
62
+ - **Chat approval** — risky tools require explicit approval before execution (in full mode)
63
+ - **Access policies** — limited and sandbox modes enforce strict command allowlists
64
+ - **Loop guard** — duplicate or recently-failed commands are suppressed to prevent runaway retries
65
+ - **No persistent access** — quitting Poke Gate closes the tunnel and deletes the connection
66
+ - **Connection cleanup** — old connections are deleted before new ones are created
24
67
 
25
68
  ## Best practices
26
69
 
27
- 1. **Only run on trusted machines** — don't run Poke Gate on shared or public computers.
28
- 2. **Quit when not needed** — close the app when you don't need remote access.
29
- 3. **Review agent scripts** — before installing a community agent, read the code. Agents run with your full user permissions.
30
- 4. **Keep env files secure** — `.env` files in `~/.config/poke-gate/agents/` may contain API tokens. Don't commit them to git.
31
- 5. **Use verbose mode** — run with `--verbose` to see what tools are being called in real time.
70
+ 1. **Choose the right access mode** — use `limited` or `sandbox` if you don't need full shell access.
71
+ 2. **Only run on trusted machines** — don't run Poke Gate on shared or public computers.
72
+ 3. **Quit when not needed** — close the app when you don't need remote access.
73
+ 4. **Review agent scripts** — before installing a community agent, read the code. Agents run with your user permissions.
74
+ 5. **Keep env files secure** — `.env` files in `~/.config/poke-gate/agents/` may contain API tokens. Don't commit them to git.
75
+ 6. **Use verbose mode** — run with `--verbose` to see what tools are being called in real time.
32
76
 
33
77
  ## Reporting issues
34
78
 
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { Poke, getToken } from "poke";
10
10
  import { execSync } from "node:child_process";
11
- import { readFileSync, writeFileSync, existsSync } from "node:fs";
11
+ import { readFileSync, writeFileSync } from "node:fs";
12
12
  import { join } from "node:path";
13
13
  import { homedir } from "node:os";
14
14
 
@@ -17,10 +17,10 @@ if (!token) {
17
17
 
18
18
  function getScreenTime() {
19
19
  try {
20
- const result = execSync(
21
- `defaults read com.apple.ScreenTimeAgent 2>/dev/null || echo "{}"`,
22
- { encoding: "utf-8", timeout: 10000 }
23
- ).trim();
20
+ execSync(`defaults read com.apple.ScreenTimeAgent 2>/dev/null || echo "{}"`, {
21
+ encoding: "utf-8",
22
+ timeout: 10000,
23
+ }).trim();
24
24
 
25
25
  // Fallback: use process list to estimate active apps
26
26
  const ps = execSync(
@@ -42,11 +42,10 @@ function getActiveApps() {
42
42
  end tell
43
43
  return appList as text
44
44
  `;
45
- const result = execSync(`osascript -e '${script}'`, {
45
+ return execSync(`osascript -e '${script}'`, {
46
46
  encoding: "utf-8",
47
47
  timeout: 10000,
48
48
  }).trim();
49
- return result.split(", ");
50
49
  } catch {
51
50
  return [];
52
51
  }
package/macOS ADDED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poke-gate",
3
- "version": "0.1.9",
3
+ "version": "0.2.1",
4
4
  "description": "Expose your machine to your Poke AI assistant via MCP tunnel",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,6 +8,8 @@
8
8
  },
9
9
  "scripts": {
10
10
  "start": "node src/app.js",
11
+ "test": "node --test",
12
+ "test:watch": "node --test --watch",
11
13
  "lint": "eslint .",
12
14
  "lint:fix": "eslint . --fix",
13
15
  "lint:md": "remark . --quiet --frail",