@kamal0808/anytoany 0.2.0 → 0.3.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 +57 -79
- package/cli.js +168 -78
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,92 +1,70 @@
|
|
|
1
|
-
# `any` —
|
|
1
|
+
# `any` — message people from inside Claude
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Send a message to a friend and it lands **inside their Claude Desktop** — a
|
|
4
|
+
banner pops, they click it, a Claude Code session opens that reads the message
|
|
5
|
+
and helps them reply. AI-mediated messaging, human-in-the-loop.
|
|
6
6
|
|
|
7
|
-
No auth. No websockets. No database. One ~180-line HTTP server over a JSON file,
|
|
8
|
-
and one CLI. Zero dependencies (Node ≥ 18 for global `fetch`; you have 25).
|
|
9
|
-
|
|
10
|
-
```
|
|
11
|
-
MacBook Air (@kamal) Mac Mini (@yash)
|
|
12
|
-
any CLI ─────────┐ ┌─────── any CLI
|
|
13
|
-
▼ ▼
|
|
14
|
-
relay.js on the Mac Mini (Tailscale 100.92.24.17:4747)
|
|
15
|
-
└─ data/messages.json
|
|
16
7
|
```
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
`.nvmrc` pinning 22 is included) or, to make it stick for every shell:
|
|
24
|
-
`nvm alias default 22`.
|
|
25
|
-
|
|
26
|
-
From the repo root on **each** Mac:
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
cd any
|
|
30
|
-
npm link # puts `any` on your PATH (or just use: node any/cli.js …)
|
|
8
|
+
you: any send @friend "hey"
|
|
9
|
+
→ hosted relay (Cloudflare Worker + D1)
|
|
10
|
+
→ friend's machine: watcher polls (cheap, 0 tokens idle)
|
|
11
|
+
→ 🔔 clickable macOS banner
|
|
12
|
+
→ click → claude://code/new opens a Claude Code session IN Claude Desktop
|
|
13
|
+
→ runs `any inbox` → reads → drafts → friend approves → any reply → back to you
|
|
31
14
|
```
|
|
32
15
|
|
|
33
|
-
|
|
34
|
-
`macbook-air` → `@kamal`). To be explicit:
|
|
16
|
+
## Install (anyone, one command)
|
|
35
17
|
|
|
36
18
|
```bash
|
|
37
|
-
|
|
38
|
-
any register @yash # on the Mac Mini
|
|
19
|
+
npx @kamal0808/anytoany@latest setup
|
|
39
20
|
```
|
|
21
|
+
Pick a unique `@handle` (+ optional display name). No token, no secrets. It
|
|
22
|
+
registers you, installs a background banner watcher, and you're on the network.
|
|
23
|
+
macOS + Node 18+ + Claude Desktop.
|
|
40
24
|
|
|
41
|
-
##
|
|
25
|
+
## Use
|
|
42
26
|
|
|
43
27
|
```bash
|
|
44
|
-
|
|
45
|
-
|
|
28
|
+
any send @handle "text" # message anyone by their handle
|
|
29
|
+
any inbox # your new messages
|
|
30
|
+
any read <id> # open one
|
|
31
|
+
any reply <id> "text" # reply
|
|
32
|
+
any whoami # your handle + status
|
|
46
33
|
```
|
|
47
34
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
any
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
The Mac Mini already has a Claude Code session. Paste `OPERATOR-YASH.md` into it
|
|
85
|
-
once. From then on it runs `any watch`, and when a task arrives it executes it in
|
|
86
|
-
Claude Code and replies with the result — autonomously. See that file.
|
|
87
|
-
|
|
88
|
-
## Config & env
|
|
89
|
-
|
|
90
|
-
- Config file: `~/.any/config.json` (`{ user, relay }`)
|
|
91
|
-
- Env overrides: `ANY_USER`, `ANY_RELAY_URL`, `ANY_HOME`
|
|
92
|
-
- Relay env: `ANY_PORT`, `ANY_HOST`, `ANY_DATA_DIR`
|
|
35
|
+
## How it works
|
|
36
|
+
|
|
37
|
+
- **Accounts:** each person claims a unique `@handle` and gets their own key.
|
|
38
|
+
`from` is server-verified (no spoofing); you can only read your own inbox.
|
|
39
|
+
Registration is open (anyone can claim a handle and DM anyone by handle).
|
|
40
|
+
- **Relay:** a Cloudflare Worker + D1 (`worker/`) — always-on, edge, durable.
|
|
41
|
+
URL: `https://anytoany-relay.kamalkhatwani69.workers.dev`.
|
|
42
|
+
- **CLI:** `cli.js` — published to npm as `@kamal0808/anytoany` (bin `any`).
|
|
43
|
+
- **Watcher → banner → Desktop:** `any poke` (run by a launchd agent every ~15s)
|
|
44
|
+
fires a `terminal-notifier` banner whose click opens
|
|
45
|
+
`claude://code/new?q=…` — a *tool-capable* Claude Code session inside Claude
|
|
46
|
+
Desktop (it can run `any`). Set up automatically by `any setup`. macOS-only.
|
|
47
|
+
|
|
48
|
+
## Layout
|
|
49
|
+
|
|
50
|
+
| Path | What |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `cli.js` | the `any` CLI (published to npm) |
|
|
53
|
+
| `worker/` | the relay: Cloudflare Worker (`worker.js`) + D1 schema (`schema.sql`) |
|
|
54
|
+
| `relay.js` | legacy standalone Node relay (superseded by the Worker) |
|
|
55
|
+
| `SETUP-YASH.md` | copy-paste onboarding to send a friend |
|
|
56
|
+
| `CLAUDE.md` | operator notes for maintaining/deploying this |
|
|
57
|
+
|
|
58
|
+
## Maintaining it
|
|
59
|
+
|
|
60
|
+
- **Deploy the relay:** `cd worker && npx wrangler deploy` (needs `wrangler login`).
|
|
61
|
+
- **Publish the CLI:** bump `version` in `package.json`, then
|
|
62
|
+
`npm publish --access public` (needs npm login + 2FA).
|
|
63
|
+
- See `CLAUDE.md` for the full operator playbook and known rough edges.
|
|
64
|
+
|
|
65
|
+
## Not built yet
|
|
66
|
+
|
|
67
|
+
- One persistent Claude thread for all messaging (deep links only open new
|
|
68
|
+
sessions; would need the MCP-connector + fixed-chat approach).
|
|
69
|
+
- A dedicated `~/anytoany` folder per install (groups sessions, path-independent).
|
|
70
|
+
- Spam/abuse controls (blocking, invite-only, rate limits) — registration is open.
|
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,19 +293,33 @@ 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
|
|
291
304
|
} catch {
|
|
292
|
-
|
|
305
|
+
// lock exists — break it if stale (a killed tick / network die() can leak it)
|
|
306
|
+
try {
|
|
307
|
+
if (Date.now() - fs.statSync(lockPath).mtimeMs > 60000) {
|
|
308
|
+
fs.rmdirSync(lockPath);
|
|
309
|
+
fs.mkdirSync(lockPath);
|
|
310
|
+
} else return;
|
|
311
|
+
} catch {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
293
314
|
}
|
|
315
|
+
// release the lock even if a later die()/process.exit() skips the finally block
|
|
316
|
+
process.on('exit', () => {
|
|
317
|
+
try {
|
|
318
|
+
fs.rmdirSync(lockPath);
|
|
319
|
+
} catch {}
|
|
320
|
+
});
|
|
294
321
|
try {
|
|
295
|
-
const { messages } = await api(relay, 'GET', `/inbox
|
|
322
|
+
const { messages } = await api(relay, 'GET', `/inbox`);
|
|
296
323
|
if (!messages || !messages.length) return;
|
|
297
324
|
let seen = new Set();
|
|
298
325
|
try {
|
|
@@ -331,29 +358,94 @@ function have(cmd) {
|
|
|
331
358
|
}
|
|
332
359
|
}
|
|
333
360
|
|
|
334
|
-
//
|
|
361
|
+
// the anytoany skill — teaches any Claude Code session how to use `any`
|
|
362
|
+
function skillMd(handle) {
|
|
363
|
+
return `---
|
|
364
|
+
name: anytoany
|
|
365
|
+
description: Message people from the terminal via the \`any\` CLI (anytoany messaging). Use whenever the user wants to send or DM a message (or the current conversation) to a person or @handle, check their anytoany inbox, or reply to a message they received. Needs a shell (Claude Code / Cowork session).
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
# anytoany — messaging via the \`any\` CLI
|
|
369
|
+
|
|
370
|
+
\`any\` sends messages between people through a hosted relay; a message lands in the
|
|
371
|
+
recipient's Claude Desktop as a banner they click to read & reply.
|
|
372
|
+
|
|
373
|
+
**On this machine you are \`${handle}\`.** Run the \`any\` CLI in the shell to act.
|
|
374
|
+
|
|
375
|
+
## Commands
|
|
376
|
+
| Intent | Command |
|
|
377
|
+
|---|---|
|
|
378
|
+
| Send a message | \`any send @handle "message text"\` |
|
|
379
|
+
| Check new messages | \`any inbox\` (or \`any inbox --all\`) |
|
|
380
|
+
| Read one (marks read) | \`any read <id>\` |
|
|
381
|
+
| Reply to one | \`any reply <id> "text"\` |
|
|
382
|
+
| Who am I / status | \`any whoami\` |
|
|
383
|
+
|
|
384
|
+
## Common asks
|
|
385
|
+
- "send X to @bob" → \`any send @bob "X"\`
|
|
386
|
+
- "send this thread to @bob" → summarize the conversation, show me, then on my ok: \`any send @bob "<summary>"\`
|
|
387
|
+
- "anything new?" → \`any inbox\`, then offer to read/reply
|
|
388
|
+
- "reply to #<id>" → draft it, then \`any reply <id> "<reply>"\`
|
|
389
|
+
|
|
390
|
+
## Contacts
|
|
391
|
+
Use the \`@handle\` the person gave you. Add friends here as you learn them
|
|
392
|
+
(e.g. \`@pulkit\` — Pulkit). If you don't know someone's handle, ask.
|
|
393
|
+
|
|
394
|
+
## Rules
|
|
395
|
+
- Handles are lowercase slugs \`a-z 0-9 _ -\`, no spaces.
|
|
396
|
+
- **Always confirm the exact message text with me before sending.**
|
|
397
|
+
- Needs a terminal — works in a Claude Code / Cowork session, not a plain chat.
|
|
398
|
+
`;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function installSkill(handle) {
|
|
402
|
+
try {
|
|
403
|
+
const dir = path.join(os.homedir(), '.claude', 'skills', 'anytoany');
|
|
404
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
405
|
+
fs.writeFileSync(path.join(dir, 'SKILL.md'), skillMd(handle));
|
|
406
|
+
return true;
|
|
407
|
+
} catch {
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// one-command onboarding: claim a handle (get your key) + install the watcher
|
|
335
413
|
async function cmdSetup() {
|
|
336
414
|
const cfg = loadConfig();
|
|
337
415
|
const tty = process.stdin.isTTY;
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (!
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
416
|
+
const relay = (process.env.ANY_RELAY_URL || cfg.relay || DEFAULT_RELAY).replace(/\/$/, '');
|
|
417
|
+
|
|
418
|
+
if (!cfg.key) {
|
|
419
|
+
// claim a new account
|
|
420
|
+
let handle = norm(process.env.ANY_USER || cfg.user);
|
|
421
|
+
if (!handle) {
|
|
422
|
+
if (!tty) die('no handle — run interactively, or: any register @you first');
|
|
423
|
+
handle = norm(await promptLine('Pick a handle (e.g. @yash): '));
|
|
424
|
+
}
|
|
425
|
+
if (!validSlug(handle)) die('handle must be 2-30 chars: a-z 0-9 _ - (e.g. @kamal)');
|
|
426
|
+
let name = cfg.displayName || process.env.ANY_NAME || null;
|
|
427
|
+
if (tty && !name) name = (await promptLine('Display name (optional): ')) || null;
|
|
428
|
+
const acct = await registerWithRelay(relay, handle, name);
|
|
429
|
+
cfg.user = acct.handle;
|
|
430
|
+
cfg.key = acct.key;
|
|
431
|
+
cfg.relay = relay;
|
|
432
|
+
if (acct.displayName) cfg.displayName = acct.displayName;
|
|
433
|
+
saveConfig(cfg);
|
|
434
|
+
console.log(`✓ registered as ${acct.handle}${acct.displayName ? ' (' + acct.displayName + ')' : ''} → ${relay}`);
|
|
435
|
+
} else {
|
|
436
|
+
if (!cfg.relay) {
|
|
437
|
+
cfg.relay = relay;
|
|
438
|
+
saveConfig(cfg);
|
|
439
|
+
}
|
|
440
|
+
console.log(`✓ already registered as ${cfg.user} → ${cfg.relay}`);
|
|
348
441
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
console.log(`✓ configured as ${user} → ${cfg.relay}`);
|
|
442
|
+
const user = cfg.user;
|
|
443
|
+
|
|
444
|
+
const me = await api(cfg.relay, 'GET', '/whoami').catch(() => null);
|
|
445
|
+
console.log(me ? `✓ connected as ${me.handle}` : '! could not verify with relay (continuing)');
|
|
354
446
|
|
|
355
|
-
|
|
356
|
-
console.log(
|
|
447
|
+
// install the skill so any Claude Code session knows how to use `any`
|
|
448
|
+
console.log(installSkill(user) ? '✓ anytoany skill installed (Claude now knows how to message)' : '! could not install skill');
|
|
357
449
|
|
|
358
450
|
if (process.platform !== 'darwin') {
|
|
359
451
|
console.log('\nThe background banner watcher is macOS-only for now.');
|
|
@@ -415,7 +507,7 @@ async function cmdSetup() {
|
|
|
415
507
|
|
|
416
508
|
// prime seen-file so existing messages don't all alert at once
|
|
417
509
|
try {
|
|
418
|
-
const { messages } = await api(cfg.relay, 'GET', `/inbox?
|
|
510
|
+
const { messages } = await api(cfg.relay, 'GET', `/inbox?all=1`);
|
|
419
511
|
fs.writeFileSync(path.join(HOME, `poke-seen-${key}.txt`), (messages || []).map((m) => m.id).join('\n') + '\n');
|
|
420
512
|
} catch {}
|
|
421
513
|
|
|
@@ -437,11 +529,10 @@ async function cmdSetup() {
|
|
|
437
529
|
function cmdHelp() {
|
|
438
530
|
console.log(`any — tiny message relay CLI
|
|
439
531
|
|
|
440
|
-
any setup one-command onboarding (
|
|
441
|
-
any register @you
|
|
532
|
+
any setup one-command onboarding (claim @handle + banner watcher)
|
|
533
|
+
any register @you [Name] claim a handle (get your key)
|
|
442
534
|
any relay <url> point at the relay server
|
|
443
|
-
any
|
|
444
|
-
any whoami show identity + relay status
|
|
535
|
+
any whoami show your handle + relay status
|
|
445
536
|
|
|
446
537
|
any send @who <text|file> send a message (a file becomes subject+body)
|
|
447
538
|
any inbox [--all] list messages for you (default: unread only)
|
|
@@ -463,7 +554,6 @@ const table = {
|
|
|
463
554
|
poke: cmdPoke,
|
|
464
555
|
register: cmdRegister,
|
|
465
556
|
relay: cmdRelay,
|
|
466
|
-
token: cmdToken,
|
|
467
557
|
whoami: cmdWhoami,
|
|
468
558
|
send: cmdSend,
|
|
469
559
|
inbox: cmdInbox,
|