poke-gate 0.0.8 → 0.1.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/docker.yml +41 -0
- package/.prettierrc +7 -0
- package/.remarkignore +3 -0
- package/.remarkrc.mjs +13 -0
- package/CODE_OF_CONDUCT.md +38 -0
- package/CONTRIBUTING.md +32 -0
- package/Dockerfile +13 -0
- package/README.md +94 -2
- package/SECURITY.md +18 -0
- package/bin/poke-gate.js +21 -93
- package/clients/Poke macOS Gate/Poke macOS Gate/AboutView.swift +54 -0
- package/clients/Poke macOS Gate/Poke macOS Gate/GateService.swift +10 -0
- package/clients/Poke macOS Gate/Poke macOS Gate/LogsView.swift +45 -11
- package/clients/Poke macOS Gate/Poke macOS Gate/Poke_macOS_GateApp.swift +143 -75
- package/clients/Poke macOS Gate/Poke macOS Gate/SettingsView.swift +76 -71
- package/clients/Poke macOS Gate/Poke macOS Gate.xcodeproj/project.pbxproj +2 -2
- package/clients/Poke macOS Gate/Poke macOS Gate.xcodeproj/project.xcworkspace/xcuserdata/fka.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/eslint.config.mjs +23 -0
- package/examples/agents/.env.beeper +6 -0
- package/examples/agents/beeper.1h.js +109 -0
- package/package.json +19 -2
- package/src/agents.js +257 -0
- package/src/app.js +24 -34
- package/src/mcp-server.js +3 -3
- package/src/tunnel.js +9 -5
|
@@ -6,21 +6,27 @@ struct Poke_macOS_GateApp: App {
|
|
|
6
6
|
|
|
7
7
|
var body: some Scene {
|
|
8
8
|
MenuBarExtra {
|
|
9
|
-
|
|
9
|
+
PopoverContent(service: service)
|
|
10
10
|
.onAppear { service.autoStartIfNeeded() }
|
|
11
11
|
} label: {
|
|
12
12
|
Image(systemName: menuBarIcon)
|
|
13
13
|
}
|
|
14
|
+
.menuBarExtraStyle(.window)
|
|
14
15
|
|
|
15
16
|
Window("Logs", id: "logs") {
|
|
16
17
|
LogsView(service: service)
|
|
17
18
|
}
|
|
18
|
-
.defaultSize(width:
|
|
19
|
+
.defaultSize(width: 560, height: 400)
|
|
19
20
|
|
|
20
21
|
Window("Settings", id: "settings") {
|
|
21
22
|
SettingsView(service: service)
|
|
22
23
|
}
|
|
23
24
|
.windowResizability(.contentSize)
|
|
25
|
+
|
|
26
|
+
Window("About", id: "about") {
|
|
27
|
+
AboutView()
|
|
28
|
+
}
|
|
29
|
+
.windowResizability(.contentSize)
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
private var menuBarIcon: String {
|
|
@@ -33,77 +39,126 @@ struct Poke_macOS_GateApp: App {
|
|
|
33
39
|
}
|
|
34
40
|
}
|
|
35
41
|
|
|
36
|
-
struct
|
|
42
|
+
struct PopoverContent: View {
|
|
37
43
|
@ObservedObject var service: GateService
|
|
38
44
|
@Environment(\.openWindow) private var openWindow
|
|
39
45
|
|
|
40
46
|
var body: some View {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
Divider()
|
|
74
|
-
|
|
75
|
-
if service.status == .connected || service.status == .starting || service.status == .disconnected {
|
|
76
|
-
Button("Restart") {
|
|
77
|
-
service.restart()
|
|
47
|
+
VStack(spacing: 0) {
|
|
48
|
+
VStack(spacing: 6) {
|
|
49
|
+
HStack(spacing: 8) {
|
|
50
|
+
Circle()
|
|
51
|
+
.fill(statusColor)
|
|
52
|
+
.frame(width: 10, height: 10)
|
|
53
|
+
|
|
54
|
+
Text(statusText)
|
|
55
|
+
.font(.system(.body, weight: .medium))
|
|
56
|
+
|
|
57
|
+
Spacer()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if service.status == .connected {
|
|
61
|
+
Text("This machine is accessible via Poke. Ask your Poke to run commands or read files.")
|
|
62
|
+
.font(.caption)
|
|
63
|
+
.foregroundStyle(.secondary)
|
|
64
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
65
|
+
.fixedSize(horizontal: false, vertical: true)
|
|
66
|
+
} else if service.status == .starting {
|
|
67
|
+
Text("Establishing connection…")
|
|
68
|
+
.font(.caption)
|
|
69
|
+
.foregroundStyle(.secondary)
|
|
70
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
71
|
+
} else if service.status == .error {
|
|
72
|
+
Text("Check Logs for details.")
|
|
73
|
+
.font(.caption)
|
|
74
|
+
.foregroundStyle(.red.opacity(0.8))
|
|
75
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
76
|
+
}
|
|
78
77
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
.padding(12)
|
|
79
|
+
|
|
80
|
+
Divider()
|
|
81
|
+
|
|
82
|
+
VStack(alignment: .leading, spacing: 4) {
|
|
83
|
+
Text("Recent activity")
|
|
84
|
+
.font(.caption2)
|
|
85
|
+
.foregroundStyle(.tertiary)
|
|
86
|
+
.textCase(.uppercase)
|
|
87
|
+
|
|
88
|
+
if service.logs.isEmpty {
|
|
89
|
+
Text("No activity yet")
|
|
90
|
+
.font(.caption)
|
|
91
|
+
.foregroundStyle(.secondary)
|
|
92
|
+
} else {
|
|
93
|
+
ForEach(Array(service.logs.suffix(4).enumerated()), id: \.offset) { _, line in
|
|
94
|
+
Text(line)
|
|
95
|
+
.font(.system(size: 9, design: .monospaced))
|
|
96
|
+
.foregroundStyle(.tertiary)
|
|
97
|
+
.lineLimit(1)
|
|
98
|
+
.truncationMode(.tail)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
82
101
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
102
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
103
|
+
.padding(12)
|
|
104
|
+
|
|
105
|
+
Divider()
|
|
106
|
+
|
|
107
|
+
HStack(spacing: 12) {
|
|
108
|
+
ActionButton(icon: "text.alignleft", label: "Logs") {
|
|
109
|
+
NSApp.activate(ignoringOtherApps: true)
|
|
110
|
+
openWindow(id: "logs")
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
ActionButton(icon: "gearshape", label: "Settings") {
|
|
114
|
+
NSApp.activate(ignoringOtherApps: true)
|
|
115
|
+
openWindow(id: "settings")
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if service.status == .connected || service.status == .starting || service.status == .disconnected {
|
|
119
|
+
ActionButton(icon: "arrow.counterclockwise", label: "Restart") {
|
|
120
|
+
service.restart()
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
ActionButton(icon: "play.fill", label: "Start") {
|
|
124
|
+
service.start()
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
Spacer()
|
|
129
|
+
|
|
130
|
+
ActionButton(icon: "xmark.circle", label: "Quit", tint: .secondary) {
|
|
131
|
+
service.stop()
|
|
132
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
|
133
|
+
NSApp.terminate(nil)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
91
136
|
}
|
|
137
|
+
.padding(12)
|
|
138
|
+
|
|
139
|
+
Divider()
|
|
140
|
+
|
|
141
|
+
HStack {
|
|
142
|
+
Button {
|
|
143
|
+
NSApp.activate(ignoringOtherApps: true)
|
|
144
|
+
openWindow(id: "about")
|
|
145
|
+
} label: {
|
|
146
|
+
Text("Poke Gate v0.0.8")
|
|
147
|
+
.font(.caption2)
|
|
148
|
+
.foregroundStyle(.tertiary)
|
|
149
|
+
}
|
|
150
|
+
.buttonStyle(.plain)
|
|
151
|
+
|
|
152
|
+
Spacer()
|
|
153
|
+
|
|
154
|
+
Text("Not affiliated with Poke")
|
|
155
|
+
.font(.caption2)
|
|
156
|
+
.foregroundStyle(.quaternary)
|
|
157
|
+
}
|
|
158
|
+
.padding(.horizontal, 12)
|
|
159
|
+
.padding(.vertical, 8)
|
|
92
160
|
}
|
|
93
|
-
.
|
|
94
|
-
|
|
95
|
-
Divider()
|
|
96
|
-
|
|
97
|
-
Text("Poke Gate v0.0.3")
|
|
98
|
-
.font(.caption)
|
|
99
|
-
.foregroundStyle(.secondary)
|
|
100
|
-
Text("Community project — not affiliated with Poke")
|
|
101
|
-
.font(.caption2)
|
|
102
|
-
.foregroundStyle(.secondary)
|
|
103
|
-
Button("GitHub") {
|
|
104
|
-
NSWorkspace.shared.open(URL(string: "https://github.com/f/poke-gate")!)
|
|
105
|
-
}
|
|
106
|
-
.font(.caption)
|
|
161
|
+
.frame(width: 320)
|
|
107
162
|
}
|
|
108
163
|
|
|
109
164
|
private var statusText: String {
|
|
@@ -120,21 +175,34 @@ struct MenuBarContent: View {
|
|
|
120
175
|
}
|
|
121
176
|
}
|
|
122
177
|
|
|
123
|
-
private var statusIcon: String {
|
|
124
|
-
switch service.status {
|
|
125
|
-
case .connected: "circle.fill"
|
|
126
|
-
case .starting, .disconnected: "circle.dotted"
|
|
127
|
-
case .error: "exclamationmark.circle.fill"
|
|
128
|
-
case .stopped: "circle"
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
178
|
private var statusColor: Color {
|
|
133
179
|
switch service.status {
|
|
134
180
|
case .connected: .green
|
|
135
181
|
case .starting, .disconnected: .yellow
|
|
136
182
|
case .error: .red
|
|
137
|
-
case .stopped: .
|
|
183
|
+
case .stopped: .gray.opacity(0.5)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
struct ActionButton: View {
|
|
189
|
+
let icon: String
|
|
190
|
+
let label: String
|
|
191
|
+
var tint: Color = .primary
|
|
192
|
+
let action: () -> Void
|
|
193
|
+
|
|
194
|
+
var body: some View {
|
|
195
|
+
Button(action: action) {
|
|
196
|
+
VStack(spacing: 2) {
|
|
197
|
+
Image(systemName: icon)
|
|
198
|
+
.font(.system(size: 14))
|
|
199
|
+
Text(label)
|
|
200
|
+
.font(.system(size: 9))
|
|
201
|
+
}
|
|
202
|
+
.foregroundStyle(tint)
|
|
203
|
+
.frame(width: 44, height: 36)
|
|
204
|
+
.contentShape(Rectangle())
|
|
138
205
|
}
|
|
206
|
+
.buttonStyle(.plain)
|
|
139
207
|
}
|
|
140
208
|
}
|
|
@@ -2,103 +2,108 @@ import SwiftUI
|
|
|
2
2
|
|
|
3
3
|
struct SettingsView: View {
|
|
4
4
|
@ObservedObject var service: GateService
|
|
5
|
-
@State private var apiKeyInput: String = ""
|
|
6
|
-
@State private var usePokeLogin: Bool = true
|
|
7
5
|
@Environment(\.dismiss) private var dismiss
|
|
8
6
|
|
|
9
7
|
var body: some View {
|
|
10
|
-
VStack(spacing: 16) {
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
VStack(alignment: .leading, spacing: 16) {
|
|
9
|
+
VStack(alignment: .leading, spacing: 8) {
|
|
10
|
+
Text("AUTHENTICATION")
|
|
11
|
+
.font(.caption2)
|
|
12
|
+
.foregroundStyle(.tertiary)
|
|
13
|
+
.textCase(.uppercase)
|
|
14
|
+
.tracking(0.5)
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
HStack(spacing: 8) {
|
|
17
|
+
Image(systemName: service.hasPokeLoginCredentials
|
|
18
|
+
? "checkmark.shield.fill" : "shield.slash")
|
|
19
|
+
.foregroundStyle(service.hasPokeLoginCredentials ? .green : .orange)
|
|
20
|
+
.font(.title3)
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
.foregroundStyle(.green)
|
|
25
|
-
.font(.subheadline)
|
|
26
|
-
} else {
|
|
27
|
-
Label("No credentials found", systemImage: "xmark.circle")
|
|
28
|
-
.foregroundStyle(.red)
|
|
22
|
+
VStack(alignment: .leading, spacing: 2) {
|
|
23
|
+
Text(service.hasPokeLoginCredentials
|
|
24
|
+
? "Signed in via Poke"
|
|
25
|
+
: "Not signed in")
|
|
29
26
|
.font(.subheadline)
|
|
27
|
+
.fontWeight(.medium)
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
Text("
|
|
37
|
-
.font(.
|
|
38
|
-
.
|
|
39
|
-
.padding(.vertical, 4)
|
|
40
|
-
.background(.quaternary)
|
|
41
|
-
.cornerRadius(4)
|
|
42
|
-
|
|
43
|
-
Button {
|
|
44
|
-
NSPasteboard.general.clearContents()
|
|
45
|
-
NSPasteboard.general.setString("npx poke login", forType: .string)
|
|
46
|
-
} label: {
|
|
47
|
-
Image(systemName: "doc.on.doc")
|
|
48
|
-
}
|
|
49
|
-
.buttonStyle(.plain)
|
|
29
|
+
if service.hasPokeLoginCredentials {
|
|
30
|
+
Text("Your Poke session is active.")
|
|
31
|
+
.font(.caption)
|
|
32
|
+
.foregroundStyle(.secondary)
|
|
33
|
+
} else {
|
|
34
|
+
Text("Run this command in Terminal to sign in:")
|
|
35
|
+
.font(.caption)
|
|
36
|
+
.foregroundStyle(.secondary)
|
|
50
37
|
}
|
|
51
|
-
|
|
52
|
-
Text("Then come back here and click Save.")
|
|
53
|
-
.font(.caption)
|
|
54
|
-
.foregroundStyle(.secondary)
|
|
55
38
|
}
|
|
56
39
|
}
|
|
40
|
+
.padding(10)
|
|
57
41
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
Text("API Key")
|
|
61
|
-
.font(.subheadline)
|
|
62
|
-
.foregroundStyle(.secondary)
|
|
42
|
+
.background(.quaternary.opacity(0.5))
|
|
43
|
+
.cornerRadius(8)
|
|
63
44
|
|
|
64
|
-
|
|
65
|
-
|
|
45
|
+
if !service.hasPokeLoginCredentials {
|
|
46
|
+
Button {
|
|
47
|
+
service.runPokeLogin()
|
|
48
|
+
} label: {
|
|
49
|
+
Label("Sign in with Poke", systemImage: "person.crop.circle.badge.plus")
|
|
50
|
+
}
|
|
51
|
+
.controlSize(.large)
|
|
66
52
|
|
|
67
|
-
|
|
68
|
-
destination: URL(string: "https://poke.com/kitchen/api-keys")!)
|
|
53
|
+
Text("Opens a browser window to sign in.")
|
|
69
54
|
.font(.caption)
|
|
70
|
-
.foregroundStyle(.
|
|
55
|
+
.foregroundStyle(.secondary)
|
|
71
56
|
}
|
|
72
57
|
}
|
|
73
58
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
59
|
+
VStack(alignment: .leading, spacing: 8) {
|
|
60
|
+
Text("CONNECTION")
|
|
61
|
+
.font(.caption2)
|
|
62
|
+
.foregroundStyle(.tertiary)
|
|
63
|
+
.textCase(.uppercase)
|
|
64
|
+
.tracking(0.5)
|
|
79
65
|
|
|
80
|
-
|
|
66
|
+
HStack(spacing: 8) {
|
|
67
|
+
Circle()
|
|
68
|
+
.fill(connectionColor)
|
|
69
|
+
.frame(width: 8, height: 8)
|
|
70
|
+
|
|
71
|
+
Text(service.status.rawValue)
|
|
72
|
+
.font(.subheadline)
|
|
73
|
+
|
|
74
|
+
Spacer()
|
|
81
75
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
service.authSource = .apiKey
|
|
76
|
+
Button {
|
|
77
|
+
service.restart()
|
|
78
|
+
} label: {
|
|
79
|
+
Label("Reconnect", systemImage: "arrow.counterclockwise")
|
|
80
|
+
.font(.caption)
|
|
88
81
|
}
|
|
82
|
+
}
|
|
83
|
+
.padding(10)
|
|
84
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
85
|
+
.background(.quaternary.opacity(0.5))
|
|
86
|
+
.cornerRadius(8)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
HStack {
|
|
90
|
+
Spacer()
|
|
91
|
+
Button("Close") {
|
|
89
92
|
dismiss()
|
|
90
|
-
service.restart()
|
|
91
93
|
}
|
|
92
|
-
.keyboardShortcut(.
|
|
93
|
-
.disabled(!usePokeLogin && apiKeyInput.isEmpty)
|
|
94
|
-
.disabled(usePokeLogin && !service.hasPokeLoginCredentials)
|
|
94
|
+
.keyboardShortcut(.cancelAction)
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
.padding(20)
|
|
98
98
|
.frame(width: 380)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private var connectionColor: Color {
|
|
102
|
+
switch service.status {
|
|
103
|
+
case .connected: .green
|
|
104
|
+
case .starting, .disconnected: .yellow
|
|
105
|
+
case .error: .red
|
|
106
|
+
case .stopped: .gray
|
|
102
107
|
}
|
|
103
108
|
}
|
|
104
109
|
}
|
|
@@ -264,7 +264,7 @@
|
|
|
264
264
|
"$(inherited)",
|
|
265
265
|
"@executable_path/../Frameworks",
|
|
266
266
|
);
|
|
267
|
-
MARKETING_VERSION = 0.0
|
|
267
|
+
MARKETING_VERSION = 0.1.0;
|
|
268
268
|
PRODUCT_BUNDLE_IDENTIFIER = "dev.fka.Poke-macOS-Gate";
|
|
269
269
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
270
270
|
REGISTER_APP_GROUPS = YES;
|
|
@@ -296,7 +296,7 @@
|
|
|
296
296
|
"$(inherited)",
|
|
297
297
|
"@executable_path/../Frameworks",
|
|
298
298
|
);
|
|
299
|
-
MARKETING_VERSION = 0.0
|
|
299
|
+
MARKETING_VERSION = 0.1.0;
|
|
300
300
|
PRODUCT_BUNDLE_IDENTIFIER = "dev.fka.Poke-macOS-Gate";
|
|
301
301
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
302
302
|
REGISTER_APP_GROUPS = YES;
|
|
Binary file
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import js from "@eslint/js";
|
|
2
|
+
import globals from "globals";
|
|
3
|
+
|
|
4
|
+
export default [
|
|
5
|
+
{
|
|
6
|
+
ignores: ["clients/**", "node_modules/**"]
|
|
7
|
+
},
|
|
8
|
+
js.configs.recommended,
|
|
9
|
+
{
|
|
10
|
+
languageOptions: {
|
|
11
|
+
ecmaVersion: "latest",
|
|
12
|
+
sourceType: "module",
|
|
13
|
+
globals: {
|
|
14
|
+
...globals.node
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
rules: {
|
|
18
|
+
"no-unused-vars": ["warn", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
|
|
19
|
+
"no-empty": ["error", { allowEmptyCatch: true }],
|
|
20
|
+
"no-console": "off"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
];
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @agent beeper
|
|
3
|
+
* @name Beeper Message Digest
|
|
4
|
+
* @description Fetches messages from the last hour via Beeper Desktop and sends a summary to Poke.
|
|
5
|
+
* @interval 1h
|
|
6
|
+
* @env BEEPER_TOKEN - Beeper Desktop local API token (Settings > API)
|
|
7
|
+
* @env BEEPER_BASE_URL - (optional) Override default http://localhost:23373
|
|
8
|
+
* @author f
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Poke, getToken } from "poke";
|
|
12
|
+
|
|
13
|
+
const BEEPER_BASE = process.env.BEEPER_BASE_URL || "http://localhost:23373";
|
|
14
|
+
const BEEPER_TOKEN = process.env.BEEPER_TOKEN;
|
|
15
|
+
|
|
16
|
+
if (!BEEPER_TOKEN) {
|
|
17
|
+
console.error("BEEPER_TOKEN not set. Create ~/.config/poke-gate/agents/.env.beeper");
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function beeperRequest(path, params = {}) {
|
|
22
|
+
const url = new URL(BEEPER_BASE + path);
|
|
23
|
+
for (const [key, value] of Object.entries(params)) {
|
|
24
|
+
if (value !== undefined) url.searchParams.set(key, String(value));
|
|
25
|
+
}
|
|
26
|
+
const res = await fetch(url, {
|
|
27
|
+
headers: {
|
|
28
|
+
Authorization: `Bearer ${BEEPER_TOKEN}`,
|
|
29
|
+
Accept: "application/json",
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
if (!res.ok) throw new Error(`Beeper API ${res.status}: ${await res.text()}`);
|
|
33
|
+
return res.json();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function getRecentMessages() {
|
|
37
|
+
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
|
|
38
|
+
|
|
39
|
+
const data = await beeperRequest("/v1/messages/search", {
|
|
40
|
+
dateAfter: oneHourAgo,
|
|
41
|
+
limit: 200,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return data.items || [];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function groupBySender(messages) {
|
|
48
|
+
const groups = {};
|
|
49
|
+
for (const msg of messages) {
|
|
50
|
+
if (msg.isSender) continue;
|
|
51
|
+
const name = msg.senderName || msg.senderID || "Unknown";
|
|
52
|
+
if (!groups[name]) groups[name] = [];
|
|
53
|
+
if (msg.text) groups[name].push(msg.text);
|
|
54
|
+
}
|
|
55
|
+
return groups;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function buildSummary(groups) {
|
|
59
|
+
const senders = Object.keys(groups);
|
|
60
|
+
if (senders.length === 0) return null;
|
|
61
|
+
|
|
62
|
+
let summary = `Messages from the last hour (${senders.length} people):\n\n`;
|
|
63
|
+
|
|
64
|
+
for (const [sender, messages] of Object.entries(groups)) {
|
|
65
|
+
summary += `${sender} (${messages.length} messages):\n`;
|
|
66
|
+
for (const text of messages.slice(-3)) {
|
|
67
|
+
const preview = text.length > 100 ? text.slice(0, 100) + "…" : text;
|
|
68
|
+
summary += ` - ${preview}\n`;
|
|
69
|
+
}
|
|
70
|
+
summary += "\n";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return summary.trim();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function main() {
|
|
77
|
+
console.log("Fetching messages from the last hour...");
|
|
78
|
+
|
|
79
|
+
const messages = await getRecentMessages();
|
|
80
|
+
console.log(`Found ${messages.length} messages`);
|
|
81
|
+
|
|
82
|
+
const groups = groupBySender(messages);
|
|
83
|
+
const summary = buildSummary(groups);
|
|
84
|
+
|
|
85
|
+
if (!summary) {
|
|
86
|
+
console.log("No new messages from others in the last hour.");
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.log("Sending summary to Poke...");
|
|
91
|
+
|
|
92
|
+
const token = getToken();
|
|
93
|
+
if (!token) {
|
|
94
|
+
console.error("Not logged in to Poke. Run: npx poke login");
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const poke = new Poke({ apiKey: token });
|
|
99
|
+
await poke.sendMessage(
|
|
100
|
+
`Here's a summary of my Beeper messages from the last hour:\n\n${summary}`
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
console.log("Summary sent to Poke.");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
main().catch((err) => {
|
|
107
|
+
console.error("Agent error:", err.message);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
});
|
package/package.json
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "poke-gate",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Expose your machine to your Poke AI assistant via MCP tunnel",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"poke-gate": "./bin/poke-gate.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"start": "node src/app.js"
|
|
10
|
+
"start": "node src/app.js",
|
|
11
|
+
"lint": "eslint .",
|
|
12
|
+
"lint:fix": "eslint . --fix",
|
|
13
|
+
"lint:md": "remark . --quiet --frail",
|
|
14
|
+
"format": "prettier --write \"{src,bin}/**/*.js\"",
|
|
15
|
+
"format:md": "remark . --output --quiet"
|
|
11
16
|
},
|
|
12
17
|
"keywords": [
|
|
13
18
|
"poke",
|
|
@@ -27,5 +32,17 @@
|
|
|
27
32
|
},
|
|
28
33
|
"dependencies": {
|
|
29
34
|
"poke": "^0.4.2"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@eslint/js": "^9.39.1",
|
|
38
|
+
"eslint": "^9.39.1",
|
|
39
|
+
"globals": "^16.5.0",
|
|
40
|
+
"prettier": "^3.6.2",
|
|
41
|
+
"remark-cli": "^12.0.1",
|
|
42
|
+
"remark-lint-list-item-indent": "^4.0.1",
|
|
43
|
+
"remark-parse": "^11.0.0",
|
|
44
|
+
"remark-preset-lint-consistent": "^6.0.1",
|
|
45
|
+
"remark-preset-lint-recommended": "^7.0.1",
|
|
46
|
+
"remark-stringify": "^11.0.0"
|
|
30
47
|
}
|
|
31
48
|
}
|