@cosmovex/agentpager 0.1.4 → 0.1.5

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/dist/cli.js CHANGED
@@ -21,7 +21,7 @@ import { staleBuildWarning } from './freshness.js';
21
21
  import { runRuleCommand, USAGE as GUARD_USAGE } from './guard/cli.js';
22
22
  import { install as guardInstall, runDeskHook, runHook, status as guardStatus, uninstall as guardUninstall } from './guard/index.js';
23
23
  import { Channel } from './relay.js';
24
- import { installService, uninstallService } from './service.js';
24
+ import { installService, serviceInstalled, uninstallService } from './service.js';
25
25
  import { loadState, updateState } from './store.js';
26
26
  import { isNewer, latestVersion, UPDATE_COMMAND } from './update.js';
27
27
  const dim = (s) => `\x1b[2m${s}\x1b[0m`;
@@ -41,6 +41,29 @@ function printPhones(devices, indent = ' ') {
41
41
  console.log(`${indent}${bold(d.name)} ${dim(`id ${d.id} · paired ${new Date(d.pairedAt).toLocaleDateString()}`)}`);
42
42
  }
43
43
  }
44
+ const HELP = `
45
+ ${bold('AgentPager')} — your phone, one tap from your coding agents.
46
+
47
+ ${bold('Everyday')}
48
+ agentpager start it (keep the window open)
49
+ agentpager stop stop it
50
+ agentpager status is it running, who is paired, is there an update
51
+
52
+ ${bold('Phones')}
53
+ agentpager pair add a phone (or the same one after a reinstall)
54
+ agentpager devices who is paired
55
+ agentpager unpair remove a phone
56
+
57
+ ${bold('Always on')}
58
+ agentpager service install start by itself at login
59
+ agentpager service uninstall turn that off
60
+
61
+ ${bold('For scripts')}
62
+ agentpager notify "text" page your phone
63
+ agentpager ask "question" wait for your yes or no
64
+
65
+ More: agentpager doctor · agentpager guard
66
+ `;
44
67
  const UNPAIR_HINT = 'Remove a phone: `agentpager unpair <id>` (or `agentpager unpair --all`). List them: `agentpager devices`.';
45
68
  async function pair(state) {
46
69
  // Re-read: this snapshot may be as old as the terminal. "Paired" with no way to see what, or to
@@ -172,6 +195,7 @@ async function serve(state, keepAwake) {
172
195
  }
173
196
  }
174
197
  }, 2000);
