poke-gate 0.2.0 → 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.
package/Gate.app ADDED
File without changes
@@ -32,9 +32,9 @@ class GateService: ObservableObject {
32
32
 
33
33
  var subtitle: String {
34
34
  switch self {
35
- case .full: return "All tools are available, subject to chat approval."
36
- case .limited: return "Safe tools and curated command families only."
37
- case .sandbox: return "Broader command support, but strictly limited by macOS sandbox-exec policies."
35
+ case .full: return "All tools enabled. Risky actions require chat approval."
36
+ case .limited: return "Read-only tools and safe commands (ls, cat, grep, curl…). File writes and screenshots disabled."
37
+ case .sandbox: return "Commands like brew, node, python, ffmpeg allowed. Writes restricted to ~/Downloads and /tmp."
38
38
  }
39
39
  }
40
40
  }
@@ -108,10 +108,26 @@ class GateService: ObservableObject {
108
108
  private var activeTerminalPreviewId: UUID?
109
109
  private var permissionPollingTimer: Timer?
110
110
 
111
+ private var appActiveObserver: NSObjectProtocol?
112
+
111
113
  init() {
112
114
  self.permissionMode = Self.loadPermissionModeStatic()
113
115
  self.hasCompletedSetup = Self.loadHasCompletedSetupStatic()
114
116
  refreshSystemPermissions()
117
+
118
+ appActiveObserver = NotificationCenter.default.addObserver(
119
+ forName: NSApplication.didBecomeActiveNotification,
120
+ object: nil,
121
+ queue: .main
122
+ ) { [weak self] _ in
123
+ self?.refreshSystemPermissions()
124
+ }
125
+ }
126
+
127
+ deinit {
128
+ if let observer = appActiveObserver {
129
+ NotificationCenter.default.removeObserver(observer)
130
+ }
115
131
  }
116
132
 
117
133
  var hasSystemPermissionsGranted: Bool {
@@ -177,20 +193,24 @@ class GateService: ObservableObject {
177
193
  }
178
194
 
179
195
  let requested = missingSystemPermissions.map { $0.title }.joined(separator: ", ")
180
- appendLog("Opening macOS settings for: \(requested).")
196
+ appendLog("Requesting macOS permissions: \(requested).")
181
197
 
182
198
  for permission in missingSystemPermissions {
183
- guard let url = URL(string: permission.settingsURL) else { continue }
184
- NSWorkspace.shared.open(url)
199
+ openSystemPermission(permission)
185
200
  }
186
-
187
- appendLog("Opened Privacy settings for missing permissions.")
188
201
  }
189
202
 
190
203
  func refreshSystemPermissions() {
204
+ let previous = systemPermissionStatuses
191
205
  systemPermissionStatuses = SystemPermission.allCases.map { permission in
192
206
  SystemPermissionStatus(permission: permission, isGranted: isPermissionGranted(permission))
193
207
  }
208
+ for status in systemPermissionStatuses {
209
+ let was = previous.first(where: { $0.permission == status.permission })
210
+ if was == nil || was?.isGranted != status.isGranted {
211
+ appendLog("\(status.permission.title): \(status.isGranted ? "granted" : "not granted")")
212
+ }
213
+ }
194
214
  }
195
215
 
196
216
  func startPermissionPolling() {
@@ -208,8 +228,11 @@ class GateService: ObservableObject {
208
228
  }
209
229
 
210
230
  func openSystemPermission(_ permission: SystemPermission) {
211
- guard let url = URL(string: permission.settingsURL) else { return }
212
- NSWorkspace.shared.open(url)
231
+ switch permission {
232
+ case .accessibility:
233
+ let options = [kAXTrustedCheckOptionPrompt.takeUnretainedValue(): true] as CFDictionary
234
+ _ = AXIsProcessTrustedWithOptions(options)
235
+ }
213
236
  }
214
237
 
215
238
  func captureAndSend() {
@@ -637,7 +660,19 @@ class GateService: ObservableObject {
637
660
  private func isPermissionGranted(_ permission: SystemPermission) -> Bool {
638
661
  switch permission {
639
662
  case .accessibility:
640
- return AXIsProcessTrusted()
663
+ if AXIsProcessTrusted() { return true }
664
+ // AXIsProcessTrusted() can return false despite the toggle being ON
665
+ // in System Settings when the TCC entry's code signature doesn't match
666
+ // the running binary (ad-hoc signing, rebuild from Xcode, etc.).
667
+ // Probe the API directly as a fallback.
668
+ let systemWide = AXUIElementCreateSystemWide()
669
+ var value: AnyObject?
670
+ let result = AXUIElementCopyAttributeValue(
671
+ systemWide,
672
+ kAXFocusedApplicationAttribute as CFString,
673
+ &value
674
+ )
675
+ return result == .success || result == .noValue || result == .attributeUnsupported
641
676
  }
642
677
  }
643
678
 
@@ -176,11 +176,7 @@ struct PopoverContent: View {
176
176
  VStack(alignment: .leading, spacing: 6) {
177
177
  sectionTitle("Recent activity")
178
178
 
179
- if service.terminalPreviews.isEmpty {
180
- Text("No activity yet")
181
- .font(.caption)
182
- .foregroundStyle(.secondary)
183
- } else {
179
+ if !service.terminalPreviews.isEmpty {
184
180
  ForEach(Array(service.terminalPreviews.suffix(4).enumerated()), id: \.element.id) { _, entry in
185
181
  HStack(spacing: 6) {
186
182
  Circle()
@@ -193,6 +189,18 @@ struct PopoverContent: View {
193
189
  .truncationMode(.tail)
194
190
  }
195
191
  }
192
+ } else if !service.logs.isEmpty {
193
+ ForEach(Array(service.logs.suffix(4).enumerated()), id: \.offset) { _, log in
194
+ Text(log)
195
+ .font(.system(size: 9, design: .monospaced))
196
+ .foregroundStyle(.tertiary)
197
+ .lineLimit(1)
198
+ .truncationMode(.tail)
199
+ }
200
+ } else {
201
+ Text("No activity yet")
202
+ .font(.caption)
203
+ .foregroundStyle(.secondary)
196
204
  }
197
205
  }
198
206
  .frame(maxWidth: .infinity, alignment: .leading)
@@ -228,6 +236,11 @@ struct PopoverContent: View {
228
236
  }
229
237
  }
230
238
 
239
+ Text(activeModeDescription)
240
+ .font(.caption2)
241
+ .foregroundStyle(.secondary)
242
+ .fixedSize(horizontal: false, vertical: true)
243
+
231
244
  if service.permissionMode == .full || pendingFullMode {
232
245
  AccessibilityPermissionView(service: service)
233
246
  }
@@ -237,6 +250,18 @@ struct PopoverContent: View {
237
250
  .macPanelStyle(.neutral, cornerRadius: 12)
238
251
  }
239
252
 
253
+ private var activeModeDescription: String {
254
+ let mode = pendingFullMode ? GateService.PermissionMode.full : service.permissionMode
255
+ switch mode {
256
+ case .full:
257
+ return "All tools enabled. Risky actions require chat approval."
258
+ case .limited:
259
+ return "Read-only tools and safe commands only (ls, cat, grep, curl…). File writes and screenshots are disabled."
260
+ case .sandbox:
261
+ return "Broader commands (brew, node, python, ffmpeg…) but file writes restricted to ~/Downloads and /tmp via macOS sandbox."
262
+ }
263
+ }
264
+
240
265
  private var actionsSection: some View {
241
266
  HStack(spacing: 12) {
242
267
  ActionButton(icon: "text.alignleft", label: "Logs") {
@@ -44,6 +44,8 @@ struct SettingsView: View {
44
44
  }
45
45
  .padding(20)
46
46
  .frame(width: 430)
47
+ .onAppear { service.startPermissionPolling() }
48
+ .onDisappear { service.stopPermissionPolling() }
47
49
  }
48
50
 
49
51
  private var authenticationSection: some View {
@@ -174,7 +174,7 @@
174
174
  COPY_PHASE_STRIP = NO;
175
175
  DEAD_CODE_STRIPPING = YES;
176
176
  DEBUG_INFORMATION_FORMAT = dwarf;
177
- DEVELOPMENT_TEAM = RJA7656U34;
177
+ DEVELOPMENT_TEAM = "";
178
178
  ENABLE_STRICT_OBJC_MSGSEND = YES;
179
179
  ENABLE_TESTABILITY = YES;
180
180
  ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -240,7 +240,7 @@
240
240
  COPY_PHASE_STRIP = NO;
241
241
  DEAD_CODE_STRIPPING = YES;
242
242
  DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
243
- DEVELOPMENT_TEAM = RJA7656U34;
243
+ DEVELOPMENT_TEAM = "";
244
244
  ENABLE_NS_ASSERTIONS = NO;
245
245
  ENABLE_STRICT_OBJC_MSGSEND = YES;
246
246
  ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -272,7 +272,7 @@
272
272
  COMBINE_HIDPI_IMAGES = YES;
273
273
  CURRENT_PROJECT_VERSION = 1;
274
274
  DEAD_CODE_STRIPPING = YES;
275
- DEVELOPMENT_TEAM = RJA7656U34;
275
+ DEVELOPMENT_TEAM = "";
276
276
  ENABLE_APP_SANDBOX = NO;
277
277
  ENABLE_HARDENED_RUNTIME = NO;
278
278
  ENABLE_PREVIEWS = YES;
@@ -286,7 +286,7 @@
286
286
  "@executable_path/../Frameworks",
287
287
  );
288
288
  MACOSX_DEPLOYMENT_TARGET = 15.0;
289
- MARKETING_VERSION = 0.1.8;
289
+ MARKETING_VERSION = 0.2.0;
290
290
  PRODUCT_BUNDLE_IDENTIFIER = "dev.fka.Poke-macOS-Gate";
291
291
  PRODUCT_NAME = "$(TARGET_NAME)";
292
292
  REGISTER_APP_GROUPS = YES;
@@ -322,7 +322,7 @@
322
322
  "@executable_path/../Frameworks",
323
323
  );
324
324
  MACOSX_DEPLOYMENT_TARGET = 15.0;
325
- MARKETING_VERSION = 0.1.9;
325
+ MARKETING_VERSION = 0.2.0;
326
326
  PRODUCT_BUNDLE_IDENTIFIER = "dev.fka.Poke-macOS-Gate";
327
327
  PRODUCT_NAME = "$(TARGET_NAME)";
328
328
  REGISTER_APP_GROUPS = YES;
package/macOS ADDED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poke-gate",
3
- "version": "0.2.0",
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": {
package/src/app.js CHANGED
@@ -115,14 +115,38 @@ async function main() {
115
115
  await connectWithRetry(mcpUrl, token);
116
116
  }
117
117
 
118
+ function buildAccessModeMessage(mode) {
119
+ switch (mode) {
120
+ case "limited":
121
+ return (
122
+ "Access mode: Limited. " +
123
+ "You can read files, list directories, and run safe read-only commands (ls, cat, grep, curl, jq…). " +
124
+ "You cannot write files, take screenshots, or run other commands."
125
+ );
126
+ case "sandbox":
127
+ return (
128
+ "Access mode: Sandbox. " +
129
+ "You can read files, list directories, and run commands like brew, node, python, ffmpeg, curl, and more. " +
130
+ "File writes are restricted to ~/Downloads and /tmp by macOS sandbox. Screenshots are disabled."
131
+ );
132
+ default:
133
+ return (
134
+ "Access mode: Full. " +
135
+ "You can run any shell command, read and write files, list directories, take screenshots, and check system info. " +
136
+ "Risky actions (commands, file writes, screenshots) require user approval in chat before execution."
137
+ );
138
+ }
139
+ }
140
+
118
141
  async function notifyPoke(connectionId, token) {
119
142
  try {
143
+ const mode = getPermissionMode();
120
144
  const poke = new Poke({ token });
121
145
  await poke.sendMessage(
122
146
  `Hey! I've connected my computer to you via Poke Gate (tunnel: ${connectionId}). ` +
123
- `You can now run commands, read and write files, list directories, take screenshots, and check system info on my machine. ` +
124
- `Just use the tools whenever I ask you to do something on my computer.` +
125
- `Now reply me with "now I am connected to your computer" but everytime write those replies in most creativev fun way.`
147
+ `${buildAccessModeMessage(mode)} ` +
148
+ `Just use the tools whenever I ask you to do something on my computer. ` +
149
+ `Now reply me with "now I am connected to your computer" but everytime write those replies in most creative fun way.`
126
150
  );
127
151
  log("Notified Poke agent about connection.");
128
152
  } catch (err) {