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.
- package/.github/workflows/docs.yml +56 -0
- package/README.md +8 -4
- package/assets/screenshots/agents-editor.png +0 -0
- package/clients/Poke macOS Gate/Poke macOS Gate/AgentsView.swift +485 -0
- package/clients/Poke macOS Gate/Poke macOS Gate/Poke_macOS_GateApp.swift +10 -0
- 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/docs/.vitepress/config.mts +75 -0
- package/docs/agents/beeper.md +107 -0
- package/docs/agents/community.md +77 -0
- package/docs/agents/creating.md +132 -0
- package/docs/agents/index.md +85 -0
- package/docs/agents/installing.md +66 -0
- package/docs/agents/sharing.md +97 -0
- package/docs/cli.md +73 -0
- package/docs/getting-started.md +62 -0
- package/docs/how-it-works.md +56 -0
- package/docs/index.md +63 -0
- package/docs/macos-app.md +74 -0
- package/docs/package-lock.json +3629 -0
- package/docs/package.json +15 -0
- package/docs/public/CNAME +1 -0
- package/docs/public/agents-editor.png +0 -0
- package/docs/public/logo.png +0 -0
- package/docs/security.md +35 -0
- package/docs/tools.md +101 -0
- package/examples/agents/battery.30m.js +78 -0
- package/examples/agents/screentime.24h.js +86 -0
- package/examples/agents/wifi.30m.js +85 -0
- package/package.json +1 -1
- package/src/agents.js +20 -1
|
@@ -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
|
package/docs/security.md
ADDED
|
@@ -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
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
|
}
|