198
+ hub.onStop = () => void quit();
175
199
  hub.onUnpair = (ch) => {
176
200
  // The phone forgot us in the app: drop it from disk first, then close the channel.
177
201
  updateState((s) => {
@@ -202,16 +226,14 @@ async function serve(state, keepAwake) {
202
226
  if (process.argv[1]?.includes('_npx')) {
203
227
  // Started with npx: this window IS AgentPager. Say so before someone closes it.
204
228
  console.log(`\n${bold('Keep this window open.')} Closing it (or Ctrl+C) disconnects your phone until you start it again.`);
205
- console.log(dim('Want it always on, even after a restart? npm install -g @cosmovex/agentpager then: agentpager service install'));
229
+ console.log(dim('Always on, even after a restart: npm install -g @cosmovex/agentpager, then agentpager service install'));
206
230
  }
207
231
  else {
208
232
  console.log(dim('\nKeep this running (or run `agentpager service install` to start it at login).'));
209
233
  }
210
234
  // Someone who reinstalls the app, or picks up a second phone, reasonably runs `agentpager` again
211
235
  // and waits for a code that never comes — this is the only place they will look for why.
212
- console.log(dim('Pairing another phone (or the same one after a reinstall)? Run `agentpager pair`.'));
213
- console.log(dim(UNPAIR_HINT));
214
- console.log(dim('Your sessions stay on this computer; the relay only sees encrypted bytes.\n'));
236
+ console.log(dim('Need anything else? Run `agentpager help`.\n'));
215
237
  // Tell the terminal AND the phone when a newer AgentPager is out.
216
238
  const checkUpdate = async () => {
217
239
  const latest = await latestVersion();
@@ -313,6 +335,32 @@ async function main() {
313
335
  console.log(dim(running ? 'The running AgentPager drops it within a couple of seconds and tells the phone.' : 'Nothing is running here, so it is gone for good.'));
314
336
  process.exit(0);
315
337
  }
338
+ case 'help':
339
+ case '--help':
340
+ case '-h':
341
+ console.log(HELP);
342
+ process.exit(0);
343
+ case 'status': {
344
+ const up = await callControl({ kind: 'ping', title: '', text: '' }, 1500).catch(() => null);
345
+ console.log(up ? `● Running on ${bold(state.machineName)}` : `○ Not running. Start it: ${bold('agentpager')} (keep the window open)`);
346
+ console.log(` ${state.devices.length} phone${state.devices.length === 1 ? '' : 's'} paired${state.devices.length ? ':' : ''}`);
347
+ printPhones(state.devices, ' ');
348
+ console.log(` Starts by itself at login: ${serviceInstalled() ? 'yes' : 'no'}`);
349
+ const latest = await latestVersion();
350
+ console.log(latest && isNewer(latest, BRIDGE_VERSION) ? ` ⬆️ ${latest} is available (you have ${BRIDGE_VERSION}): ${bold(UPDATE_COMMAND)}` : ` Version ${BRIDGE_VERSION}, up to date`);
351
+ process.exit(0);
352
+ }
353
+ case 'stop': {
354
+ const up = await callControl({ kind: 'stop', title: '', text: '' }, 3000).catch(() => null);
355
+ if (!up)
356
+ console.log('AgentPager is not running.');
357
+ else {
358
+ console.log('Stopped. Your phone will say so.');
359
+ if (serviceInstalled())
360
+ console.log(dim('It is set to start at login and may restart by itself. To turn that off: agentpager service uninstall'));
361
+ }
362
+ process.exit(0);
363
+ }
316
364
  case 'notify':
317
365
  case 'ask': {
318
366
  // Parse flags properly: a flag's VALUE must not leak into the message body.
@@ -443,7 +491,7 @@ async function main() {
443
491
  process.exit(0);
444
492
  }
445
493
  default:
446
- console.log('Usage: agentpager [start | pair | devices | unpair <id> | doctor | guard install|status|rules|test | notify <text> | ask <text> | service install|uninstall]');
494
+ console.log(HELP);
447
495
  process.exit(1);
448
496
  }
449
497
  }
package/dist/hub.js CHANGED
@@ -60,6 +60,8 @@ export class Hub {
60
60
  detach(channel) {
61
61
  this.channels = this.channels.filter((c) => c !== channel);
62
62
  }
63
+ /** `agentpager stop` from another terminal. */
64
+ onStop;
63
65
  /** Called with the phone that asked to be unpaired; the CLI owns the state file. */
64
66
  onUnpair;
65
67
  /** Newest version on npm, once known. Sent with every hello so the phone can say "update". */
@@ -343,6 +345,10 @@ export class Hub {
343
345
  };
344
346
  if (req.kind === 'ping')
345
347
  return { decision: 'allow', ...reach };
348
+ if (req.kind === 'stop') {
349
+ setTimeout(() => this.onStop?.(), 150); // answer first, then go
350
+ return { decision: 'allow', ...reach };
351
+ }
346
352
  // Nobody to ask is not a denial and not a wait — it's a setup problem, and the script must
347
353
  // learn that in milliseconds, not after an hour of blocking a deploy.
348
354
  if (!reach.phones)
package/dist/service.js CHANGED
@@ -115,3 +115,21 @@ export function uninstallService() {
115
115
  }
116
116
  return 'Nothing to remove.';
117
117
  }
118
+ /** Is the start-at-login service set up? Cheap and offline: it only looks for the file we wrote. */
119
+ export function serviceInstalled() {
120
+ const os = platform();
121
+ if (os === 'darwin')
122
+ return existsSync(join(homedir(), 'Library', 'LaunchAgents', `${LABEL}.plist`));
123
+ if (os === 'linux')
124
+ return existsSync(join(homedir(), '.config', 'systemd', 'user', 'agentpager.service'));
125
+ if (os === 'win32') {
126
+ try {
127
+ execFileSync('schtasks', ['/Query', '/TN', 'AgentPager'], { stdio: 'ignore' });
128
+ return true;
129
+ }
130
+ catch {
131
+ return false;
132
+ }
133
+ }
134
+ return false;
135
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmovex/agentpager",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Pager for your AI coding agents — approve, reply and hear results on your phone. End-to-end encrypted.",
5
5
  "main": "dist/cli.js",
6
6
  "scripts": {