poke-gate 0.1.0 → 0.1.1
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 +4 -4
- package/examples/agents/beeper.1h.js +13 -5
- package/package.json +1 -1
- package/src/agents.js +87 -9
package/README.md
CHANGED
|
@@ -238,16 +238,16 @@ Only run Poke Gate on machines and networks you trust.
|
|
|
238
238
|
clients/
|
|
239
239
|
Poke macOS Gate/ macOS menu bar app (SwiftUI)
|
|
240
240
|
bin/
|
|
241
|
-
poke-gate.js CLI entry point, run-agent
|
|
241
|
+
poke-gate.js CLI entry point, run-agent subcommand
|
|
242
242
|
src/
|
|
243
243
|
app.js Startup: MCP server + tunnel + agent scheduler
|
|
244
|
-
agents.js Agent discovery, scheduling,
|
|
244
|
+
agents.js Agent discovery, scheduling, and runner
|
|
245
245
|
mcp-server.js JSON-RPC MCP handler with OS tools
|
|
246
246
|
tunnel.js PokeTunnel wrapper
|
|
247
247
|
examples/
|
|
248
248
|
agents/
|
|
249
|
-
beeper.1h.js Example: Beeper message digest
|
|
250
|
-
.env.beeper Example env file
|
|
249
|
+
beeper.1h.js Example: Beeper message digest
|
|
250
|
+
.env.beeper Example env file
|
|
251
251
|
```
|
|
252
252
|
|
|
253
253
|
## Credits
|
|
@@ -36,12 +36,20 @@ async function beeperRequest(path, params = {}) {
|
|
|
36
36
|
async function getRecentMessages() {
|
|
37
37
|
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
let allMessages = [];
|
|
40
|
+
let cursor = null;
|
|
41
|
+
|
|
42
|
+
while (true) {
|
|
43
|
+
const params = { dateAfter: oneHourAgo, limit: 20 };
|
|
44
|
+
if (cursor) params.cursor = cursor;
|
|
45
|
+
const data = await beeperRequest("/v1/messages/search", params);
|
|
46
|
+
const items = data.items || [];
|
|
47
|
+
allMessages.push(...items);
|
|
48
|
+
if (!data.hasMore || !data.oldestCursor) break;
|
|
49
|
+
cursor = data.oldestCursor;
|
|
50
|
+
}
|
|
43
51
|
|
|
44
|
-
return
|
|
52
|
+
return allMessages;
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
function groupBySender(messages) {
|
package/package.json
CHANGED
package/src/agents.js
CHANGED
|
@@ -2,6 +2,7 @@ import { readdirSync, readFileSync, writeFileSync, existsSync, mkdirSync } from
|
|
|
2
2
|
import { join, basename } from "node:path";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { exec } from "node:child_process";
|
|
5
|
+
import { createInterface } from "node:readline";
|
|
5
6
|
|
|
6
7
|
const CONFIG_DIR = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
7
8
|
const AGENTS_DIR = join(CONFIG_DIR, "poke-gate", "agents");
|
|
@@ -109,10 +110,31 @@ export function discoverAgents() {
|
|
|
109
110
|
return agents;
|
|
110
111
|
}
|
|
111
112
|
|
|
113
|
+
import { symlinkSync, lstatSync } from "node:fs";
|
|
114
|
+
|
|
115
|
+
function ensureNodeModulesLink() {
|
|
116
|
+
const pkgRoot = join(new URL(".", import.meta.url).pathname, "..");
|
|
117
|
+
const source = join(pkgRoot, "node_modules");
|
|
118
|
+
const target = join(AGENTS_DIR, "node_modules");
|
|
119
|
+
|
|
120
|
+
if (!existsSync(source)) return;
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const stat = lstatSync(target);
|
|
124
|
+
if (stat.isSymbolicLink()) return;
|
|
125
|
+
} catch {}
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
symlinkSync(source, target, "junction");
|
|
129
|
+
} catch {}
|
|
130
|
+
}
|
|
131
|
+
|
|
112
132
|
function runAgentProcess(agent) {
|
|
113
133
|
const agentEnv = parseEnvFile(agent.envFile);
|
|
114
134
|
const env = { ...process.env, ...agentEnv };
|
|
115
135
|
|
|
136
|
+
ensureNodeModulesLink();
|
|
137
|
+
|
|
116
138
|
log(`Running agent: ${agent.name} (${agent.file})`);
|
|
117
139
|
|
|
118
140
|
return new Promise((resolve) => {
|
|
@@ -212,25 +234,81 @@ export async function downloadAgent(name) {
|
|
|
212
234
|
writeFileSync(dest, jsContent);
|
|
213
235
|
console.log(` Saved: ${dest}`);
|
|
214
236
|
|
|
215
|
-
// Try to download matching .env file
|
|
216
237
|
const envName = name.split(".")[0];
|
|
238
|
+
const envDest = join(AGENTS_DIR, `.env.${envName}`);
|
|
239
|
+
|
|
240
|
+
if (existsSync(envDest)) {
|
|
241
|
+
console.log(` .env.${envName} already exists, skipped.`);
|
|
242
|
+
console.log(`\n Test it: npx poke-gate run-agent ${envName}`);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
217
246
|
const envRes = await fetch(`${REPO_BASE}/.env.${envName}`).catch(() => null);
|
|
218
247
|
if (envRes?.ok) {
|
|
219
|
-
const
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
console.log(
|
|
224
|
-
|
|
225
|
-
|
|
248
|
+
const envTemplate = await envRes.text();
|
|
249
|
+
const keys = parseEnvKeys(envTemplate);
|
|
250
|
+
|
|
251
|
+
if (keys.length > 0) {
|
|
252
|
+
console.log(`\n This agent needs ${keys.length} env variable(s):\n`);
|
|
253
|
+
const values = await promptEnvKeys(keys);
|
|
254
|
+
let content = "";
|
|
255
|
+
for (const { key, comment } of keys) {
|
|
256
|
+
if (comment) content += `# ${comment}\n`;
|
|
257
|
+
content += `${key}=${values[key] || ""}\n`;
|
|
258
|
+
}
|
|
259
|
+
writeFileSync(envDest, content);
|
|
260
|
+
console.log(`\n Saved: ${envDest}`);
|
|
226
261
|
} else {
|
|
227
|
-
|
|
262
|
+
writeFileSync(envDest, envTemplate);
|
|
263
|
+
console.log(` Saved: ${envDest}`);
|
|
228
264
|
}
|
|
229
265
|
}
|
|
230
266
|
|
|
231
267
|
console.log(`\n Test it: npx poke-gate run-agent ${envName}`);
|
|
232
268
|
}
|
|
233
269
|
|
|
270
|
+
function parseEnvKeys(template) {
|
|
271
|
+
const keys = [];
|
|
272
|
+
const lines = template.split("\n");
|
|
273
|
+
let lastComment = null;
|
|
274
|
+
for (const line of lines) {
|
|
275
|
+
const trimmed = line.trim();
|
|
276
|
+
if (trimmed.startsWith("#")) {
|
|
277
|
+
lastComment = trimmed.slice(1).trim();
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
const eqIdx = trimmed.indexOf("=");
|
|
281
|
+
if (eqIdx === -1) { lastComment = null; continue; }
|
|
282
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
283
|
+
const value = trimmed.slice(eqIdx + 1).trim();
|
|
284
|
+
const isPlaceholder = !value || value.includes("your_") || value.includes("_here");
|
|
285
|
+
if (isPlaceholder) {
|
|
286
|
+
keys.push({ key, comment: lastComment });
|
|
287
|
+
}
|
|
288
|
+
lastComment = null;
|
|
289
|
+
}
|
|
290
|
+
return keys;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function ask(question) {
|
|
294
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
295
|
+
return new Promise((resolve) => {
|
|
296
|
+
rl.question(question, (answer) => {
|
|
297
|
+
rl.close();
|
|
298
|
+
resolve(answer.trim());
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function promptEnvKeys(keys) {
|
|
304
|
+
const values = {};
|
|
305
|
+
for (const { key, comment } of keys) {
|
|
306
|
+
const hint = comment ? ` (${comment})` : "";
|
|
307
|
+
values[key] = await ask(` ${key}${hint}: `);
|
|
308
|
+
}
|
|
309
|
+
return values;
|
|
310
|
+
}
|
|
311
|
+
|
|
234
312
|
export function startAgentScheduler() {
|
|
235
313
|
const agents = discoverAgents();
|
|
236
314
|
|