pal-explorer-cli 0.4.7 → 0.4.9

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/bin/pal.js CHANGED
@@ -1,231 +1,231 @@
1
- #!/usr/bin/env node
2
-
3
- // Enable V8 code caching for faster subsequent boots
4
- import 'v8-compile-cache-lib';
5
-
6
- import { program } from 'commander';
7
- import chalk from 'chalk';
8
-
9
- // Utility for lazy loading commands
10
- const lazy = (path) => async (program) => {
11
- const mod = await import(path);
12
- if (mod.default) mod.default(program);
13
- else if (typeof mod === 'function') mod(program);
14
- };
15
-
16
- // --- Eagerly load ONLY what is needed for initial setup/parsing ---
17
- import logger from '../lib/utils/logger.js';
18
- import { installGlobalErrorHandler } from '../lib/core/analytics.js';
19
- installGlobalErrorHandler();
20
-
21
- globalThis.__palMode = 'cli';
22
-
23
- // --- Lazy load the big migration/core logic only if needed ---
24
- const runMigrations = async () => {
25
- const { migrateShareKeys } = await import('../lib/core/shares.js');
26
- const { migrateToMultiUser } = await import('../lib/core/users.js');
27
- await migrateShareKeys();
28
- migrateToMultiUser();
29
- };
30
-
31
- // Apply persisted log level from config before anything else
32
- import config from '../lib/utils/config.js';
33
- const settings = config.get('settings') || {};
34
- const configuredLevel = settings.logLevel;
35
- if (configuredLevel) logger.setLevel(configuredLevel);
36
-
37
- // Parse --log-level early (before Commander) so it works before subcommands
38
- const logLevelIdx = process.argv.indexOf('--log-level');
39
- if (logLevelIdx !== -1 && process.argv[logLevelIdx + 1]) {
40
- const cliLevel = process.argv[logLevelIdx + 1];
41
- if (logger.LEVELS.includes(cliLevel)) {
42
- logger.setLevel(cliLevel);
43
- process.argv.splice(logLevelIdx, 2);
44
- }
45
- }
46
-
47
- logger.applyGlobalOverride();
48
-
49
- program
50
- .name('pe')
51
- .description('p2p file sharing for friends')
52
- .version('0.4.0')
53
- .option('--json', 'output in JSON format')
54
- .option('--log-level <level>', 'set log level (debug, info, warn, error, silent)');
55
-
56
- // Register Commands LAZILY
57
- // Note: Commander requires commands to be defined upfront for help/autocompletion,
58
- // but the actual .js files containing the complex registration logic (with their own imports)
59
- // are what we are lazy-loading here.
60
- const commands = [
61
- ['init', '../lib/commands/init.js'],
62
- ['whoami', '../lib/commands/whoami.js'],
63
- ['register', '../lib/commands/register.js'],
64
- ['recover', '../lib/commands/recover.js'],
65
- ['share', '../lib/commands/share.js'],
66
- ['download', '../lib/commands/download.js'],
67
- ['list', '../lib/commands/list.js'],
68
- ['revoke', '../lib/commands/revoke.js'],
69
- ['serve', '../lib/commands/serve.js'],
70
- ['pal', '../lib/commands/pal.js'],
71
- ['invite', '../lib/commands/invite.js'],
72
- ['nearby', '../lib/commands/nearby.js'],
73
- ['group', '../lib/commands/group.js'],
74
- ['sync', '../lib/commands/sync.js'],
75
- ['transfers', '../lib/commands/transfers.js'],
76
- ['config', '../lib/commands/config.js'],
77
- ['device', '../lib/commands/device.js'],
78
- ['server', '../lib/commands/server.js'],
79
- ['explorer', '../lib/commands/explorer.js'],
80
- ['status', '../lib/commands/status.js'],
81
- ['log', '../lib/commands/log.js'],
82
- ['completion', '../lib/commands/completion.js'],
83
- ['web', '../lib/commands/web.js'],
84
- ['web-login', '../lib/commands/web-login.js'],
85
- ['gui-share', '../lib/commands/gui-share.js'],
86
- ['vfs', '../lib/commands/vfs.js'],
87
- ['file', '../lib/commands/file.js'],
88
- ['remote', '../lib/commands/remote.js'],
89
- ['user', '../lib/commands/user.js'],
90
- ['schedule', '../lib/commands/schedule.js'],
91
- ['chat', '../lib/commands/chat.js'],
92
- ['backup', '../lib/commands/backup.js'],
93
- ['api-keys', '../lib/commands/api-keys.js'],
94
- ['share-link', '../lib/commands/share-link.js'],
95
- ['comment', '../lib/commands/comment.js'],
96
- ['update', '../lib/commands/update.js'],
97
- ['auth', '../lib/commands/auth.js'],
98
- ['network', '../lib/commands/network.js'],
99
- ['search', '../lib/commands/search.js'],
100
- ['connect', '../lib/commands/connect.js'],
101
- ['protocol', '../lib/commands/protocol.js'],
102
- ['workspace', '../lib/commands/workspace.js'],
103
- ['favorite', '../lib/commands/favorite.js'],
104
- ['pin', '../lib/commands/pin.js'],
105
- ['stream', '../lib/commands/stream.js'],
106
- ['uninstall', '../lib/commands/uninstall.js'],
107
- ['extension', '../lib/commands/extension.js'],
108
- ['ext', '../lib/commands/extension.js'],
109
- ['billing', '../lib/commands/billing.js'],
110
- ['permissions', '../lib/commands/permissions.js'],
111
- ['org', '../lib/commands/org.js'],
112
- ['su', '../lib/commands/su.js'],
113
- ['federation', '../lib/commands/federation.js'],
114
- ['analytics', '../lib/commands/analytics.js'],
115
- ['webhook', '../lib/commands/webhook.js'],
116
- ['relay', '../lib/commands/relay.js'],
117
- ['compliance', '../lib/commands/compliance.js'],
118
- ['sso', '../lib/commands/sso.js'],
119
- ['rbac', '../lib/commands/rbac.js'],
120
- ['scim', '../lib/commands/scim.js'],
121
- ['audit', '../lib/commands/audit.js'],
122
- ['scanner', '../lib/commands/scanner.js'],
123
- ['notify', '../lib/commands/notify.js'],
124
- ['cloud-backup', '../lib/commands/cloud-backup.js'],
125
- ['dept', '../lib/commands/dept.js'],
126
- ];
127
-
128
- // To keep it truly fast, we should only register the specific command being run.
129
- // Commander's parseAsync will handle the matching.
130
- const currentCmd = process.argv[2];
131
- const cmdMatch = commands.find(c => c[0] === currentCmd);
132
-
133
- // Commands that don't modify data — skip migrations for speed
134
- const readOnlyCommands = new Set([
135
- 'whoami', 'status', 'list', 'log', 'completion', 'nearby',
136
- 'protocol', 'pin', 'favorite', 'search', 'workspace',
137
- ]);
138
- const isVersionOrHelp = !currentCmd || currentCmd === '--help' || currentCmd === '-h'
139
- || currentCmd === '--version' || currentCmd === '-V';
140
- const needsMigrations = cmdMatch && !readOnlyCommands.has(cmdMatch[0]) && !isVersionOrHelp;
141
-
142
- if (cmdMatch) {
143
- if (needsMigrations) await runMigrations();
144
- const register = await lazy(cmdMatch[1]);
145
- await register(program);
146
- } else if (isVersionOrHelp) {
147
- // Help/version — load all commands for full help text, skip migrations
148
- await Promise.all(commands.map(c => lazy(c[1])(program)));
149
- } else {
150
- // Unknown command — load all for Commander to handle
151
- await runMigrations();
152
- await Promise.all(commands.map(c => lazy(c[1])(program)));
153
- }
154
-
155
- // Grouped help text
156
- program.addHelpText('after', `
157
- ${chalk.cyan.bold('Command Groups:')}
158
-
159
- ${chalk.yellow('Identity:')} init, whoami, register, verify, recover
160
- ${chalk.yellow('Users:')} user list/add/remove/promote, login
161
- ${chalk.yellow('Sharing:')} share, list, revoke, serve, download, share-link, share-rename, permissions
162
- ${chalk.yellow('Sync:')} sync push/pull/status/watch/list/remove/diff
163
- ${chalk.yellow('Files:')} file ls/tree/info/copy/move/rename/mkdir/delete/search/open/reveal/audit
164
- ${chalk.yellow('Remote:')} remote browse/files/download
165
- ${chalk.yellow('Social:')} pal, invite, nearby, group, chat, comment
166
- ${chalk.yellow('Search:')} search (files, pals, groups)
167
- ${chalk.yellow('Network:')} network create/list/info/invite/join/members/groups/connect/disconnect
168
- ${chalk.yellow('Workspace:')} workspace list/create/delete/add/remove
169
- ${chalk.yellow('Management:')} transfers, config, device, server, explorer, vfs, web, schedule, favorite
170
- ${chalk.yellow('Protocol:')} protocol info/policy/route/envelope/keys
171
- ${chalk.yellow('PIN:')} pin set/remove/status
172
- ${chalk.yellow('Billing:')} billing status/plans/activate/deactivate/checkout
173
- ${chalk.yellow('Pro:')} backup, api-keys
174
- ${chalk.yellow('Streaming:')} stream local/remote/stop/status/broadcast/join/transport
175
- ${chalk.yellow('Extensions:')} ext list/install/remove/enable/disable/info/config/create
176
- ${chalk.yellow('Orgs:')} org create/list/info/invite/remove/subscribe/unsubscribe/billing
177
- ${chalk.yellow('Utilities:')} status, log, completion, gui-share, uninstall
178
-
179
- ${chalk.gray('Aliases: pe connect → pe network connect, pe disconnect → pe network disconnect')}
180
- `);
181
-
182
- // Load extensions and emit lifecycle hooks
183
- let extensionsLoaded = false;
184
- if (!isVersionOrHelp) {
185
- try {
186
- const { loadAllExtensions, hooks, ensureDefaultExtensions } = await import('../lib/core/extensions.js');
187
- ensureDefaultExtensions();
188
- await loadAllExtensions();
189
- extensionsLoaded = true;
190
-
191
- // Clean stale transfers (0% for >24h)
192
- const { cleanStaleTransfers } = await import('../lib/core/transfers.js');
193
- const cleaned = cleanStaleTransfers(24);
194
- if (cleaned > 0) console.log(`[transfers] Cleaned ${cleaned} stale transfer(s)`);
195
-
196
- const identity = (await import('../lib/utils/config.js')).default.get('identity');
197
- await hooks.emit('on:app:ready', {
198
- version: program.version(),
199
- identity: identity ? { handle: identity.handle, publicKey: identity.publicKey } : null,
200
- mode: 'cli',
201
- });
202
-
203
- const shutdown = async () => {
204
- await hooks.emit('on:app:shutdown', {});
205
- process.exit(process.exitCode || 0);
206
- };
207
- process.on('SIGINT', shutdown);
208
- process.on('SIGTERM', shutdown);
209
- } catch (err) {
210
- // Extension loading is non-fatal
211
- logger.warn?.(`Extension loading failed: ${err.message}`) || console.warn(`[extensions] ${err.message}`);
212
- }
213
- }
214
-
215
- if (!process.argv.slice(2).length) {
216
- program.outputHelp();
217
- } else {
218
- try {
219
- await program.parseAsync(process.argv);
220
- } catch (err) {
221
- if (err.code !== 'commander.helpDisplayed' && err.code !== 'commander.unknownCommand') {
222
- console.error(chalk.red(`Error: ${err.message}`));
223
- }
224
- }
225
- }
226
-
227
- // keytar's native D-Bus handles block the event loop on headless Linux
228
- const longRunning = new Set(['serve', 'nearby', 'stream']);
229
- if (!longRunning.has(currentCmd)) {
230
- process.exit(process.exitCode || 0);
231
- }
1
+ #!/usr/bin/env node
2
+
3
+ // Enable V8 code caching for faster subsequent boots
4
+ import 'v8-compile-cache-lib';
5
+
6
+ import { program } from 'commander';
7
+ import chalk from 'chalk';
8
+
9
+ // Utility for lazy loading commands
10
+ const lazy = (path) => async (program) => {
11
+ const mod = await import(path);
12
+ if (mod.default) mod.default(program);
13
+ else if (typeof mod === 'function') mod(program);
14
+ };
15
+
16
+ // --- Eagerly load ONLY what is needed for initial setup/parsing ---
17
+ import logger from '../lib/utils/logger.js';
18
+ import { installGlobalErrorHandler } from '../lib/core/analytics.js';
19
+ installGlobalErrorHandler();
20
+
21
+ globalThis.__palMode = 'cli';
22
+
23
+ // --- Lazy load the big migration/core logic only if needed ---
24
+ const runMigrations = async () => {
25
+ const { migrateShareKeys } = await import('../lib/core/shares.js');
26
+ const { migrateToMultiUser } = await import('../lib/core/users.js');
27
+ await migrateShareKeys();
28
+ migrateToMultiUser();
29
+ };
30
+
31
+ // Apply persisted log level from config before anything else
32
+ import config from '../lib/utils/config.js';
33
+ const settings = config.get('settings') || {};
34
+ const configuredLevel = settings.logLevel;
35
+ if (configuredLevel) logger.setLevel(configuredLevel);
36
+
37
+ // Parse --log-level early (before Commander) so it works before subcommands
38
+ const logLevelIdx = process.argv.indexOf('--log-level');
39
+ if (logLevelIdx !== -1 && process.argv[logLevelIdx + 1]) {
40
+ const cliLevel = process.argv[logLevelIdx + 1];
41
+ if (logger.LEVELS.includes(cliLevel)) {
42
+ logger.setLevel(cliLevel);
43
+ process.argv.splice(logLevelIdx, 2);
44
+ }
45
+ }
46
+
47
+ logger.applyGlobalOverride();
48
+
49
+ program
50
+ .name('pe')
51
+ .description('p2p file sharing for friends')
52
+ .version('0.4.0')
53
+ .option('--json', 'output in JSON format')
54
+ .option('--log-level <level>', 'set log level (debug, info, warn, error, silent)');
55
+
56
+ // Register Commands LAZILY
57
+ // Note: Commander requires commands to be defined upfront for help/autocompletion,
58
+ // but the actual .js files containing the complex registration logic (with their own imports)
59
+ // are what we are lazy-loading here.
60
+ const commands = [
61
+ ['init', '../lib/commands/init.js'],
62
+ ['whoami', '../lib/commands/whoami.js'],
63
+ ['register', '../lib/commands/register.js'],
64
+ ['recover', '../lib/commands/recover.js'],
65
+ ['share', '../lib/commands/share.js'],
66
+ ['download', '../lib/commands/download.js'],
67
+ ['list', '../lib/commands/list.js'],
68
+ ['revoke', '../lib/commands/revoke.js'],
69
+ ['serve', '../lib/commands/serve.js'],
70
+ ['pal', '../lib/commands/pal.js'],
71
+ ['invite', '../lib/commands/invite.js'],
72
+ ['nearby', '../lib/commands/nearby.js'],
73
+ ['group', '../lib/commands/group.js'],
74
+ ['sync', '../lib/commands/sync.js'],
75
+ ['transfers', '../lib/commands/transfers.js'],
76
+ ['config', '../lib/commands/config.js'],
77
+ ['device', '../lib/commands/device.js'],
78
+ ['server', '../lib/commands/server.js'],
79
+ ['explorer', '../lib/commands/explorer.js'],
80
+ ['status', '../lib/commands/status.js'],
81
+ ['log', '../lib/commands/log.js'],
82
+ ['completion', '../lib/commands/completion.js'],
83
+ ['web', '../lib/commands/web.js'],
84
+ ['web-login', '../lib/commands/web-login.js'],
85
+ ['gui-share', '../lib/commands/gui-share.js'],
86
+ ['vfs', '../lib/commands/vfs.js'],
87
+ ['file', '../lib/commands/file.js'],
88
+ ['remote', '../lib/commands/remote.js'],
89
+ ['user', '../lib/commands/user.js'],
90
+ ['schedule', '../lib/commands/schedule.js'],
91
+ ['chat', '../lib/commands/chat.js'],
92
+ ['backup', '../lib/commands/backup.js'],
93
+ ['api-keys', '../lib/commands/api-keys.js'],
94
+ ['share-link', '../lib/commands/share-link.js'],
95
+ ['comment', '../lib/commands/comment.js'],
96
+ ['update', '../lib/commands/update.js'],
97
+ ['auth', '../lib/commands/auth.js'],
98
+ ['network', '../lib/commands/network.js'],
99
+ ['search', '../lib/commands/search.js'],
100
+ ['connect', '../lib/commands/connect.js'],
101
+ ['protocol', '../lib/commands/protocol.js'],
102
+ ['workspace', '../lib/commands/workspace.js'],
103
+ ['favorite', '../lib/commands/favorite.js'],
104
+ ['pin', '../lib/commands/pin.js'],
105
+ ['stream', '../lib/commands/stream.js'],
106
+ ['uninstall', '../lib/commands/uninstall.js'],
107
+ ['extension', '../lib/commands/extension.js'],
108
+ ['ext', '../lib/commands/extension.js'],
109
+ ['billing', '../lib/commands/billing.js'],
110
+ ['permissions', '../lib/commands/permissions.js'],
111
+ ['org', '../lib/commands/org.js'],
112
+ ['su', '../lib/commands/su.js'],
113
+ ['federation', '../lib/commands/federation.js'],
114
+ ['analytics', '../lib/commands/analytics.js'],
115
+ ['webhook', '../lib/commands/webhook.js'],
116
+ ['relay', '../lib/commands/relay.js'],
117
+ ['compliance', '../lib/commands/compliance.js'],
118
+ ['sso', '../lib/commands/sso.js'],
119
+ ['rbac', '../lib/commands/rbac.js'],
120
+ ['scim', '../lib/commands/scim.js'],
121
+ ['audit', '../lib/commands/audit.js'],
122
+ ['scanner', '../lib/commands/scanner.js'],
123
+ ['notify', '../lib/commands/notify.js'],
124
+ ['cloud-backup', '../lib/commands/cloud-backup.js'],
125
+ ['dept', '../lib/commands/dept.js'],
126
+ ];
127
+
128
+ // To keep it truly fast, we should only register the specific command being run.
129
+ // Commander's parseAsync will handle the matching.
130
+ const currentCmd = process.argv[2];
131
+ const cmdMatch = commands.find(c => c[0] === currentCmd);
132
+
133
+ // Commands that don't modify data — skip migrations for speed
134
+ const readOnlyCommands = new Set([
135
+ 'whoami', 'status', 'list', 'log', 'completion', 'nearby',
136
+ 'protocol', 'pin', 'favorite', 'search', 'workspace',
137
+ ]);
138
+ const isVersionOrHelp = !currentCmd || currentCmd === '--help' || currentCmd === '-h'
139
+ || currentCmd === '--version' || currentCmd === '-V';
140
+ const needsMigrations = cmdMatch && !readOnlyCommands.has(cmdMatch[0]) && !isVersionOrHelp;
141
+
142
+ if (cmdMatch) {
143
+ if (needsMigrations) await runMigrations();
144
+ const register = await lazy(cmdMatch[1]);
145
+ await register(program);
146
+ } else if (isVersionOrHelp) {
147
+ // Help/version — load all commands for full help text, skip migrations
148
+ await Promise.all(commands.map(c => lazy(c[1])(program)));
149
+ } else {
150
+ // Unknown command — load all for Commander to handle
151
+ await runMigrations();
152
+ await Promise.all(commands.map(c => lazy(c[1])(program)));
153
+ }
154
+
155
+ // Grouped help text
156
+ program.addHelpText('after', `
157
+ ${chalk.cyan.bold('Command Groups:')}
158
+
159
+ ${chalk.yellow('Identity:')} init, whoami, register, verify, recover
160
+ ${chalk.yellow('Users:')} user list/add/remove/promote, login
161
+ ${chalk.yellow('Sharing:')} share, list, revoke, serve, download, share-link, share-rename, permissions
162
+ ${chalk.yellow('Sync:')} sync push/pull/status/watch/list/remove/diff
163
+ ${chalk.yellow('Files:')} file ls/tree/info/copy/move/rename/mkdir/delete/search/open/reveal/audit
164
+ ${chalk.yellow('Remote:')} remote browse/files/download
165
+ ${chalk.yellow('Social:')} pal, invite, nearby, group, chat, comment
166
+ ${chalk.yellow('Search:')} search (files, pals, groups)
167
+ ${chalk.yellow('Network:')} network create/list/info/invite/join/members/groups/connect/disconnect
168
+ ${chalk.yellow('Workspace:')} workspace list/create/delete/add/remove
169
+ ${chalk.yellow('Management:')} transfers, config, device, server, explorer, vfs, web, schedule, favorite
170
+ ${chalk.yellow('Protocol:')} protocol info/policy/route/envelope/keys
171
+ ${chalk.yellow('PIN:')} pin set/remove/status
172
+ ${chalk.yellow('Billing:')} billing status/plans/activate/deactivate/checkout
173
+ ${chalk.yellow('Pro:')} backup, api-keys
174
+ ${chalk.yellow('Streaming:')} stream local/remote/stop/status/broadcast/join/transport
175
+ ${chalk.yellow('Extensions:')} ext list/install/remove/enable/disable/info/config/create
176
+ ${chalk.yellow('Orgs:')} org create/list/info/invite/remove/subscribe/unsubscribe/billing
177
+ ${chalk.yellow('Utilities:')} status, log, completion, gui-share, uninstall
178
+
179
+ ${chalk.gray('Aliases: pe connect → pe network connect, pe disconnect → pe network disconnect')}
180
+ `);
181
+
182
+ // Load extensions and emit lifecycle hooks
183
+ let extensionsLoaded = false;
184
+ if (!isVersionOrHelp) {
185
+ try {
186
+ const { loadAllExtensions, hooks, ensureDefaultExtensions } = await import('../lib/core/extensions.js');
187
+ ensureDefaultExtensions();
188
+ await loadAllExtensions();
189
+ extensionsLoaded = true;
190
+
191
+ // Clean stale transfers (0% for >24h)
192
+ const { cleanStaleTransfers } = await import('../lib/core/transfers.js');
193
+ const cleaned = cleanStaleTransfers(24);
194
+ if (cleaned > 0) console.log(`[transfers] Cleaned ${cleaned} stale transfer(s)`);
195
+
196
+ const identity = (await import('../lib/utils/config.js')).default.get('identity');
197
+ await hooks.emit('on:app:ready', {
198
+ version: program.version(),
199
+ identity: identity ? { handle: identity.handle, publicKey: identity.publicKey } : null,
200
+ mode: 'cli',
201
+ });
202
+
203
+ const shutdown = async () => {
204
+ await hooks.emit('on:app:shutdown', {});
205
+ process.exit(process.exitCode || 0);
206
+ };
207
+ process.on('SIGINT', shutdown);
208
+ process.on('SIGTERM', shutdown);
209
+ } catch (err) {
210
+ // Extension loading is non-fatal
211
+ logger.warn?.(`Extension loading failed: ${err.message}`) || console.warn(`[extensions] ${err.message}`);
212
+ }
213
+ }
214
+
215
+ if (!process.argv.slice(2).length) {
216
+ program.outputHelp();
217
+ } else {
218
+ try {
219
+ await program.parseAsync(process.argv);
220
+ } catch (err) {
221
+ if (err.code !== 'commander.helpDisplayed' && err.code !== 'commander.unknownCommand') {
222
+ console.error(chalk.red(`Error: ${err.message}`));
223
+ }
224
+ }
225
+ }
226
+
227
+ // keytar's native D-Bus handles block the event loop on headless Linux
228
+ const longRunning = new Set(['serve', 'nearby', 'stream']);
229
+ if (!longRunning.has(currentCmd)) {
230
+ process.exit(process.exitCode || 0);
231
+ }
@@ -17,7 +17,7 @@
17
17
  ],
