@kamal0808/anytoany 0.2.0 → 0.3.0
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/cli.js +99 -77
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -55,13 +55,17 @@ function resolve() {
|
|
|
55
55
|
const cfg = loadConfig();
|
|
56
56
|
const user = process.env.ANY_USER || cfg.user || hostnameUser();
|
|
57
57
|
const relay = process.env.ANY_RELAY_URL || cfg.relay || DEFAULT_RELAY;
|
|
58
|
-
const
|
|
59
|
-
return { user: norm(user), relay: relay.replace(/\/$/, ''),
|
|
58
|
+
const key = process.env.ANY_KEY || cfg.key || null;
|
|
59
|
+
return { user: norm(user), relay: relay.replace(/\/$/, ''), key };
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
function norm(user) {
|
|
63
63
|
if (!user) return null;
|
|
64
|
-
return user.startsWith('@') ? user : '@' + user;
|
|
64
|
+
return user.startsWith('@') ? user.toLowerCase() : '@' + user.toLowerCase();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function validSlug(h) {
|
|
68
|
+
return /^@[a-z0-9_-]{2,30}$/.test(h);
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
function die(msg) {
|
|
@@ -69,11 +73,28 @@ function die(msg) {
|
|
|
69
73
|
process.exit(1);
|
|
70
74
|
}
|
|
71
75
|
|
|
76
|
+
// register (claim a handle, get your own key) — no auth needed
|
|
77
|
+
async function registerWithRelay(relay, handle, displayName) {
|
|
78
|
+
let res;
|
|
79
|
+
try {
|
|
80
|
+
res = await fetch(relay + '/register', {
|
|
81
|
+
method: 'POST',
|
|
82
|
+
headers: { 'content-type': 'application/json' },
|
|
83
|
+
body: JSON.stringify({ handle, displayName }),
|
|
84
|
+
});
|
|
85
|
+
} catch (e) {
|
|
86
|
+
die(`can't reach relay at ${relay} (${e.message})`);
|
|
87
|
+
}
|
|
88
|
+
const j = await res.json().catch(() => ({}));
|
|
89
|
+
if (!res.ok) die(j.error || `register failed (${res.status})`);
|
|
90
|
+
return j; // { handle, key, displayName }
|
|
91
|
+
}
|
|
92
|
+
|
|
72
93
|
async function api(relay, method, route, body) {
|
|
73
|
-
const {
|
|
94
|
+
const { key } = resolve();
|
|
74
95
|
const headers = {};
|
|
75
96
|
if (body) headers['content-type'] = 'application/json';
|
|
76
|
-
if (
|
|
97
|
+
if (key) headers['authorization'] = `Bearer ${key}`;
|
|
77
98
|
let res;
|
|
78
99
|
try {
|
|
79
100
|
res = await fetch(relay + route, {
|
|
@@ -122,12 +143,19 @@ function printMessage(m, { full = false } = {}) {
|
|
|
122
143
|
// ---- commands ------------------------------------------------------------
|
|
123
144
|
|
|
124
145
|
async function cmdRegister(args) {
|
|
125
|
-
|
|
126
|
-
if (!
|
|
146
|
+
let handle = norm(args[0]);
|
|
147
|
+
if (!handle) die('usage: any register @you [Display Name]');
|
|
148
|
+
if (!validSlug(handle)) die('handle must be 2-30 chars: a-z 0-9 _ - (e.g. @kamal)');
|
|
127
149
|
const cfg = loadConfig();
|
|
128
|
-
cfg.
|
|
150
|
+
const relay = (process.env.ANY_RELAY_URL || cfg.relay || DEFAULT_RELAY).replace(/\/$/, '');
|
|
151
|
+
const name = args.slice(1).join(' ') || null;
|
|
152
|
+
const acct = await registerWithRelay(relay, handle, name);
|
|
153
|
+
cfg.user = acct.handle;
|
|
154
|
+
cfg.key = acct.key;
|
|
155
|
+
cfg.relay = relay;
|
|
156
|
+
if (acct.displayName) cfg.displayName = acct.displayName;
|
|
129
157
|
saveConfig(cfg);
|
|
130
|
-
console.log(
|
|
158
|
+
console.log(`✓ registered as ${acct.handle}${acct.displayName ? ' (' + acct.displayName + ')' : ''}`);
|
|
131
159
|
}
|
|
132
160
|
|
|
133
161
|
async function cmdRelay(args) {
|
|
@@ -139,27 +167,20 @@ async function cmdRelay(args) {
|
|
|
139
167
|
console.log(`relay set to ${cfg.relay}`);
|
|
140
168
|
}
|
|
141
169
|
|
|
142
|
-
async function cmdToken(args) {
|
|
143
|
-
const t = args[0];
|
|
144
|
-
if (!t) die('usage: any token <shared-secret>');
|
|
145
|
-
const cfg = loadConfig();
|
|
146
|
-
cfg.token = t;
|
|
147
|
-
saveConfig(cfg);
|
|
148
|
-
console.log('token saved');
|
|
149
|
-
}
|
|
150
|
-
|
|
151
170
|
async function cmdWhoami() {
|
|
152
|
-
const { user, relay,
|
|
153
|
-
console.log(`
|
|
154
|
-
console.log(`relay:
|
|
155
|
-
console.log(`
|
|
156
|
-
|
|
157
|
-
|
|
171
|
+
const { user, relay, key } = resolve();
|
|
172
|
+
console.log(`handle: ${user || '(unregistered — run: any setup)'}`);
|
|
173
|
+
console.log(`relay: ${relay}`);
|
|
174
|
+
console.log(`key: ${key ? 'set' : '(none — run: any setup)'}`);
|
|
175
|
+
if (key) {
|
|
176
|
+
const me = await api(relay, 'GET', '/whoami').catch(() => null);
|
|
177
|
+
if (me) console.log(`status: online as ${me.handle}${me.displayName ? ' (' + me.displayName + ')' : ''}`);
|
|
178
|
+
}
|
|
158
179
|
}
|
|
159
180
|
|
|
160
181
|
async function cmdSend(args) {
|
|
161
|
-
const {
|
|
162
|
-
if (!
|
|
182
|
+
const { key, relay } = resolve();
|
|
183
|
+
if (!key) die('not registered. Run: any setup');
|
|
163
184
|
const to = norm(args[0]);
|
|
164
185
|
if (!to) die('usage: any send @who <text | file>');
|
|
165
186
|
const rest = args.slice(1);
|
|
@@ -175,24 +196,20 @@ async function cmdSend(args) {
|
|
|
175
196
|
body = rest.join(' ');
|
|
176
197
|
}
|
|
177
198
|
|
|
178
|
-
const { id } = await api(relay, 'POST', '/send', {
|
|
199
|
+
const { id } = await api(relay, 'POST', '/send', { to, subject, body });
|
|
179
200
|
console.log(`sent → ${to} (#${id})`);
|
|
180
201
|
}
|
|
181
202
|
|
|
182
203
|
async function cmdInbox(args) {
|
|
183
|
-
const {
|
|
184
|
-
if (!
|
|
204
|
+
const { key, relay } = resolve();
|
|
205
|
+
if (!key) die('not registered. Run: any setup');
|
|
185
206
|
const all = args.includes('--all');
|
|
186
|
-
const { messages, count } = await api(
|
|
187
|
-
relay,
|
|
188
|
-
'GET',
|
|
189
|
-
`/inbox?user=${encodeURIComponent(user)}${all ? '&all=1' : ''}`,
|
|
190
|
-
);
|
|
207
|
+
const { messages, count } = await api(relay, 'GET', `/inbox${all ? '?all=1' : ''}`);
|
|
191
208
|
if (count === 0) {
|
|
192
209
|
console.log(all ? 'inbox empty' : 'no new messages');
|
|
193
210
|
return;
|
|
194
211
|
}
|
|
195
|
-
console.log(`${count} ${all ? 'message(s)' : 'new message(s)'}
|
|
212
|
+
console.log(`${count} ${all ? 'message(s)' : 'new message(s)'}:\n`);
|
|
196
213
|
for (const m of messages) {
|
|
197
214
|
printMessage(m);
|
|
198
215
|
console.log('');
|
|
@@ -211,34 +228,30 @@ async function cmdRead(args) {
|
|
|
211
228
|
}
|
|
212
229
|
|
|
213
230
|
async function cmdReply(args) {
|
|
214
|
-
const {
|
|
215
|
-
if (!
|
|
231
|
+
const { key, relay } = resolve();
|
|
232
|
+
if (!key) die('not registered. Run: any setup');
|
|
216
233
|
const id = Number(args[0]);
|
|
217
234
|
if (!id) die('usage: any reply <id> <text>');
|
|
218
235
|
const body = args.slice(1).join(' ');
|
|
219
236
|
if (!body) die('nothing to reply with');
|
|
220
|
-
const { id: newId } = await api(relay, 'POST', '/reply', { id,
|
|
237
|
+
const { id: newId } = await api(relay, 'POST', '/reply', { id, body });
|
|
221
238
|
console.log(`replied to #${id} (#${newId})`);
|
|
222
239
|
}
|
|
223
240
|
|
|
224
241
|
async function cmdWatch(args) {
|
|
225
|
-
const {
|
|
226
|
-
if (!
|
|
242
|
+
const { key, relay } = resolve();
|
|
243
|
+
if (!key) die('not registered. Run: any setup');
|
|
227
244
|
const i = args.indexOf('--interval');
|
|
228
245
|
const interval = (i >= 0 ? Number(args[i + 1]) : 5) * 1000;
|
|
229
246
|
const seen = new Set();
|
|
230
|
-
console.log(`watching inbox
|
|
247
|
+
console.log(`watching inbox (every ${interval / 1000}s). Ctrl-C to stop.\n`);
|
|
231
248
|
// Prime with existing unread so we only announce genuinely new arrivals.
|
|
232
|
-
const first = await api(relay, 'GET', `/inbox?
|
|
249
|
+
const first = await api(relay, 'GET', `/inbox?all=1`);
|
|
233
250
|
for (const m of first.messages) seen.add(m.id);
|
|
234
251
|
if (first.messages.length) console.log(`(${first.messages.length} existing message(s), waiting for new ones)\n`);
|
|
235
252
|
|
|
236
253
|
async function poll() {
|
|
237
|
-
const { messages } = await api(
|
|
238
|
-
relay,
|
|
239
|
-
'GET',
|
|
240
|
-
`/inbox?user=${encodeURIComponent(user)}&all=1`,
|
|
241
|
-
).catch(() => ({ messages: [] }));
|
|
254
|
+
const { messages } = await api(relay, 'GET', `/inbox?all=1`).catch(() => ({ messages: [] }));
|
|
242
255
|
for (const m of messages.sort((a, b) => a.ts - b.ts)) {
|
|
243
256
|
if (seen.has(m.id)) continue;
|
|
244
257
|
seen.add(m.id);
|
|
@@ -280,11 +293,11 @@ function fireBanner(m, link) {
|
|
|
280
293
|
|
|
281
294
|
// one watch tick: alert on any new unread message. Run by launchd every ~15s.
|
|
282
295
|
async function cmdPoke() {
|
|
283
|
-
const { user, relay } = resolve();
|
|
284
|
-
if (!
|
|
285
|
-
const
|
|
286
|
-
const seenPath = path.join(HOME, `poke-seen-${
|
|
287
|
-
const lockPath = path.join(HOME, `poke-${
|
|
296
|
+
const { user, relay, key: authKey } = resolve();
|
|
297
|
+
if (!authKey) die('not registered (run: any setup)');
|
|
298
|
+
const slug = user.replace('@', '');
|
|
299
|
+
const seenPath = path.join(HOME, `poke-seen-${slug}.txt`);
|
|
300
|
+
const lockPath = path.join(HOME, `poke-${slug}.lock`);
|
|
288
301
|
fs.mkdirSync(HOME, { recursive: true });
|
|
289
302
|
try {
|
|
290
303
|
fs.mkdirSync(lockPath); // single-flight
|
|
@@ -292,7 +305,7 @@ async function cmdPoke() {
|
|
|
292
305
|
return;
|
|
293
306
|
}
|
|
294
307
|
try {
|
|
295
|
-
const { messages } = await api(relay, 'GET', `/inbox
|
|
308
|
+
const { messages } = await api(relay, 'GET', `/inbox`);
|
|
296
309
|
if (!messages || !messages.length) return;
|
|
297
310
|
let seen = new Set();
|
|
298
311
|
try {
|
|
@@ -331,29 +344,40 @@ function have(cmd) {
|
|
|
331
344
|
}
|
|
332
345
|
}
|
|
333
346
|
|
|
334
|
-
// one-command onboarding:
|
|
347
|
+
// one-command onboarding: claim a handle (get your key) + install the watcher
|
|
335
348
|
async function cmdSetup() {
|
|
336
349
|
const cfg = loadConfig();
|
|
337
350
|
const tty = process.stdin.isTTY;
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (!
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
351
|
+
const relay = (process.env.ANY_RELAY_URL || cfg.relay || DEFAULT_RELAY).replace(/\/$/, '');
|
|
352
|
+
|
|
353
|
+
if (!cfg.key) {
|
|
354
|
+
// claim a new account
|
|
355
|
+
let handle = norm(process.env.ANY_USER || cfg.user);
|
|
356
|
+
if (!handle) {
|
|
357
|
+
if (!tty) die('no handle — run interactively, or: any register @you first');
|
|
358
|
+
handle = norm(await promptLine('Pick a handle (e.g. @yash): '));
|
|
359
|
+
}
|
|
360
|
+
if (!validSlug(handle)) die('handle must be 2-30 chars: a-z 0-9 _ - (e.g. @kamal)');
|
|
361
|
+
let name = cfg.displayName || process.env.ANY_NAME || null;
|
|
362
|
+
if (tty && !name) name = (await promptLine('Display name (optional): ')) || null;
|
|
363
|
+
const acct = await registerWithRelay(relay, handle, name);
|
|
364
|
+
cfg.user = acct.handle;
|
|
365
|
+
cfg.key = acct.key;
|
|
366
|
+
cfg.relay = relay;
|
|
367
|
+
if (acct.displayName) cfg.displayName = acct.displayName;
|
|
368
|
+
saveConfig(cfg);
|
|
369
|
+
console.log(`✓ registered as ${acct.handle}${acct.displayName ? ' (' + acct.displayName + ')' : ''} → ${relay}`);
|
|
370
|
+
} else {
|
|
371
|
+
if (!cfg.relay) {
|
|
372
|
+
cfg.relay = relay;
|
|
373
|
+
saveConfig(cfg);
|
|
374
|
+
}
|
|
375
|
+
console.log(`✓ already registered as ${cfg.user} → ${cfg.relay}`);
|
|
348
376
|
}
|
|
349
|
-
|
|
350
|
-
cfg.token = token;
|
|
351
|
-
if (!cfg.relay) cfg.relay = DEFAULT_RELAY;
|
|
352
|
-
saveConfig(cfg);
|
|
353
|
-
console.log(`✓ configured as ${user} → ${cfg.relay}`);
|
|
377
|
+
const user = cfg.user;
|
|
354
378
|
|
|
355
|
-
const
|
|
356
|
-
console.log(
|
|
379
|
+
const me = await api(cfg.relay, 'GET', '/whoami').catch(() => null);
|
|
380
|
+
console.log(me ? `✓ connected as ${me.handle}` : '! could not verify with relay (continuing)');
|
|
357
381
|
|
|
358
382
|
if (process.platform !== 'darwin') {
|
|
359
383
|
console.log('\nThe background banner watcher is macOS-only for now.');
|
|
@@ -415,7 +439,7 @@ async function cmdSetup() {
|
|
|
415
439
|
|
|
416
440
|
// prime seen-file so existing messages don't all alert at once
|
|
417
441
|
try {
|
|
418
|
-
const { messages } = await api(cfg.relay, 'GET', `/inbox?
|
|
442
|
+
const { messages } = await api(cfg.relay, 'GET', `/inbox?all=1`);
|
|
419
443
|
fs.writeFileSync(path.join(HOME, `poke-seen-${key}.txt`), (messages || []).map((m) => m.id).join('\n') + '\n');
|
|
420
444
|
} catch {}
|
|
421
445
|
|
|
@@ -437,11 +461,10 @@ async function cmdSetup() {
|
|
|
437
461
|
function cmdHelp() {
|
|
438
462
|
console.log(`any — tiny message relay CLI
|
|
439
463
|
|
|
440
|
-
any setup one-command onboarding (
|
|
441
|
-
any register @you
|
|
464
|
+
any setup one-command onboarding (claim @handle + banner watcher)
|
|
465
|
+
any register @you [Name] claim a handle (get your key)
|
|
442
466
|
any relay <url> point at the relay server
|
|
443
|
-
any
|
|
444
|
-
any whoami show identity + relay status
|
|
467
|
+
any whoami show your handle + relay status
|
|
445
468
|
|
|
446
469
|
any send @who <text|file> send a message (a file becomes subject+body)
|
|
447
470
|
any inbox [--all] list messages for you (default: unread only)
|
|
@@ -463,7 +486,6 @@ const table = {
|
|
|
463
486
|
poke: cmdPoke,
|
|
464
487
|
register: cmdRegister,
|
|
465
488
|
relay: cmdRelay,
|
|
466
|
-
token: cmdToken,
|
|
467
489
|
whoami: cmdWhoami,
|
|
468
490
|
send: cmdSend,
|
|
469
491
|
inbox: cmdInbox,
|