poke-gate 0.1.9 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yml +53 -3
- package/README.md +48 -14
- package/bin/poke-gate.js +17 -0
- package/clients/Poke macOS Gate/Poke macOS Gate/AboutView.swift +7 -1
- package/clients/Poke macOS Gate/Poke macOS Gate/AccessibilityPermissionView.swift +58 -0
- package/clients/Poke macOS Gate/Poke macOS Gate/GateService.swift +354 -23
- package/clients/Poke macOS Gate/Poke macOS Gate/Info.plist +2 -0
- package/clients/Poke macOS Gate/Poke macOS Gate/LogsView.swift +1 -1
- package/clients/Poke macOS Gate/Poke macOS Gate/MacVisualStyle.swift +89 -0
- package/clients/Poke macOS Gate/Poke macOS Gate/PermissionRowView.swift +55 -0
- package/clients/Poke macOS Gate/Poke macOS Gate/Poke_macOS_GateApp.swift +209 -91
- package/clients/Poke macOS Gate/Poke macOS Gate/SettingsView.swift +123 -81
- package/clients/Poke macOS Gate/Poke macOS Gate/SetupView.swift +157 -0
- package/clients/Poke macOS Gate/Poke macOS Gate.xcodeproj/project.pbxproj +28 -8
- package/docs/cli.md +19 -0
- package/docs/getting-started.md +9 -6
- package/docs/index.md +23 -18
- package/docs/macos-app.md +39 -4
- package/docs/security.md +62 -18
- package/examples/agents/battery.30m.js +1 -1
- package/examples/agents/screentime.24h.js +5 -6
- package/package.json +3 -1
- package/src/agents.js +5 -8
- package/src/app.js +3 -3
- package/src/mcp-server.js +502 -27
- package/src/permission-service.js +128 -0
- package/test/mcp-server-access-policy.test.js +40 -0
- package/test/mcp-server-loop-guard.test.js +57 -0
- package/test/mcp-server-sandbox-command.test.js +18 -0
- package/test/permission-service.test.js +97 -0
|
@@ -24,7 +24,10 @@ struct Poke_macOS_GateApp: App {
|
|
|
24
24
|
.onAppear {
|
|
25
25
|
service.autoStartIfNeeded()
|
|
26
26
|
appDelegate.service = service
|
|
27
|
-
|
|
27
|
+
service.startPermissionPolling()
|
|
28
|
+
}
|
|
29
|
+
.onDisappear {
|
|
30
|
+
service.stopPermissionPolling()
|
|
28
31
|
}
|
|
29
32
|
} label: {
|
|
30
33
|
Image(systemName: menuBarIcon)
|
|
@@ -36,6 +39,11 @@ struct Poke_macOS_GateApp: App {
|
|
|
36
39
|
}
|
|
37
40
|
.defaultSize(width: 560, height: 400)
|
|
38
41
|
|
|
42
|
+
Window("Setup", id: "setup") {
|
|
43
|
+
SetupView(service: service)
|
|
44
|
+
}
|
|
45
|
+
.windowResizability(.contentSize)
|
|
46
|
+
|
|
39
47
|
Window("Settings", id: "settings") {
|
|
40
48
|
SettingsView(service: service)
|
|
41
49
|
}
|
|
@@ -96,56 +104,89 @@ struct Poke_macOS_GateApp: App {
|
|
|
96
104
|
struct PopoverContent: View {
|
|
97
105
|
@ObservedObject var service: GateService
|
|
98
106
|
@Environment(\.openWindow) private var openWindow
|
|
107
|
+
@State private var pendingFullMode = false
|
|
99
108
|
|
|
100
109
|
var body: some View {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
110
|
+
if !service.hasCompletedSetup {
|
|
111
|
+
SetupView(service: service)
|
|
112
|
+
} else {
|
|
113
|
+
VStack(spacing: 10) {
|
|
114
|
+
statusSection
|
|
115
|
+
recentActivitySection
|
|
116
|
+
accessModeSection
|
|
117
|
+
actionsSection
|
|
118
|
+
footerSection
|
|
119
|
+
}
|
|
120
|
+
.frame(width: 320)
|
|
121
|
+
.padding(10)
|
|
122
|
+
.onChange(of: service.hasSystemPermissionsGranted) { _, granted in
|
|
123
|
+
if granted && pendingFullMode {
|
|
124
|
+
pendingFullMode = false
|
|
125
|
+
service.setPermissionMode(.full)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
107
130
|
|
|
108
|
-
|
|
109
|
-
|
|
131
|
+
private var statusSection: some View {
|
|
132
|
+
VStack(alignment: .leading, spacing: 6) {
|
|
133
|
+
HStack(spacing: 8) {
|
|
134
|
+
Circle()
|
|
135
|
+
.fill(statusColor)
|
|
136
|
+
.frame(width: 10, height: 10)
|
|
110
137
|
|
|
111
|
-
|
|
112
|
-
|
|
138
|
+
Text(statusText)
|
|
139
|
+
.font(.system(.body, weight: .medium))
|
|
113
140
|
|
|
114
|
-
|
|
115
|
-
Text("This machine is accessible via Poke. Ask your Poke to run commands or read files.")
|
|
116
|
-
.font(.caption)
|
|
117
|
-
.foregroundStyle(.secondary)
|
|
118
|
-
.frame(maxWidth: .infinity, alignment: .leading)
|
|
119
|
-
.fixedSize(horizontal: false, vertical: true)
|
|
120
|
-
} else if service.status == .starting {
|
|
121
|
-
Text("Establishing connection…")
|
|
122
|
-
.font(.caption)
|
|
123
|
-
.foregroundStyle(.secondary)
|
|
124
|
-
.frame(maxWidth: .infinity, alignment: .leading)
|
|
125
|
-
} else if service.status == .error {
|
|
126
|
-
Text("Check Logs for details.")
|
|
127
|
-
.font(.caption)
|
|
128
|
-
.foregroundStyle(.red.opacity(0.8))
|
|
129
|
-
.frame(maxWidth: .infinity, alignment: .leading)
|
|
130
|
-
}
|
|
141
|
+
Spacer()
|
|
131
142
|
}
|
|
132
|
-
.padding(12)
|
|
133
143
|
|
|
134
|
-
|
|
144
|
+
statusMessage
|
|
145
|
+
}
|
|
146
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
147
|
+
.padding(12)
|
|
148
|
+
.macPanelStyle(.neutral, cornerRadius: 12)
|
|
149
|
+
}
|
|
135
150
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
151
|
+
@ViewBuilder
|
|
152
|
+
private var statusMessage: some View {
|
|
153
|
+
switch service.status {
|
|
154
|
+
case .connected:
|
|
155
|
+
Text("This machine is accessible via Poke. Ask your Poke to run commands or read files.")
|
|
156
|
+
.font(.caption)
|
|
157
|
+
.foregroundStyle(.secondary)
|
|
158
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
159
|
+
.fixedSize(horizontal: false, vertical: true)
|
|
160
|
+
case .starting:
|
|
161
|
+
Text("Establishing connection…")
|
|
162
|
+
.font(.caption)
|
|
163
|
+
.foregroundStyle(.secondary)
|
|
164
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
165
|
+
case .error:
|
|
166
|
+
Text("Check Logs for details.")
|
|
167
|
+
.font(.caption)
|
|
168
|
+
.foregroundStyle(.red.opacity(0.8))
|
|
169
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
170
|
+
case .disconnected, .stopped:
|
|
171
|
+
EmptyView()
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private var recentActivitySection: some View {
|
|
176
|
+
VStack(alignment: .leading, spacing: 6) {
|
|
177
|
+
sectionTitle("Recent activity")
|
|
178
|
+
|
|
179
|
+
if service.terminalPreviews.isEmpty {
|
|
180
|
+
Text("No activity yet")
|
|
181
|
+
.font(.caption)
|
|
182
|
+
.foregroundStyle(.secondary)
|
|
183
|
+
} else {
|
|
184
|
+
ForEach(Array(service.terminalPreviews.suffix(4).enumerated()), id: \.element.id) { _, entry in
|
|
185
|
+
HStack(spacing: 6) {
|
|
186
|
+
Circle()
|
|
187
|
+
.fill(entry.exitCode == 0 ? Color.green : (entry.exitCode == nil ? Color.gray : Color.red))
|
|
188
|
+
.frame(width: 5, height: 5)
|
|
189
|
+
Text("$ \(entry.command)")
|
|
149
190
|
.font(.system(size: 9, design: .monospaced))
|
|
150
191
|
.foregroundStyle(.tertiary)
|
|
151
192
|
.lineLimit(1)
|
|
@@ -153,71 +194,117 @@ struct PopoverContent: View {
|
|
|
153
194
|
}
|
|
154
195
|
}
|
|
155
196
|
}
|
|
156
|
-
|
|
157
|
-
|
|
197
|
+
}
|
|
198
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
199
|
+
.padding(12)
|
|
200
|
+
.macPanelStyle(.neutral, cornerRadius: 12)
|
|
201
|
+
}
|
|
158
202
|
|
|
159
|
-
|
|
203
|
+
private var accessModeSection: some View {
|
|
204
|
+
VStack(alignment: .leading, spacing: 8) {
|
|
205
|
+
HStack {
|
|
206
|
+
sectionTitle("Access mode")
|
|
207
|
+
Spacer()
|
|
208
|
+
Text(service.permissionMode.title)
|
|
209
|
+
.font(.caption2)
|
|
210
|
+
.foregroundStyle(.secondary)
|
|
211
|
+
}
|
|
160
212
|
|
|
161
|
-
HStack(spacing:
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
213
|
+
HStack(spacing: 6) {
|
|
214
|
+
ForEach(GateService.PermissionMode.allCases) { mode in
|
|
215
|
+
let isActive = service.permissionMode == mode || (mode == .full && pendingFullMode)
|
|
216
|
+
Button {
|
|
217
|
+
handleModeSelection(mode)
|
|
218
|
+
} label: {
|
|
219
|
+
Text(modeChipTitle(mode))
|
|
220
|
+
.font(.system(size: 10, weight: .semibold))
|
|
221
|
+
.foregroundStyle(isActive ? .white : .secondary)
|
|
222
|
+
.padding(.vertical, 5)
|
|
223
|
+
.padding(.horizontal, 8)
|
|
224
|
+
.background(isActive ? MacVisualStyle.chipActiveFill : MacVisualStyle.chipInactiveFill)
|
|
225
|
+
.clipShape(Capsule())
|
|
226
|
+
}
|
|
227
|
+
.buttonStyle(.plain)
|
|
165
228
|
}
|
|
229
|
+
}
|
|
166
230
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
231
|
+
if service.permissionMode == .full || pendingFullMode {
|
|
232
|
+
AccessibilityPermissionView(service: service)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
236
|
+
.padding(12)
|
|
237
|
+
.macPanelStyle(.neutral, cornerRadius: 12)
|
|
238
|
+
}
|
|
171
239
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
240
|
+
private var actionsSection: some View {
|
|
241
|
+
HStack(spacing: 12) {
|
|
242
|
+
ActionButton(icon: "text.alignleft", label: "Logs") {
|
|
243
|
+
NSApp.activate(ignoringOtherApps: true)
|
|
244
|
+
openWindow(id: "logs")
|
|
245
|
+
}
|
|
176
246
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
} else {
|
|
182
|
-
ActionButton(icon: "play.fill", label: "Start") {
|
|
183
|
-
service.start()
|
|
184
|
-
}
|
|
185
|
-
}
|
|
247
|
+
ActionButton(icon: "bolt.fill", label: "Agents") {
|
|
248
|
+
NSApp.activate(ignoringOtherApps: true)
|
|
249
|
+
openWindow(id: "agents")
|
|
250
|
+
}
|
|
186
251
|
|
|
187
|
-
|
|
252
|
+
ActionButton(icon: "gearshape", label: "Settings") {
|
|
253
|
+
NSApp.activate(ignoringOtherApps: true)
|
|
254
|
+
openWindow(id: "settings")
|
|
255
|
+
}
|
|
188
256
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
257
|
+
if service.status == .connected || service.status == .starting || service.status == .disconnected {
|
|
258
|
+
ActionButton(icon: "arrow.counterclockwise", label: "Restart") {
|
|
259
|
+
service.restart()
|
|
260
|
+
}
|
|
261
|
+
} else {
|
|
262
|
+
ActionButton(icon: "play.fill", label: "Start") {
|
|
263
|
+
service.start()
|
|
194
264
|
}
|
|
195
265
|
}
|
|
196
|
-
.padding(12)
|
|
197
266
|
|
|
198
|
-
|
|
267
|
+
Spacer()
|
|
199
268
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
} label: {
|
|
205
|
-
Text("Poke Gate v0.0.8")
|
|
206
|
-
.font(.caption2)
|
|
207
|
-
.foregroundStyle(.tertiary)
|
|
269
|
+
ActionButton(icon: "xmark.circle", label: "Quit", tint: .secondary) {
|
|
270
|
+
service.stop()
|
|
271
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
|
272
|
+
NSApp.terminate(nil)
|
|
208
273
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
.padding(12)
|
|
277
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
278
|
+
.macPanelStyle(.neutral, cornerRadius: 12)
|
|
279
|
+
}
|
|
212
280
|
|
|
213
|
-
|
|
281
|
+
private var footerSection: some View {
|
|
282
|
+
HStack {
|
|
283
|
+
Button {
|
|
284
|
+
NSApp.activate(ignoringOtherApps: true)
|
|
285
|
+
openWindow(id: "about")
|
|
286
|
+
} label: {
|
|
287
|
+
Text(appVersionText)
|
|
214
288
|
.font(.caption2)
|
|
215
|
-
.foregroundStyle(.
|
|
289
|
+
.foregroundStyle(MacVisualStyle.sectionTitleColor)
|
|
216
290
|
}
|
|
217
|
-
.
|
|
218
|
-
|
|
291
|
+
.buttonStyle(.plain)
|
|
292
|
+
|
|
293
|
+
Spacer()
|
|
294
|
+
|
|
295
|
+
Text("Not affiliated with Poke")
|
|
296
|
+
.font(.caption2)
|
|
297
|
+
.foregroundStyle(MacVisualStyle.sectionTitleColor.opacity(0.7))
|
|
219
298
|
}
|
|
220
|
-
.
|
|
299
|
+
.padding(.horizontal, 6)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private func sectionTitle(_ text: String) -> some View {
|
|
303
|
+
Text(text)
|
|
304
|
+
.font(.caption2)
|
|
305
|
+
.foregroundStyle(MacVisualStyle.sectionTitleColor)
|
|
306
|
+
.textCase(.uppercase)
|
|
307
|
+
.tracking(0.5)
|
|
221
308
|
}
|
|
222
309
|
|
|
223
310
|
private var statusText: String {
|
|
@@ -242,6 +329,37 @@ struct PopoverContent: View {
|
|
|
242
329
|
case .stopped: .gray.opacity(0.5)
|
|
243
330
|
}
|
|
244
331
|
}
|
|
332
|
+
|
|
333
|
+
private func modeChipTitle(_ mode: GateService.PermissionMode) -> String {
|
|
334
|
+
switch mode {
|
|
335
|
+
case .full: return "Full"
|
|
336
|
+
case .limited: return "Limited"
|
|
337
|
+
case .sandbox: return "Sandbox"
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private func handleModeSelection(_ mode: GateService.PermissionMode) {
|
|
342
|
+
guard mode != service.permissionMode else { return }
|
|
343
|
+
|
|
344
|
+
if mode == .full && !service.hasSystemPermissionsGranted {
|
|
345
|
+
pendingFullMode = true
|
|
346
|
+
service.openSystemPermission(.accessibility)
|
|
347
|
+
} else {
|
|
348
|
+
pendingFullMode = false
|
|
349
|
+
service.setPermissionMode(mode)
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
private var appVersionText: String {
|
|
354
|
+
let short = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "0.0.0"
|
|
355
|
+
let build = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String
|
|
356
|
+
|
|
357
|
+
if let build, !build.isEmpty, build != short {
|
|
358
|
+
return "Poke Gate v\(short) (\(build))"
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return "Poke Gate v\(short)"
|
|
362
|
+
}
|
|
245
363
|
}
|
|
246
364
|
|
|
247
365
|
struct ActionButton: View {
|
|
@@ -8,90 +8,14 @@ struct SettingsView: View {
|
|
|
8
8
|
|
|
9
9
|
var body: some View {
|
|
10
10
|
VStack(alignment: .leading, spacing: 16) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
.foregroundStyle(.tertiary)
|
|
15
|
-
.textCase(.uppercase)
|
|
16
|
-
.tracking(0.5)
|
|
17
|
-
|
|
18
|
-
HStack(spacing: 8) {
|
|
19
|
-
Image(systemName: service.hasPokeLoginCredentials
|
|
20
|
-
? "checkmark.shield.fill" : "shield.slash")
|
|
21
|
-
.foregroundStyle(service.hasPokeLoginCredentials ? .green : .orange)
|
|
22
|
-
.font(.title3)
|
|
23
|
-
|
|
24
|
-
VStack(alignment: .leading, spacing: 2) {
|
|
25
|
-
Text(service.hasPokeLoginCredentials
|
|
26
|
-
? "Signed in via Poke"
|
|
27
|
-
: "Not signed in")
|
|
28
|
-
.font(.subheadline)
|
|
29
|
-
.fontWeight(.medium)
|
|
30
|
-
|
|
31
|
-
if service.hasPokeLoginCredentials {
|
|
32
|
-
Text("Your Poke session is active.")
|
|
33
|
-
.font(.caption)
|
|
34
|
-
.foregroundStyle(.secondary)
|
|
35
|
-
} else {
|
|
36
|
-
Text("Run this command in Terminal to sign in:")
|
|
37
|
-
.font(.caption)
|
|
38
|
-
.foregroundStyle(.secondary)
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
.padding(10)
|
|
43
|
-
.frame(maxWidth: .infinity, alignment: .leading)
|
|
44
|
-
.background(.quaternary.opacity(0.5))
|
|
45
|
-
.cornerRadius(8)
|
|
46
|
-
|
|
47
|
-
if !service.hasPokeLoginCredentials {
|
|
48
|
-
Button {
|
|
49
|
-
service.runPokeLogin()
|
|
50
|
-
} label: {
|
|
51
|
-
Label("Sign in with Poke", systemImage: "person.crop.circle.badge.plus")
|
|
52
|
-
}
|
|
53
|
-
.controlSize(.large)
|
|
54
|
-
|
|
55
|
-
Text("Opens a browser window to sign in.")
|
|
56
|
-
.font(.caption)
|
|
57
|
-
.foregroundStyle(.secondary)
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
VStack(alignment: .leading, spacing: 8) {
|
|
62
|
-
Text("CONNECTION")
|
|
63
|
-
.font(.caption2)
|
|
64
|
-
.foregroundStyle(.tertiary)
|
|
65
|
-
.textCase(.uppercase)
|
|
66
|
-
.tracking(0.5)
|
|
67
|
-
|
|
68
|
-
HStack(spacing: 8) {
|
|
69
|
-
Circle()
|
|
70
|
-
.fill(connectionColor)
|
|
71
|
-
.frame(width: 8, height: 8)
|
|
72
|
-
|
|
73
|
-
Text(service.status.rawValue)
|
|
74
|
-
.font(.subheadline)
|
|
75
|
-
|
|
76
|
-
Spacer()
|
|
77
|
-
|
|
78
|
-
Button {
|
|
79
|
-
service.restart()
|
|
80
|
-
} label: {
|
|
81
|
-
Label("Reconnect", systemImage: "arrow.counterclockwise")
|
|
82
|
-
.font(.caption)
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
.padding(10)
|
|
86
|
-
.frame(maxWidth: .infinity, alignment: .leading)
|
|
87
|
-
.background(.quaternary.opacity(0.5))
|
|
88
|
-
.cornerRadius(8)
|
|
89
|
-
}
|
|
11
|
+
authenticationSection
|
|
12
|
+
accessModeSection
|
|
13
|
+
connectionSection
|
|
90
14
|
|
|
91
15
|
VStack(alignment: .leading, spacing: 8) {
|
|
92
16
|
Text("GENERAL")
|
|
93
17
|
.font(.caption2)
|
|
94
|
-
.foregroundStyle(.
|
|
18
|
+
.foregroundStyle(MacVisualStyle.sectionTitleColor)
|
|
95
19
|
.textCase(.uppercase)
|
|
96
20
|
.tracking(0.5)
|
|
97
21
|
|
|
@@ -119,7 +43,125 @@ struct SettingsView: View {
|
|
|
119
43
|
}
|
|
120
44
|
}
|
|
121
45
|
.padding(20)
|
|
122
|
-
.frame(width:
|
|
46
|
+
.frame(width: 430)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private var authenticationSection: some View {
|
|
50
|
+
VStack(alignment: .leading, spacing: 8) {
|
|
51
|
+
sectionTitle("AUTHENTICATION")
|
|
52
|
+
|
|
53
|
+
HStack(spacing: 8) {
|
|
54
|
+
Image(systemName: service.hasPokeLoginCredentials ? "checkmark.shield.fill" : "shield.slash")
|
|
55
|
+
.foregroundStyle(service.hasPokeLoginCredentials ? .green : .orange)
|
|
56
|
+
.font(.title3)
|
|
57
|
+
|
|
58
|
+
VStack(alignment: .leading, spacing: 2) {
|
|
59
|
+
Text(service.hasPokeLoginCredentials ? "Signed in via Poke" : "Not signed in")
|
|
60
|
+
.font(.subheadline)
|
|
61
|
+
.fontWeight(.medium)
|
|
62
|
+
|
|
63
|
+
Text(service.hasPokeLoginCredentials ? "Your Poke session is active." : "Run this command in Terminal to sign in:")
|
|
64
|
+
.font(.caption)
|
|
65
|
+
.foregroundStyle(.secondary)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
.padding(10)
|
|
69
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
70
|
+
.macPanelStyle(.neutral, cornerRadius: 8)
|
|
71
|
+
|
|
72
|
+
if !service.hasPokeLoginCredentials {
|
|
73
|
+
Button {
|
|
74
|
+
service.runPokeLogin()
|
|
75
|
+
} label: {
|
|
76
|
+
Label("Sign in with Poke", systemImage: "person.crop.circle.badge.plus")
|
|
77
|
+
}
|
|
78
|
+
.controlSize(.large)
|
|
79
|
+
|
|
80
|
+
Text("Opens a browser window to sign in.")
|
|
81
|
+
.font(.caption)
|
|
82
|
+
.foregroundStyle(.secondary)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private var accessModeSection: some View {
|
|
88
|
+
VStack(alignment: .leading, spacing: 10) {
|
|
89
|
+
sectionTitle("ACCESS MODE")
|
|
90
|
+
|
|
91
|
+
VStack(spacing: 8) {
|
|
92
|
+
ForEach(GateService.PermissionMode.allCases) { mode in
|
|
93
|
+
permissionModeRow(mode)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if service.permissionMode == .full {
|
|
98
|
+
AccessibilityPermissionView(service: service)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private func permissionModeRow(_ mode: GateService.PermissionMode) -> some View {
|
|
104
|
+
Button {
|
|
105
|
+
service.setPermissionMode(mode)
|
|
106
|
+
} label: {
|
|
107
|
+
HStack(alignment: .top, spacing: 10) {
|
|
108
|
+
Image(systemName: service.permissionMode == mode ? "checkmark.circle.fill" : "circle")
|
|
109
|
+
.font(.headline)
|
|
110
|
+
.foregroundStyle(service.permissionMode == mode ? .green : .secondary)
|
|
111
|
+
|
|
112
|
+
VStack(alignment: .leading, spacing: 2) {
|
|
113
|
+
Text(mode.title)
|
|
114
|
+
.font(.subheadline)
|
|
115
|
+
.fontWeight(.medium)
|
|
116
|
+
.foregroundStyle(.primary)
|
|
117
|
+
|
|
118
|
+
Text(mode.subtitle)
|
|
119
|
+
.font(.caption)
|
|
120
|
+
.foregroundStyle(.secondary)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
Spacer()
|
|
124
|
+
}
|
|
125
|
+
.padding(10)
|
|
126
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
127
|
+
.macPanelStyle(service.permissionMode == mode ? .selected : .neutral)
|
|
128
|
+
}
|
|
129
|
+
.buttonStyle(.plain)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private var connectionSection: some View {
|
|
133
|
+
VStack(alignment: .leading, spacing: 8) {
|
|
134
|
+
sectionTitle("CONNECTION")
|
|
135
|
+
|
|
136
|
+
HStack(spacing: 8) {
|
|
137
|
+
Circle()
|
|
138
|
+
.fill(connectionColor)
|
|
139
|
+
.frame(width: 8, height: 8)
|
|
140
|
+
|
|
141
|
+
Text(service.status.rawValue)
|
|
142
|
+
.font(.subheadline)
|
|
143
|
+
|
|
144
|
+
Spacer()
|
|
145
|
+
|
|
146
|
+
Button {
|
|
147
|
+
service.restart()
|
|
148
|
+
} label: {
|
|
149
|
+
Label("Reconnect", systemImage: "arrow.counterclockwise")
|
|
150
|
+
.font(.caption)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
.padding(10)
|
|
154
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
155
|
+
.macPanelStyle(.neutral, cornerRadius: 8)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private func sectionTitle(_ text: String) -> some View {
|
|
160
|
+
Text(text)
|
|
161
|
+
.font(.caption2)
|
|
162
|
+
.foregroundStyle(MacVisualStyle.sectionTitleColor)
|
|
163
|
+
.textCase(.uppercase)
|
|
164
|
+
.tracking(0.5)
|
|
123
165
|
}
|
|
124
166
|
|
|
125
167
|
private var connectionColor: Color {
|