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 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 + agent get subcommands
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, env loading, download
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 agent
250
- .env.beeper Example env file for beeper agent
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
- const data = await beeperRequest("/v1/messages/search", {
40
- dateAfter: oneHourAgo,
41
- limit: 200,
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 data.items || [];
52
+ return allMessages;
45
53
  }
46
54
 
47
55
  function groupBySender(messages) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poke-gate",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
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
@@ -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 envContent = await envRes.text();
220
- const envDest = join(AGENTS_DIR, `.env.${envName}`);
221
- if (!existsSync(envDest)) {
222
- writeFileSync(envDest, envContent);
223
- console.log(` Saved: ${envDest}`);
224
- console.log(`\n Edit the env file with your credentials:`);
225
- console.log(` nano ${envDest}`);
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
- console.log(` .env.${envName} already exists, skipped.`);
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