pal-explorer-cli 0.4.11 → 0.4.13

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 (99) hide show
  1. package/README.md +149 -149
  2. package/bin/pal.js +63 -2
  3. package/extensions/@palexplorer/analytics/extension.json +20 -1
  4. package/extensions/@palexplorer/analytics/index.js +19 -9
  5. package/extensions/@palexplorer/audit/extension.json +14 -0
  6. package/extensions/@palexplorer/auth-email/extension.json +15 -0
  7. package/extensions/@palexplorer/auth-oauth/extension.json +15 -0
  8. package/extensions/@palexplorer/chat/extension.json +14 -0
  9. package/extensions/@palexplorer/discovery/extension.json +17 -0
  10. package/extensions/@palexplorer/discovery/index.js +1 -1
  11. package/extensions/@palexplorer/email-notifications/extension.json +23 -0
  12. package/extensions/@palexplorer/groups/extension.json +15 -0
  13. package/extensions/@palexplorer/share-links/extension.json +15 -0
  14. package/extensions/@palexplorer/sync/extension.json +16 -0
  15. package/extensions/@palexplorer/user-mgmt/extension.json +15 -0
  16. package/lib/capabilities.js +24 -24
  17. package/lib/commands/analytics.js +175 -175
  18. package/lib/commands/api-keys.js +131 -131
  19. package/lib/commands/audit.js +235 -235
  20. package/lib/commands/auth.js +137 -137
  21. package/lib/commands/backup.js +76 -76
  22. package/lib/commands/billing.js +148 -148
  23. package/lib/commands/chat.js +217 -217
  24. package/lib/commands/cloud-backup.js +231 -231
  25. package/lib/commands/comment.js +99 -99
  26. package/lib/commands/completion.js +203 -203
  27. package/lib/commands/compliance.js +218 -218
  28. package/lib/commands/config.js +136 -136
  29. package/lib/commands/connect.js +44 -44
  30. package/lib/commands/dept.js +294 -294
  31. package/lib/commands/device.js +146 -146
  32. package/lib/commands/download.js +240 -226
  33. package/lib/commands/explorer.js +178 -178
  34. package/lib/commands/extension.js +1060 -970
  35. package/lib/commands/favorite.js +90 -90
  36. package/lib/commands/federation.js +270 -270
  37. package/lib/commands/file.js +533 -533
  38. package/lib/commands/group.js +271 -271
  39. package/lib/commands/gui-share.js +29 -29
  40. package/lib/commands/init.js +61 -61
  41. package/lib/commands/invite.js +59 -59
  42. package/lib/commands/list.js +58 -58
  43. package/lib/commands/log.js +116 -116
  44. package/lib/commands/nearby.js +108 -108
  45. package/lib/commands/network.js +251 -251
  46. package/lib/commands/notify.js +198 -198
  47. package/lib/commands/org.js +273 -273
  48. package/lib/commands/pal.js +403 -180
  49. package/lib/commands/permissions.js +216 -216
  50. package/lib/commands/pin.js +97 -97
  51. package/lib/commands/protocol.js +357 -357
  52. package/lib/commands/rbac.js +147 -147
  53. package/lib/commands/recover.js +36 -36
  54. package/lib/commands/register.js +171 -171
  55. package/lib/commands/relay.js +131 -131
  56. package/lib/commands/remote.js +368 -368
  57. package/lib/commands/revoke.js +50 -50
  58. package/lib/commands/scanner.js +280 -280
  59. package/lib/commands/schedule.js +344 -344
  60. package/lib/commands/scim.js +203 -203
  61. package/lib/commands/search.js +181 -181
  62. package/lib/commands/serve.js +438 -438
  63. package/lib/commands/server.js +350 -350
  64. package/lib/commands/share-link.js +199 -199
  65. package/lib/commands/share.js +336 -323
  66. package/lib/commands/sso.js +200 -200
  67. package/lib/commands/status.js +145 -145
  68. package/lib/commands/stream.js +562 -562
  69. package/lib/commands/su.js +187 -187
  70. package/lib/commands/sync.js +979 -979
  71. package/lib/commands/transfers.js +152 -152
  72. package/lib/commands/uninstall.js +188 -188
  73. package/lib/commands/update.js +204 -204
  74. package/lib/commands/user.js +276 -276
  75. package/lib/commands/vfs.js +84 -84
  76. package/lib/commands/web-login.js +79 -79
  77. package/lib/commands/web.js +52 -52
  78. package/lib/commands/webhook.js +180 -180
  79. package/lib/commands/whoami.js +59 -59
  80. package/lib/commands/workspace.js +121 -121
  81. package/lib/core/billing.js +16 -5
  82. package/lib/core/dhtDiscovery.js +9 -2
  83. package/lib/core/discoveryClient.js +13 -7
  84. package/lib/core/extensions.js +142 -1
  85. package/lib/core/identity.js +33 -2
  86. package/lib/core/imageProcessor.js +109 -0
  87. package/lib/core/imageTorrent.js +167 -0
  88. package/lib/core/permissions.js +1 -1
  89. package/lib/core/pro.js +11 -4
  90. package/lib/core/serverList.js +4 -1
  91. package/lib/core/shares.js +12 -1
  92. package/lib/core/signalingServer.js +14 -2
  93. package/lib/core/su.js +1 -1
  94. package/lib/core/users.js +1 -1
  95. package/lib/protocol/messages.js +12 -3
  96. package/lib/utils/explorer.js +1 -1
  97. package/lib/utils/help.js +357 -357
  98. package/lib/utils/torrent.js +1 -0
  99. package/package.json +4 -3
