poke-gate 0.3.0 → 0.3.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/bin/poke-gate.js +3 -0
- package/clients/Poke macOS Gate/Poke macOS Gate/AccessibilityPermissionView.swift +1 -1
- package/clients/Poke macOS Gate/Poke macOS Gate/GateService.swift +12 -66
- package/clients/Poke macOS Gate/Poke macOS Gate/Info.plist +0 -11
- package/clients/Poke macOS Gate/Poke macOS Gate/Poke_macOS_GateApp.swift +3 -12
- package/clients/Poke macOS Gate/Poke macOS Gate.xcodeproj/project.pbxproj +2 -2
- package/package.json +1 -1
- package/src/app.js +8 -7
- package/src/mcp-server.js +4 -16
- package/src/take-screenshot.js +42 -0
- package/src/tunnel.js +5 -21
- package/src/webhook.js +43 -0
- package/Gate.app +0 -0
package/bin/poke-gate.js
CHANGED
|
@@ -42,6 +42,9 @@ async function main() {
|
|
|
42
42
|
} else if (args[0] === "download-macos") {
|
|
43
43
|
const { downloadMacOSApp } = await import("../src/download-macos.js");
|
|
44
44
|
await downloadMacOSApp();
|
|
45
|
+
} else if (args[0] === "take-screenshot") {
|
|
46
|
+
const { takeScreenshot } = await import("../src/take-screenshot.js");
|
|
47
|
+
await takeScreenshot();
|
|
45
48
|
} else {
|
|
46
49
|
const mode = parseMode();
|
|
47
50
|
if (mode) {
|
|
@@ -4,7 +4,7 @@ struct AccessibilityPermissionView: View {
|
|
|
4
4
|
@ObservedObject var service: GateService
|
|
5
5
|
|
|
6
6
|
var body: some View {
|
|
7
|
-
let granted = service.
|
|
7
|
+
let granted = service.isPermissionGranted(.accessibility)
|
|
8
8
|
|
|
9
9
|
VStack(alignment: .leading, spacing: 10) {
|
|
10
10
|
HStack(spacing: 8) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
import Combine
|
|
3
|
-
import ScreenCaptureKit
|
|
4
3
|
import AppKit
|
|
5
4
|
import CoreGraphics
|
|
6
5
|
import ApplicationServices
|
|
@@ -41,30 +40,35 @@ class GateService: ObservableObject {
|
|
|
41
40
|
|
|
42
41
|
enum SystemPermission: String, CaseIterable, Identifiable {
|
|
43
42
|
case accessibility
|
|
43
|
+
case screenRecording
|
|
44
44
|
|
|
45
45
|
var id: String { rawValue }
|
|
46
46
|
|
|
47
47
|
var title: String {
|
|
48
48
|
switch self {
|
|
49
49
|
case .accessibility: return "Accessibility"
|
|
50
|
+
case .screenRecording: return "Screen Recording"
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
var subtitle: String {
|
|
54
55
|
switch self {
|
|
55
56
|
case .accessibility: return "Needed for keyboard, mouse, and automation-style control."
|
|
57
|
+
case .screenRecording: return "Needed for the take_screenshot tool."
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
var settingsURL: String {
|
|
60
62
|
switch self {
|
|
61
63
|
case .accessibility: return "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"
|
|
64
|
+
case .screenRecording: return "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture"
|
|
62
65
|
}
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
var systemImageName: String {
|
|
66
69
|
switch self {
|
|
67
70
|
case .accessibility: return "figure.wave"
|
|
71
|
+
case .screenRecording: return "camera.viewfinder"
|
|
68
72
|
}
|
|
69
73
|
}
|
|
70
74
|
}
|
|
@@ -239,68 +243,12 @@ class GateService: ObservableObject {
|
|
|
239
243
|
case .accessibility:
|
|
240
244
|
let options = [kAXTrustedCheckOptionPrompt.takeUnretainedValue(): true] as CFDictionary
|
|
241
245
|
_ = AXIsProcessTrustedWithOptions(options)
|
|
246
|
+
case .screenRecording:
|
|
247
|
+
guard let url = URL(string: permission.settingsURL) else { return }
|
|
248
|
+
NSWorkspace.shared.open(url)
|
|
242
249
|
}
|
|
243
250
|
}
|
|
244
251
|
|
|
245
|
-
func captureAndSend() {
|
|
246
|
-
appendLog("Screenshot requested via deeplink.")
|
|
247
|
-
|
|
248
|
-
Task {
|
|
249
|
-
do {
|
|
250
|
-
let content = try await SCShareableContent.current
|
|
251
|
-
guard let display = content.displays.first else {
|
|
252
|
-
appendLog("No display found for screenshot.")
|
|
253
|
-
return
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
let filter = SCContentFilter(display: display, excludingWindows: [])
|
|
257
|
-
let config = SCStreamConfiguration()
|
|
258
|
-
config.width = display.width * 2
|
|
259
|
-
config.height = display.height * 2
|
|
260
|
-
config.capturesAudio = false
|
|
261
|
-
|
|
262
|
-
let image = try await SCScreenshotManager.captureImage(
|
|
263
|
-
contentFilter: filter,
|
|
264
|
-
configuration: config
|
|
265
|
-
)
|
|
266
|
-
|
|
267
|
-
let rep = NSBitmapImageRep(cgImage: image)
|
|
268
|
-
guard let pngData = rep.representation(using: .png, properties: [:]) else {
|
|
269
|
-
appendLog("Failed to encode screenshot as PNG.")
|
|
270
|
-
return
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
let tempPath = NSTemporaryDirectory() + "poke-gate-screenshot.png"
|
|
274
|
-
let tempURL = URL(fileURLWithPath: tempPath)
|
|
275
|
-
try pngData.write(to: tempURL)
|
|
276
|
-
appendLog("Screenshot saved to \(tempPath) (\(pngData.count) bytes)")
|
|
277
|
-
|
|
278
|
-
guard let token = loadPokeLoginToken() else {
|
|
279
|
-
appendLog("Cannot send screenshot: not signed in to Poke.")
|
|
280
|
-
return
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
let url = URL(string: "https://poke.com/api/v1/inbound/api-message")!
|
|
284
|
-
var request = URLRequest(url: url)
|
|
285
|
-
request.httpMethod = "POST"
|
|
286
|
-
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
|
287
|
-
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
288
|
-
|
|
289
|
-
let message = "Here's a screenshot of my screen right now. [Image attached as base64 PNG, \(pngData.count) bytes, \(display.width)x\(display.height)]"
|
|
290
|
-
let body: [String: Any] = ["message": message]
|
|
291
|
-
request.httpBody = try JSONSerialization.data(withJSONObject: body)
|
|
292
|
-
|
|
293
|
-
let (_, response) = try await URLSession.shared.data(for: request)
|
|
294
|
-
if let httpResp = response as? HTTPURLResponse, httpResp.statusCode == 200 {
|
|
295
|
-
appendLog("Screenshot sent to Poke.")
|
|
296
|
-
} else {
|
|
297
|
-
appendLog("Failed to send screenshot to Poke.")
|
|
298
|
-
}
|
|
299
|
-
} catch {
|
|
300
|
-
appendLog("Screenshot error: \(error.localizedDescription)")
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
252
|
|
|
305
253
|
func autoStartIfNeeded() {
|
|
306
254
|
guard !hasAutoStarted else { return }
|
|
@@ -608,7 +556,7 @@ class GateService: ObservableObject {
|
|
|
608
556
|
private func killOrphanedProcesses() {
|
|
609
557
|
let task = Process()
|
|
610
558
|
task.executableURL = URL(fileURLWithPath: "/usr/bin/pkill")
|
|
611
|
-
task.arguments = ["-f", "poke-gate"]
|
|
559
|
+
task.arguments = ["-f", "node.*poke-gate.*app\\.js"]
|
|
612
560
|
try? task.run()
|
|
613
561
|
task.waitUntilExit()
|
|
614
562
|
}
|
|
@@ -789,14 +737,10 @@ class GateService: ObservableObject {
|
|
|
789
737
|
writeConfig(json)
|
|
790
738
|
}
|
|
791
739
|
|
|
792
|
-
|
|
740
|
+
func isPermissionGranted(_ permission: SystemPermission) -> Bool {
|
|
793
741
|
switch permission {
|
|
794
742
|
case .accessibility:
|
|
795
743
|
if AXIsProcessTrusted() { return true }
|
|
796
|
-
// AXIsProcessTrusted() can return false despite the toggle being ON
|
|
797
|
-
// in System Settings when the TCC entry's code signature doesn't match
|
|
798
|
-
// the running binary (ad-hoc signing, rebuild from Xcode, etc.).
|
|
799
|
-
// Probe the API directly as a fallback.
|
|
800
744
|
let systemWide = AXUIElementCreateSystemWide()
|
|
801
745
|
var value: AnyObject?
|
|
802
746
|
let result = AXUIElementCopyAttributeValue(
|
|
@@ -805,6 +749,8 @@ class GateService: ObservableObject {
|
|
|
805
749
|
&value
|
|
806
750
|
)
|
|
807
751
|
return result == .success || result == .noValue || result == .attributeUnsupported
|
|
752
|
+
case .screenRecording:
|
|
753
|
+
return CGPreflightScreenCaptureAccess()
|
|
808
754
|
}
|
|
809
755
|
}
|
|
810
756
|
|
|
@@ -6,16 +6,5 @@
|
|
|
6
6
|
<string>dev.fka.Poke-macOS-Gate</string>
|
|
7
7
|
<key>LSUIElement</key>
|
|
8
8
|
<true/>
|
|
9
|
-
<key>CFBundleURLTypes</key>
|
|
10
|
-
<array>
|
|
11
|
-
<dict>
|
|
12
|
-
<key>CFBundleURLName</key>
|
|
13
|
-
<string>dev.fka.Poke-macOS-Gate</string>
|
|
14
|
-
<key>CFBundleURLSchemes</key>
|
|
15
|
-
<array>
|
|
16
|
-
<string>poke-gate</string>
|
|
17
|
-
</array>
|
|
18
|
-
</dict>
|
|
19
|
-
</array>
|
|
20
9
|
</dict>
|
|
21
10
|
</plist>
|
|
@@ -3,15 +3,6 @@ import ServiceManagement
|
|
|
3
3
|
|
|
4
4
|
class AppDelegate: NSObject, NSApplicationDelegate {
|
|
5
5
|
var service: GateService?
|
|
6
|
-
|
|
7
|
-
func application(_ application: NSApplication, open urls: [URL]) {
|
|
8
|
-
for url in urls {
|
|
9
|
-
guard url.scheme == "poke-gate" else { continue }
|
|
10
|
-
if url.host == "screenshot" {
|
|
11
|
-
service?.captureAndSend()
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
6
|
}
|
|
16
7
|
|
|
17
8
|
@main
|
|
@@ -122,8 +113,8 @@ struct PopoverContent: View {
|
|
|
122
113
|
}
|
|
123
114
|
.frame(width: 320)
|
|
124
115
|
.padding(10)
|
|
125
|
-
.onChange(of: service.
|
|
126
|
-
if
|
|
116
|
+
.onChange(of: service.systemPermissionStatuses) { _, _ in
|
|
117
|
+
if service.isPermissionGranted(.accessibility) && pendingFullMode {
|
|
127
118
|
pendingFullMode = false
|
|
128
119
|
service.setPermissionMode(.full)
|
|
129
120
|
}
|
|
@@ -436,7 +427,7 @@ struct PopoverContent: View {
|
|
|
436
427
|
private func handleModeSelection(_ mode: GateService.PermissionMode) {
|
|
437
428
|
guard mode != service.permissionMode else { return }
|
|
438
429
|
|
|
439
|
-
if mode == .full && !service.
|
|
430
|
+
if mode == .full && !service.isPermissionGranted(.accessibility) {
|
|
440
431
|
pendingFullMode = true
|
|
441
432
|
service.openSystemPermission(.accessibility)
|
|
442
433
|
} else {
|
|
@@ -286,7 +286,7 @@
|
|
|
286
286
|
"@executable_path/../Frameworks",
|
|
287
287
|
);
|
|
288
288
|
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
|
289
|
-
MARKETING_VERSION = 0.
|
|
289
|
+
MARKETING_VERSION = 0.3.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.
|
|
325
|
+
MARKETING_VERSION = 0.3.1;
|
|
326
326
|
PRODUCT_BUNDLE_IDENTIFIER = "dev.fka.Poke-macOS-Gate";
|
|
327
327
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
328
328
|
REGISTER_APP_GROUPS = YES;
|
package/package.json
CHANGED
package/src/app.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { startMcpServer, enableLogging, getPermissionMode } from "./mcp-server.js";
|
|
2
2
|
import { startTunnel } from "./tunnel.js";
|
|
3
3
|
import { startAgentScheduler, stopAgentScheduler } from "./agents.js";
|
|
4
|
-
import {
|
|
4
|
+
import { sendToWebhook } from "./webhook.js";
|
|
5
|
+
import { isLoggedIn, login, getToken } from "poke";
|
|
5
6
|
import { execSync } from "node:child_process";
|
|
6
7
|
|
|
7
8
|
const verbose = process.argv.includes("--verbose") || process.argv.includes("-v");
|
|
@@ -9,9 +10,10 @@ enableLogging(verbose);
|
|
|
9
10
|
|
|
10
11
|
function killExistingInstances() {
|
|
11
12
|
const myPid = process.pid;
|
|
13
|
+
const ppid = process.ppid;
|
|
12
14
|
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
|
+
const out = execSync("pgrep -f 'node.*poke-gate.*app\\.js'", { encoding: "utf-8" }).trim();
|
|
16
|
+
const pids = out.split("\n").map(Number).filter((p) => p && p !== myPid && p !== ppid);
|
|
15
17
|
for (const pid of pids) {
|
|
16
18
|
try { process.kill(pid, "SIGTERM"); } catch {}
|
|
17
19
|
}
|
|
@@ -66,7 +68,7 @@ async function connectWithRetry(mcpUrl, token) {
|
|
|
66
68
|
reconnectWatchdog = null;
|
|
67
69
|
log(`Tunnel connected (${data.connectionId})`);
|
|
68
70
|
log("Ready — your Poke agent can now access this machine.");
|
|
69
|
-
notifyPoke(data.connectionId
|
|
71
|
+
notifyPoke(data.connectionId);
|
|
70
72
|
startAgentScheduler();
|
|
71
73
|
break;
|
|
72
74
|
case "disconnected":
|
|
@@ -152,11 +154,10 @@ function buildAccessModeMessage(mode) {
|
|
|
152
154
|
}
|
|
153
155
|
}
|
|
154
156
|
|
|
155
|
-
async function notifyPoke(connectionId
|
|
157
|
+
async function notifyPoke(connectionId) {
|
|
156
158
|
try {
|
|
157
159
|
const mode = getPermissionMode();
|
|
158
|
-
|
|
159
|
-
await poke.sendMessage(
|
|
160
|
+
await sendToWebhook(
|
|
160
161
|
`Hey! I've connected my computer to you via Poke Gate (tunnel: ${connectionId}). ` +
|
|
161
162
|
`${buildAccessModeMessage(mode)} ` +
|
|
162
163
|
`Just use the tools whenever I ask you to do something on my computer. ` +
|
package/src/mcp-server.js
CHANGED
|
@@ -779,23 +779,11 @@ function handleToolCall(name, args, context = {}) {
|
|
|
779
779
|
case "take_screenshot": {
|
|
780
780
|
logTool(name, cleanArgs);
|
|
781
781
|
|
|
782
|
-
return runCommand(
|
|
783
|
-
if (
|
|
784
|
-
return
|
|
785
|
-
return { content: [{ type: "text", text: "Screenshot captured and sent to Poke via the macOS app." }] };
|
|
786
|
-
});
|
|
782
|
+
return runCommand("npx -y poke-gate@latest take-screenshot", homedir(), { permissionMode: "full" }).then((result) => {
|
|
783
|
+
if (result.exitCode === 0) {
|
|
784
|
+
return { content: [{ type: "text", text: "Screenshot captured and sent to Poke." }] };
|
|
787
785
|
}
|
|
788
|
-
|
|
789
|
-
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
790
|
-
const dest = cleanArgs.path
|
|
791
|
-
? resolve(cleanArgs.path.replace(/^~/, homedir()))
|
|
792
|
-
: join(homedir(), "Desktop", `screenshot-${ts}.png`);
|
|
793
|
-
return runCommand(`/usr/sbin/screencapture -x "${dest}"`, homedir()).then((result) => {
|
|
794
|
-
if (result.exitCode === 0) {
|
|
795
|
-
return { content: [{ type: "text", text: `Screenshot saved to ${dest}` }] };
|
|
796
|
-
}
|
|
797
|
-
return { content: [{ type: "text", text: `Screenshot failed: ${result.stderr || "unknown error"}. Grant Screen Recording permission to Terminal or install the Poke macOS Gate app.` }], isError: true };
|
|
798
|
-
});
|
|
786
|
+
return { content: [{ type: "text", text: `Screenshot failed: ${result.stderr || result.stdout || "unknown error"}` }], isError: true };
|
|
799
787
|
});
|
|
800
788
|
}
|
|
801
789
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { readFileSync, unlinkSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir, platform } from "node:os";
|
|
5
|
+
import { isLoggedIn, login } from "poke";
|
|
6
|
+
import { sendToWebhook } from "./webhook.js";
|
|
7
|
+
|
|
8
|
+
export async function takeScreenshot() {
|
|
9
|
+
if (platform() !== "darwin") {
|
|
10
|
+
console.error("Screenshots are only supported on macOS.");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!isLoggedIn()) {
|
|
15
|
+
console.log("Signing in to Poke...");
|
|
16
|
+
await login();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const dest = join(tmpdir(), `poke-gate-screenshot-${Date.now()}.png`);
|
|
20
|
+
|
|
21
|
+
console.log("Capturing screenshot...");
|
|
22
|
+
try {
|
|
23
|
+
execSync(`/usr/sbin/screencapture -x "${dest}"`, { stdio: "pipe" });
|
|
24
|
+
} catch {
|
|
25
|
+
console.error("Screenshot failed. Grant Screen Recording permission in System Settings > Privacy & Security > Screen Recording.");
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const png = readFileSync(dest);
|
|
30
|
+
const base64 = png.toString("base64");
|
|
31
|
+
|
|
32
|
+
console.log(`Screenshot captured (${(png.length / 1024).toFixed(0)} KB). Sending to Poke...`);
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
await sendToWebhook(
|
|
36
|
+
`Here is a screenshot of my screen right now. Reply me with the image.\n\n\`\`\`\ndata:image/png;base64,${base64}\n\`\`\``
|
|
37
|
+
);
|
|
38
|
+
console.log("Screenshot sent to Poke.");
|
|
39
|
+
} finally {
|
|
40
|
+
try { unlinkSync(dest); } catch {}
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/tunnel.js
CHANGED
|
@@ -1,29 +1,11 @@
|
|
|
1
1
|
import { PokeTunnel, getToken } from "poke";
|
|
2
|
-
import {
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { homedir } from "node:os";
|
|
5
|
-
|
|
6
|
-
const CONFIG_DIR = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
7
|
-
const STATE_PATH = join(CONFIG_DIR, "poke-gate", "state.json");
|
|
2
|
+
import { loadState, saveState } from "./webhook.js";
|
|
8
3
|
|
|
9
4
|
function log(msg) {
|
|
10
5
|
const ts = new Date().toISOString().slice(11, 19);
|
|
11
6
|
console.log(`[${ts}] ${msg}`);
|
|
12
7
|
}
|
|
13
8
|
|
|
14
|
-
function loadState() {
|
|
15
|
-
try {
|
|
16
|
-
return JSON.parse(readFileSync(STATE_PATH, "utf-8"));
|
|
17
|
-
} catch {
|
|
18
|
-
return {};
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function saveState(state) {
|
|
23
|
-
mkdirSync(join(CONFIG_DIR, "poke-gate"), { recursive: true });
|
|
24
|
-
writeFileSync(STATE_PATH, JSON.stringify(state, null, 2));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
9
|
async function cleanupStaleConnections() {
|
|
28
10
|
const token = getToken();
|
|
29
11
|
if (!token) return;
|
|
@@ -49,7 +31,8 @@ async function cleanupStaleConnections() {
|
|
|
49
31
|
} catch {}
|
|
50
32
|
}
|
|
51
33
|
|
|
52
|
-
|
|
34
|
+
const { webhookUrl, webhookToken } = loadState();
|
|
35
|
+
saveState({ webhookUrl, webhookToken });
|
|
53
36
|
}
|
|
54
37
|
|
|
55
38
|
export async function startTunnel({ mcpUrl, onEvent }) {
|
|
@@ -64,7 +47,7 @@ export async function startTunnel({ mcpUrl, onEvent }) {
|
|
|
64
47
|
url: mcpUrl,
|
|
65
48
|
name: "poke-gate",
|
|
66
49
|
token,
|
|
67
|
-
cleanupOnStop:
|
|
50
|
+
cleanupOnStop: true,
|
|
68
51
|
});
|
|
69
52
|
|
|
70
53
|
tunnel.on("connected", (info) => {
|
|
@@ -72,6 +55,7 @@ export async function startTunnel({ mcpUrl, onEvent }) {
|
|
|
72
55
|
const history = state.connectionHistory || [];
|
|
73
56
|
history.push(info.connectionId);
|
|
74
57
|
saveState({
|
|
58
|
+
...state,
|
|
75
59
|
connectionId: info.connectionId,
|
|
76
60
|
connectionHistory: history.slice(-10),
|
|
77
61
|
});
|
package/src/webhook.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { Poke, getToken } from "poke";
|
|
5
|
+
|
|
6
|
+
const CONFIG_DIR = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
7
|
+
const STATE_PATH = join(CONFIG_DIR, "poke-gate", "state.json");
|
|
8
|
+
|
|
9
|
+
export function loadState() {
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(readFileSync(STATE_PATH, "utf-8"));
|
|
12
|
+
} catch {
|
|
13
|
+
return {};
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function saveState(state) {
|
|
18
|
+
mkdirSync(join(CONFIG_DIR, "poke-gate"), { recursive: true });
|
|
19
|
+
writeFileSync(STATE_PATH, JSON.stringify(state, null, 2));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function getWebhook() {
|
|
23
|
+
const state = loadState();
|
|
24
|
+
if (state.webhookUrl && state.webhookToken) {
|
|
25
|
+
return { webhookUrl: state.webhookUrl, webhookToken: state.webhookToken };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const token = getToken();
|
|
29
|
+
if (!token) throw new Error("No Poke auth token available.");
|
|
30
|
+
|
|
31
|
+
const poke = new Poke({ token });
|
|
32
|
+
const result = await poke.createWebhook({ condition: "poke-gate", action: "poke-gate" });
|
|
33
|
+
|
|
34
|
+
const webhook = { webhookUrl: result.webhookUrl, webhookToken: result.webhookToken };
|
|
35
|
+
saveState({ ...state, ...webhook });
|
|
36
|
+
return webhook;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function sendToWebhook(message) {
|
|
40
|
+
const { webhookUrl, webhookToken } = await getWebhook();
|
|
41
|
+
const poke = new Poke({ token: getToken() });
|
|
42
|
+
return poke.sendWebhook({ webhookUrl, webhookToken, data: { message } });
|
|
43
|
+
}
|
package/Gate.app
DELETED
|
File without changes
|