myceliumail 1.0.6 → 1.0.7

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/CHANGELOG.md CHANGED
@@ -18,7 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
18
18
  - Urgent detection: messages with "urgent" in subject get status 2
19
19
  - Updated README with status notification documentation
20
20
 
21
- ## [1.0.5]
21
+ ## [1.0.5] - 2025-12-20
22
22
 
23
23
  ### Added
24
24
  - Comprehensive architecture documentation for public release
@@ -27,6 +27,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
27
27
  ### Changed
28
28
  - Complete README rewrite for public release messaging
29
29
 
30
+ ## [1.0.4] - 2025-12-20
31
+
32
+ ### Fixed
33
+ - Partial ID lookup bug in `mycmail read` command for Supabase storage
34
+ - Applied same fix to `myceliumail-mcp` server
35
+
36
+ ## [1.0.3] - 2025-12-18
37
+
38
+ ### Fixed
39
+ - Agent ID case normalization (all IDs now lowercase)
40
+ - Key import and send commands now handle case correctly
41
+
42
+ ## [1.0.2] - 2025-12-18
43
+
44
+ ### Fixed
45
+ - Desktop notification sound and click behavior
46
+ - Dashboard real-time connection stability
47
+
48
+ ## [1.0.1] - 2025-12-18
49
+
50
+ ### Fixed
51
+ - npm package bin paths corrected
52
+ - Missing dependencies in published package
53
+
30
54
  ## [1.0.0] - 2025-12-18
31
55
 
32
56
  ### Added
@@ -78,6 +102,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
78
102
 
79
103
  ## MCP Server Releases
80
104
 
105
+ > See [mcp-server/CHANGELOG.md](mcp-server/CHANGELOG.md) for full history.
106
+
107
+ ### [myceliumail-mcp@1.0.11] - 2025-12-21
108
+
109
+ #### Fixed
110
+ - DMG installer and desktop app fixes
111
+ - Improved error handling in MCP tools
112
+
113
+ ### [myceliumail-mcp@1.0.9] - 2025-12-20
114
+
115
+ #### Fixed
116
+ - Partial ID lookup for message reading
117
+
81
118
  ### [myceliumail-mcp@1.0.7] - 2025-12-18
82
119
 
83
120
  #### Fixed
package/CODEX_SETUP.md CHANGED
@@ -9,8 +9,8 @@
9
9
  export MYCELIUMAIL_AGENT_ID=your-agent-name
10
10
 
11
11
  # 2. Set Supabase credentials (provided by user)
12
- export SUPABASE_URL="<provided by user>"
13
- export SUPABASE_ANON_KEY="<provided by user>"
12
+ export SUPABASE_URL="https://your-project.supabase.co"
13
+ export SUPABASE_ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." # JWT format!
14
14
 
15
15
  # 3. Generate your encryption keys
16
16
  mycmail keygen
@@ -23,6 +23,17 @@ mycmail inbox
23
23
  mycmail send <other-agent> "Hello!"
24
24
  ```
25
25
 
26
+ ## ⚠️ Key Format Warning
27
+
28
+ **Use JWT anon keys, NOT publishable keys:**
29
+
30
+ | Format | Example | Works? |
31
+ |--------|---------|--------|
32
+ | ✅ JWT anon | `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...` | Yes |
33
+ | ❌ Publishable | `sb_publishable_...` | No |
34
+
35
+ Find the correct key in Supabase Dashboard → Settings → API → "anon public" (the long JWT string).
36
+
26
37
  ## Why environment variables?
27
38
 
28
39
  Environment variables **override** the config file. This lets multiple agents share `~/.myceliumail/` (for keys and data) but have different identities.
package/README.md CHANGED
@@ -98,10 +98,12 @@ Create `~/.myceliumail/config.json`:
98
98
  {
99
99
  "agent_id": "my-agent",
100
100
  "supabase_url": "https://xxx.supabase.co",
101
- "supabase_key": "eyJ..."
101
+ "supabase_key": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
102
102
  }
103
103
  ```
104
104
 
105
+ > **⚠️ Key Format:** Use the JWT anon key (starts with `eyJ...`), NOT the publishable key (`sb_publishable_...`). Find it in Supabase Dashboard → Settings → API → "anon public".
106
+
105
107
  ### Storage Modes
106
108
 
107
109
  | Mode | Backend | Use Case |
@@ -303,6 +305,51 @@ For cloud sync and multi-machine messaging:
303
305
 
304
306
  ---
305
307
 
