@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.
Files changed (3) hide show
  1. package/README.md +57 -79
  2. package/cli.js +168 -78
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,92 +1,70 @@
1
- # `any` — Milestone 1
1
+ # `any` — message people from inside Claude
2
2
 
3
- A deliberately tiny message relay + CLI so two machines (each running a Claude
4
- Code session) can **send / inbox / reply** and, once that feels magical,
5
- **delegate tasks**.
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
- ## Setup (once per machine)
19
-
20
- Requires **Node 18** (for global `fetch`). Note the `any` shim runs whatever
21
- `node` is first on your `PATH` — if a shell defaults to an old Node (e.g. nvm
22
- pinned to 14), bare `any` will crash. Fix per machine with `nvm use` (an
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
- Identity is auto-detected from hostname (`mac-mini` `@yash`,
34
- `macbook-air` → `@kamal`). To be explicit:
16
+ ## Install (anyone, one command)
35
17
 
36
18
  ```bash
37
- any register @kamal # on the MacBook Air
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
- ## Run the relay (on the Mac Mini — it's always on)
25
+ ## Use
42
26
 
43
27
  ```bash
44
- cd any
45
- node relay.js # listens on 0.0.0.0:4747
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
- The default relay URL baked into the CLI is the Mac Mini's Tailscale IP
49
- (`http://100.92.24.17:4747`), so both Macs already point at it. Override with:
50
-
51
- ```bash
52
- any relay http://100.92.24.17:4747
53
- # or: export ANY_RELAY_URL=...
54
- ```
55
-
56
- Check it from either machine:
57
-
58
- ```bash
59
- any whoami # shows identity + "relay: online"
60
- ```
61
-
62
- ## The five commands
63
-
64
- ```bash
65
- any send @yash transcript.md # a file becomes subject + body
66
- any send @yash "quick question…" # or send plain text
67
- any inbox # unread messages for you
68
- any read 3 # show #3 in full (marks it read)
69
- any reply 3 "Looks good. Ship it." # reply goes back to the sender
70
- any watch # live-poll the inbox (agent mode)
71
- ```
72
-
73
- ## The 60-second demo
74
-
75
- 1. **Mac Mini:** `node any/relay.js` (leave it running).
76
- 2. **MacBook Air:** `any send @yash transcript.md`
77
- 3. **Mac Mini:** `any inbox` → `any read 1` → `any reply 1 "Looks good. Ship it."`
78
- 4. **MacBook Air:** `any inbox` → the reply is there.
79
-
80
- That's the illusion. If it feels magical, it's worth showing to someone.
81
-
82
- ## Milestone 3 delegation (the actual point)
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 token = process.env.ANY_TOKEN || cfg.token || null;
59
- return { user: norm(user), relay: relay.replace(/\/$/, ''), token };
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 { token } = resolve();
94
+ const { key } = resolve();
74
95
  const headers = {};
75
96
  if (body) headers['content-type'] = 'application/json';
76
- if (token) headers['authorization'] = `Bearer ${token}`;
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
- const user = norm(args[0]);
126
- if (!user) die('usage: any register @you');
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.user = user;
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(`registered as ${user}`);
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, token } = resolve();
153
- console.log(`user: ${user || '(unset — run: any register @you)'}`);
154
- console.log(`relay: ${relay}`);
155
- console.log(`token: ${token ? 'set' : '(none)'}`);
156
- const health = await api(relay, 'GET', '/health').catch(() => null);
157
- if (health) console.log(`relay: online (${health.messages} messages stored)`);
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 { user, relay } = resolve();
162
- if (!user) die('you are not registered. Run: any register @you');
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', { from: user, to, subject, body });
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 { user, relay } = resolve();
184
- if (!user) die('you are not registered. Run: any register @you');
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)'} for ${user}:\n`);
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 { user, relay } = resolve();
215
- if (!user) die('you are not registered. Run: any register @you');
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, from: user, body });
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 { user, relay } = resolve();
226
- if (!user) die('you are not registered. Run: any register @you');
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 for ${user} (every ${interval / 1000}s). Ctrl-C to stop.\n`);
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?user=${encodeURIComponent(user)}&all=1`);
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 (!user) die('not registered (run: any setup)');
285
- const key = user.replace('@', '');
286
- const seenPath = path.join(HOME, `poke-seen-${key}.txt`);
287
- const lockPath = path.join(HOME, `poke-${key}.lock`);
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
- return;
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?user=${encodeURIComponent(user)}`);
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
- // one-command onboarding: identity + token + background watcher
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
- let user = norm(process.env.ANY_USER || cfg.user);
340
- if (!user) {
341
- if (!tty) die('no identity — run interactively, or: any register @you first');
342
- user = norm(await promptLine('Your handle (e.g. @yash): '));
343
- }
344
- let token = process.env.ANY_TOKEN || cfg.token;
345
- if (!token) {
346
- if (!tty) die('no token — run interactively, or: any token <secret> first');
347
- token = await promptLine('Shared token: ');
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
- cfg.user = user;
350
- cfg.token = token;
351
- if (!cfg.relay) cfg.relay = DEFAULT_RELAY;
352
- saveConfig(cfg);
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
- const health = await api(cfg.relay, 'GET', '/health').catch(() => null);
356
- console.log(health ? '✓ relay reachable' : '! could not reach relay — check the token/URL (continuing)');
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?user=${encodeURIComponent(user)}&all=1`);
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 (identity + token + banner watcher)
441
- any register @you set your identity
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 token <secret> set the shared secret (for a hosted relay)
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kamal0808/anytoany",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
5
  "description": "Tiny CLI to send/inbox/reply messages between Claude Code sessions via the anytoany relay",
6
6
  "bin": {