18
18
  "permissions": ["config:read", "config:write", "net:http"],
19
19
  "config": {
20
- "enabled": { "type": "boolean", "default": true, "description": "Enable anonymous usage analytics" },
20
+ "enabled": { "type": "boolean", "default": true, "description": "Enable anonymous usage analytics (PostHog)" },
21
21
  "posthogKey": { "type": "string", "default": "phc_PSslPgpRuFzQf6s5qbN9atXFGUDHzCvMlmpBTtdTkte", "description": "PostHog project API key (leave empty for default)" },
22
22
  "posthogHost": { "type": "string", "default": "https://us.i.posthog.com", "description": "PostHog ingestion host" },
23
23
  "sessionTracking": { "type": "boolean", "default": true, "description": "Track session duration" }
@@ -8,6 +8,7 @@
8
8
  "hooks": ["on:app:ready"],
9
9
  "permissions": ["config:read", "config:write", "identity:read"],
10
10
  "config": {
11
+ "enabled": { "type": "boolean", "default": true, "description": "Enable discovery server integration" },
11
12
  "bootstrapServers": { "type": "array", "default": ["https://discovery-1.palexplorer.com", "https://discovery-2.palexplorer.com"], "description": "Bootstrap discovery server URLs" },
12
13
  "refreshInterval": { "type": "number", "default": 1800000, "description": "Server list refresh interval in ms (default: 30 min)" },
13
14
  "enableGossip": { "type": "boolean", "default": true, "description": "Exchange server lists with connected peers" }
@@ -8,6 +8,6 @@
8
8
  "hooks": [],
9
9
  "permissions": ["fs:write"],
10
10
  "config": {},
11
- "tier": "free",
11
+ "tier": "pro",
12
12
  "minAppVersion": "0.5.0"
13
13
  }
@@ -13,5 +13,5 @@
13
13
  ]
14
14
  },
15
15
  "config": { "enabled": { "type": "boolean", "default": false } },
16
- "tier": "free"
16
+ "tier": "enterprise"
17
17
  }
@@ -12,6 +12,6 @@
12
12
  "driveLetter": { "type": "string", "default": "P", "description": "Windows drive letter" },
13
13
  "autoMount": { "type": "boolean", "default": true, "description": "Auto-mount on startup" }
14
14
  },
15
- "tier": "free",
15
+ "tier": "pro",
16
16
  "minAppVersion": "0.5.0"
17
17
  }