wiki-plugin-allyabase 0.0.6 → 0.0.8

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/BUGFIXES.md ADDED
@@ -0,0 +1,277 @@
1
+ # Allyabase Plugin - Critical Bug Fixes
2
+
3
+ ## Date: February 9, 2026
4
+
5
+ ### Critical Bug #1: Invalid Express Route Wildcard
6
+
7
+ **Problem:**
8
+ Multiple routes used the wildcard `*` syntax which is invalid in newer versions of path-to-regexp (used by Express):
9
+
10
+ ```javascript
11
+ // Federation route (line 291)
12
+ app.use('/plugin/allyabase/federation/*', function(req, res, next) {
13
+
14
+ // Service proxy routes (line 633)
15
+ app.all(`/plugin/allyabase/${service}/*`, function(req, res) {
16
+ ```
17
+
18
+ This caused:
19
+ ```
20
+ PathError: Missing parameter name at index 30: /plugin/allyabase/federation/*
21
+ PathError: Missing parameter name at index 25: /plugin/allyabase/julia/*
22
+ ```
23
+
24
+ **Fix:**
25
+ Changed both to use regex patterns:
26
+
27
+ ```javascript
28
+ // Federation route (line 291)
29
+ app.use(/^\/plugin\/allyabase\/federation\/.*/, function(req, res, next) {
30
+
31
+ // Service proxy routes (line 633)
32
+ app.all(new RegExp(`^\\/plugin\\/allyabase\\/${service}\\/.*`), function(req, res) {
33
+ ```
34
+
35
+ **Locations:**
36
+ - `server/server.js:291` - Federation route
37
+ - `server/server.js:633` - Service proxy routes (all 14 services)
38
+
39
+ **Impact:** Plugin would crash immediately on load, making all federation endpoints and service proxies unusable.
40
+
41
+ ---
42
+
43
+ ### Critical Bug #2: Process Suicide in Docker Containers
44
+
45
+ **Problem:**
46
+ The `cleanupOrphanedProcesses()` function scans ports and kills any process using them. In Docker containers:
47
+ - PID 1 is always the main container process (the wiki server)
48
+ - When the plugin finds the wiki using a port, it tries to kill PID 1
49
+ - This kills the entire container
50
+
51
+ **Symptoms:**
52
+ ```
53
+ [wiki-plugin-allyabase] Found process 1 using port 3005
54
+ [wiki-plugin-allyabase] Attempting to kill process 1...
55
+ [wiki-plugin-allyabase] Process 1 still running, sending SIGKILL...
56
+ ```
57
+
58
+ Followed by container crash.
59
+
60
+ **Fix #1: PID Protection**
61
+ Added safety checks to `killProcessByPid()`:
62
+ ```javascript
63
+ // CRITICAL: Never kill PID 1 in Docker containers
64
+ if (pid === 1) {
65
+ console.log('⚠️ Skipping PID 1 (init process)');
66
+ return false;
67
+ }
68
+
69
+ // Also don't kill our own process
70
+ if (pid === process.pid) {
71
+ console.log('⚠️ Skipping PID ${pid} (this is us!)');
72
+ return false;
73
+ }
74
+ ```
75
+
76
+ **Fix #2: Docker Detection**
77
+ Added Docker environment detection to skip port cleanup entirely:
78
+ ```javascript
79
+ const isDocker = fs.existsSync('/.dockerenv') || fs.existsSync('/run/.containerenv');
80
+
81
+ if (isDocker) {
82
+ console.log('🐳 Detected Docker environment - skipping port cleanup');
83
+ console.log('In Docker, services should run in separate containers');
84
+ return;
85
+ }
86
+ ```
87
+
88
+ **Location:** `server/server.js:63-100, 156-175`
89
+
90
+ **Rationale:**
91
+ In Docker/containerized environments:
92
+ - Each service runs in its own container
93
+ - Services don't share the same process space
94
+ - Attempting to kill processes by port is:
95
+ - Dangerous (can kill the wiki itself)
96
+ - Unnecessary (services are isolated)
97
+ - Won't work anyway (can't kill processes in other containers)
98
+
99
+ ---
100
+
101
+ ## Testing the Fixes
102
+
103
+ ### Before Fix:
104
+ ```bash
105
+ # Install allyabase plugin
106
+ docker exec -u node -w /home/node/lib/node_modules/wiki wiki-dave npm install wiki-plugin-allyabase
107
+
108
+ # Restart wiki
109
+ docker-compose restart wiki-dave
110
+
111
+ # Result: Wiki crashes immediately
112
+ # Logs show: Attempting to kill process 1... [crash]
113
+ ```
114
+
115
+ ### After Fix:
116
+ ```bash
117
+ # Install allyabase plugin
118
+ docker exec -u node -w /home/node/lib/node_modules/wiki wiki-dave npm install wiki-plugin-allyabase
119
+
120
+ # Restart wiki
121
+ docker-compose restart wiki-dave
122
+
123
+ # Result: Wiki starts successfully
124
+ # Logs show:
125
+ # 🐳 Detected Docker environment - skipping port cleanup
126
+ # ✅ wiki-plugin-allyabase ready!
127
+ ```
128
+
129
+ ---
130
+
131
+ ## Architecture Implications
132
+
133
+ ### Original Design (Non-Docker):
134
+ ```
135
+ ┌─────────────────────────────┐
136
+ │ Single Machine/VM │
137
+ │ │
138
+ │ ┌──────────────────────┐ │
139
+ │ │ Wiki (PID 123) │ │
140
+ │ │ Port 3333 │ │
141
+ │ └──────────────────────┘ │
142
+ │ │
143
+ │ ┌──────────────────────┐ │
144
+ │ │ PM2 │ │
145
+ │ │ ├─ Fount (PID 456) │ │
146
+ │ │ ├─ BDO (PID 789) │ │
147
+ │ │ ├─ Joan (PID 101) │ │
148
+ │ │ └─ ...13 more │ │
149
+ │ └──────────────────────┘ │
150
+ │ │
151
+ │ Allyabase plugin can: │
152
+ │ - Kill orphaned PM2 │
153
+ │ - Kill processes on ports │
154
+ │ - Restart services │
155
+ └─────────────────────────────┘
156
+ ```
157
+
158
+ ### Docker Design (Current):
159
+ ```
160
+ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
161
+ │ Wiki Container │ │ Fount Container │ │ BDO Container │
162
+ │ │ │ │ │ │
163
+ │ ┌──────────────┐ │ │ ┌──────────────┐ │ │ ┌──────────────┐ │
164
+ │ │ Wiki (PID 1) │ │ │ │ Fount (PID 1)│ │ │ │ BDO (PID 1) │ │
165
+ │ │ Port 3000 │ │ │ │ Port 3006 │ │ │ │ Port 3003 │ │
166
+ │ └──────────────┘ │ │ └──────────────┘ │ │ └──────────────┘ │
167
+ └──────────────────┘ └──────────────────┘ └──────────────────┘
168
+ │ │ │
169
+ └─────────────────────┴──────────────────────┘
170
+
171
+ Docker Network
172
+
173
+ Each container:
174
+ - Has its own PID 1 (init process)
175
+ - Isolated process space
176
+ - Cannot kill processes in other containers
177
+ - Managed by Docker Compose, not PM2
178
+
179
+ Allyabase plugin in Docker:
180
+ - ✅ Provides proxy routes to services
181
+ - ✅ Handles federation resolution
182
+ - ❌ Cannot manage service lifecycles (that's Docker's job)
183
+ - ❌ Cannot kill processes (dangerous and won't work)
184
+ ```
185
+
186
+ ---
187
+
188
+ ## Future Considerations
189
+
190
+ ### Service Management in Docker
191
+
192
+ The plugin's service management features (launch, healthcheck) need rethinking for Docker:
193
+
194
+ **Current Approach (Non-Docker):**
195
+ - `POST /plugin/allyabase/launch` - Runs `allyabase_setup.sh` which spawns PM2
196
+ - PM2 manages all 14 services as child processes
197
+ - Plugin can kill/restart services
198
+
199
+ **Docker Approach Options:**
200
+
201
+ 1. **Docker Compose (Recommended):**
202
+ ```yaml
203
+ services:
204
+ wiki:
205
+ image: wiki-federation:latest
206
+ fount:
207
+ image: fount:latest
208
+ bdo:
209
+ image: bdo:latest
210
+ # ...etc
211
+ ```
212
+ - Services managed by Docker Compose
213
+ - Plugin acts as pure proxy layer
214
+ - No lifecycle management in plugin
215
+
216
+ 2. **Docker API Integration:**
217
+ - Plugin calls Docker API to start/stop containers
218
+ - Requires Docker socket mount
219
+ - More complex but gives control
220
+
221
+ 3. **Hybrid (Current State):**
222
+ - Plugin works as proxy in Docker
223
+ - Manual service management via docker-compose CLI
224
+ - Plugin's /launch endpoint doesn't work in Docker (services already running)
225
+
226
+ ### Recommendation
227
+
228
+ For Docker deployments:
229
+ 1. Use docker-compose to manage all services
230
+ 2. Allyabase plugin provides:
231
+ - ✅ Service proxy routes
232
+ - ✅ Federation resolution
233
+ - ✅ Healthcheck endpoint (checks if services respond)
234
+ - ❌ Launch/lifecycle management (handled by Docker)
235
+
236
+ For non-Docker deployments:
237
+ 1. Original design works as intended
238
+ 2. PM2 process management
239
+ 3. Full lifecycle control via plugin
240
+
241
+ ---
242
+
243
+ ## Files Modified
244
+
245
+ 1. **server/server.js**
246
+ - Line 291: Fixed Express federation route wildcard (`*` → regex)
247
+ - Line 633: Fixed Express service proxy route wildcards (all 14 services, `*` → regex)
248
+ - Lines 63-100: Added PID 1 protection and self-protection
249
+ - Lines 156-175: Added Docker detection and port cleanup skip
250
+
251
+ ## Backward Compatibility
252
+
253
+ These changes are **fully backward compatible**:
254
+
255
+ - **Non-Docker environments:** Port cleanup still works (unless PID 1 or self)
256
+ - **Docker environments:** Automatically detected and handled safely
257
+ - **Existing functionality:** All proxy routes, federation, and endpoints unchanged
258
+
259
+ ## Testing Checklist
260
+
261
+ - [ ] Plugin loads without crash in Docker
262
+ - [ ] Federation endpoints respond (no PathError)
263
+ - [ ] Wiki doesn't kill itself on startup
264
+ - [ ] Proxy routes work (e.g., `/plugin/allyabase/bdo/*`)
265
+ - [ ] Healthcheck endpoint works
266
+ - [ ] Docker detection logs appear
267
+ - [ ] Non-Docker deployment still works (if applicable)
268
+
269
+ ---
270
+
271
+ ## Related Issues
272
+
273
+ - Original crash report: Dave's wiki crashed when allyabase installed
274
+ - Error 1: `PathError: Missing parameter name at index 30`
275
+ - Error 2: `Attempting to kill process 1` followed by container death
276
+
277
+ Both issues now resolved.
@@ -139,6 +139,128 @@ function emit($item, item) {
139
139
  console.log('[allyabase] Fork detected from:', forkInfo);
140
140
  }
141
141
 
142
+ // Add configuration section
143
+ const configContainer = document.createElement('div');
144
+ configContainer.style.padding = '10px';
145
+ configContainer.style.marginBottom = '15px';
146
+ configContainer.style.backgroundColor = '#f0f8ff';
147
+ configContainer.style.borderRadius = '5px';
148
+ configContainer.style.border = '1px solid #cce5ff';
149
+
150
+ const configTitle = document.createElement('div');
151
+ configTitle.style.fontWeight = 'bold';
152
+ configTitle.style.marginBottom = '10px';
153
+ configTitle.textContent = '⚙️ Configuration';
154
+ configContainer.appendChild(configTitle);
155
+
156
+ // Location Emoji Input
157
+ const locationLabel = document.createElement('div');
158
+ locationLabel.style.fontSize = '12px';
159
+ locationLabel.style.marginBottom = '4px';
160
+ locationLabel.textContent = 'Location Emoji (3 emoji):';
161
+ configContainer.appendChild(locationLabel);
162
+
163
+ const locationInput = document.createElement('input');
164
+ locationInput.type = 'text';
165
+ locationInput.id = 'location-emoji-input';
166
+ locationInput.style.width = '200px';
167
+ locationInput.style.padding = '5px';
168
+ locationInput.style.fontSize = '16px';
169
+ locationInput.style.marginBottom = '10px';
170
+ locationInput.placeholder = 'e.g., 🔥💎🌟';
171
+ configContainer.appendChild(locationInput);
172
+
173
+ // Federation Emoji Input
174
+ const federationLabel = document.createElement('div');
175
+ federationLabel.style.fontSize = '12px';
176
+ federationLabel.style.marginBottom = '4px';
177
+ federationLabel.textContent = 'Federation Emoji (1 emoji):';
178
+ configContainer.appendChild(federationLabel);
179
+
180
+ const federationInput = document.createElement('input');
181
+ federationInput.type = 'text';
182
+ federationInput.id = 'federation-emoji-input';
183
+ federationInput.style.width = '200px';
184
+ federationInput.style.padding = '5px';
185
+ federationInput.style.fontSize = '16px';
186
+ federationInput.style.marginBottom = '10px';
187
+ federationInput.placeholder = 'e.g., 💚';
188
+ federationInput.value = '💚';
189
+ configContainer.appendChild(federationInput);
190
+
191
+ // Save button
192
+ const saveConfigBtn = document.createElement('button');
193
+ saveConfigBtn.textContent = 'Save Configuration';
194
+ saveConfigBtn.style.padding = '8px 15px';
195
+ saveConfigBtn.style.fontSize = '12px';
196
+ saveConfigBtn.style.cursor = 'pointer';
197
+ saveConfigBtn.style.marginTop = '5px';
198
+ saveConfigBtn.style.backgroundColor = '#4CAF50';
199
+ saveConfigBtn.style.color = 'white';
200
+ saveConfigBtn.style.border = 'none';
201
+ saveConfigBtn.style.borderRadius = '4px';
202
+ configContainer.appendChild(saveConfigBtn);
203
+
204
+ // Config status message
205
+ const configStatus = document.createElement('div');
206
+ configStatus.id = 'config-status';
207
+ configStatus.style.fontSize = '11px';
208
+ configStatus.style.marginTop = '8px';
209
+ configStatus.style.minHeight = '16px';
210
+ configContainer.appendChild(configStatus);
211
+
212
+ container.appendChild(configContainer);
213
+
214
+ // Load current configuration
215
+ async function loadConfig() {
216
+ try {
217
+ const response = await get('/plugin/allyabase/config');
218
+ const data = await response.json();
219
+ if (data.success && data.config) {
220
+ locationInput.value = data.config.locationEmoji || '';
221
+ federationInput.value = data.config.federationEmoji || '💚';
222
+ }
223
+ } catch (err) {
224
+ console.error('[allyabase] Error loading config:', err);
225
+ }
226
+ }
227
+
228
+ // Save configuration
229
+ saveConfigBtn.addEventListener('click', async () => {
230
+ try {
231
+ configStatus.textContent = 'Saving...';
232
+ configStatus.style.color = '#666';
233
+
234
+ const response = await post('/plugin/allyabase/config', {
235
+ locationEmoji: locationInput.value.trim(),
236
+ federationEmoji: federationInput.value.trim() || '💚'
237
+ });
238
+
239
+ const result = await response.json();
240
+
241
+ if (result.success) {
242
+ configStatus.textContent = '✓ Configuration saved successfully!';
243
+ configStatus.style.color = 'green';
244
+ // Reload base emoji display
245
+ loadBaseEmoji();
246
+ } else {
247
+ configStatus.textContent = `✗ Error: ${result.error}`;
248
+ configStatus.style.color = 'red';
249
+ }
250
+
251
+ setTimeout(() => {
252
+ configStatus.textContent = '';
253
+ }, 3000);
254
+ } catch (err) {
255
+ console.error('[allyabase] Error saving config:', err);
256
+ configStatus.textContent = `✗ Error: ${err.message}`;
257
+ configStatus.style.color = 'red';
258
+ }
259
+ });
260
+
261
+ // Load config on initialization
262
+ loadConfig();
263
+
142
264
  // Add base emoji identifier section
143
265
  const baseEmojiContainer = document.createElement('div');
144
266
  baseEmojiContainer.style.padding = '10px';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wiki-plugin-allyabase",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "Allyabase management plugin for the federated wiki",
5
5
  "keywords": [
6
6
  "wiki",
package/server/server.js CHANGED
@@ -29,6 +29,9 @@ const SERVICE_PORTS = {
29
29
  // PID file for tracking PM2 process
30
30
  const PM2_PID_FILE = path.join(__dirname, 'allyabase-pm2.pid');
31
31
 
32
+ // Plugin configuration file (stores locationEmoji, federationEmoji, etc.)
33
+ const CONFIG_FILE = path.join(__dirname, 'plugin-config.json');
34
+
32
35
  let baseStatus = {
33
36
  running: false,
34
37
  services: {},
@@ -37,7 +40,38 @@ let baseStatus = {
37
40
 
38
41
  let allyabaseProcess = null;
39
42
 
40
- // Function to load wiki's owner.json for keypair and location emoji
43
+ // Load plugin configuration (locationEmoji, federationEmoji, etc.)
44
+ function loadPluginConfig() {
45
+ try {
46
+ if (fs.existsSync(CONFIG_FILE)) {
47
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
48
+ }
49
+ // Return defaults if no config file exists
50
+ return {
51
+ locationEmoji: null,
52
+ federationEmoji: '💚'
53
+ };
54
+ } catch (err) {
55
+ console.error('[wiki-plugin-allyabase] Error loading plugin config:', err);
56
+ return {
57
+ locationEmoji: null,
58
+ federationEmoji: '💚'
59
+ };
60
+ }
61
+ }
62
+
63
+ // Save plugin configuration
64
+ function savePluginConfig(config) {
65
+ try {
66
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8');
67
+ return true;
68
+ } catch (err) {
69
+ console.error('[wiki-plugin-allyabase] Error saving plugin config:', err);
70
+ return false;
71
+ }
72
+ }
73
+
74
+ // Function to load wiki's sessionless keys from owner.json (authentication only)
41
75
  function loadWikiKeypair() {
42
76
  try {
43
77
  const ownerPath = path.join(process.env.HOME || '/root', '.wiki/status/owner.json');
@@ -46,9 +80,7 @@ function loadWikiKeypair() {
46
80
  if (ownerData.sessionlessKeys) {
47
81
  return {
48
82
  pubKey: ownerData.sessionlessKeys.pubKey,
49
- privateKey: ownerData.sessionlessKeys.privateKey,
50
- locationEmoji: ownerData.locationEmoji,
51
- federationEmoji: ownerData.federationEmoji
83
+ privateKey: ownerData.sessionlessKeys.privateKey
52
84
  };
53
85
  }
54
86
  }
@@ -63,6 +95,18 @@ function loadWikiKeypair() {
63
95
  // Function to kill process by PID
64
96
  function killProcessByPid(pid) {
65
97
  try {
98
+ // CRITICAL: Never kill PID 1 in Docker containers - it's the main container process
99
+ if (pid === 1) {
100
+ console.log(`[wiki-plugin-allyabase] ⚠️ Skipping PID 1 (init process) - this is likely the wiki server itself in Docker`);
101
+ return false;
102
+ }
103
+
104
+ // Also don't kill our own process
105
+ if (pid === process.pid) {
106
+ console.log(`[wiki-plugin-allyabase] ⚠️ Skipping PID ${pid} (this is us!)`);
107
+ return false;
108
+ }
109
+
66
110
  console.log(`[wiki-plugin-allyabase] Attempting to kill process ${pid}...`);
67
111
  process.kill(pid, 'SIGTERM');
68
112
 
@@ -156,7 +200,18 @@ async function cleanupOrphanedProcesses() {
156
200
  // Stop PM2 if it's running (cleans up all managed services)
157
201
  await stopPM2();
158
202
 
159
- // Fallback: kill any processes using our ports
203
+ // Detect if we're in a Docker container
204
+ // In Docker, services run in separate containers, so port cleanup is unnecessary and dangerous
205
+ const isDocker = fs.existsSync('/.dockerenv') || fs.existsSync('/run/.containerenv');
206
+
207
+ if (isDocker) {
208
+ console.log('[wiki-plugin-allyabase] 🐳 Detected Docker environment - skipping port cleanup');
209
+ console.log('[wiki-plugin-allyabase] In Docker, services should run in separate containers, not as processes on ports');
210
+ console.log('[wiki-plugin-allyabase] Cleanup complete (Docker mode)');
211
+ return;
212
+ }
213
+
214
+ // Fallback: kill any processes using our ports (only in non-Docker environments)
160
215
  console.log('[wiki-plugin-allyabase] Cleaning up any processes on service ports...');
161
216
  for (const [service, port] of Object.entries(SERVICE_PORTS)) {
162
217
  await killProcessByPort(port);
@@ -288,7 +343,8 @@ async function startServer(params) {
288
343
 
289
344
  // CORS middleware for federation endpoints
290
345
  // Allows cross-origin requests from other federated wikis
291
- app.use('/plugin/allyabase/federation/*', function(req, res, next) {
346
+ // Use regex to match all federation paths (Express doesn't support * wildcard in newer versions)
347
+ app.use(/^\/plugin\/allyabase\/federation\/.*/, function(req, res, next) {
292
348
  res.setHeader('Access-Control-Allow-Origin', '*');
293
349
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
294
350
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
@@ -497,8 +553,8 @@ async function startServer(params) {
497
553
  if (targetWikiUrls && targetWikiUrls.length > 0) {
498
554
  // This is a cross-wiki request - try all registered URLs
499
555
  const currentWikiUrl = `http://localhost:${req.socket.localPort}`;
500
- const wikiInfo = loadWikiKeypair();
501
- const thisWikiLocationEmoji = wikiInfo ? wikiInfo.locationEmoji : null;
556
+ const config = loadPluginConfig();
557
+ const thisWikiLocationEmoji = config.locationEmoji;
502
558
 
503
559
  console.log('[BDO EMOJI] Cross-wiki request detected');
504
560
  console.log('[BDO EMOJI] Target wikis (${targetWikiUrls.length}): ${targetWikiUrls.join(', ')}');
@@ -606,7 +662,8 @@ async function startServer(params) {
606
662
  // Create proxy routes for each service
607
663
  Object.entries(SERVICE_PORTS).forEach(([service, port]) => {
608
664
  // Proxy all methods (GET, POST, PUT, DELETE, etc.)
609
- app.all(`/plugin/allyabase/${service}/*`, function(req, res) {
665
+ // Use regex pattern instead of wildcard to avoid PathError in newer path-to-regexp
666
+ app.all(new RegExp(`^\\/plugin\\/allyabase\\/${service}\\/.*`), function(req, res) {
610
667
  const targetPath = req.url.replace(`/plugin/allyabase/${service}`, '');
611
668
  console.log(`[PROXY] ${req.method} /plugin/allyabase/${service}${targetPath} -> http://localhost:${port}${targetPath}`);
612
669
  console.log(`[PROXY] Headers:`, JSON.stringify(req.headers, null, 2));
@@ -726,58 +783,122 @@ async function startServer(params) {
726
783
  res.send(baseStatus);
727
784
  });
728
785
 
729
- // Endpoint to get wiki's base emoji identifier
786
+ // Endpoint to get wiki's base emoji identifier (from plugin config)
730
787
  app.get('/plugin/allyabase/base-emoji', function(req, res) {
731
788
  try {
732
- const ownerPath = path.join(process.env.HOME || '/root', '.wiki/status/owner.json');
733
- if (fs.existsSync(ownerPath)) {
734
- const ownerData = JSON.parse(fs.readFileSync(ownerPath, 'utf8'));
735
-
736
- const response = {
737
- federationEmoji: ownerData.federationEmoji || '💚',
738
- locationEmoji: ownerData.locationEmoji || null,
739
- baseEmoji: (ownerData.federationEmoji || '💚') + (ownerData.locationEmoji || ''),
740
- warnings: []
741
- };
789
+ const config = loadPluginConfig();
790
+ const keypair = loadWikiKeypair();
742
791
 
743
- // Validation warnings
744
- if (!ownerData.locationEmoji) {
745
- response.warnings.push({
746
- severity: 'error',
747
- message: 'Missing locationEmoji in owner.json',
748
- fix: 'Add "locationEmoji": "🔥💎🌟" (3 emoji) to your owner.json'
749
- });
750
- } else if (!ownerData.federationEmoji) {
751
- response.warnings.push({
752
- severity: 'warning',
753
- message: 'Missing federationEmoji in owner.json, using default 💚',
754
- fix: 'Add "federationEmoji": "💚" to your owner.json'
792
+ const response = {
793
+ federationEmoji: config.federationEmoji || '💚',
794
+ locationEmoji: config.locationEmoji || null,
795
+ baseEmoji: (config.federationEmoji || '💚') + (config.locationEmoji || ''),
796
+ warnings: []
797
+ };
798
+
799
+ // Validation warnings
800
+ if (!config.locationEmoji) {
801
+ response.warnings.push({
802
+ severity: 'error',
803
+ message: 'Location emoji not configured',
804
+ fix: 'Set your 3-emoji location identifier in the plugin settings'
805
+ });
806
+ }
807
+
808
+ if (!keypair || !keypair.pubKey) {
809
+ response.warnings.push({
810
+ severity: 'error',
811
+ message: 'Missing sessionless keys in owner.json',
812
+ fix: 'BDO operations will fail without sessionless keys in ~/.wiki/status/owner.json'
813
+ });
814
+ }
815
+
816
+ res.send(response);
817
+ } catch (err) {
818
+ console.error('Error reading base emoji config:', err);
819
+ res.status(500).send({
820
+ error: 'Failed to load base emoji',
821
+ message: err.message
822
+ });
823
+ }
824
+ });
825
+
826
+ // Endpoint to get plugin configuration
827
+ app.get('/plugin/allyabase/config', function(req, res) {
828
+ try {
829
+ const config = loadPluginConfig();
830
+ res.send({
831
+ success: true,
832
+ config: config
833
+ });
834
+ } catch (err) {
835
+ console.error('Error loading plugin config:', err);
836
+ res.status(500).send({
837
+ success: false,
838
+ error: 'Failed to load configuration',
839
+ message: err.message
840
+ });
841
+ }
842
+ });
843
+
844
+ // Endpoint to save plugin configuration (requires owner auth)
845
+ app.post('/plugin/allyabase/config', owner, function(req, res) {
846
+ try {
847
+ const { locationEmoji, federationEmoji } = req.body;
848
+
849
+ // Validate locationEmoji (should be exactly 3 emoji)
850
+ if (locationEmoji) {
851
+ const emojiRegex = /[\u{1F1E6}-\u{1F1FF}]{2}|(?:[\u{1F3F4}\u{1F3F3}][\u{FE0F}]?(?:\u{200D}[\u{2620}\u{2695}\u{2696}\u{2708}\u{1F308}][\u{FE0F}]?)?)|(?:\p{Emoji_Presentation}|\p{Emoji}\uFE0F)(?:\u{200D}(?:\p{Emoji_Presentation}|\p{Emoji}\uFE0F))*/gu;
852
+ const emojis = locationEmoji.match(emojiRegex) || [];
853
+
854
+ if (emojis.length !== 3) {
855
+ return res.status(400).send({
856
+ success: false,
857
+ error: 'Location emoji must be exactly 3 emoji characters',
858
+ got: emojis.length
755
859
  });
756
860
  }
861
+ }
862
+
863
+ // Validate federationEmoji (should be exactly 1 emoji)
864
+ if (federationEmoji) {
865
+ const emojiRegex = /[\u{1F1E6}-\u{1F1FF}]{2}|(?:[\u{1F3F4}\u{1F3F3}][\u{FE0F}]?(?:\u{200D}[\u{2620}\u{2695}\u{2696}\u{2708}\u{1F308}][\u{FE0F}]?)?)|(?:\p{Emoji_Presentation}|\p{Emoji}\uFE0F)(?:\u{200D}(?:\p{Emoji_Presentation}|\p{Emoji}\uFE0F))*/gu;
866
+ const emojis = federationEmoji.match(emojiRegex) || [];
757
867
 
758
- if (!ownerData.sessionlessKeys) {
759
- response.warnings.push({
760
- severity: 'error',
761
- message: 'Missing sessionlessKeys in owner.json',
762
- fix: 'BDO operations will fail without sessionless keys'
868
+ if (emojis.length !== 1) {
869
+ return res.status(400).send({
870
+ success: false,
871
+ error: 'Federation emoji must be exactly 1 emoji character',
872
+ got: emojis.length
763
873
  });
764
874
  }
875
+ }
876
+
877
+ const config = loadPluginConfig();
765
878
 
766
- res.send(response);
879
+ // Update config with provided values
880
+ if (locationEmoji !== undefined) config.locationEmoji = locationEmoji;
881
+ if (federationEmoji !== undefined) config.federationEmoji = federationEmoji;
882
+
883
+ const saved = savePluginConfig(config);
884
+
885
+ if (saved) {
886
+ res.send({
887
+ success: true,
888
+ config: config,
889
+ message: 'Configuration saved successfully'
890
+ });
767
891
  } else {
768
- res.status(404).send({
769
- error: 'Owner configuration not found',
770
- warnings: [{
771
- severity: 'error',
772
- message: 'owner.json file not found',
773
- fix: 'Create ~/.wiki/status/owner.json with locationEmoji and sessionlessKeys'
774
- }]
892
+ res.status(500).send({
893
+ success: false,
894
+ error: 'Failed to save configuration'
775
895
  });
776
896
  }
777
897
  } catch (err) {
778
- console.error('Error reading owner.json:', err);
898
+ console.error('Error saving plugin config:', err);
779
899
  res.status(500).send({
780
- error: 'Failed to load base emoji',
900
+ success: false,
901
+ error: 'Failed to save configuration',
781
902
  message: err.message
782
903
  });
783
904
  }