poke-gate 0.1.1 → 0.1.4

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.
@@ -0,0 +1,15 @@
1
+ {
2
+ "private": true,
3
+ "scripts": {
4
+ "dev": "vitepress dev",
5
+ "build": "vitepress build",
6
+ "preview": "vitepress preview"
7
+ },
8
+ "devDependencies": {
9
+ "vitepress": "^1.6.3"
10
+ },
11
+ "dependencies": {
12
+ "mermaid": "^11.13.0",
13
+ "vitepress-plugin-mermaid": "^2.0.17"
14
+ }
15
+ }
@@ -0,0 +1 @@
1
+ poke-gate.fka.dev
Binary file
Binary file
@@ -0,0 +1,35 @@
1
+ # Security
2
+
3
+ ::: danger Full shell access
4
+ Poke Gate grants **full shell access** to your Poke agent. Understand the implications before running it.
5
+ :::
6
+
7
+ ## What the agent can do
8
+
9
+ When Poke Gate is running, your Poke agent can:
10
+
11
+ - **Run any command** with your user's permissions (`run_command`)
12
+ - **Read any file** your user can access (`read_file`, `read_image`)
13
+ - **Write any file** your user can access (`write_file`)
14
+ - **List any directory** (`list_directory`)
15
+ - **Take screenshots** of your screen (`take_screenshot`)
16
+ - **See system info** — hostname, memory, uptime (`system_info`)
17
+
18
+ ## What protects you
19
+
20
+ - **Authentication** — only your Poke agent (authenticated via your Poke OAuth session) can reach the tunnel. No one else can send tool calls.
21
+ - **Tunnel isolation** — the MCP server only listens on `127.0.0.1` (localhost). It's not exposed to the network. The tunnel is the only way to reach it.
22
+ - **No persistent access** — when you quit Poke Gate (Ctrl-C or Quit from menu bar), the tunnel closes and the connection is deleted. Your machine is no longer reachable.
23
+ - **Connection cleanup** — old connections are deleted before new ones are created, preventing stale tunnels.
24
+
25
+ ## Best practices
26
+
27
+ 1. **Only run on trusted machines** — don't run Poke Gate on shared or public computers.
28
+ 2. **Quit when not needed** — close the app when you don't need remote access.
29
+ 3. **Review agent scripts** — before installing a community agent, read the code. Agents run with your full user permissions.
30
+ 4. **Keep env files secure** — `.env` files in `~/.config/poke-gate/agents/` may contain API tokens. Don't commit them to git.
31
+ 5. **Use verbose mode** — run with `--verbose` to see what tools are being called in real time.
32
+
33
+ ## Reporting issues
34
+
35
+ If you discover a security vulnerability, please email [security@fka.dev](mailto:security@fka.dev) instead of opening a public issue.
package/docs/tools.md ADDED
@@ -0,0 +1,101 @@
1
+ # Tools
2
+
3
+ Poke Gate exposes 7 tools to your Poke agent via MCP.
4
+
5
+ ## run_command
6
+
7
+ Execute any shell command on your machine.
8
+
9
+ | Parameter | Type | Required | Description |
10
+ |-----------|------|----------|-------------|
11
+ | `command` | string | yes | The shell command to execute |
12
+ | `cwd` | string | no | Working directory (defaults to home) |
13
+
14
+ **Returns:** `{ stdout, stderr, exitCode }`
15
+
16
+ **Examples from Poke:**
17
+ - "Run `ls -la` in my home directory"
18
+ - "What's running on port 3000?"
19
+ - "Show me the git log for my project"
20
+ - "Install lodash in my project"
21
+
22
+ ::: info
23
+ Commands have a 30-second timeout and 1MB output buffer.
24
+ :::
25
+
26
+ ## read_file
27
+
28
+ Read the contents of a text file.
29
+
30
+ | Parameter | Type | Required | Description |
31
+ |-----------|------|----------|-------------|
32
+ | `path` | string | yes | Absolute or relative path (supports `~`) |
33
+
34
+ **Examples:**
35
+ - "Read my ~/.zshrc"
36
+ - "Show me the package.json in my project"
37
+
38
+ ## read_image
39
+
40
+ Read an image or binary file and return it as base64.
41
+
42
+ | Parameter | Type | Required | Description |
43
+ |-----------|------|----------|-------------|
44
+ | `path` | string | yes | Path to the image file |
45
+
46
+ Supports: png, jpg, gif, webp, svg, pdf, bmp, ico.
47
+
48
+ For image files, returns MCP `image` content type with base64 data so the agent can "see" the image.
49
+
50
+ ## write_file
51
+
52
+ Write content to a file. Creates the file if it doesn't exist, overwrites if it does.
53
+
54
+ | Parameter | Type | Required | Description |
55
+ |-----------|------|----------|-------------|
56
+ | `path` | string | yes | Absolute or relative path (supports `~`) |
57
+ | `content` | string | yes | Content to write |
58
+
59
+ **Examples:**
60
+ - "Create a file called notes.txt on my Desktop"
61
+ - "Write a Python script that..."
62
+
63
+ ## list_directory
64
+
65
+ List files and directories at a given path.
66
+
67
+ | Parameter | Type | Required | Description |
68
+ |-----------|------|----------|-------------|
69
+ | `path` | string | no | Directory path (defaults to home, supports `~`) |
70
+
71
+ Returns entries with `d` for directories and `-` for files.
72
+
73
+ ## system_info
74
+
75
+ Get system information. No parameters needed.
76
+
77
+ **Returns:**
78
+ ```json
79
+ {
80
+ "hostname": "MacBook-Pro.local",
81
+ "platform": "darwin",
82
+ "arch": "arm64",
83
+ "uptime": "5h 23m",
84
+ "totalMemory": "16GB",
85
+ "freeMemory": "4GB",
86
+ "homeDir": "/Users/you",
87
+ "nodeVersion": "v22.21.1"
88
+ }
89
+ ```
90
+
91
+ ## take_screenshot
92
+
93
+ Capture the screen and save it to a file.
94
+
95
+ | Parameter | Type | Required | Description |
96
+ |-----------|------|----------|-------------|
97
+ | `path` | string | no | Save path (defaults to `~/Desktop/screenshot-<timestamp>.png`) |
98
+
99
+ ::: warning
100
+ Requires **Screen Recording** permission on macOS. The system will prompt you the first time.
101
+ :::
@@ -0,0 +1,78 @@
1
+ /**
2
+ * @agent battery
3
+ * @name Battery Guardian
4
+ * @description Alerts you via Poke when your battery drops below 20%.
5
+ * @interval 30m
6
+ * @author f
7
+ */
8
+
9
+ import { Poke, getToken } from "poke";
10
+ import { execSync } from "node:child_process";
11
+ import { readFileSync, writeFileSync, existsSync } from "node:fs";
12
+ import { join } from "node:path";
13
+ import { homedir } from "node:os";
14
+
15
+ const token = getToken();
16
+ if (!token) {
17
+ console.error("Not signed in. Run: npx poke login");
18
+ process.exit(1);
19
+ }
20
+
21
+ const THRESHOLD = parseInt(process.env.BATTERY_THRESHOLD || "20", 10);
22
+ const STATE_FILE = join(homedir(), ".config", "poke-gate", "agents", ".battery-state.json");
23
+
24
+ function getBattery() {
25
+ try {
26
+ const output = execSync("pmset -g batt", { encoding: "utf-8", timeout: 5000 });
27
+ const match = output.match(/(\d+)%/);
28
+ const charging = output.includes("AC Power") || output.includes("charging");
29
+ return {
30
+ level: match ? parseInt(match[1], 10) : null,
31
+ charging,
32
+ };
33
+ } catch {
34
+ return { level: null, charging: false };
35
+ }
36
+ }
37
+
38
+ function loadState() {
39
+ try {
40
+ return JSON.parse(readFileSync(STATE_FILE, "utf-8"));
41
+ } catch {
42
+ return { alerted: false };
43
+ }
44
+ }
45
+
46
+ function saveState(state) {
47
+ writeFileSync(STATE_FILE, JSON.stringify(state));
48
+ }
49
+
50
+ const { level, charging } = getBattery();
51
+
52
+ if (level === null) {
53
+ console.log("Could not read battery level.");
54
+ process.exit(0);
55
+ }
56
+
57
+ console.log(`Battery: ${level}% ${charging ? "(charging)" : "(on battery)"}`);
58
+
59
+ const state = loadState();
60
+
61
+ if (level <= THRESHOLD && !charging && !state.alerted) {
62
+ console.log(`Battery low (${level}%). Alerting Poke...`);
63
+
64
+ const poke = new Poke({ apiKey: token });
65
+ await poke.sendMessage(
66
+ `⚠️ Battery alert: your Mac is at ${level}%. You're not plugged in. Consider charging soon.`
67
+ );
68
+
69
+ saveState({ alerted: true });
70
+ console.log("Alert sent.");
71
+ } else if (level > THRESHOLD || charging) {
72
+ if (state.alerted) {
73
+ saveState({ alerted: false });
74
+ console.log("Battery recovered, reset alert state.");
75
+ } else {
76
+ console.log("Battery OK, no alert needed.");
77
+ }
78
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * @agent screentime
3
+ * @name Screen Time Report
4
+ * @description Sends a daily summary of your most-used apps to Poke.
5
+ * @interval 24h
6
+ * @author f
7
+ */
8
+
9
+ import { Poke, getToken } from "poke";
10
+ import { execSync } from "node:child_process";
11
+
12
+ const token = getToken();
13
+ if (!token) {
14
+ console.error("Not signed in. Run: npx poke login");
15
+ process.exit(1);
16
+ }
17
+
18
+ function getScreenTime() {
19
+ try {
20
+ const result = execSync(
21
+ `defaults read com.apple.ScreenTimeAgent 2>/dev/null || echo "{}"`,
22
+ { encoding: "utf-8", timeout: 10000 }
23
+ ).trim();
24
+
25
+ // Fallback: use process list to estimate active apps
26
+ const ps = execSync(
27
+ `ps -eo etime,comm | grep -i "/Applications/" | sort -rn | head -20`,
28
+ { encoding: "utf-8", timeout: 10000 }
29
+ ).trim();
30
+
31
+ return ps;
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+
37
+ function getActiveApps() {
38
+ try {
39
+ const script = `
40
+ tell application "System Events"
41
+ set appList to name of every application process whose background only is false
42
+ end tell
43
+ return appList as text
44
+ `;
45
+ const result = execSync(`osascript -e '${script}'`, {
46
+ encoding: "utf-8",
47
+ timeout: 10000,
48
+ }).trim();
49
+ return result.split(", ");
50
+ } catch {
51
+ return [];
52
+ }
53
+ }
54
+
55
+ function getUptime() {
56
+ try {
57
+ return execSync("uptime", { encoding: "utf-8", timeout: 5000 }).trim();
58
+ } catch {
59
+ return "unknown";
60
+ }
61
+ }
62
+
63
+ const apps = getActiveApps();
64
+ const uptimeStr = getUptime();
65
+ const screenData = getScreenTime();
66
+
67
+ let report = `Daily screen report:\n\n`;
68
+ report += `Uptime: ${uptimeStr}\n\n`;
69
+
70
+ if (apps.length > 0) {
71
+ report += `Currently running apps (${apps.length}):\n`;
72
+ for (const app of apps) {
73
+ report += ` - ${app}\n`;
74
+ }
75
+ }
76
+
77
+ if (screenData) {
78
+ report += `\nTop processes by runtime:\n${screenData}\n`;
79
+ }
80
+
81
+ console.log("Sending screen time report...");
82
+
83
+ const poke = new Poke({ apiKey: token });
84
+ await poke.sendMessage(report);
85
+
86
+ console.log("Report sent.");
@@ -0,0 +1,85 @@
1
+ /**
2
+ * @agent wifi
3
+ * @name WiFi Logger
4
+ * @description Logs your current WiFi network to Poke so it knows where you are.
5
+ * @interval 30m
6
+ * @author f
7
+ */
8
+
9
+ import { Poke, getToken } from "poke";
10
+ import { execSync } from "node:child_process";
11
+ import { readFileSync, writeFileSync } from "node:fs";
12
+ import { join } from "node:path";
13
+ import { homedir } from "node:os";
14
+
15
+ const token = getToken();
16
+ if (!token) {
17
+ console.error("Not signed in. Run: npx poke login");
18
+ process.exit(1);
19
+ }
20
+
21
+ const STATE_FILE = join(homedir(), ".config", "poke-gate", "agents", ".wifi-state.json");
22
+
23
+ function getCurrentNetwork() {
24
+ try {
25
+ const iface = execSync(
26
+ "networksetup -listallhardwareports | awk '/Wi-Fi/{getline; print $2}'",
27
+ { encoding: "utf-8", timeout: 5000 }
28
+ ).trim();
29
+
30
+ const ssid = execSync(
31
+ `networksetup -getairportnetwork ${iface || "en0"} 2>/dev/null | sed 's/Current Wi-Fi Network: //'`,
32
+ { encoding: "utf-8", timeout: 5000 }
33
+ ).trim();
34
+
35
+ if (ssid.includes("not associated") || !ssid) return null;
36
+ return ssid;
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+
42
+ function loadState() {
43
+ try {
44
+ return JSON.parse(readFileSync(STATE_FILE, "utf-8"));
45
+ } catch {
46
+ return { lastNetwork: null };
47
+ }
48
+ }
49
+
50
+ function saveState(state) {
51
+ writeFileSync(STATE_FILE, JSON.stringify(state));
52
+ }
53
+
54
+ const network = getCurrentNetwork();
55
+ const state = loadState();
56
+
57
+ console.log(`Current WiFi: ${network || "not connected"}`);
58
+
59
+ if (network && network !== state.lastNetwork) {
60
+ console.log(`Network changed from "${state.lastNetwork}" to "${network}". Notifying Poke...`);
61
+
62
+ const poke = new Poke({ apiKey: token });
63
+ await poke.sendMessage(
64
+ `I just connected to WiFi network "${network}". ` +
65
+ (state.lastNetwork
66
+ ? `Previously I was on "${state.lastNetwork}".`
67
+ : `This is the first network I've logged.`) +
68
+ ` Remember this for context about where I am.`
69
+ );
70
+
71
+ saveState({ lastNetwork: network });
72
+ console.log("Poke notified.");
73
+ } else if (!network && state.lastNetwork) {
74
+ console.log("Disconnected from WiFi. Notifying Poke...");
75
+
76
+ const poke = new Poke({ apiKey: token });
77
+ await poke.sendMessage(
78
+ `I've disconnected from WiFi (was on "${state.lastNetwork}"). I might be on the move.`
79
+ );
80
+
81
+ saveState({ lastNetwork: null });
82
+ console.log("Poke notified.");
83
+ } else {
84
+ console.log("No network change.");
85
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poke-gate",
3
- "version": "0.1.1",
3
+ "version": "0.1.4",
4
4
  "description": "Expose your machine to your Poke AI assistant via MCP tunnel",
5
5
  "type": "module",
6
6
  "bin": {
package/src/agents.js CHANGED
@@ -309,7 +309,16 @@ async function promptEnvKeys(keys) {
309
309
  return values;
310
310
  }
311
311
 
312
+ let schedulerRunning = false;
313
+ const activeTimers = [];
314
+
312
315
  export function startAgentScheduler() {
316
+ if (schedulerRunning) {
317
+ log("Agent scheduler already running, skipping.");
318
+ return;
319
+ }
320
+ schedulerRunning = true;
321
+
313
322
  const agents = discoverAgents();
314
323
 
315
324
  if (agents.length === 0) {
@@ -328,8 +337,18 @@ export function startAgentScheduler() {
328
337
  for (const agent of agents) {
329
338
  runAgentProcess(agent);
330
339
 
331
- setInterval(() => {
340
+ const timer = setInterval(() => {
332
341
  runAgentProcess(agent);
333
342
  }, agent.intervalMs);
343
+ activeTimers.push(timer);
344
+ }
345
+ }
346
+
347
+ export function stopAgentScheduler() {
348
+ for (const timer of activeTimers) {
349
+ clearInterval(timer);
334
350
  }
351
+ activeTimers.length = 0;
352
+ schedulerRunning = false;
353
+ log("Agent scheduler stopped.");
335
354
  }