308
+ ## Troubleshooting
309
+
310
+ ### "Invalid API key" Error
311
+
312
+ **Cause:** Using the wrong key format.
313
+
314
+ | Key Type | Format | Works? |
315
+ |----------|--------|--------|
316
+ | ✅ JWT anon key | `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...` | Yes |
317
+ | ❌ Publishable key | `sb_publishable_...` | No |
318
+
319
+ **Fix:** Go to Supabase Dashboard → Settings → API → copy the "anon public" key (the long JWT string starting with `eyJ...`).
320
+
321
+ ### Version Mismatch (`-V` shows old version)
322
+
323
+ **Cause:** npm global install caching.
324
+
325
+ **Fix:**
326
+ ```bash
327
+ npm uninstall -g myceliumail
328
+ npm install -g myceliumail@latest
329
+ ```
330
+
331
+ ### "Connection refused" or timeout errors
332
+
333
+ **Cause:** Supabase project paused or incorrect URL.
334
+
335
+ **Fix:**
336
+ 1. Verify your `SUPABASE_URL` is correct
337
+ 2. Check if the project is active at [supabase.com/dashboard](https://supabase.com/dashboard)
338
+ 3. Try `mycmail inbox` with `MYCELIUMAIL_STORAGE=local` to test locally
339
+
340
+ ### Encryption errors
341
+
342
+ **Cause:** Missing or mismatched keys.
343
+
344
+ **Fix:**
345
+ ```bash
346
+ mycmail keygen # Generate your keypair
347
+ mycmail key-announce # Share your public key
348
+ mycmail keys # Verify keys are present
349
+ ```
350
+
351
+ ---
352
+
306
353
  ## Roadmap
307
354
 
308
355
  Myceliumail is part of the **Treebird ecosystem**—a suite of tools for AI agent coordination.
@@ -21,6 +21,7 @@ import { createBroadcastCommand } from '../commands/broadcast.js';
21
21
  import { createWatchCommand } from '../commands/watch.js';
22
22
  import { createExportCommand } from '../commands/export.js';
23
23
  import { createStatusCommand } from '../commands/status.js';
24
+ import { createActivateCommand, createLicenseStatusCommand } from '../commands/activate.js';
24
25
  const program = new Command();
25
26
  program
26
27
  .name('mycmail')
@@ -45,6 +46,9 @@ program.addCommand(createBroadcastCommand());
45
46
  program.addCommand(createWatchCommand());
46
47
  program.addCommand(createExportCommand());
47
48
  program.addCommand(createStatusCommand());
49
+ // License management
50
+ program.addCommand(createActivateCommand());
51
+ program.addCommand(createLicenseStatusCommand());
48
52
  // Parse and run
49
53
  program.parse();
50
54
  //# sourceMappingURL=myceliumail.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"myceliumail.js","sourceRoot":"","sources":["../../src/bin/myceliumail.ts"],"names":[],"mappings":";AAEA;;;;GAIG;AAEH,4CAA4C;AAC5C,OAAO,eAAe,CAAC;AAEvB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,kBAAkB;AAClB,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACF,IAAI,CAAC,SAAS,CAAC;KACf,WAAW,CAAC,+DAA+D,CAAC;KAC5E,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB,6BAA6B;AAC7B,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;AAC5B,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE;iBACZ,MAAM,CAAC,OAAO;;CAE9B,CAAC,CAAC;AAEH,oBAAoB;AACpB,OAAO,CAAC,UAAU,CAAC,mBAAmB,EAAE,CAAC,CAAC;AAC1C,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC;AACxC,OAAO,CAAC,UAAU,CAAC,sBAAsB,EAAE,CAAC,CAAC;AAC7C,OAAO,CAAC,UAAU,CAAC,wBAAwB,EAAE,CAAC,CAAC;AAC/C,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC;AACxC,OAAO,CAAC,UAAU,CAAC,kBAAkB,EAAE,CAAC,CAAC;AACzC,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC;AACxC,OAAO,CAAC,UAAU,CAAC,sBAAsB,EAAE,CAAC,CAAC;AAC7C,OAAO,CAAC,UAAU,CAAC,sBAAsB,EAAE,CAAC,CAAC;AAC7C,OAAO,CAAC,UAAU,CAAC,kBAAkB,EAAE,CAAC,CAAC;AACzC,OAAO,CAAC,UAAU,CAAC,mBAAmB,EAAE,CAAC,CAAC;AAC1C,OAAO,CAAC,UAAU,CAAC,mBAAmB,EAAE,CAAC,CAAC;AAE1C,gBAAgB;AAChB,OAAO,CAAC,KAAK,EAAE,CAAC"}
1
+ {"version":3,"file":"myceliumail.js","sourceRoot":"","sources":["../../src/bin/myceliumail.ts"],"names":[],"mappings":";AAEA;;;;GAIG;AAEH,4CAA4C;AAC5C,OAAO,eAAe,CAAC;AAEvB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,kBAAkB;AAClB,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAE5F,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACF,IAAI,CAAC,SAAS,CAAC;KACf,WAAW,CAAC,+DAA+D,CAAC;KAC5E,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB,6BAA6B;AAC7B,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;AAC5B,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE;iBACZ,MAAM,CAAC,OAAO;;CAE9B,CAAC,CAAC;AAEH,oBAAoB;AACpB,OAAO,CAAC,UAAU,CAAC,mBAAmB,EAAE,CAAC,CAAC;AAC1C,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC;AACxC,OAAO,CAAC,UAAU,CAAC,sBAAsB,EAAE,CAAC,CAAC;AAC7C,OAAO,CAAC,UAAU,CAAC,wBAAwB,EAAE,CAAC,CAAC;AAC/C,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC;AACxC,OAAO,CAAC,UAAU,CAAC,kBAAkB,EAAE,CAAC,CAAC;AACzC,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC;AACxC,OAAO,CAAC,UAAU,CAAC,sBAAsB,EAAE,CAAC,CAAC;AAC7C,OAAO,CAAC,UAAU,CAAC,sBAAsB,EAAE,CAAC,CAAC;AAC7C,OAAO,CAAC,UAAU,CAAC,kBAAkB,EAAE,CAAC,CAAC;AACzC,OAAO,CAAC,UAAU,CAAC,mBAAmB,EAAE,CAAC,CAAC;AAC1C,OAAO,CAAC,UAAU,CAAC,mBAAmB,EAAE,CAAC,CAAC;AAE1C,qBAAqB;AACrB,OAAO,CAAC,UAAU,CAAC,qBAAqB,EAAE,CAAC,CAAC;AAC5C,OAAO,CAAC,UAAU,CAAC,0BAA0B,EAAE,CAAC,CAAC;AAEjD,gBAAgB;AAChB,OAAO,CAAC,KAAK,EAAE,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * License Activation Command
3
+ *
4
+ * Activates a Pro license key.
5
+ * Usage: mycmail activate <license-key>
6
+ */
7
+ import { Command } from 'commander';
8
+ export declare function createActivateCommand(): Command;
9
+ export declare function createLicenseStatusCommand(): Command;
10
+ //# sourceMappingURL=activate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activate.d.ts","sourceRoot":"","sources":["../../src/commands/activate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,wBAAgB,qBAAqB,IAAI,OAAO,CA0C/C;AAED,wBAAgB,0BAA0B,IAAI,OAAO,CA6BpD"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * License Activation Command
3
+ *
4
+ * Activates a Pro license key.
5
+ * Usage: mycmail activate <license-key>
6
+ */
7
+ import { Command } from 'commander';
8
+ import { verifyLicense, saveLicense, getLicenseStatus, FREE_TIER_LIMITS } from '../lib/license.js';
9
+ import { loadKnownKeys } from '../lib/crypto.js';
10
+ export function createActivateCommand() {
11
+ return new Command('activate')
12
+ .description('Activate a Pro license key')
13
+ .argument('<license-key>', 'Your license key from myceliumail.dev/pro')
14
+ .action(async (licenseKey) => {
15
+ console.log('🍄 Activating license...\n');
16
+ // Verify the license
17
+ const license = verifyLicense(licenseKey);
18
+ if (!license) {
19
+ console.error('❌ Invalid license key');
20
+ console.error(' Please check your key and try again.');
21
+ console.error(' Get a license at: myceliumail.dev/pro');
22
+ process.exit(1);
23
+ }
24
+ if (license.isExpired) {
25
+ console.error('❌ This license has expired');
26
+ console.error(` Expired on: ${new Date(license.data.expiresAt).toLocaleDateString()}`);
27
+ console.error(' Renew at: myceliumail.dev/pro');
28
+ process.exit(1);
29
+ }
30
+ // Save the license
31
+ const saved = saveLicense(licenseKey);
32
+ if (!saved) {
33
+ console.error('❌ Failed to save license');
34
+ console.error(' Please check file permissions for ~/.myceliumail/');
35
+ process.exit(1);
36
+ }
37
+ // Show success
38
+ const status = getLicenseStatus();
39
+ console.log('✅ Pro License activated!\n');
40
+ console.log(` Email: ${status.email}`);
41
+ console.log(` Plan: ${status.plan.toUpperCase()}`);
42
+ console.log(` Expires: ${new Date(status.expiresAt).toLocaleDateString()}`);
43
+ console.log(` Features: ${status.features.join(', ')}`);
44
+ console.log('');
45
+ console.log('🍄 Thank you for supporting Myceliumail!');
46
+ });
47
+ }
48
+ export function createLicenseStatusCommand() {
49
+ return new Command('license')
50
+ .description('Show license status and plan details')
51
+ .action(async () => {
52
+ const status = getLicenseStatus();
53
+ const knownKeys = loadKnownKeys();
54
+ const keyCount = Object.keys(knownKeys).length;
55
+ console.log('🍄 Myceliumail License Status\n');
56
+ if (status.plan === 'pro') {
57
+ console.log(` Plan: 💎 Pro`);
58
+ console.log(` Email: ${status.email}`);
59
+ console.log(` Expires: ${new Date(status.expiresAt).toLocaleDateString()} (${status.daysRemaining} days)`);
60
+ console.log(` Features: ${status.features.join(', ')}`);
61
+ console.log(` Keys: ${keyCount} imported (unlimited)`);
62
+ }
63
+ else {
64
+ console.log(` Plan: Free`);
65
+ console.log(` Keys: ${keyCount}/${FREE_TIER_LIMITS.maxImportedKeys} imported`);
66
+ console.log('');
67
+ console.log(' 💎 Upgrade to Pro for:');
68
+ console.log(' • Unlimited imported keys');
69
+ console.log(' • MCP Server integration');
70
+ console.log(' • Cloud key backup/restore');
71
+ console.log(' • Real-time notifications');
72
+ console.log('');
73
+ console.log(' Get Pro: myceliumail.dev/pro');
74
+ }
75
+ });
76
+ }
77
+ //# sourceMappingURL=activate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activate.js","sourceRoot":"","sources":["../../src/commands/activate.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"key-import.d.ts","sourceRoot":"","sources":["../../src/commands/key-import.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,wBAAgB,sBAAsB,IAAI,OAAO,CA2BhD"}
1
+ {"version":3,"file":"key-import.d.ts","sourceRoot":"","sources":["../../src/commands/key-import.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,wBAAgB,sBAAsB,IAAI,OAAO,CAgChD"}
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { Command } from 'commander';
5
5
  import { saveKnownKey, getKnownKey } from '../lib/crypto.js';
6
+ import { checkKeyLimit } from '../lib/license.js';
6
7
  export function createKeyImportCommand() {
7
8
  return new Command('key-import')
8
9
  .description('Import a peer agent\'s public key')
@@ -22,6 +23,10 @@ export function createKeyImportCommand() {
22
23
  console.log('\nUse --force to overwrite');
23
24
  return;
24
25
  }
26
+ // Check key limit for free tier (only for new keys)
27
+ if (!existing) {
28
+ checkKeyLimit();
29
+ }
25
30
  saveKnownKey(agentId, publicKey);
26
31
  console.log(`✅ Imported public key for ${agentId}`);
27
32
  console.log(`Key: ${publicKey.slice(0, 20)}...`);
@@ -1 +1 @@
1
- {"version":3,"file":"key-import.js","sourceRoot":"","sources":["../../src/commands/key-import.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE7D,MAAM,UAAU,sBAAsB;IAClC,OAAO,IAAI,OAAO,CAAC,YAAY,CAAC;SAC3B,WAAW,CAAC,mCAAmC,CAAC;SAChD,QAAQ,CAAC,SAAS,EAAE,4BAA4B,CAAC;SACjD,QAAQ,CAAC,OAAO,EAAE,2BAA2B,CAAC;SAC9C,MAAM,CAAC,aAAa,EAAE,wBAAwB,CAAC;SAC/C,MAAM,CAAC,KAAK,EAAE,OAAe,EAAE,SAAiB,EAAE,OAAO,EAAE,EAAE;QAC1D,oCAAoC;QACpC,IAAI,SAAS,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;YACxB,OAAO,CAAC,KAAK,CAAC,gEAAgE,CAAC,CAAC;YAChF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;QAED,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;QACtC,IAAI,QAAQ,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YAC7B,OAAO,CAAC,GAAG,CAAC,8BAA8B,OAAO,EAAE,CAAC,CAAC;YACrD,OAAO,CAAC,GAAG,CAAC,iBAAiB,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC;YACzD,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;YAC1C,OAAO;QACX,CAAC;QAED,YAAY,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAEjC,OAAO,CAAC,GAAG,CAAC,6BAA6B,OAAO,EAAE,CAAC,CAAC;QACpD,OAAO,CAAC,GAAG,CAAC,QAAQ,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC;QACjD,OAAO,CAAC,GAAG,CAAC,wDAAwD,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;AACX,CAAC"}
1
+ {"version":3,"file":"key-import.js","sourceRoot":"","sources":["../../src/commands/key-import.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,MAAM,UAAU,sBAAsB;IAClC,OAAO,IAAI,OAAO,CAAC,YAAY,CAAC;SAC3B,WAAW,CAAC,mCAAmC,CAAC;SAChD,QAAQ,CAAC,SAAS,EAAE,4BAA4B,CAAC;SACjD,QAAQ,CAAC,OAAO,EAAE,2BAA2B,CAAC;SAC9C,MAAM,CAAC,aAAa,EAAE,wBAAwB,CAAC;SAC/C,MAAM,CAAC,KAAK,EAAE,OAAe,EAAE,SAAiB,EAAE,OAAO,EAAE,EAAE;QAC1D,oCAAoC;QACpC,IAAI,SAAS,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;YACxB,OAAO,CAAC,KAAK,CAAC,gEAAgE,CAAC,CAAC;YAChF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;QAED,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;QACtC,IAAI,QAAQ,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YAC7B,OAAO,CAAC,GAAG,CAAC,8BAA8B,OAAO,EAAE,CAAC,CAAC;YACrD,OAAO,CAAC,GAAG,CAAC,iBAAiB,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC;YACzD,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;YAC1C,OAAO;QACX,CAAC;QAED,oDAAoD;QACpD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACZ,aAAa,EAAE,CAAC;QACpB,CAAC;QAED,YAAY,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAEjC,OAAO,CAAC,GAAG,CAAC,6BAA6B,OAAO,EAAE,CAAC,CAAC;QACpD,OAAO,CAAC,GAAG,CAAC,QAAQ,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC;QACjD,OAAO,CAAC,GAAG,CAAC,wDAAwD,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;AACX,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Myceliumail License Verification
3
+ *
4
+ * Ed25519-based license verification for Pro features.
5
+ * Public key is embedded; private key is kept by treebird for signing.
6
+ */
7
+ export declare const FREE_TIER_LIMITS: {
8
+ maxImportedKeys: number;
9
+ };
10
+ export type ProFeature = 'unlimited_keys' | 'mcp_server' | 'cloud_sync' | 'key_backup' | 'realtime_watch';
11
+ export interface LicenseData {
12
+ email: string;
13
+ plan: 'free' | 'pro';
14
+ expiresAt: string;
15
+ issuedAt: string;
16
+ features: ProFeature[];
17
+ }
18
+ export interface License {
19
+ data: LicenseData;
20
+ isValid: boolean;
21
+ isExpired: boolean;
22
+ }
23
+ /**
24
+ * Verify a license string using Ed25519 detached signature
25
+ */
26
+ export declare function verifyLicense(licenseString: string): License | null;
27
+ /**
28
+ * Save a license key to disk
29
+ */
30
+ export declare function saveLicense(licenseString: string): boolean;
31
+ /**
32
+ * Load the saved license from disk
33
+ */
34
+ export declare function loadLicense(): License | null;
35
+ /**
36
+ * Check if user has a valid Pro license
37
+ */
38
+ export declare function isPro(): boolean;
39
+ /**
40
+ * Check if a specific Pro feature is enabled
41
+ */
42
+ export declare function hasFeature(feature: ProFeature): boolean;
43
+ /**
44
+ * Get license status summary
45
+ */
46
+ export declare function getLicenseStatus(): {
47
+ plan: 'free' | 'pro';
48
+ email?: string;
49
+ expiresAt?: string;
50
+ features: ProFeature[];
51
+ daysRemaining?: number;
52
+ };
53
+ /**
54
+ * Check imported key limit and throw if exceeded (for free tier)
55
+ */
56
+ export declare function checkKeyLimit(): void;
57
+ /**
58
+ * Print Pro upsell message (soft sell)
59
+ */
60
+ export declare function printProUpsell(feature: string): void;
61
+ //# sourceMappingURL=license.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"license.d.ts","sourceRoot":"","sources":["../../src/lib/license.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAkBH,eAAO,MAAM,gBAAgB;;CAE5B,CAAC;AAGF,MAAM,MAAM,UAAU,GAChB,gBAAgB,GAChB,YAAY,GACZ,YAAY,GACZ,YAAY,GACZ,gBAAgB,CAAC;AAEvB,MAAM,WAAW,WAAW;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,UAAU,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,OAAO;IACpB,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;CACtB;AAyBD;;GAEG;AACH,wBAAgB,aAAa,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CA6BnE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAQ1D;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,OAAO,GAAG,IAAI,CAS5C;AAED;;GAEG;AACH,wBAAgB,KAAK,IAAI,OAAO,CAM/B;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAIvD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI;IAChC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B,CAqBA;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,IAAI,CAepC;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAMpD"}
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Myceliumail License Verification
3
+ *
4
+ * Ed25519-based license verification for Pro features.
5
+ * Public key is embedded; private key is kept by treebird for signing.
6
+ */
7
+ import nacl from 'tweetnacl';
8
+ import util from 'tweetnacl-util';
9
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
10
+ import { join } from 'path';
11
+ import { homedir } from 'os';
12
+ import { loadKnownKeys } from './crypto.js';
13
+ // License storage location
14
+ const LICENSE_DIR = join(homedir(), '.myceliumail');
15
+ const LICENSE_FILE = join(LICENSE_DIR, 'license.key');
16
+ // Treebird's public key for license verification (Ed25519)
17
+ // Use the same key as Spidersan for unified licensing
18
+ const TREEBIRD_PUBLIC_KEY = 'XqIqSlybZGKkKemgLKKl8P9MepnObhcJcxxZHtgG8/o=';
19
+ // Free tier limits
20
+ export const FREE_TIER_LIMITS = {
21
+ maxImportedKeys: 5,
22
+ };
23
+ /**
24
+ * Ensure license directory exists
25
+ */
26
+ function ensureLicenseDir() {
27
+ if (!existsSync(LICENSE_DIR)) {
28
+ mkdirSync(LICENSE_DIR, { recursive: true });
29
+ }
30
+ }
31
+ /**
32
+ * Parse a license string into components
33
+ * Format: LICENSE_V1.BASE64_DATA.BASE64_SIGNATURE
34
+ */
35
+ function parseLicenseString(licenseString) {
36
+ const parts = licenseString.trim().split('.');
37
+ if (parts.length !== 3)
38
+ return null;
39
+ const [version, data, signature] = parts;
40
+ if (version !== 'LICENSE_V1')
41
+ return null;
42
+ return { version, data, signature };
43
+ }
44
+ /**
45
+ * Verify a license string using Ed25519 detached signature
46
+ */
47
+ export function verifyLicense(licenseString) {
48
+ try {
49
+ const parsed = parseLicenseString(licenseString);
50
+ if (!parsed)
51
+ return null;
52
+ const dataBytes = util.decodeBase64(parsed.data);
53
+ // Verify detached Ed25519 signature
54
+ const publicKey = util.decodeBase64(TREEBIRD_PUBLIC_KEY);
55
+ const signatureBytes = util.decodeBase64(parsed.signature);
56
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
+ const isValid = nacl.sign.detached.verify(dataBytes, signatureBytes, publicKey);
58
+ if (!isValid)
59
+ return null;
60
+ const dataString = util.encodeUTF8(dataBytes);
61
+ const data = JSON.parse(dataString);
62
+ const expiresAt = new Date(data.expiresAt);
63
+ const isExpired = expiresAt < new Date();
64
+ return {
65
+ data,
66
+ isValid: true,
67
+ isExpired,
68
+ };
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ }
74
+ /**
75
+ * Save a license key to disk
76
+ */
77
+ export function saveLicense(licenseString) {
78
+ try {
79
+ ensureLicenseDir();
80
+ writeFileSync(LICENSE_FILE, licenseString.trim(), { mode: 0o600 });
81
+ return true;
82
+ }
83
+ catch {
84
+ return false;
85
+ }
86
+ }
87
+ /**
88
+ * Load the saved license from disk
89
+ */
90
+ export function loadLicense() {
91
+ if (!existsSync(LICENSE_FILE))
92
+ return null;
93
+ try {
94
+ const licenseString = readFileSync(LICENSE_FILE, 'utf-8');
95
+ return verifyLicense(licenseString);
96
+ }
97
+ catch {
98
+ return null;
99
+ }
100
+ }
101
+ /**
102
+ * Check if user has a valid Pro license
103
+ */
104
+ export function isPro() {
105
+ const license = loadLicense();
106
+ if (!license)
107
+ return false;
108
+ if (!license.isValid)
109
+ return false;
110
+ if (license.isExpired)
111
+ return false;
112
+ return license.data.plan === 'pro';
113
+ }
114
+ /**
115
+ * Check if a specific Pro feature is enabled
116
+ */
117
+ export function hasFeature(feature) {
118
+ const license = loadLicense();
119
+ if (!license || !license.isValid || license.isExpired)
120
+ return false;
121
+ return license.data.features.includes(feature);
122
+ }
123
+ /**
124
+ * Get license status summary
125
+ */
126
+ export function getLicenseStatus() {
127
+ const license = loadLicense();
128
+ if (!license || !license.isValid || license.isExpired) {
129
+ return {
130
+ plan: 'free',
131
+ features: [],
132
+ };
133
+ }
134
+ const expiresAt = new Date(license.data.expiresAt);
135
+ const now = new Date();
136
+ const daysRemaining = Math.ceil((expiresAt.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
137
+ return {
138
+ plan: license.data.plan,
139
+ email: license.data.email,
140
+ expiresAt: license.data.expiresAt,
141
+ features: license.data.features,
142
+ daysRemaining,
143
+ };
144
+ }
145
+ /**
146
+ * Check imported key limit and throw if exceeded (for free tier)
147
+ */
148
+ export function checkKeyLimit() {
149
+ if (isPro())
150
+ return; // Pro has unlimited
151
+ const knownKeys = loadKnownKeys();
152
+ const keyCount = Object.keys(knownKeys).length;
153
+ if (keyCount >= FREE_TIER_LIMITS.maxImportedKeys) {
154
+ console.error(`\n🍄 Free tier limit reached: ${keyCount}/${FREE_TIER_LIMITS.maxImportedKeys} imported keys`);
155
+ console.error('');
156
+ console.error(' Options:');
157
+ console.error(' • Remove unused keys from ~/.myceliumail/keys/known_keys.json');
158
+ console.error(' • Upgrade: myceliumail.dev/pro for unlimited keys');
159
+ console.error('');
160
+ process.exit(1);
161
+ }
162
+ }
163
+ /**
164
+ * Print Pro upsell message (soft sell)
165
+ */
166
+ export function printProUpsell(feature) {
167
+ if (isPro())
168
+ return;
169
+ console.log('');
170
+ console.log(`💎 Pro tip: Upgrade for ${feature}`);
171
+ console.log(' myceliumail.dev/pro');
172
+ }
173
+ //# sourceMappingURL=license.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"license.js","sourceRoot":"","sources":["../../src/lib/license.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ # Changelog - Myceliumail MCP Server
2
+
3
+ All notable changes to the MCP server will be documented in this file.
4
+
5
+ ## [1.0.11] - 2025-12-21
6
+
7
+ ### Fixed
8
+ - DMG installer and desktop app fixes
9
+ - Improved error handling in MCP tools
10
+
11
+ ## [1.0.9] - 2025-12-20
12
+
13
+ ### Fixed
14
+ - Partial ID lookup for message reading (matches main CLI fix)
15
+
16
+ ## [1.0.8] - 2025-12-20
17
+
18
+ ### Added
19
+ - Sync with main myceliumail package updates
20
+
21
+ ## [1.0.7] - 2025-12-18
22
+
23
+ ### Fixed
24
+ - Supabase column names to match actual schema (`to_agent` vs `recipient`)
25
+ - Added error logging (removed silent catch blocks)
26
+ - Config file support (`~/.myceliumail/config.json`)
27
+
28
+ ## [1.0.6] - 2025-12-18
29
+
30
+ ### Fixed
31
+ - Agent ID case normalization (all lowercase)
32
+
33
+ ## [1.0.5] - 2025-12-18
34
+
35
+ ### Fixed
36
+ - Key import handling improvements
37
+
38
+ ## [1.0.4] - 2025-12-18
39
+
40
+ ### Fixed
41
+ - Encryption/decryption edge cases
42
+
43
+ ## [1.0.3] - 2025-12-17
44
+
45
+ ### Fixed
46
+ - Message sending reliability improvements
47
+
48
+ ## [1.0.2] - 2025-12-16
49
+
50
+ ### Fixed
51
+ - Initial bug fixes after launch
52
+
53
+ ## [1.0.0] - 2025-12-16
54
+
55
+ ### Added
56
+ - Initial MCP server release
57
+ - 8 messaging tools for Claude Desktop:
58
+ - `check_inbox` - List received messages
59
+ - `read_message` - Read and decrypt messages
60
+ - `send_message` - Send messages to other agents
61
+ - `reply_message` - Reply to messages
62
+ - `generate_keys` - Create encryption keypair
63
+ - `list_keys` - Show available keys
64
+ - `import_key` - Import peer public keys
65
+ - `archive_message` - Archive messages
66
+ - End-to-end encryption support (NaCl/TweetNaCl.js)
67
+ - Local and Supabase storage backends
68
+ - Automatic fallback from cloud to local storage
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Myceliumail MCP License Verification
3
+ *
4
+ * Ed25519-based license verification for MCP Pro features.
5
+ * MCP server is a Pro feature - requires valid license to start.
6
+ */
7
+
8
+ import nacl from 'tweetnacl';
9
+ import util from 'tweetnacl-util';
10
+ import { existsSync, readFileSync } from 'fs';
11
+ import { join } from 'path';
12
+ import { homedir } from 'os';
13
+
14
+ // License storage location (shared with CLI)
15
+ const LICENSE_FILE = join(homedir(), '.myceliumail', 'license.key');
16
+
17
+ // Treebird's public key for license verification (Ed25519)
18
+ const TREEBIRD_PUBLIC_KEY = 'XqIqSlybZGKkKemgLKKl8P9MepnObhcJcxxZHtgG8/o=';
19
+
20
+ export interface LicenseData {
21
+ email: string;
22
+ plan: 'free' | 'pro';
23
+ expiresAt: string;
24
+ issuedAt: string;
25
+ features: string[];
26
+ }
27
+
28
+ export interface License {
29
+ data: LicenseData;
30
+ isValid: boolean;
31
+ isExpired: boolean;
32
+ }
33
+
34
+ /**
35
+ * Parse a license string into components
36
+ */
37
+ function parseLicenseString(licenseString: string): { version: string; data: string; signature: string } | null {
38
+ const parts = licenseString.trim().split('.');
39
+ if (parts.length !== 3) return null;
40
+
41
+ const [version, data, signature] = parts;
42
+ if (version !== 'LICENSE_V1') return null;
43
+
44
+ return { version, data, signature };
45
+ }
46
+
47
+ /**
48
+ * Verify a license string using Ed25519 detached signature
49
+ */
50
+ export function verifyLicense(licenseString: string): License | null {
51
+ try {
52
+ const parsed = parseLicenseString(licenseString);
53
+ if (!parsed) return null;
54
+
55
+ const dataBytes = util.decodeBase64(parsed.data);
56
+ const publicKey = util.decodeBase64(TREEBIRD_PUBLIC_KEY);
57
+ const signatureBytes = util.decodeBase64(parsed.signature);
58
+
59
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
+ const isValid = (nacl as any).sign.detached.verify(dataBytes, signatureBytes, publicKey);
61
+ if (!isValid) return null;
62
+
63
+ const dataString = util.encodeUTF8(dataBytes);
64
+ const data = JSON.parse(dataString) as LicenseData;
65
+
66
+ const expiresAt = new Date(data.expiresAt);
67
+ const isExpired = expiresAt < new Date();
68
+
69
+ return {
70
+ data,
71
+ isValid: true,
72
+ isExpired,
73
+ };
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Load and verify license from disk
81
+ */
82
+ export function loadLicense(): License | null {
83
+ if (!existsSync(LICENSE_FILE)) return null;
84
+
85
+ try {
86
+ const licenseString = readFileSync(LICENSE_FILE, 'utf-8');
87
+ return verifyLicense(licenseString);
88
+ } catch {
89
+ return null;
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Check if user has a valid Pro license
95
+ */
96
+ export function isPro(): boolean {
97
+ const license = loadLicense();
98
+ if (!license) return false;
99
+ if (!license.isValid) return false;
100
+ if (license.isExpired) return false;
101
+ return license.data.plan === 'pro';
102
+ }
103
+
104
+ /**
105
+ * Check if MCP feature is enabled
106
+ */
107
+ export function hasMcpAccess(): boolean {
108
+ const license = loadLicense();
109
+ if (!license || !license.isValid || license.isExpired) return false;
110
+ return license.data.features.includes('mcp_server');
111
+ }
112
+
113
+ /**
114
+ * Verify Pro license or exit with upgrade message
115
+ */
116
+ export function requireProLicense(): void {
117
+ const license = loadLicense();
118
+
119
+ if (!license) {
120
+ console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
121
+ console.error('🍄 Myceliumail MCP Server - Pro Feature');
122
+ console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
123
+ console.error('');
124
+ console.error('This feature requires a Pro license.');
125
+ console.error('');
126
+ console.error('Activate with: mycmail activate <license-key>');
127
+ console.error('Get a license: myceliumail.dev/pro');
128
+ console.error('');
129
+ console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
130
+ process.exit(1);
131
+ }
132
+
133
+ if (!license.isValid) {
134
+ console.error('❌ Invalid license. Please re-activate.');
135
+ console.error(' mycmail activate <license-key>');
136
+ process.exit(1);
137
+ }
138
+
139
+ if (license.isExpired) {
140
+ console.error('❌ License expired on', new Date(license.data.expiresAt).toLocaleDateString());
141
+ console.error(' Renew at: myceliumail.dev/pro');
142
+ process.exit(1);
143
+ }
144
+
145
+ // Valid Pro license - show confirmation
146
+ console.error(`🍄 Myceliumail MCP (Pro) - ${license.data.email}`);
147
+ }
@@ -14,6 +14,7 @@ import { z } from 'zod';
14
14
  import * as crypto from './lib/crypto.js';
15
15
  import * as storage from './lib/storage.js';
16
16
  import { getAgentId } from './lib/config.js';
17
+ import { requireProLicense } from './lib/license.js';
17
18
 
18
19
  // Create the MCP server
19
20
  const server = new McpServer({
@@ -379,6 +380,9 @@ server.tool(
379
380
 
380
381
  // Start the server
381
382
  async function main() {
383
+ // Verify Pro license before starting
384
+ requireProLicense();
385
+
382
386
  const transport = new StdioServerTransport();
383
387
  await server.connect(transport);
384
388
  console.error('Myceliumail MCP server running');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myceliumail",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "End-to-End Encrypted Messaging for AI Agents",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -25,6 +25,7 @@ import { createBroadcastCommand } from '../commands/broadcast.js';
25
25
  import { createWatchCommand } from '../commands/watch.js';
26
26
  import { createExportCommand } from '../commands/export.js';
27
27
  import { createStatusCommand } from '../commands/status.js';
28
+ import { createActivateCommand, createLicenseStatusCommand } from '../commands/activate.js';
28
29
 
29
30
  const program = new Command();
30
31
 
@@ -54,5 +55,10 @@ program.addCommand(createWatchCommand());
54
55
  program.addCommand(createExportCommand());
55
56
  program.addCommand(createStatusCommand());
56
57
 
58
+ // License management
59
+ program.addCommand(createActivateCommand());
60
+ program.addCommand(createLicenseStatusCommand());
61
+
57
62
  // Parse and run
58
63
  program.parse();
64
+
@@ -0,0 +1,85 @@
1
+ /**
2
+ * License Activation Command
3
+ *
4
+ * Activates a Pro license key.
5
+ * Usage: mycmail activate <license-key>
6
+ */
7
+
8
+ import { Command } from 'commander';
9
+ import { verifyLicense, saveLicense, getLicenseStatus, FREE_TIER_LIMITS } from '../lib/license.js';
10
+ import { loadKnownKeys } from '../lib/crypto.js';
11
+
12
+ export function createActivateCommand(): Command {
13
+ return new Command('activate')
14
+ .description('Activate a Pro license key')
15
+ .argument('<license-key>', 'Your license key from myceliumail.dev/pro')
16
+ .action(async (licenseKey: string) => {
17
+ console.log('🍄 Activating license...\n');
18
+
19
+ // Verify the license
20
+ const license = verifyLicense(licenseKey);
21
+
22
+ if (!license) {
23
+ console.error('❌ Invalid license key');
24
+ console.error(' Please check your key and try again.');
25
+ console.error(' Get a license at: myceliumail.dev/pro');
26
+ process.exit(1);
27
+ }
28
+
29
+ if (license.isExpired) {
30
+ console.error('❌ This license has expired');
31
+ console.error(` Expired on: ${new Date(license.data.expiresAt).toLocaleDateString()}`);
32
+ console.error(' Renew at: myceliumail.dev/pro');
33
+ process.exit(1);
34
+ }
35
+
36
+ // Save the license
37
+ const saved = saveLicense(licenseKey);
38
+ if (!saved) {
39
+ console.error('❌ Failed to save license');
40
+ console.error(' Please check file permissions for ~/.myceliumail/');
41
+ process.exit(1);
42
+ }
43
+
44
+ // Show success
45
+ const status = getLicenseStatus();
46
+ console.log('✅ Pro License activated!\n');
47
+ console.log(` Email: ${status.email}`);
48
+ console.log(` Plan: ${status.plan.toUpperCase()}`);
49
+ console.log(` Expires: ${new Date(status.expiresAt!).toLocaleDateString()}`);
50
+ console.log(` Features: ${status.features.join(', ')}`);
51
+ console.log('');
52
+ console.log('🍄 Thank you for supporting Myceliumail!');
53
+ });
54
+ }
55
+
56
+ export function createLicenseStatusCommand(): Command {
57
+ return new Command('license')
58
+ .description('Show license status and plan details')
59
+ .action(async () => {
60
+ const status = getLicenseStatus();
61
+ const knownKeys = loadKnownKeys();
62
+ const keyCount = Object.keys(knownKeys).length;
63
+
64
+ console.log('🍄 Myceliumail License Status\n');
65
+
66
+ if (status.plan === 'pro') {
67
+ console.log(` Plan: 💎 Pro`);
68
+ console.log(` Email: ${status.email}`);
69
+ console.log(` Expires: ${new Date(status.expiresAt!).toLocaleDateString()} (${status.daysRemaining} days)`);
70
+ console.log(` Features: ${status.features.join(', ')}`);
71
+ console.log(` Keys: ${keyCount} imported (unlimited)`);
72
+ } else {
73
+ console.log(` Plan: Free`);
74
+ console.log(` Keys: ${keyCount}/${FREE_TIER_LIMITS.maxImportedKeys} imported`);
75
+ console.log('');
76
+ console.log(' 💎 Upgrade to Pro for:');
77
+ console.log(' • Unlimited imported keys');
78
+ console.log(' • MCP Server integration');
79
+ console.log(' • Cloud key backup/restore');
80
+ console.log(' • Real-time notifications');
81
+ console.log('');
82
+ console.log(' Get Pro: myceliumail.dev/pro');
83
+ }
84
+ });
85
+ }
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { Command } from 'commander';
6
6
  import { saveKnownKey, getKnownKey } from '../lib/crypto.js';
7
+ import { checkKeyLimit } from '../lib/license.js';
7
8
 
8
9
  export function createKeyImportCommand(): Command {
9
10
  return new Command('key-import')
@@ -26,6 +27,11 @@ export function createKeyImportCommand(): Command {
26
27
  return;
27
28
  }
28
29
 
30
+ // Check key limit for free tier (only for new keys)
31
+ if (!existing) {
32
+ checkKeyLimit();
33
+ }
34
+
29
35
  saveKnownKey(agentId, publicKey);
30
36
 
31
37
  console.log(`✅ Imported public key for ${agentId}`);
@@ -33,3 +39,4 @@ export function createKeyImportCommand(): Command {
33
39
  console.log('\n🔐 You can now send encrypted messages to this agent');
34
40
  });
35
41
  }
42
+
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Myceliumail License Verification
3
+ *
4
+ * Ed25519-based license verification for Pro features.
5
+ * Public key is embedded; private key is kept by treebird for signing.
6
+ */
7
+
8
+ import nacl from 'tweetnacl';
9
+ import util from 'tweetnacl-util';
10
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
11
+ import { join } from 'path';
12
+ import { homedir } from 'os';
13
+ import { loadKnownKeys } from './crypto.js';
14
+
15
+ // License storage location
16
+ const LICENSE_DIR = join(homedir(), '.myceliumail');
17
+ const LICENSE_FILE = join(LICENSE_DIR, 'license.key');
18
+
19
+ // Treebird's public key for license verification (Ed25519)
20
+ // Use the same key as Spidersan for unified licensing
21
+ const TREEBIRD_PUBLIC_KEY = 'XqIqSlybZGKkKemgLKKl8P9MepnObhcJcxxZHtgG8/o=';
22
+
23
+ // Free tier limits
24
+ export const FREE_TIER_LIMITS = {
25
+ maxImportedKeys: 5,
26
+ };
27
+
28
+ // Pro features
29
+ export type ProFeature =
30
+ | 'unlimited_keys'
31
+ | 'mcp_server'
32
+ | 'cloud_sync'
33
+ | 'key_backup'
34
+ | 'realtime_watch';
35
+
36
+ export interface LicenseData {
37
+ email: string;
38
+ plan: 'free' | 'pro';
39
+ expiresAt: string; // ISO date
40
+ issuedAt: string; // ISO date
41
+ features: ProFeature[];
42
+ }
43
+
44
+ export interface License {
45
+ data: LicenseData;
46
+ isValid: boolean;
47
+ isExpired: boolean;
48
+ }
49
+
50
+ /**
51
+ * Ensure license directory exists
52
+ */
53
+ function ensureLicenseDir(): void {
54
+ if (!existsSync(LICENSE_DIR)) {
55
+ mkdirSync(LICENSE_DIR, { recursive: true });
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Parse a license string into components
61
+ * Format: LICENSE_V1.BASE64_DATA.BASE64_SIGNATURE
62
+ */
63
+ function parseLicenseString(licenseString: string): { version: string; data: string; signature: string } | null {
64
+ const parts = licenseString.trim().split('.');
65
+ if (parts.length !== 3) return null;
66
+
67
+ const [version, data, signature] = parts;
68
+ if (version !== 'LICENSE_V1') return null;
69
+
70
+ return { version, data, signature };
71
+ }
72
+
73
+ /**
74
+ * Verify a license string using Ed25519 detached signature
75
+ */
76
+ export function verifyLicense(licenseString: string): License | null {
77
+ try {
78
+ const parsed = parseLicenseString(licenseString);
79
+ if (!parsed) return null;
80
+
81
+ const dataBytes = util.decodeBase64(parsed.data);
82
+
83
+ // Verify detached Ed25519 signature
84
+ const publicKey = util.decodeBase64(TREEBIRD_PUBLIC_KEY);
85
+ const signatureBytes = util.decodeBase64(parsed.signature);
86
+
87
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
88
+ const isValid = (nacl as any).sign.detached.verify(dataBytes, signatureBytes, publicKey);
89
+ if (!isValid) return null;
90
+
91
+ const dataString = util.encodeUTF8(dataBytes);
92
+ const data = JSON.parse(dataString) as LicenseData;
93
+
94
+ const expiresAt = new Date(data.expiresAt);
95
+ const isExpired = expiresAt < new Date();
96
+
97
+ return {
98
+ data,
99
+ isValid: true,
100
+ isExpired,
101
+ };
102
+ } catch {
103
+ return null;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Save a license key to disk
109
+ */
110
+ export function saveLicense(licenseString: string): boolean {
111
+ try {
112
+ ensureLicenseDir();
113
+ writeFileSync(LICENSE_FILE, licenseString.trim(), { mode: 0o600 });
114
+ return true;
115
+ } catch {
116
+ return false;
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Load the saved license from disk
122
+ */
123
+ export function loadLicense(): License | null {
124
+ if (!existsSync(LICENSE_FILE)) return null;
125
+
126
+ try {
127
+ const licenseString = readFileSync(LICENSE_FILE, 'utf-8');
128
+ return verifyLicense(licenseString);
129
+ } catch {
130
+ return null;
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Check if user has a valid Pro license
136
+ */
137
+ export function isPro(): boolean {
138
+ const license = loadLicense();
139
+ if (!license) return false;
140
+ if (!license.isValid) return false;
141
+ if (license.isExpired) return false;
142
+ return license.data.plan === 'pro';
143
+ }
144
+
145
+ /**
146
+ * Check if a specific Pro feature is enabled
147
+ */
148
+ export function hasFeature(feature: ProFeature): boolean {
149
+ const license = loadLicense();
150
+ if (!license || !license.isValid || license.isExpired) return false;
151
+ return license.data.features.includes(feature);
152
+ }
153
+
154
+ /**
155
+ * Get license status summary
156
+ */
157
+ export function getLicenseStatus(): {
158
+ plan: 'free' | 'pro';
159
+ email?: string;
160
+ expiresAt?: string;
161
+ features: ProFeature[];
162
+ daysRemaining?: number;
163
+ } {
164
+ const license = loadLicense();
165
+
166
+ if (!license || !license.isValid || license.isExpired) {
167
+ return {
168
+ plan: 'free',
169
+ features: [],
170
+ };
171
+ }
172
+
173
+ const expiresAt = new Date(license.data.expiresAt);
174
+ const now = new Date();
175
+ const daysRemaining = Math.ceil((expiresAt.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
176
+
177
+ return {
178
+ plan: license.data.plan,
179
+ email: license.data.email,
180
+ expiresAt: license.data.expiresAt,
181
+ features: license.data.features,
182
+ daysRemaining,
183
+ };
184
+ }
185
+
186
+ /**
187
+ * Check imported key limit and throw if exceeded (for free tier)
188
+ */
189
+ export function checkKeyLimit(): void {
190
+ if (isPro()) return; // Pro has unlimited
191
+
192
+ const knownKeys = loadKnownKeys();
193
+ const keyCount = Object.keys(knownKeys).length;
194
+
195
+ if (keyCount >= FREE_TIER_LIMITS.maxImportedKeys) {
196
+ console.error(`\n🍄 Free tier limit reached: ${keyCount}/${FREE_TIER_LIMITS.maxImportedKeys} imported keys`);
197
+ console.error('');
198
+ console.error(' Options:');
199
+ console.error(' • Remove unused keys from ~/.myceliumail/keys/known_keys.json');
200
+ console.error(' • Upgrade: myceliumail.dev/pro for unlimited keys');
201
+ console.error('');
202
+ process.exit(1);
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Print Pro upsell message (soft sell)
208
+ */
209
+ export function printProUpsell(feature: string): void {
210
+ if (isPro()) return;
211
+
212
+ console.log('');
213
+ console.log(`💎 Pro tip: Upgrade for ${feature}`);
214
+ console.log(' myceliumail.dev/pro');
215
+ }