@cosmovex/agentpager 0.1.3 → 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 +64 -6
- package/dist/hub.js +6 -0
- package/dist/service.js +18 -0
- package/package.json +1 -1
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) => {
|
|
@@ -199,12 +223,17 @@ async function serve(state, keepAwake) {
|
|
|
199
223
|
console.log(` ${s.available ? '●' : '○'} ${s.name}${s.version ? ` ${dim(s.version)}` : ''}${s.available ? '' : dim(` ${s.reason}`)}`);
|
|
200
224
|
console.log(` ${state.devices.length} phone${state.devices.length === 1 ? '' : 's'} paired:`);
|
|
201
225
|
printPhones(state.devices, ' ');
|
|
202
|
-
|
|
226
|
+
if (process.argv[1]?.includes('_npx')) {
|
|
227
|
+
// Started with npx: this window IS AgentPager. Say so before someone closes it.
|
|
228
|
+
console.log(`\n${bold('Keep this window open.')} Closing it (or Ctrl+C) disconnects your phone until you start it again.`);
|
|
229
|
+
console.log(dim('Always on, even after a restart: npm install -g @cosmovex/agentpager, then agentpager service install'));
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
console.log(dim('\nKeep this running (or run `agentpager service install` to start it at login).'));
|
|
233
|
+
}
|
|
203
234
|
// Someone who reinstalls the app, or picks up a second phone, reasonably runs `agentpager` again
|
|
204
235
|
// and waits for a code that never comes — this is the only place they will look for why.
|
|
205
|
-
console.log(dim('
|
|
206
|
-
console.log(dim(UNPAIR_HINT));
|
|
207
|
-
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'));
|
|
208
237
|
// Tell the terminal AND the phone when a newer AgentPager is out.
|
|
209
238
|
const checkUpdate = async () => {
|
|
210
239
|
const latest = await latestVersion();
|
|
@@ -217,6 +246,9 @@ async function serve(state, keepAwake) {
|
|
|
217
246
|
const updateTimer = setInterval(() => void checkUpdate(), 12 * 3600_000);
|
|
218
247
|
const quit = async () => {
|
|
219
248
|
console.log(dim('\nDisconnecting…'));
|
|
249
|
+
for (const c of channels.values())
|
|
250
|
+
c.send({ type: 'bye' });
|
|
251
|
+
await new Promise((r) => setTimeout(r, 800)); // let it leave before the socket closes
|
|
220
252
|
hub.shutdown();
|
|
221
253
|
stopControl();
|
|
222
254
|
codex.shutdown();
|
|
@@ -303,6 +335,32 @@ async function main() {
|
|
|
303
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.'));
|
|
304
336
|
process.exit(0);
|
|
305
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
|
+
}
|
|
306
364
|
case 'notify':
|
|
307
365
|
case 'ask': {
|
|
308
366
|
// Parse flags properly: a flag's VALUE must not leak into the message body.
|
|
@@ -433,7 +491,7 @@ async function main() {
|
|
|
433
491
|
process.exit(0);
|
|
434
492
|
}
|
|
435
493
|
default:
|
|
436
|
-
console.log(
|
|
494
|
+
console.log(HELP);
|
|
437
495
|
process.exit(1);
|
|
438
496
|
}
|
|
439
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