@@ -1,270 +1,270 @@
1
- import chalk from 'chalk';
2
-
3
- export default function federationCommand(program) {
4
- const cmd = program
5
- .command('federation')
6
- .description('manage federation between discovery servers (Enterprise)')
7
- .addHelpText('after', `
8
- Examples:
9
- $ pe federation registry add https://discovery2.example.com
10
- $ pe federation registry list
11
- $ pe federation registry resolve @user@server.com
12
- $ pe federation bridge add https://network2.example.com
13
- $ pe federation bridge stats
14
- `)
15
- .action(() => { cmd.outputHelp(); });
16
-
17
- // --- Registry subcommands ---
18
- const registry = cmd
19
- .command('registry')
20
- .description('manage federated discovery server registry')
21
- .action(() => { registry.outputHelp(); });
22
-
23
- registry
24
- .command('add <url>')
25
- .description('add a discovery server to federation registry')
26
- .option('--trust <level>', 'trust level: trusted, verified, unknown', 'unknown')
27
- .action(async (url, opts) => {
28
- try {
29
- const extConfig = (await import('../utils/config.js')).default;
30
- const servers = extConfig.get('ext_store.federation-registry')?.registry || {};
31
- const id = url.replace(/[^a-z0-9]/gi, '_');
32
- servers[id] = {
33
- url,
34
- trust: opts.trust,
35
- category: 'public',
36
- trustScore: opts.trust === 'trusted' ? 100 : 0,
37
- lastSeen: null,
38
- healthy: null,
39
- addedAt: new Date().toISOString(),
40
- };
41
- const store = extConfig.get('ext_store.federation-registry') || {};
42
- store.registry = servers;
43
- extConfig.set('ext_store.federation-registry', store);
44
- console.log(chalk.green(`✔ Server added to federation registry: ${url}`));
45
- console.log(` Trust: ${chalk.white(opts.trust)}`);
46
- } catch (err) {
47
- console.log(chalk.red(`Failed to add server: ${err.message}`));
48
- process.exitCode = 1;
49
- }
50
- });
51
-
52
- registry
53
- .command('remove <url>')
54
- .description('remove a server from federation registry')
55
- .action(async (url) => {
56
- try {
57
- const extConfig = (await import('../utils/config.js')).default;
58
- const store = extConfig.get('ext_store.federation-registry') || {};
59
- const registry = store.registry || {};
60
- const id = url.replace(/[^a-z0-9]/gi, '_');
61
- if (!registry[id]) {
62
- console.log(chalk.yellow(`Server not found in registry: ${url}`));
63
- process.exitCode = 1;
64
- return;
65
- }
66
- delete registry[id];
67
- store.registry = registry;
68
- extConfig.set('ext_store.federation-registry', store);
69
- console.log(chalk.green(`✔ Server removed from registry: ${url}`));
70
- } catch (err) {
71
- console.log(chalk.red(`Failed to remove server: ${err.message}`));
72
- process.exitCode = 1;
73
- }
74
- });
75
-
76
- registry
77
- .command('list')
78
- .description('list all servers in federation registry')
79
- .action(async () => {
80
- try {
81
- const extConfig = (await import('../utils/config.js')).default;
82
- const store = extConfig.get('ext_store.federation-registry') || {};
83
- const registry = store.registry || {};
84
- const entries = Object.values(registry);
85
- if (entries.length === 0) {
86
- console.log(chalk.dim('No servers in federation registry.'));
87
- return;
88
- }
89
- console.log(chalk.bold(`Federation Registry (${entries.length} servers)\n`));
90
- for (const s of entries) {
91
- const health = s.healthy === true ? chalk.green('●') : s.healthy === false ? chalk.red('●') : chalk.dim('○');
92
- console.log(` ${health} ${chalk.cyan(s.url)}`);
93
- console.log(` Trust: ${chalk.white(s.trust)} Score: ${s.trustScore} Added: ${s.addedAt || 'unknown'}`);
94
- }
95
- } catch (err) {
96
- console.log(chalk.red(`Failed to list registry: ${err.message}`));
97
- process.exitCode = 1;
98
- }
99
- });
100
-
101
- registry
102
- .command('peer <url>')
103
- .description('initiate peering handshake with another server')
104
- .action(async (url) => {
105
- try {
106
- const extConfig = (await import('../utils/config.js')).default;
107
- const store = extConfig.get('ext_store.federation-registry') || {};
108
- const registry = store.registry || {};
109
- const id = url.replace(/[^a-z0-9]/gi, '_');
110
- if (!registry[id]) {
111
- console.log(chalk.yellow(`Server not in registry. Add it first with: pe federation registry add ${url}`));
112
- process.exitCode = 1;
113
- return;
114
- }
115
- registry[id].trust = 'verified';
116
- registry[id].trustScore = Math.max(registry[id].trustScore || 0, 50);
117
- registry[id].lastSeen = new Date().toISOString();
118
- store.registry = registry;
119
- extConfig.set('ext_store.federation-registry', store);
120
- console.log(chalk.green(`✔ Peering initiated with: ${url}`));
121
- console.log(` Trust level upgraded to: ${chalk.white('verified')}`);
122
- } catch (err) {
123
- console.log(chalk.red(`Peering failed: ${err.message}`));
124
- process.exitCode = 1;
125
- }
126
- });
127
-
128
- registry
129
- .command('resolve <handle>')
130
- .description('resolve a handle across federated servers (e.g. @user@server.com)')
131
- .action(async (handle) => {
132
- try {
133
- const extConfig = (await import('../utils/config.js')).default;
134
- const store = extConfig.get('ext_store.federation-registry') || {};
135
- const registry = store.registry || {};
136
- const entries = Object.values(registry).filter(s => s.healthy !== false);
137
-
138
- if (entries.length === 0) {
139
- console.log(chalk.yellow('No healthy servers in federation registry.'));
140
- process.exitCode = 1;
141
- return;
142
- }
143
-
144
- let parts = handle.replace(/^@/, '').split('@');
145
- let username = parts[0];
146
- let server = parts[1] || null;
147
-
148
- console.log(chalk.dim(`Resolving ${handle} across ${entries.length} server(s)...`));
149
-
150
- if (server) {
151
- const match = entries.find(s => s.url.includes(server));
152
- if (match) {
153
- console.log(chalk.green(`✔ Resolved via ${match.url}`));
154
- console.log(` Handle: ${chalk.cyan('@' + username)}`);
155
- console.log(` Server: ${chalk.white(match.url)}`);
156
- } else {
157
- console.log(chalk.yellow(`Server ${server} not in registry.`));
158
- process.exitCode = 1;
159
- }
160
- } else {
161
- console.log(chalk.dim(`Querying all ${entries.length} servers for @${username}...`));
162
- for (const s of entries) {
163
- console.log(` ${chalk.dim('→')} ${s.url}: ${chalk.dim('queried')}`);
164
- }
165
- console.log(chalk.green(`✔ Resolution complete.`));
166
- }
167
- } catch (err) {
168
- console.log(chalk.red(`Resolution failed: ${err.message}`));
169
- process.exitCode = 1;
170
- }
171
- });
172
-
173
- // --- Bridge subcommands ---
174
- const bridge = cmd
175
- .command('bridge')
176
- .description('manage federation bridge connections')
177
- .action(() => { bridge.outputHelp(); });
178
-
179
- bridge
180
- .command('add <url>')
181
- .description('connect to a federated network')
182
- .option('--relay-policy <policy>', 'relay policy: none, all', 'none')
183
- .action(async (url, opts) => {
184
- try {
185
- const extConfig = (await import('../utils/config.js')).default;
186
- const store = extConfig.get('ext_store.federation-bridge') || {};
187
- const networks = store.bridgedNetworks || [];
188
- if (networks.includes(url)) {
189
- console.log(chalk.yellow(`Already bridged to: ${url}`));
190
- return;
191
- }
192
- networks.push(url);
193
- store.bridgedNetworks = networks;
194
- store.relayPolicy = opts.relayPolicy;
195
- extConfig.set('ext_store.federation-bridge', store);
196
- console.log(chalk.green(`✔ Bridge connected to: ${url}`));
197
- console.log(` Relay policy: ${chalk.white(opts.relayPolicy)}`);
198
- } catch (err) {
199
- console.log(chalk.red(`Bridge failed: ${err.message}`));
200
- process.exitCode = 1;
201
- }
202
- });
203
-
204
- bridge
205
- .command('remove <url>')
206
- .description('disconnect from a federated network')
207
- .action(async (url) => {
208
- try {
209
- const extConfig = (await import('../utils/config.js')).default;
210
- const store = extConfig.get('ext_store.federation-bridge') || {};
211
- const networks = store.bridgedNetworks || [];
212
- const idx = networks.indexOf(url);
213
- if (idx === -1) {
214
- console.log(chalk.yellow(`Not bridged to: ${url}`));
215
- process.exitCode = 1;
216
- return;
217
- }
218
- networks.splice(idx, 1);
219
- store.bridgedNetworks = networks;
220
- extConfig.set('ext_store.federation-bridge', store);
221
- console.log(chalk.green(`✔ Bridge disconnected from: ${url}`));
222
- } catch (err) {
223
- console.log(chalk.red(`Failed: ${err.message}`));
224
- process.exitCode = 1;
225
- }
226
- });
227
-
228
- bridge
229
- .command('list')
230
- .description('list all bridge connections')
231
- .action(async () => {
232
- try {
233
- const extConfig = (await import('../utils/config.js')).default;
234
- const store = extConfig.get('ext_store.federation-bridge') || {};
235
- const networks = store.bridgedNetworks || [];
236
- if (networks.length === 0) {
237
- console.log(chalk.dim('No bridge connections.'));
238
- return;
239
- }
240
- console.log(chalk.bold(`Federation Bridges (${networks.length})\n`));
241
- for (const url of networks) {
242
- console.log(` ${chalk.green('●')} ${chalk.cyan(url)}`);
243
- }
244
- console.log(`\n Relay policy: ${chalk.white(store.relayPolicy || 'none')}`);
245
- } catch (err) {
246
- console.log(chalk.red(`Failed: ${err.message}`));
247
- process.exitCode = 1;
248
- }
249
- });
250
-
251
- bridge
252
- .command('stats')
253
- .description('show bridge relay statistics')
254
- .action(async () => {
255
- try {
256
- const extConfig = (await import('../utils/config.js')).default;
257
- const store = extConfig.get('ext_store.federation-bridge') || {};
258
- const networks = store.bridgedNetworks || [];
259
- const messageLog = store.messageLog || [];
260
- console.log(chalk.bold('Bridge Statistics\n'));
261
- console.log(` Connected networks: ${chalk.white(networks.length)}`);
262
- console.log(` Messages relayed: ${chalk.white(messageLog.length)}`);
263
- console.log(` Relay policy: ${chalk.white(store.relayPolicy || 'none')}`);
264
- console.log(` Last activity: ${chalk.white(store.lastActivity || 'never')}`);
265
- } catch (err) {
266
- console.log(chalk.red(`Failed: ${err.message}`));
267
- process.exitCode = 1;
268
- }
269
- });
270
- }
1
+ import chalk from 'chalk';
2
+
3
+ export default function federationCommand(program) {
4
+ const cmd = program
5
+ .command('federation')
6
+ .description('manage federation between discovery servers (Enterprise)')
7
+ .addHelpText('after', `
8
+ Examples:
9
+ $ pal federation registry add https://discovery2.example.com
10
+ $ pal federation registry list
11
+ $ pal federation registry resolve @user@server.com
12
+ $ pal federation bridge add https://network2.example.com
13
+ $ pal federation bridge stats
14
+ `)
15
+ .action(() => { cmd.outputHelp(); });
16
+
17
+ // --- Registry subcommands ---
18
+ const registry = cmd
19
+ .command('registry')
20
+ .description('manage federated discovery server registry')
21
+ .action(() => { registry.outputHelp(); });
22
+
23
+ registry
24
+ .command('add <url>')
25
+ .description('add a discovery server to federation registry')
26
+ .option('--trust <level>', 'trust level: trusted, verified, unknown', 'unknown')
27
+ .action(async (url, opts) => {
28
+ try {
29
+ const extConfig = (await import('../utils/config.js')).default;
30
+ const servers = extConfig.get('ext_store.federation-registry')?.registry || {};
31
+ const id = url.replace(/[^a-z0-9]/gi, '_');
32
+ servers[id] = {
33
+ url,
34
+ trust: opts.trust,
35
+ category: 'public',
36
+ trustScore: opts.trust === 'trusted' ? 100 : 0,
37
+ lastSeen: null,
38
+ healthy: null,
39
+ addedAt: new Date().toISOString(),
40
+ };
41
+ const store = extConfig.get('ext_store.federation-registry') || {};
42
+ store.registry = servers;
43
+ extConfig.set('ext_store.federation-registry', store);
44
+ console.log(chalk.green(`✔ Server added to federation registry: ${url}`));
45
+ console.log(` Trust: ${chalk.white(opts.trust)}`);
46
+ } catch (err) {
47
+ console.log(chalk.red(`Failed to add server: ${err.message}`));
48
+ process.exitCode = 1;
49
+ }
50
+ });
51
+
52
+ registry
53
+ .command('remove <url>')
54
+ .description('remove a server from federation registry')
55
+ .action(async (url) => {
56
+ try {
57
+ const extConfig = (await import('../utils/config.js')).default;
58
+ const store = extConfig.get('ext_store.federation-registry') || {};
59
+ const registry = store.registry || {};
60
+ const id = url.replace(/[^a-z0-9]/gi, '_');
61
+ if (!registry[id]) {
62
+ console.log(chalk.yellow(`Server not found in registry: ${url}`));
63
+ process.exitCode = 1;
64
+ return;
65
+ }
66
+ delete registry[id];
67
+ store.registry = registry;
68
+ extConfig.set('ext_store.federation-registry', store);
69
+ console.log(chalk.green(`✔ Server removed from registry: ${url}`));
70
+ } catch (err) {
71
+ console.log(chalk.red(`Failed to remove server: ${err.message}`));
72
+ process.exitCode = 1;
73
+ }
74
+ });
75
+
76
+ registry
77
+ .command('list')
78
+ .description('list all servers in federation registry')
79
+ .action(async () => {
80
+ try {
81
+ const extConfig = (await import('../utils/config.js')).default;
82
+ const store = extConfig.get('ext_store.federation-registry') || {};
83
+ const registry = store.registry || {};
84
+ const entries = Object.values(registry);
85
+ if (entries.length === 0) {
86
+ console.log(chalk.dim('No servers in federation registry.'));
87
+ return;
88
+ }
89
+ console.log(chalk.bold(`Federation Registry (${entries.length} servers)\n`));
90
+ for (const s of entries) {
91
+ const health = s.healthy === true ? chalk.green('●') : s.healthy === false ? chalk.red('●') : chalk.dim('○');
92
+ console.log(` ${health} ${chalk.cyan(s.url)}`);
93
+ console.log(` Trust: ${chalk.white(s.trust)} Score: ${s.trustScore} Added: ${s.addedAt || 'unknown'}`);
94
+ }
95
+ } catch (err) {
96
+ console.log(chalk.red(`Failed to list registry: ${err.message}`));
97
+ process.exitCode = 1;
98
+ }
99
+ });
100
+
101
+ registry
102
+ .command('peer <url>')
103
+ .description('initiate peering handshake with another server')
104
+ .action(async (url) => {
105
+ try {
106
+ const extConfig = (await import('../utils/config.js')).default;
107
+ const store = extConfig.get('ext_store.federation-registry') || {};
108
+ const registry = store.registry || {};
109
+ const id = url.replace(/[^a-z0-9]/gi, '_');
110
+ if (!registry[id]) {
111
+ console.log(chalk.yellow(`Server not in registry. Add it first with: pal federation registry add ${url}`));
112
+ process.exitCode = 1;
113
+ return;
114
+ }
115
+ registry[id].trust = 'verified';
116
+ registry[id].trustScore = Math.max(registry[id].trustScore || 0, 50);
117
+ registry[id].lastSeen = new Date().toISOString();
118
+ store.registry = registry;
119
+ extConfig.set('ext_store.federation-registry', store);
120
+ console.log(chalk.green(`✔ Peering initiated with: ${url}`));
121
+ console.log(` Trust level upgraded to: ${chalk.white('verified')}`);
122
+ } catch (err) {
123
+ console.log(chalk.red(`Peering failed: ${err.message}`));
124
+ process.exitCode = 1;
125
+ }
126
+ });
127
+
128
+ registry
129
+ .command('resolve <handle>')
130
+ .description('resolve a handle across federated servers (e.g. @user@server.com)')
131
+ .action(async (handle) => {
132
+ try {
133
+ const extConfig = (await import('../utils/config.js')).default;
134
+ const store = extConfig.get('ext_store.federation-registry') || {};
135
+ const registry = store.registry || {};
136
+ const entries = Object.values(registry).filter(s => s.healthy !== false);
137
+
138
+ if (entries.length === 0) {
139
+ console.log(chalk.yellow('No healthy servers in federation registry.'));
140
+ process.exitCode = 1;
141
+ return;
142
+ }
143
+
144
+ let parts = handle.replace(/^@/, '').split('@');
145
+ let username = parts[0];
146
+ let server = parts[1] || null;
147
+
148
+ console.log(chalk.dim(`Resolving ${handle} across ${entries.length} server(s)...`));
149
+
150
+ if (server) {
151
+ const match = entries.find(s => s.url.includes(server));
152
+ if (match) {
153
+ console.log(chalk.green(`✔ Resolved via ${match.url}`));
154
+ console.log(` Handle: ${chalk.cyan('@' + username)}`);
155
+ console.log(` Server: ${chalk.white(match.url)}`);
156
+ } else {
157
+ console.log(chalk.yellow(`Server ${server} not in registry.`));
158
+ process.exitCode = 1;
159
+ }
160
+ } else {
161
+ console.log(chalk.dim(`Querying all ${entries.length} servers for @${username}...`));
162
+ for (const s of entries) {
163
+ console.log(` ${chalk.dim('→')} ${s.url}: ${chalk.dim('queried')}`);
164
+ }
165
+ console.log(chalk.green(`✔ Resolution complete.`));
166
+ }
167
+ } catch (err) {
168
+ console.log(chalk.red(`Resolution failed: ${err.message}`));
169
+ process.exitCode = 1;
170
+ }
171
+ });
172
+
173
+ // --- Bridge subcommands ---
174
+ const bridge = cmd
175
+ .command('bridge')
176
+ .description('manage federation bridge connections')
177
+ .action(() => { bridge.outputHelp(); });
178
+
179
+ bridge
180
+ .command('add <url>')
181
+ .description('connect to a federated network')
182
+ .option('--relay-policy <policy>', 'relay policy: none, all', 'none')
183
+ .action(async (url, opts) => {
184
+ try {
185
+ const extConfig = (await import('../utils/config.js')).default;
186
+ const store = extConfig.get('ext_store.federation-bridge') || {};
187
+ const networks = store.bridgedNetworks || [];
188
+ if (networks.includes(url)) {
189
+ console.log(chalk.yellow(`Already bridged to: ${url}`));
190
+ return;
191
+ }
192
+ networks.push(url);
193
+ store.bridgedNetworks = networks;
194
+ store.relayPolicy = opts.relayPolicy;
195
+ extConfig.set('ext_store.federation-bridge', store);
196
+ console.log(chalk.green(`✔ Bridge connected to: ${url}`));
197
+ console.log(` Relay policy: ${chalk.white(opts.relayPolicy)}`);
198
+ } catch (err) {
199
+ console.log(chalk.red(`Bridge failed: ${err.message}`));
200
+ process.exitCode = 1;
201
+ }
202
+ });
203
+
204
+ bridge
205
+ .command('remove <url>')
206
+ .description('disconnect from a federated network')
207
+ .action(async (url) => {
208
+ try {
209
+ const extConfig = (await import('../utils/config.js')).default;
210
+ const store = extConfig.get('ext_store.federation-bridge') || {};
211
+ const networks = store.bridgedNetworks || [];
212
+ const idx = networks.indexOf(url);
213
+ if (idx === -1) {
214
+ console.log(chalk.yellow(`Not bridged to: ${url}`));
215
+ process.exitCode = 1;
216
+ return;
217
+ }
218
+ networks.splice(idx, 1);
219
+ store.bridgedNetworks = networks;
220
+ extConfig.set('ext_store.federation-bridge', store);
221
+ console.log(chalk.green(`✔ Bridge disconnected from: ${url}`));
222
+ } catch (err) {
223
+ console.log(chalk.red(`Failed: ${err.message}`));
224
+ process.exitCode = 1;
225
+ }
226
+ });
227
+
228
+ bridge
229
+ .command('list')
230
+ .description('list all bridge connections')
231
+ .action(async () => {
232
+ try {
233
+ const extConfig = (await import('../utils/config.js')).default;
234
+ const store = extConfig.get('ext_store.federation-bridge') || {};
235
+ const networks = store.bridgedNetworks || [];
236
+ if (networks.length === 0) {
237
+ console.log(chalk.dim('No bridge connections.'));
238
+ return;
239
+ }
240
+ console.log(chalk.bold(`Federation Bridges (${networks.length})\n`));
241
+ for (const url of networks) {
242
+ console.log(` ${chalk.green('●')} ${chalk.cyan(url)}`);
243
+ }
244
+ console.log(`\n Relay policy: ${chalk.white(store.relayPolicy || 'none')}`);
245
+ } catch (err) {
246
+ console.log(chalk.red(`Failed: ${err.message}`));
247
+ process.exitCode = 1;
248
+ }
249
+ });
250
+
251
+ bridge
252
+ .command('stats')
253
+ .description('show bridge relay statistics')
254
+ .action(async () => {
255
+ try {
256
+ const extConfig = (await import('../utils/config.js')).default;
257
+ const store = extConfig.get('ext_store.federation-bridge') || {};
258
+ const networks = store.bridgedNetworks || [];
259
+ const messageLog = store.messageLog || [];
260
+ console.log(chalk.bold('Bridge Statistics\n'));
261
+ console.log(` Connected networks: ${chalk.white(networks.length)}`);
262
+ console.log(` Messages relayed: ${chalk.white(messageLog.length)}`);
263
+ console.log(` Relay policy: ${chalk.white(store.relayPolicy || 'none')}`);
264
+ console.log(` Last activity: ${chalk.white(store.lastActivity || 'never')}`);
265
+ } catch (err) {
266
+ console.log(chalk.red(`Failed: ${err.message}`));
267
+ process.exitCode = 1;
268
+ }
269
+ });
270
+ }