@xanomyrox/opencode-memory 1.0.0 → 1.0.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.
@@ -0,0 +1,151 @@
1
+ # Gumroad Setup Guide for @xanomyrox/opencode-memory
2
+
3
+ ## Overview
4
+
5
+ This guide explains how to sell PRO licenses for your OpenCode Memory plugin using Gumroad.
6
+
7
+ ## Workflow
8
+
9
+ ```
10
+ Buyer purchases on Gumroad
11
+
12
+ Gumroad sends purchase email with instructions
13
+
14
+ You generate license key (using license-gen.cjs)
15
+
16
+ You email the license key to buyer
17
+
18
+ Buyer activates with /activate <key>
19
+ ```
20
+
21
+ ## Step 1: Create Gumroad Account
22
+
23
+ 1. Go to: https://gumroad.com
24
+ 2. Sign up / Login
25
+ 3. Verify your email
26
+
27
+ ## Step 2: Create Product
28
+
29
+ 1. Click **"New Product"**
30
+
31
+ 2. **Product Details:**
32
+ - **Name**: `OpenCode Memory PRO`
33
+ - **Description**:
34
+ ```
35
+ Lifetime PRO license for @xanomyrox/opencode-memory
36
+
37
+ Includes:
38
+ - Unlimited memories (10,000+)
39
+ - Priority extraction
40
+ - Custom tags
41
+ - Priority support
42
+ - Lifetime updates
43
+
44
+ After purchase, contact the seller to receive your license key.
45
+ ```
46
+ - **Price**: `$29` (one-time)
47
+
48
+ 3. **Product Type**: Select **"Single product"**
49
+
50
+ 4. **Customization**:
51
+ - Add a nice cover image
52
+ - Add screenshots of the plugin
53
+
54
+ 5. Click **"Publish"**
55
+
56
+ ## Step 3: Update README
57
+
58
+ Update your Gumroad URL in the plugin. The URL should be like:
59
+ ```
60
+ https://xanomyrox.gumroad.com/l/opencode-memory-pro
61
+ ```
62
+
63
+ ## Step 4: Generate & Deliver License Keys
64
+
65
+ ### Generate a Key:
66
+ ```bash
67
+ cd ~/.config/opencode/plugins/@xanomyrox/opencode-memory
68
+ node license-gen.cjs
69
+ ```
70
+
71
+ ### Example Output:
72
+ ```
73
+ ========================================
74
+ NEW LICENSE KEY GENERATED!
75
+ ========================================
76
+
77
+ MEMORY-PRO-PG7N-2ZAT-GNE5-9K3O
78
+
79
+ ========================================
80
+
81
+ Order #1
82
+ Generated: 2026-04-01T23:51:57.480Z
83
+
84
+ Send this key to the buyer!
85
+ ```
86
+
87
+ ### Deliver to Buyer:
88
+ 1. Check your Gumroad sales dashboard
89
+ 2. Find the buyer's email
90
+ 3. Email them their license key
91
+
92
+ ## Step 5: Buyer Activates
93
+
94
+ Buyer runs in OpenCode:
95
+ ```
96
+ /activate MEMORY-PRO-PG7N-2ZAT-GNE5-9K3O
97
+ ```
98
+
99
+ ## Automating Delivery (Optional)
100
+
101
+ For automatic key delivery, you can:
102
+
103
+ 1. Use Gumroad's **Overlay** feature
104
+ 2. Set up an email auto-responder
105
+ 3. Or use Gumroad's **API** with a custom backend
106
+
107
+ ## Pricing Options
108
+
109
+ | Option | Price | Recommended |
110
+ |--------|-------|-------------|
111
+ | Lifetime | $29 | ✅ Best for you |
112
+ | Monthly | $5/month | Better for income |
113
+ | Freemium | $0/$9 | Complex setup |
114
+
115
+ ## Gumroad Fees
116
+
117
+ | Sales | Gumroad Fee |
118
+ |-------|-------------|
119
+ | $0-500 | 10% |
120
+ | $500+ | 5% |
121
+ | With Gumroad Pro | 0% + $10/month |
122
+
123
+ ## Support Email Template
124
+
125
+ When a buyer purchases, email them:
126
+
127
+ ```
128
+ Subject: Your OpenCode Memory PRO License Key
129
+
130
+ Hi,
131
+
132
+ Thank you for purchasing OpenCode Memory PRO!
133
+
134
+ Your license key:
135
+ MEMORY-PRO-XXXX-XXXX-XXXX-XXXX
136
+
137
+ To activate:
138
+ 1. Install the plugin: opencode plugin install @xanomyrox/opencode-memory
139
+ 2. Open OpenCode
140
+ 3. Type: /activate MEMORY-PRO-XXXX-XXXX-XXXX-XXXX
141
+
142
+ Need help? Reply to this email!
143
+
144
+ Best regards,
145
+ xanomyrox
146
+ ```
147
+
148
+ ## Files
149
+
150
+ - `license-gen.cjs` - Generate license keys
151
+ - `sold-keys.json` - Records of all keys generated
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const KEYS_FILE = path.join(__dirname, 'sold-keys.json');
7
+
8
+ function generateLicenseKey() {
9
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
10
+ const segments = [];
11
+
12
+ for (let s = 0; s < 4; s++) {
13
+ let segment = '';
14
+ for (let i = 0; i < 4; i++) {
15
+ segment += chars[Math.floor(Math.random() * chars.length)];
16
+ }
17
+ segments.push(segment);
18
+ }
19
+
20
+ const checksum = segments.join('').split('').reduce((a, c) => a + c.charCodeAt(0), 0);
21
+ const validChecksum = checksum % 7 === 0;
22
+
23
+ if (!validChecksum) {
24
+ return generateLicenseKey();
25
+ }
26
+
27
+ return `MEMORY-PRO-${segments.join('-')}`;
28
+ }
29
+
30
+ function loadSoldKeys() {
31
+ try {
32
+ if (fs.existsSync(KEYS_FILE)) {
33
+ return JSON.parse(fs.readFileSync(KEYS_FILE, 'utf-8'));
34
+ }
35
+ } catch (e) {}
36
+ return { keys: [], sold: 0 };
37
+ }
38
+
39
+ function saveSoldKeys(data) {
40
+ fs.writeFileSync(KEYS_FILE, JSON.stringify(data, null, 2));
41
+ }
42
+
43
+ function generateNewKey() {
44
+ const key = generateLicenseKey();
45
+ const data = loadSoldKeys();
46
+
47
+ if (data.keys.includes(key)) {
48
+ return generateNewKey();
49
+ }
50
+
51
+ data.keys.push(key);
52
+ data.sold++;
53
+ saveSoldKeys(data);
54
+
55
+ console.log('\n========================================');
56
+ console.log(' NEW LICENSE KEY GENERATED!');
57
+ console.log('========================================\n');
58
+ console.log(` ${key}`);
59
+ console.log('\n========================================\n');
60
+ console.log(` Order #${data.sold}`);
61
+ console.log(` Generated: ${new Date().toISOString()}\n`);
62
+ console.log(' Send this key to the buyer!\n');
63
+
64
+ return { key, order: data.sold };
65
+ }
66
+
67
+ function listKeys() {
68
+ const data = loadSoldKeys();
69
+ console.log(`\n Total Keys Generated: ${data.sold}`);
70
+ console.log(` Total Keys in File: ${data.keys.length}\n`);
71
+
72
+ if (data.keys.length > 0) {
73
+ console.log(' Recent Keys:');
74
+ data.keys.slice(-10).forEach((key, i) => {
75
+ console.log(` ${i + 1}. ${key}`);
76
+ });
77
+ }
78
+ console.log('');
79
+ }
80
+
81
+ function showHelp() {
82
+ console.log(`
83
+ License Key Generator for @xanomyrox/opencode-memory
84
+
85
+ Usage:
86
+ node license-gen.js Generate new key
87
+ node license-gen.js list List all keys
88
+ node license-gen.js check <key> Check if key is valid
89
+
90
+ Examples:
91
+ node license-gen.js
92
+ node license-gen.js list
93
+ node license-gen.js check MEMORY-PRO-XXXX-XXXX-XXXX-XXXX
94
+ `);
95
+ }
96
+
97
+ function checkKey(key) {
98
+ const data = loadSoldKeys();
99
+ const isSold = data.keys.includes(key);
100
+
101
+ const pattern = /^MEMORY-PRO-([A-Z0-9]{4})-([A-Z0-9]{4})-([A-Z0-9]{4})-([A-Z0-9]{4})$/;
102
+ const match = key.match(pattern);
103
+
104
+ if (!match) {
105
+ console.log('\n Invalid format\n');
106
+ return false;
107
+ }
108
+
109
+ const checksum = key.replace('MEMORY-PRO-', '').replace(/-/g, '').split('').reduce((a, c) => a + c.charCodeAt(0), 0);
110
+ const validChecksum = checksum % 7 === 0;
111
+
112
+ console.log('\n========================================');
113
+ console.log(' LICENSE KEY CHECK');
114
+ console.log('========================================\n');
115
+ console.log(` Key: ${key}`);
116
+ console.log(` Format: ${match ? 'Valid' : 'Invalid'}`);
117
+ console.log(` Checksum: ${validChecksum ? 'Valid' : 'Invalid'}`);
118
+ console.log(` Sold: ${isSold ? 'Yes ✅' : 'No ❌'}`);
119
+ console.log('\n========================================\n');
120
+
121
+ return isSold && validChecksum;
122
+ }
123
+
124
+ const cmd = process.argv[2];
125
+
126
+ switch (cmd) {
127
+ case 'list':
128
+ listKeys();
129
+ break;
130
+ case 'check':
131
+ const keyToCheck = process.argv[3];
132
+ if (keyToCheck) {
133
+ checkKey(keyToCheck);
134
+ } else {
135
+ console.log('\n Please provide a key to check\n');
136
+ }
137
+ break;
138
+ case 'help':
139
+ showHelp();
140
+ break;
141
+ default:
142
+ if (cmd) {
143
+ showHelp();
144
+ } else {
145
+ generateNewKey();
146
+ }
147
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xanomyrox/opencode-memory",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Memory system plugin for OpenCode AI with FREE/PRO tiers",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/sold-keys.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "keys": [
3
+ "MEMORY-PRO-PG7N-2ZAT-GNE5-9K3O"
4
+ ],
5
+ "sold": 1
6
+ }
package/src/index.js CHANGED
@@ -262,8 +262,9 @@ function getLicenseStatus() {
262
262
  }
