poke-gate 0.2.1 → 0.2.2
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/README.md +1 -1
- package/clients/Poke macOS Gate/Poke macOS Gate/GateService.swift +20 -0
- package/clients/Poke macOS Gate/Poke macOS Gate/Poke_macOS_GateApp.swift +3 -3
- package/clients/Poke macOS Gate/Poke macOS Gate.xcodeproj/project.pbxproj +2 -2
- package/docs/security.md +4 -2
- package/package.json +1 -1
- package/src/app.js +15 -1
- package/src/mcp-server.js +1 -1
package/README.md
CHANGED
|
@@ -241,7 +241,7 @@ Poke Gate supports three access modes that control what your agent can do:
|
|
|
241
241
|
|
|
242
242
|
| Mode | Description |
|
|
243
243
|
|------|-------------|
|
|
244
|
-
| **Full** (default) | All tools available.
|
|
244
|
+
| **Full** (default) | All tools available with no approval required. The agent can run commands, write files, and take screenshots directly. |
|
|
245
245
|
| **Limited** | Read-only tools plus a curated set of safe commands (`ls`, `cat`, `grep`, `curl`, etc.). `write_file` and `take_screenshot` are disabled. |
|
|
246
246
|
| **Sandbox** | Broader command support than Limited, but writes are restricted to `~/Downloads` and `/tmp` via macOS `sandbox-exec`. |
|
|
247
247
|
|
|
@@ -91,6 +91,7 @@ class GateService: ObservableObject {
|
|
|
91
91
|
|
|
92
92
|
@Published var status: Status = .stopped
|
|
93
93
|
@Published var logs: [String] = []
|
|
94
|
+
@Published var processLogs: [String] = []
|
|
94
95
|
@Published var terminalPreviews: [TerminalPreview] = []
|
|
95
96
|
@Published var userName: String? = nil
|
|
96
97
|
@Published var permissionMode: PermissionMode
|
|
@@ -489,11 +490,21 @@ class GateService: ObservableObject {
|
|
|
489
490
|
outputPipe?.fileHandleForReading.readabilityHandler = nil
|
|
490
491
|
process = nil
|
|
491
492
|
outputPipe = nil
|
|
493
|
+
killOrphanedProcesses()
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
private func killOrphanedProcesses() {
|
|
497
|
+
let task = Process()
|
|
498
|
+
task.executableURL = URL(fileURLWithPath: "/usr/bin/pkill")
|
|
499
|
+
task.arguments = ["-f", "poke-gate"]
|
|
500
|
+
try? task.run()
|
|
501
|
+
task.waitUntilExit()
|
|
492
502
|
}
|
|
493
503
|
|
|
494
504
|
private func handleOutput(_ raw: String) {
|
|
495
505
|
for line in raw.components(separatedBy: .newlines) where !line.isEmpty {
|
|
496
506
|
appendLog(line)
|
|
507
|
+
appendProcessLog(line)
|
|
497
508
|
parseTerminalPreviewLine(line)
|
|
498
509
|
|
|
499
510
|
if line.contains("Tunnel connected") || line.contains("Ready") {
|
|
@@ -510,6 +521,15 @@ class GateService: ObservableObject {
|
|
|
510
521
|
}
|
|
511
522
|
}
|
|
512
523
|
|
|
524
|
+
private func appendProcessLog(_ line: String) {
|
|
525
|
+
let stripped = stripToolTimestamp(from: line)
|
|
526
|
+
guard !stripped.isEmpty else { return }
|
|
527
|
+
processLogs.append(stripped)
|
|
528
|
+
if processLogs.count > maxLogs {
|
|
529
|
+
processLogs.removeFirst(processLogs.count - maxLogs)
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
513
533
|
private func handleTermination(exitCode: Int32) {
|
|
514
534
|
appendLog("Process exited with code \(exitCode)")
|
|
515
535
|
stopHealthCheck()
|
|
@@ -189,9 +189,9 @@ struct PopoverContent: View {
|
|
|
189
189
|
.truncationMode(.tail)
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
|
-
} else if !service.
|
|
193
|
-
ForEach(Array(service.
|
|
194
|
-
Text(
|
|
192
|
+
} else if !service.processLogs.isEmpty {
|
|
193
|
+
ForEach(Array(service.processLogs.suffix(4).enumerated()), id: \.offset) { _, entry in
|
|
194
|
+
Text(entry)
|
|
195
195
|
.font(.system(size: 9, design: .monospaced))
|
|
196
196
|
.foregroundStyle(.tertiary)
|
|
197
197
|
.lineLimit(1)
|
|
@@ -286,7 +286,7 @@
|
|
|
286
286
|
"@executable_path/../Frameworks",
|
|
287
287
|
);
|
|
288
288
|
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
|
289
|
-
MARKETING_VERSION = 0.2.
|
|
289
|
+
MARKETING_VERSION = 0.2.1;
|
|
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.2.
|
|
325
|
+
MARKETING_VERSION = 0.2.1;
|
|
326
326
|
PRODUCT_BUNDLE_IDENTIFIER = "dev.fka.Poke-macOS-Gate";
|
|
327
327
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
328
328
|
REGISTER_APP_GROUPS = YES;
|
package/docs/security.md
CHANGED
|
@@ -10,7 +10,7 @@ Poke Gate supports three access modes that control what tools your agent can use
|
|
|
10
10
|
|
|
11
11
|
### Full (default)
|
|
12
12
|
|
|
13
|
-
All tools are available
|
|
13
|
+
All tools are available with no approval required. The agent can run commands, write files, and take screenshots directly.
|
|
14
14
|
|
|
15
15
|
### Limited
|
|
16
16
|
|
|
@@ -48,13 +48,15 @@ POKE_GATE_PERMISSION_MODE=sandbox npx poke-gate
|
|
|
48
48
|
|
|
49
49
|
## Tool approval flow
|
|
50
50
|
|
|
51
|
-
In **
|
|
51
|
+
In **limited** and **sandbox** modes, risky tools (`run_command`, `write_file`, `take_screenshot`) use an HMAC-signed approval flow:
|
|
52
52
|
|
|
53
53
|
1. The agent calls the tool — Poke Gate returns `AWAITING_APPROVAL` with a signed token
|
|
54
54
|
2. The agent asks you in chat to approve
|
|
55
55
|
3. You approve — the agent re-calls the tool with the approval token
|
|
56
56
|
4. Optionally, you can `remember_in_session` (same command) or `remember_all_risky` (all risky tools for the session)
|
|
57
57
|
|
|
58
|
+
In **full** mode, all tools execute directly without approval.
|
|
59
|
+
|
|
58
60
|
## What protects you
|
|
59
61
|
|
|
60
62
|
- **Authentication** — only your Poke agent (authenticated via Poke OAuth) can reach the tunnel
|
package/package.json
CHANGED
package/src/app.js
CHANGED
|
@@ -2,10 +2,23 @@ import { startMcpServer, enableLogging, getPermissionMode } from "./mcp-server.j
|
|
|
2
2
|
import { startTunnel } from "./tunnel.js";
|
|
3
3
|
import { startAgentScheduler, stopAgentScheduler } from "./agents.js";
|
|
4
4
|
import { Poke, isLoggedIn, login, getToken } from "poke";
|
|
5
|
+
import { execSync } from "node:child_process";
|
|
5
6
|
|
|
6
7
|
const verbose = process.argv.includes("--verbose") || process.argv.includes("-v");
|
|
7
8
|
enableLogging(verbose);
|
|
8
9
|
|
|
10
|
+
function killExistingInstances() {
|
|
11
|
+
const myPid = process.pid;
|
|
12
|
+
try {
|
|
13
|
+
const out = execSync("pgrep -f 'poke-gate'", { encoding: "utf-8" }).trim();
|
|
14
|
+
const pids = out.split("\n").map(Number).filter((p) => p && p !== myPid);
|
|
15
|
+
for (const pid of pids) {
|
|
16
|
+
try { process.kill(pid, "SIGTERM"); } catch {}
|
|
17
|
+
}
|
|
18
|
+
if (pids.length > 0) log(`Killed ${pids.length} existing poke-gate process(es).`);
|
|
19
|
+
} catch {}
|
|
20
|
+
}
|
|
21
|
+
|
|
9
22
|
function log(msg) {
|
|
10
23
|
const ts = new Date().toISOString().slice(11, 19);
|
|
11
24
|
console.log(`[${ts}] ${msg}`);
|
|
@@ -102,6 +115,7 @@ function scheduleReconnect(mcpUrl, token) {
|
|
|
102
115
|
}
|
|
103
116
|
|
|
104
117
|
async function main() {
|
|
118
|
+
killExistingInstances();
|
|
105
119
|
log("poke-gate starting...");
|
|
106
120
|
log(`Access mode: ${getPermissionMode()}`);
|
|
107
121
|
|
|
@@ -133,7 +147,7 @@ function buildAccessModeMessage(mode) {
|
|
|
133
147
|
return (
|
|
134
148
|
"Access mode: Full. " +
|
|
135
149
|
"You can run any shell command, read and write files, list directories, take screenshots, and check system info. " +
|
|
136
|
-
"
|
|
150
|
+
"Use the tools directly whenever needed — no approval required."
|
|
137
151
|
);
|
|
138
152
|
}
|
|
139
153
|
}
|
package/src/mcp-server.js
CHANGED
|
@@ -565,7 +565,7 @@ function handleToolCall(name, args, context = {}) {
|
|
|
565
565
|
return blocked;
|
|
566
566
|
}
|
|
567
567
|
|
|
568
|
-
if (permissionService.isRisky(name)) {
|
|
568
|
+
if (PERMISSION_MODE !== "full" && permissionService.isRisky(name)) {
|
|
569
569
|
const commandText = typeof cleanArgs.command === "string" ? cleanArgs.command : "";
|
|
570
570
|
const alreadyAllowed = sessionAutoApproveAllRisky.has(sessionId) ||
|
|
571
571
|
(commandText && permissionService.isAllowedBySessionPattern(sessionId, commandText));
|