poke-gate 0.1.4 → 0.1.5
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/clients/Poke macOS Gate/Poke macOS Gate/AgentsView.swift +59 -4
- package/clients/Poke macOS Gate/Poke macOS Gate/GateService.swift +83 -17
- package/clients/Poke macOS Gate/Poke macOS Gate.xcodeproj/project.pbxproj +8 -2
- package/clients/Poke macOS Gate/Poke macOS Gate.xcodeproj/project.xcworkspace/xcuserdata/fka.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/docs/index.md +2 -2
- package/package.json +1 -1
|
@@ -25,6 +25,8 @@ class AgentsViewModel: ObservableObject {
|
|
|
25
25
|
@Published var selectedAgent: AgentFile?
|
|
26
26
|
@Published var editorContent: String = ""
|
|
27
27
|
@Published var showingEnv: Bool = false
|
|
28
|
+
@Published var isRunning: Bool = false
|
|
29
|
+
@Published var lastRunOutput: String = ""
|
|
28
30
|
|
|
29
31
|
private var fileWatcher: DispatchSourceFileSystemObject?
|
|
30
32
|
private var dirFD: Int32 = -1
|
|
@@ -108,15 +110,20 @@ class AgentsViewModel: ObservableObject {
|
|
|
108
110
|
}
|
|
109
111
|
|
|
110
112
|
func select(_ agent: AgentFile) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
113
|
+
DispatchQueue.main.async {
|
|
114
|
+
self.selectedAgent = agent
|
|
115
|
+
self.showingEnv = false
|
|
116
|
+
self.loadContent()
|
|
117
|
+
}
|
|
114
118
|
}
|
|
115
119
|
|
|
116
120
|
func loadContent() {
|
|
117
121
|
guard let agent = selectedAgent else { return }
|
|
118
122
|
let url = showingEnv ? agent.envPath : agent.path
|
|
119
|
-
|
|
123
|
+
let content = (try? String(contentsOf: url, encoding: .utf8)) ?? (showingEnv ? "# No .env file yet\n" : "")
|
|
124
|
+
DispatchQueue.main.async {
|
|
125
|
+
self.editorContent = content
|
|
126
|
+
}
|
|
120
127
|
}
|
|
121
128
|
|
|
122
129
|
func save() {
|
|
@@ -169,6 +176,45 @@ class AgentsViewModel: ObservableObject {
|
|
|
169
176
|
}
|
|
170
177
|
}
|
|
171
178
|
|
|
179
|
+
func runAgent(_ agent: AgentFile) {
|
|
180
|
+
guard !isRunning else { return }
|
|
181
|
+
isRunning = true
|
|
182
|
+
lastRunOutput = ""
|
|
183
|
+
|
|
184
|
+
let fullPath = GateService().shellPath()
|
|
185
|
+
let proc = Process()
|
|
186
|
+
let pipe = Pipe()
|
|
187
|
+
|
|
188
|
+
proc.executableURL = URL(fileURLWithPath: "/bin/zsh")
|
|
189
|
+
proc.arguments = ["-c", "npx -y poke-gate run-agent \(agent.agentId)"]
|
|
190
|
+
proc.environment = ["HOME": NSHomeDirectory(), "PATH": fullPath]
|
|
191
|
+
proc.standardOutput = pipe
|
|
192
|
+
proc.standardError = pipe
|
|
193
|
+
proc.currentDirectoryURL = FileManager.default.homeDirectoryForCurrentUser
|
|
194
|
+
|
|
195
|
+
let handle = pipe.fileHandleForReading
|
|
196
|
+
handle.readabilityHandler = { [weak self] fh in
|
|
197
|
+
let data = fh.availableData
|
|
198
|
+
guard !data.isEmpty, let text = String(data: data, encoding: .utf8) else { return }
|
|
199
|
+
DispatchQueue.main.async {
|
|
200
|
+
self?.lastRunOutput += text
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
proc.terminationHandler = { [weak self] _ in
|
|
205
|
+
DispatchQueue.main.async {
|
|
206
|
+
self?.isRunning = false
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
do {
|
|
211
|
+
try proc.run()
|
|
212
|
+
} catch {
|
|
213
|
+
isRunning = false
|
|
214
|
+
lastRunOutput = "Failed to run: \(error.localizedDescription)"
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
172
218
|
func deleteAgent(_ agent: AgentFile) {
|
|
173
219
|
try? FileManager.default.removeItem(at: agent.path)
|
|
174
220
|
if agent.hasEnv {
|
|
@@ -301,6 +347,15 @@ struct AgentDetailView: View {
|
|
|
301
347
|
}
|
|
302
348
|
}
|
|
303
349
|
}
|
|
350
|
+
|
|
351
|
+
Button {
|
|
352
|
+
viewModel.runAgent(agent)
|
|
353
|
+
} label: {
|
|
354
|
+
Label(viewModel.isRunning ? "Running…" : "Run", systemImage: "play.fill")
|
|
355
|
+
.font(.caption)
|
|
356
|
+
}
|
|
357
|
+
.disabled(viewModel.isRunning)
|
|
358
|
+
.padding(.leading, 4)
|
|
304
359
|
}
|
|
305
360
|
.padding(.horizontal, 12)
|
|
306
361
|
.padding(.vertical, 8)
|
|
@@ -32,12 +32,13 @@ class GateService: ObservableObject {
|
|
|
32
32
|
|
|
33
33
|
func runPokeLogin() {
|
|
34
34
|
let fullPath = shellPath()
|
|
35
|
+
let npxBin = findNpx()
|
|
35
36
|
let proc = Process()
|
|
36
37
|
proc.executableURL = URL(fileURLWithPath: "/bin/zsh")
|
|
37
|
-
proc.arguments = ["-c", "
|
|
38
|
+
proc.arguments = ["-c", "\(npxBin) -y poke@latest login"]
|
|
38
39
|
proc.environment = ["HOME": NSHomeDirectory(), "PATH": fullPath]
|
|
39
40
|
try? proc.run()
|
|
40
|
-
appendLog("Launched poke login — check your browser.")
|
|
41
|
+
appendLog("Launched poke login (npx: \(npxBin)) — check your browser.")
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
func autoStartIfNeeded() {
|
|
@@ -90,19 +91,82 @@ class GateService: ObservableObject {
|
|
|
90
91
|
}
|
|
91
92
|
}
|
|
92
93
|
|
|
93
|
-
|
|
94
|
-
let
|
|
95
|
-
let
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
94
|
+
func shellPath() -> String {
|
|
95
|
+
let home = NSHomeDirectory()
|
|
96
|
+
let fallback = "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin"
|
|
97
|
+
|
|
98
|
+
// Try multiple shells/strategies to get PATH
|
|
99
|
+
let strategies: [(String, [String])] = [
|
|
100
|
+
("/bin/zsh", ["-ilc", "echo $PATH"]),
|
|
101
|
+
("/bin/zsh", ["-lc", "echo $PATH"]),
|
|
102
|
+
("/bin/bash", ["-lc", "echo $PATH"]),
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
for (shell, args) in strategies {
|
|
106
|
+
let proc = Process()
|
|
107
|
+
let pipe = Pipe()
|
|
108
|
+
proc.executableURL = URL(fileURLWithPath: shell)
|
|
109
|
+
proc.arguments = args
|
|
110
|
+
proc.standardOutput = pipe
|
|
111
|
+
proc.standardError = FileHandle.nullDevice
|
|
112
|
+
proc.environment = ["HOME": home]
|
|
113
|
+
do {
|
|
114
|
+
try proc.run()
|
|
115
|
+
proc.waitUntilExit()
|
|
116
|
+
if proc.terminationStatus == 0 {
|
|
117
|
+
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
|
118
|
+
if let path = String(data: data, encoding: .utf8)?
|
|
119
|
+
.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
120
|
+
!path.isEmpty {
|
|
121
|
+
return path
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
} catch {
|
|
125
|
+
continue
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Fallback: build PATH from common locations
|
|
130
|
+
var paths = fallback.split(separator: ":").map(String.init)
|
|
131
|
+
|
|
132
|
+
let commonDirs = [
|
|
133
|
+
"\(home)/.nvm/versions/node",
|
|
134
|
+
"\(home)/.volta/bin",
|
|
135
|
+
"\(home)/.fnm/aliases/default/bin",
|
|
136
|
+
"\(home)/.local/bin",
|
|
137
|
+
"\(home)/.cargo/bin",
|
|
138
|
+
"/opt/homebrew/bin",
|
|
139
|
+
"/usr/local/bin",
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
for dir in commonDirs {
|
|
143
|
+
if FileManager.default.fileExists(atPath: dir) {
|
|
144
|
+
if dir.contains(".nvm") {
|
|
145
|
+
// Find the latest node version in nvm
|
|
146
|
+
if let versions = try? FileManager.default.contentsOfDirectory(atPath: dir) {
|
|
147
|
+
if let latest = versions.sorted().last {
|
|
148
|
+
let binPath = "\(dir)/\(latest)/bin"
|
|
149
|
+
if !paths.contains(binPath) { paths.insert(binPath, at: 0) }
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} else if !paths.contains(dir) {
|
|
153
|
+
paths.insert(dir, at: 0)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return paths.joined(separator: ":")
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private func findNpx() -> String {
|
|
162
|
+
let path = shellPath()
|
|
163
|
+
for dir in path.split(separator: ":") {
|
|
164
|
+
let npxPath = "\(dir)/npx"
|
|
165
|
+
if FileManager.default.isExecutableFile(atPath: npxPath) {
|
|
166
|
+
return npxPath
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return "npx"
|
|
106
170
|
}
|
|
107
171
|
|
|
108
172
|
private func launchProcess() {
|
|
@@ -112,15 +176,17 @@ class GateService: ObservableObject {
|
|
|
112
176
|
appendLog("Starting poke-gate…")
|
|
113
177
|
|
|
114
178
|
let fullPath = shellPath()
|
|
179
|
+
let npxBin = findNpx()
|
|
180
|
+
|
|
181
|
+
appendLog("Using npx at: \(npxBin)")
|
|
115
182
|
|
|
116
183
|
let proc = Process()
|
|
117
184
|
let pipe = Pipe()
|
|
118
185
|
|
|
119
186
|
proc.executableURL = URL(fileURLWithPath: "/bin/zsh")
|
|
120
|
-
proc.arguments = ["-c", "
|
|
187
|
+
proc.arguments = ["-c", "\(npxBin) -y poke-gate --verbose"]
|
|
121
188
|
proc.environment = ProcessInfo.processInfo.environment.merging(
|
|
122
189
|
[
|
|
123
|
-
"POKE_API_KEY": resolveToken() ?? "",
|
|
124
190
|
"PATH": fullPath,
|
|
125
191
|
],
|
|
126
192
|
uniquingKeysWith: { _, new in new }
|
|
@@ -259,12 +259,15 @@
|
|
|
259
259
|
ENABLE_PREVIEWS = YES;
|
|
260
260
|
GENERATE_INFOPLIST_FILE = YES;
|
|
261
261
|
INFOPLIST_FILE = "Poke macOS Gate/Info.plist";
|
|
262
|
+
INFOPLIST_KEY_CFBundleDisplayName = "Poke macOS Gate";
|
|
263
|
+
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
|
262
264
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
|
263
265
|
LD_RUNPATH_SEARCH_PATHS = (
|
|
264
266
|
"$(inherited)",
|
|
265
267
|
"@executable_path/../Frameworks",
|
|
266
268
|
);
|
|
267
|
-
|
|
269
|
+
MACOSX_DEPLOYMENT_TARGET = 26.0;
|
|
270
|
+
MARKETING_VERSION = 0.1.5;
|
|
268
271
|
PRODUCT_BUNDLE_IDENTIFIER = "dev.fka.Poke-macOS-Gate";
|
|
269
272
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
270
273
|
REGISTER_APP_GROUPS = YES;
|
|
@@ -291,12 +294,15 @@
|
|
|
291
294
|
ENABLE_PREVIEWS = YES;
|
|
292
295
|
GENERATE_INFOPLIST_FILE = YES;
|
|
293
296
|
INFOPLIST_FILE = "Poke macOS Gate/Info.plist";
|
|
297
|
+
INFOPLIST_KEY_CFBundleDisplayName = "Poke macOS Gate";
|
|
298
|
+
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
|
294
299
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
|
295
300
|
LD_RUNPATH_SEARCH_PATHS = (
|
|
296
301
|
"$(inherited)",
|
|
297
302
|
"@executable_path/../Frameworks",
|
|
298
303
|
);
|
|
299
|
-
|
|
304
|
+
MACOSX_DEPLOYMENT_TARGET = 26.0;
|
|
305
|
+
MARKETING_VERSION = 0.1.5;
|
|
300
306
|
PRODUCT_BUNDLE_IDENTIFIER = "dev.fka.Poke-macOS-Gate";
|
|
301
307
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
302
308
|
REGISTER_APP_GROUPS = YES;
|
|
Binary file
|
package/docs/index.md
CHANGED
|
@@ -3,8 +3,8 @@ layout: home
|
|
|
3
3
|
|
|
4
4
|
hero:
|
|
5
5
|
name: Poke Gate
|
|
6
|
-
text:
|
|
7
|
-
tagline:
|
|
6
|
+
text: A two-way bridge between your Mac and your AI.
|
|
7
|
+
tagline: Poke pulls from your Mac when you ask. Your Mac pushes to Poke when something happens. Run commands, read files, take screenshots — and automate it all with Agents.
|
|
8
8
|
image:
|
|
9
9
|
src: /logo.png
|
|
10
10
|
alt: Poke Gate
|