263
263
 
264
264
  export default {
265
- async onLoad(api) {
266
- api.command.register(() => [
265
+ id: 'opencode-memory',
266
+ tui: async (api, options, meta) => {
267
+ const unregister = api.command.register(() => [
267
268
  {
268
269
  title: 'Extract memories from conversation',
269
270
  value: '/extract',
@@ -311,18 +312,18 @@ export default {
311
312
  const icon = TOPIC_KEYWORDS[m.memory_type]?.icon || '📋';
312
313
  return `${icon} ${m.title.slice(0, 40)}${m.title.length > 40 ? '...' : ''}`;
313
314
  }).join('\n');
314
- api.ui.dialog?.alert?.({ title: `🧠 Memories (${status.tier}) ${status.memoryLimit}`, message: formatList(memories) });
315
+ api.ui.dialog.alert?.({ title: `🧠 Memories (${status.tier}) ${status.memoryLimit}`, message: formatList(memories) });
315
316
  },
316
317
  },
317
318
  {
318
319
  title: 'Search memories',
319
320
  value: '/m-search',
320
321
  async onSelect() {
321
- const q = await api.ui.dialog?.prompt?.({ title: 'Search', message: 'Search term:' });
322
+ const q = await api.ui.dialog.prompt?.({ title: 'Search', message: 'Search term:' });
322
323
  if (!q) return;
323
324
  const results = memoryApi.list({ search: q });
324
325
  if (results.length === 0) { api.ui.toast?.({ variant: 'info', title: 'No results' }); return; }
325
- api.ui.dialog?.alert?.({ title: `Results (${results.length})`, message: results.map(r => `• ${r.title}`).join('\n') });
326
+ api.ui.dialog.alert?.({ title: `Results (${results.length})`, message: results.map(r => `• ${r.title}`).join('\n') });
326
327
  },
327
328
  },
328
329
  {
@@ -330,19 +331,19 @@ export default {
330
331
  value: '/m-filter',
331
332
  async onSelect() {
332
333
  const types = ['all', 'concept', 'preference', 'code_pattern', 'fact', 'project'];
333
- const selected = await api.ui.dialog?.select?.({ title: 'Filter', options: types.map(t => ({ label: t, value: t })) });
334
+ const selected = await api.ui.dialog.select?.({ title: 'Filter', options: types.map(t => ({ label: t, value: t })) });
334
335
  if (!selected) return;
335
336
  const results = selected === 'all' ? memoryApi.list({ limit: 20 }) : memoryApi.list({ type: selected, limit: 20 });
336
- api.ui.dialog?.alert?.({ title: `${selected} (${results.length})`, message: results.map(r => `• ${r.title}`).join('\n') || 'None' });
337
+ api.ui.dialog.alert?.({ title: `${selected} (${results.length})`, message: results.map(r => `• ${r.title}`).join('\n') || 'None' });
337
338
  },
338
339
  },
339
340
  {
340
341
  title: 'Delete memory',
341
342
  value: '/m-delete',
342
343
  async onSelect() {
343
- const id = await api.ui.dialog?.prompt?.({ title: 'Delete', message: 'Memory ID:' });
344
+ const id = await api.ui.dialog.prompt?.({ title: 'Delete', message: 'Memory ID:' });
344
345
  if (!id) return;
345
- const ok = await api.ui.dialog?.confirm?.({ title: 'Confirm Delete', message: `Delete ${id}?` });
346
+ const ok = await api.ui.dialog.confirm?.({ title: 'Confirm Delete', message: `Delete ${id}?` });
346
347
  if (ok && memoryApi.delete(id)) api.ui.toast?.({ variant: 'success', title: 'Deleted' });
347
348
  },
348
349
  },
@@ -363,7 +364,7 @@ export default {
363
364
  const sessions = await api.session?.list?.({ roots: true, limit: 10 });
364
365
  if (!sessions?.length) { api.ui.toast?.({ variant: 'info', title: 'No sessions' }); return; }
365
366
  const list = sessions.map((s, i) => `${i + 1}. ${s.title?.slice(0, 35)} (${formatAge(Date.now() - s.time?.updated)})`).join('\n');
366
- api.ui.dialog?.alert?.({ title: 'Recent Sessions', message: list });
367
+ api.ui.dialog.alert?.({ title: 'Recent Sessions', message: list });
367
368
  },
368
369
  },
369
370
  {
@@ -386,7 +387,7 @@ export default {
386
387
  value: '/export',
387
388
  keybind: 'ctrl+x',
388
389
  async onSelect() {
389
- const vaultPath = await api.ui.dialog?.prompt?.({
390
+ const vaultPath = await api.ui.dialog.prompt?.({
390
391
  title: 'Export',
391
392
  message: 'Vault path:',
392
393
  placeholder: join(process.env.HOME || '~', 'Documents', 'Obsidian', 'opencode-memories')
@@ -425,7 +426,7 @@ export default {
425
426
  const byType = {};
426
427
  for (const m of memoryStore.memories) byType[m.memory_type] = (byType[m.memory_type] || 0) + 1;
427
428
  const stats = Object.entries(byType).map(([k, v]) => `${TOPIC_KEYWORDS[k]?.icon || '📋'} ${k}: ${v}`).join('\n');
428
- api.ui.dialog?.alert?.({ title: `📊 ${status.tier} Stats`, message: `${stats || 'No memories yet'}\n\nTier: ${status.tier}\nMemories: ${status.memoryLimit}` });
429
+ api.ui.dialog.alert?.({ title: `📊 ${status.tier} Stats`, message: `${stats || 'No memories yet'}\n\nTier: ${status.tier}\nMemories: ${status.memoryLimit}` });
429
430
  },
430
431
  },
431
432
  {
@@ -439,9 +440,9 @@ export default {
439
440
  return;
440
441
  }
441
442
 
442
- api.ui.dialog?.alert?.({
443
+ api.ui.dialog.alert?.({
443
444
  title: '🌟 Upgrade to PRO',
444
- message: `Current: FREE (${memoryStore.memories.length}/${FREE_MEMORY_LIMIT} memories)\n\nPRO Benefits:\n• Unlimited memories (10,000+)\n• Priority extraction\n• Custom tags\n• Priority support\n• Early access to new features\n\nPrice: $29 one-time\nLifetime license, no subscription!\n\nTo purchase, visit:\nhttps://anomarynox.gumroad.com/memory-plugin\n\nAfter purchase, activate with:\n/activate <license-key>`
445
+ message: `Current: FREE (${memoryStore.memories.length}/${FREE_MEMORY_LIMIT} memories)\n\nPRO Benefits:\n• Unlimited memories (10,000+)\n• Priority extraction\n• Custom tags\n• Priority support\n• Early access to new features\n\nPrice: $29 one-time\nLifetime license, no subscription!\n\nTo purchase, visit:\nhttps://xanomyrox.gumroad.com/l/xhfmla\n\nAfter purchase, activate with:\n/activate <license-key>`
445
446
  });
446
447
  },
447
448
  },
@@ -449,7 +450,7 @@ export default {
449
450
  title: 'Activate license',
450
451
  value: '/activate',
451
452
  async onSelect() {
452
- const key = await api.ui.dialog?.prompt?.({
453
+ const key = await api.ui.dialog.prompt?.({
453
454
  title: '🔑 Activate License',
454
455
  message: 'Enter your license key:',
455
456
  placeholder: 'MEMORY-PRO-XXXX-XXXX-XXXX-XXXX'
@@ -472,7 +473,7 @@ export default {
472
473
  async onSelect() {
473
474
  const status = getLicenseStatus();
474
475
  const features = status.features.map(f => `• ${f}`).join('\n');
475
- api.ui.dialog?.alert?.({
476
+ api.ui.dialog.alert?.({
476
477
  title: `📋 License Status: ${status.tier}`,
477
478
  message: `Tier: ${status.tier}\nActivated: ${status.activated || 'Not activated'}\nMemories: ${status.memoryLimit}\n\nFeatures:\n${features}\n\n${status.tier === 'FREE' ? '\nUpgrade: /upgrade' : '\nThank you for supporting!'}`)
478
479
  });
@@ -480,7 +481,7 @@ export default {
480
481
  },
481
482
  ]);
482
483
 
483
- api.event?.on?.('app.started', async () => {
484
+ api.event.on('app.started', async () => {
484
485
  setTimeout(async () => {
485
486
  try {
486
487
  const sessions = await api.session?.list?.({ roots: true, limit: 1 });
@@ -494,7 +495,7 @@ export default {
494
495
  if (state[SESSION_PROMPT_KEY] === last.id) return;
495
496
 
496
497
  const ageStr = formatAge(age);
497
- const ok = await api.ui.dialog?.confirm?.({
498
+ const ok = await api.ui.dialog.confirm({
498
499
  title: 'Resume Session?',
499
500
  message: `"${last.title}"\n\nLast: ${ageStr}`,
500
501
  confirmText: 'Resume',
@@ -506,5 +507,7 @@ export default {
506
507
  } catch (e) {}
507
508
  }, 2000);
508
509
  });
509
- },
510
+
511
+ return () => unregister();
512
+ }
510
513
  };