pal-explorer-cli 0.4.0

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 (156) hide show
  1. package/LICENSE.md +18 -0
  2. package/README.md +314 -0
  3. package/bin/pal.js +230 -0
  4. package/extensions/@palexplorer/analytics/README.md +45 -0
  5. package/extensions/@palexplorer/analytics/docs/MONETIZATION.md +14 -0
  6. package/extensions/@palexplorer/analytics/docs/PLAN.md +23 -0
  7. package/extensions/@palexplorer/analytics/docs/PRIVACY.md +38 -0
  8. package/extensions/@palexplorer/analytics/extension.json +27 -0
  9. package/extensions/@palexplorer/analytics/index.js +186 -0
  10. package/extensions/@palexplorer/analytics/test/analytics.test.js +82 -0
  11. package/extensions/@palexplorer/audit/extension.json +17 -0
  12. package/extensions/@palexplorer/audit/index.js +2 -0
  13. package/extensions/@palexplorer/auth-email/extension.json +17 -0
  14. package/extensions/@palexplorer/auth-email/index.js +102 -0
  15. package/extensions/@palexplorer/auth-oauth/extension.json +16 -0
  16. package/extensions/@palexplorer/auth-oauth/index.js +199 -0
  17. package/extensions/@palexplorer/chat/extension.json +17 -0
  18. package/extensions/@palexplorer/chat/index.js +2 -0
  19. package/extensions/@palexplorer/discovery/extension.json +16 -0
  20. package/extensions/@palexplorer/discovery/index.js +111 -0
  21. package/extensions/@palexplorer/email-notifications/extension.json +23 -0
  22. package/extensions/@palexplorer/email-notifications/index.js +242 -0
  23. package/extensions/@palexplorer/explorer-integration/extension.json +13 -0
  24. package/extensions/@palexplorer/explorer-integration/index.js +122 -0
  25. package/extensions/@palexplorer/groups/extension.json +17 -0
  26. package/extensions/@palexplorer/groups/index.js +2 -0
  27. package/extensions/@palexplorer/networks/extension.json +17 -0
  28. package/extensions/@palexplorer/networks/index.js +2 -0
  29. package/extensions/@palexplorer/share-links/extension.json +17 -0
  30. package/extensions/@palexplorer/share-links/index.js +2 -0
  31. package/extensions/@palexplorer/sync/extension.json +17 -0
  32. package/extensions/@palexplorer/sync/index.js +2 -0
  33. package/extensions/@palexplorer/user-mgmt/extension.json +17 -0
  34. package/extensions/@palexplorer/user-mgmt/index.js +2 -0
  35. package/extensions/@palexplorer/vfs/extension.json +17 -0
  36. package/extensions/@palexplorer/vfs/index.js +167 -0
  37. package/lib/capabilities.js +263 -0
  38. package/lib/commands/analytics.js +175 -0
  39. package/lib/commands/api-keys.js +131 -0
  40. package/lib/commands/audit.js +235 -0
  41. package/lib/commands/auth.js +137 -0
  42. package/lib/commands/backup.js +76 -0
  43. package/lib/commands/billing.js +148 -0
  44. package/lib/commands/chat.js +217 -0
  45. package/lib/commands/cloud-backup.js +231 -0
  46. package/lib/commands/comment.js +99 -0
  47. package/lib/commands/completion.js +203 -0
  48. package/lib/commands/compliance.js +218 -0
  49. package/lib/commands/config.js +136 -0
  50. package/lib/commands/connect.js +44 -0
  51. package/lib/commands/dept.js +294 -0
  52. package/lib/commands/device.js +146 -0
  53. package/lib/commands/download.js +226 -0
  54. package/lib/commands/explorer.js +178 -0
  55. package/lib/commands/extension.js +970 -0
  56. package/lib/commands/favorite.js +90 -0
  57. package/lib/commands/federation.js +270 -0
  58. package/lib/commands/file.js +533 -0
  59. package/lib/commands/group.js +271 -0
  60. package/lib/commands/gui-share.js +29 -0
  61. package/lib/commands/init.js +61 -0
  62. package/lib/commands/invite.js +59 -0
  63. package/lib/commands/list.js +59 -0
  64. package/lib/commands/log.js +116 -0
  65. package/lib/commands/nearby.js +108 -0
  66. package/lib/commands/network.js +251 -0
  67. package/lib/commands/notify.js +198 -0
  68. package/lib/commands/org.js +273 -0
  69. package/lib/commands/pal.js +180 -0
  70. package/lib/commands/permissions.js +216 -0
  71. package/lib/commands/pin.js +97 -0
  72. package/lib/commands/protocol.js +357 -0
  73. package/lib/commands/rbac.js +147 -0
  74. package/lib/commands/recover.js +36 -0
  75. package/lib/commands/register.js +171 -0
  76. package/lib/commands/relay.js +131 -0
  77. package/lib/commands/remote.js +368 -0
  78. package/lib/commands/revoke.js +50 -0
  79. package/lib/commands/scanner.js +280 -0
  80. package/lib/commands/schedule.js +344 -0
  81. package/lib/commands/scim.js +203 -0
  82. package/lib/commands/search.js +181 -0
  83. package/lib/commands/serve.js +438 -0
  84. package/lib/commands/server.js +350 -0
  85. package/lib/commands/share-link.js +199 -0
  86. package/lib/commands/share.js +323 -0
  87. package/lib/commands/sso.js +200 -0
  88. package/lib/commands/status.js +136 -0
  89. package/lib/commands/stream.js +562 -0
  90. package/lib/commands/su.js +187 -0
  91. package/lib/commands/sync.js +827 -0
  92. package/lib/commands/transfers.js +152 -0
  93. package/lib/commands/uninstall.js +188 -0
  94. package/lib/commands/update.js +204 -0
  95. package/lib/commands/user.js +276 -0
  96. package/lib/commands/vfs.js +84 -0
  97. package/lib/commands/web.js +52 -0
  98. package/lib/commands/webhook.js +180 -0
  99. package/lib/commands/whoami.js +59 -0
  100. package/lib/commands/workspace.js +121 -0
  101. package/lib/core/accessLog.js +54 -0
  102. package/lib/core/analytics.js +99 -0
  103. package/lib/core/backup.js +84 -0
  104. package/lib/core/billing.js +336 -0
  105. package/lib/core/bitfieldStore.js +53 -0
  106. package/lib/core/connectionManager.js +182 -0
  107. package/lib/core/dhtDiscovery.js +148 -0
  108. package/lib/core/discoveryClient.js +408 -0
  109. package/lib/core/extensionAnalyzer.js +357 -0
  110. package/lib/core/extensionSandbox.js +250 -0
  111. package/lib/core/extensionWorkerHost.js +166 -0
  112. package/lib/core/extensions.js +1082 -0
  113. package/lib/core/fileDiff.js +69 -0
  114. package/lib/core/groups.js +119 -0
  115. package/lib/core/identity.js +340 -0
  116. package/lib/core/mdnsService.js +126 -0
  117. package/lib/core/networks.js +81 -0
  118. package/lib/core/permissions.js +109 -0
  119. package/lib/core/pro.js +27 -0
  120. package/lib/core/resolver.js +74 -0
  121. package/lib/core/serverList.js +224 -0
  122. package/lib/core/sharePolicy.js +69 -0
  123. package/lib/core/shares.js +325 -0
  124. package/lib/core/signalingServer.js +441 -0
  125. package/lib/core/streamTransport.js +106 -0
  126. package/lib/core/su.js +55 -0
  127. package/lib/core/syncEngine.js +264 -0
  128. package/lib/core/syncState.js +159 -0
  129. package/lib/core/transfers.js +259 -0
  130. package/lib/core/users.js +225 -0
  131. package/lib/core/vfs.js +216 -0
  132. package/lib/core/webServer.js +702 -0
  133. package/lib/core/webrtcStream.js +396 -0
  134. package/lib/crypto/chatEncryption.js +57 -0
  135. package/lib/crypto/shareEncryption.js +195 -0
  136. package/lib/crypto/sharePassword.js +35 -0
  137. package/lib/crypto/streamEncryption.js +189 -0
  138. package/lib/package.json +1 -0
  139. package/lib/protocol/envelope.js +271 -0
  140. package/lib/protocol/handler.js +191 -0
  141. package/lib/protocol/index.js +27 -0
  142. package/lib/protocol/messages.js +247 -0
  143. package/lib/protocol/negotiation.js +127 -0
  144. package/lib/protocol/policy.js +142 -0
  145. package/lib/protocol/router.js +86 -0
  146. package/lib/protocol/sync.js +122 -0
  147. package/lib/utils/cli.js +15 -0
  148. package/lib/utils/config.js +123 -0
  149. package/lib/utils/configIntegrity.js +87 -0
  150. package/lib/utils/downloadDir.js +9 -0
  151. package/lib/utils/explorer.js +83 -0
  152. package/lib/utils/format.js +12 -0
  153. package/lib/utils/help.js +357 -0
  154. package/lib/utils/logger.js +103 -0
  155. package/lib/utils/torrent.js +203 -0
  156. package/package.json +71 -0
@@ -0,0 +1,203 @@
1
+ import chalk from 'chalk';
2
+
3
+ const BASH_COMPLETION = `
4
+ # pe bash completion
5
+ _pe_completions() {
6
+ local cur="\${COMP_WORDS[COMP_CWORD]}"
7
+ local prev="\${COMP_WORDS[COMP_CWORD-1]}"
8
+
9
+ local commands="init whoami register recover share download list revoke serve pal invite nearby group sync transfers config device server explorer status log completion web gui-share vfs file remote user schedule chat backup api-keys share-link comment update auth network search connect protocol workspace favorite pin stream uninstall extension ext billing permissions org su federation analytics webhook relay compliance sso rbac scim audit scanner notify cloud-backup dept"
10
+
11
+ case "\$prev" in
12
+ pe)
13
+ COMPREPLY=( $(compgen -W "\$commands" -- "\$cur") )
14
+ ;;
15
+ pal)
16
+ COMPREPLY=( $(compgen -W "add list remove" -- "\$cur") )
17
+ ;;
18
+ group)
19
+ COMPREPLY=( $(compgen -W "create add remove delete list broadcast" -- "\$cur") )
20
+ ;;
21
+ sync)
22
+ COMPREPLY=( $(compgen -W "push pull status watch list remove diff" -- "\$cur") )
23
+ ;;
24
+ file)
25
+ COMPREPLY=( $(compgen -W "ls tree info copy move rename mkdir delete search open reveal audit" -- "\$cur") )
26
+ ;;
27
+ remote)
28
+ COMPREPLY=( $(compgen -W "browse files download" -- "\$cur") )
29
+ ;;
30
+ user)
31
+ COMPREPLY=( $(compgen -W "list add remove promote" -- "\$cur") )
32
+ ;;
33
+ chat)
34
+ COMPREPLY=( $(compgen -W "send history fetch" -- "\$cur") )
35
+ ;;
36
+ comment)
37
+ COMPREPLY=( $(compgen -W "list add delete" -- "\$cur") )
38
+ ;;
39
+ schedule)
40
+ COMPREPLY=( $(compgen -W "add cancel clear" -- "\$cur") )
41
+ ;;
42
+ backup)
43
+ COMPREPLY=( $(compgen -W "create restore" -- "\$cur") )
44
+ ;;
45
+ api-keys)
46
+ COMPREPLY=( $(compgen -W "create revoke" -- "\$cur") )
47
+ ;;
48
+ share-link)
49
+ COMPREPLY=( $(compgen -W "create list" -- "\$cur") )
50
+ ;;
51
+ vfs)
52
+ COMPREPLY=( $(compgen -W "mount unmount status" -- "\$cur") )
53
+ ;;
54
+ config)
55
+ COMPREPLY=( $(compgen -W "list get set reset" -- "\$cur") )
56
+ ;;
57
+ device)
58
+ COMPREPLY=( $(compgen -W "rename register list" -- "\$cur") )
59
+ ;;
60
+ server)
61
+ COMPREPLY=( $(compgen -W "install config register status add remove list set-primary" -- "\$cur") )
62
+ ;;
63
+ transfers)
64
+ COMPREPLY=( $(compgen -W "cancel pause resume stats" -- "\$cur") )
65
+ ;;
66
+ explorer)
67
+ COMPREPLY=( $(compgen -W "install uninstall" -- "\$cur") )
68
+ ;;
69
+ *)
70
+ COMPREPLY=()
71
+ ;;
72
+ esac
73
+ }
74
+ complete -F _pe_completions pe
75
+ `;
76
+
77
+ const ZSH_COMPLETION = `
78
+ #compdef pe
79
+
80
+ _pe() {
81
+ local -a commands subcommands
82
+
83
+ commands=(
84
+ 'init:Initialize your identity'
85
+ 'whoami:Display your current identity'
86
+ 'register:Register a handle'
87
+ 'verify:Verify OTP code'
88
+ 'recover:Recover identity from mnemonic'
89
+ 'share:Share a file or folder'
90
+ 'list:List active shares'
91
+ 'revoke:Revoke a share'
92
+ 'serve:Start the sharing daemon'
93
+ 'download:Download from magnet link'
94
+ 'pal:Manage pals'
95
+ 'invite:Generate invite link'
96
+ 'nearby:Discover nearby peers'
97
+ 'group:Manage groups'
98
+ 'sync:Sync a directory with a pal'
99
+ 'transfers:View and manage transfers'
100
+ 'config:View and manage config'
101
+ 'device:Manage devices'
102
+ 'server:Manage headless server'
103
+ 'explorer:Windows Explorer integration'
104
+ 'vfs:Manage virtual filesystem drive'
105
+ 'web:Open web dashboard'
106
+ 'status:System health dashboard'
107
+ 'log:View application logs'
108
+ 'completion:Generate shell completion script'
109
+ 'file:File operations with permissions'
110
+ 'remote:Browse and download from pals'
111
+ 'user:Manage user profiles'
112
+ 'login:Switch active user profile'
113
+ 'schedule:Manage scheduled tasks'
114
+ 'chat:Encrypted chat messages'
115
+ 'backup:Create and restore backups (Pro)'
116
+ 'api-keys:Manage API keys (Pro)'
117
+ 'share-link:Create web share links'
118
+ 'comment:Manage comments on shares'
119
+ 'update:Check for updates'
120
+ 'auth:Authentication commands'
121
+ 'network:Network management'
122
+ 'search:Search files, pals, or groups'
123
+ 'connect:Connect to a pal'
124
+ 'protocol:PAL/1.0 protocol tools'
125
+ 'workspace:Manage workspaces'
126
+ 'favorite:Manage favorites'
127
+ 'pin:Pin shares'
128
+ 'stream:Media streaming'
129
+ 'uninstall:Uninstall Palexplorer'
130
+ 'extension:Manage extensions'
131
+ 'ext:Manage extensions (alias)'
132
+ 'billing:Billing and subscription'
133
+ 'permissions:Manage permissions'
134
+ 'org:Organization management'
135
+ 'su:Switch user context'
136
+ 'federation:Federation settings'
137
+ 'analytics:Usage analytics'
138
+ 'webhook:Manage webhooks'
139
+ 'relay:TURN relay management'
140
+ 'compliance:Compliance tools'
141
+ 'sso:Single sign-on settings'
142
+ 'rbac:Role-based access control'
143
+ 'scim:SCIM provisioning'
144
+ 'audit:Audit log'
145
+ 'scanner:Security scanner'
146
+ 'notify:Notification settings'
147
+ 'cloud-backup:Cloud backup management'
148
+ 'dept:Department management'
149
+ )
150
+
151
+ if (( CURRENT == 2 )); then
152
+ _describe 'command' commands
153
+ elif (( CURRENT == 3 )); then
154
+ case "\$words[2]" in
155
+ pal) subcommands=('add:Add a pal' 'list:List pals' 'remove:Remove a pal'); _describe 'subcommand' subcommands ;;
156
+ group) subcommands=('create:Create group' 'add:Add to group' 'remove:Remove from group' 'delete:Delete group' 'list:List groups' 'broadcast:Broadcast message'); _describe 'subcommand' subcommands ;;
157
+ sync) subcommands=('push:Push local changes' 'pull:Pull remote changes' 'status:Show sync status' 'watch:Watch and auto-push' 'list:List sync pairs' 'remove:Remove sync pair' 'diff:Compare folders'); _describe 'subcommand' subcommands ;;
158
+ vfs) subcommands=('mount:Mount virtual drive' 'unmount:Unmount drive' 'status:Show status'); _describe 'subcommand' subcommands ;;
159
+ config) subcommands=('list:Show settings' 'get:Get value' 'set:Set value' 'reset:Reset all'); _describe 'subcommand' subcommands ;;
160
+ device) subcommands=('rename:Rename device' 'register:Register device' 'list:List devices'); _describe 'subcommand' subcommands ;;
161
+ server) subcommands=('install:Install service' 'config:Configure' 'register:Register handle' 'status:Check health' 'add:Add server' 'remove:Remove server' 'list:List servers' 'set-primary:Set primary server'); _describe 'subcommand' subcommands ;;
162
+ transfers) subcommands=('cancel:Cancel transfer' 'pause:Pause transfer' 'resume:Resume transfer' 'stats:Show statistics'); _describe 'subcommand' subcommands ;;
163
+ explorer) subcommands=('install:Install integration' 'uninstall:Remove integration'); _describe 'subcommand' subcommands ;;
164
+ file) subcommands=('ls:List directory' 'tree:Show tree' 'info:File properties' 'copy:Copy file' 'move:Move file' 'rename:Rename file' 'mkdir:Create directory' 'delete:Delete file' 'search:Search files' 'open:Open file' 'reveal:Show in explorer' 'audit:Audit log'); _describe 'subcommand' subcommands ;;
165
+ remote) subcommands=('browse:List pal shares' 'files:List files in share' 'download:Download share'); _describe 'subcommand' subcommands ;;
166
+ user) subcommands=('list:List profiles' 'add:Add profile' 'remove:Remove profile' 'promote:Promote/demote'); _describe 'subcommand' subcommands ;;
167
+ chat) subcommands=('send:Send message' 'history:Show history' 'fetch:Fetch new messages'); _describe 'subcommand' subcommands ;;
168
+ comment) subcommands=('list:List comments' 'add:Add comment' 'delete:Delete comment'); _describe 'subcommand' subcommands ;;
169
+ schedule) subcommands=('add:Schedule task' 'cancel:Cancel task' 'clear:Clear old tasks'); _describe 'subcommand' subcommands ;;
170
+ backup) subcommands=('create:Create backup' 'restore:Restore backup'); _describe 'subcommand' subcommands ;;
171
+ api-keys) subcommands=('create:Create key' 'revoke:Revoke key'); _describe 'subcommand' subcommands ;;
172
+ share-link) subcommands=('create:Create link' 'list:List links'); _describe 'subcommand' subcommands ;;
173
+ update) subcommands=('check:Check for updates'); _describe 'subcommand' subcommands ;;
174
+ esac
175
+ fi
176
+ }
177
+
178
+ _pe
179
+ `;
180
+
181
+ export default function completionCommand(program) {
182
+ program
183
+ .command('completion')
184
+ .description('generate shell completion script')
185
+ .option('--shell <shell>', 'Shell type: bash or zsh', 'bash')
186
+ .addHelpText('after', `
187
+ Examples:
188
+ $ pe completion Output bash completion script
189
+ $ pe completion --shell zsh Output zsh completion script
190
+ $ source <(pe completion) Activate completion in current shell
191
+ `)
192
+ .action((opts) => {
193
+ if (opts.shell === 'zsh') {
194
+ console.log(ZSH_COMPLETION.trim());
195
+ console.log('');
196
+ console.log(chalk.gray('# Add to ~/.zshrc or save to a file in your fpath'));
197
+ } else {
198
+ console.log(BASH_COMPLETION.trim());
199
+ console.log('');
200
+ console.log(chalk.gray('# Add to ~/.bashrc or run: eval "$(pe completion)"'));
201
+ }
202
+ });
203
+ }
@@ -0,0 +1,218 @@
1
+ import chalk from 'chalk';
2
+
3
+ const SUPPORTED_MODULES = [
4
+ 'gdpr', 'hipaa', 'soc2', 'pci', 'financial', 'fedramp',
5
+ 'cmmc', 'iso27001', 'dora', 'itar', 'ccpa', 'ferpa',
6
+ ];
7
+
8
+ export default function complianceCommand(program) {
9
+ const cmd = program
10
+ .command('compliance')
11
+ .description('manage compliance modules (GDPR, HIPAA, etc.)')
12
+ .addHelpText('after', `
13
+ Examples:
14
+ $ pe compliance status Show enabled compliance modules
15
+ $ pe compliance enable gdpr Enable GDPR compliance
16
+ $ pe compliance disable hipaa Disable HIPAA compliance
17
+ $ pe compliance consent grant <pubkey> Record GDPR consent
18
+ $ pe compliance consent revoke <pubkey> Revoke GDPR consent
19
+ $ pe compliance erasure <pubkey> GDPR right to erasure
20
+ $ pe compliance export <pubkey> GDPR data subject access request
21
+
22
+ Supported modules: ${SUPPORTED_MODULES.join(', ')}
23
+ `)
24
+ .action(() => {
25
+ cmd.outputHelp();
26
+ });
27
+
28
+ cmd
29
+ .command('status')
30
+ .description('show which compliance modules are enabled')
31
+ .action(async () => {
32
+ try {
33
+ const extConfig = (await import('../utils/config.js')).default;
34
+ const cfg = extConfig.get('ext.compliance') || {};
35
+ const enabled = cfg.enabledModules || [];
36
+
37
+ console.log('');
38
+ console.log(chalk.cyan.bold('Compliance Modules'));
39
+ console.log('');
40
+ for (const mod of SUPPORTED_MODULES) {
41
+ const active = enabled.includes(mod);
42
+ const icon = active ? chalk.green('●') : chalk.gray('○');
43
+ console.log(` ${icon} ${active ? chalk.white(mod.toUpperCase()) : chalk.gray(mod.toUpperCase())}`);
44
+ }
45
+ console.log('');
46
+ } catch (err) {
47
+ console.log(chalk.red(`Failed to get status: ${err.message}`));
48
+ process.exitCode = 1;
49
+ }
50
+ });
51
+
52
+ cmd
53
+ .command('enable <module>')
54
+ .description('enable a compliance module')
55
+ .action(async (mod) => {
56
+ try {
57
+ const module = mod.toLowerCase();
58
+ if (!SUPPORTED_MODULES.includes(module)) {
59
+ console.log(chalk.red(`Unknown module "${mod}". Supported: ${SUPPORTED_MODULES.join(', ')}`));
60
+ process.exitCode = 1;
61
+ return;
62
+ }
63
+
64
+ const extConfig = (await import('../utils/config.js')).default;
65
+ const cfg = extConfig.get('ext.compliance') || {};
66
+ const enabled = cfg.enabledModules || [];
67
+
68
+ if (enabled.includes(module)) {
69
+ console.log(chalk.yellow(`${module.toUpperCase()} is already enabled.`));
70
+ return;
71
+ }
72
+
73
+ enabled.push(module);
74
+ extConfig.set('ext.compliance', { ...cfg, enabledModules: enabled });
75
+ console.log(chalk.green(`✔ Enabled ${module.toUpperCase()} compliance module`));
76
+ } catch (err) {
77
+ console.log(chalk.red(`Failed to enable module: ${err.message}`));
78
+ process.exitCode = 1;
79
+ }
80
+ });
81
+
82
+ cmd
83
+ .command('disable <module>')
84
+ .description('disable a compliance module')
85
+ .action(async (mod) => {
86
+ try {
87
+ const module = mod.toLowerCase();
88
+ if (!SUPPORTED_MODULES.includes(module)) {
89
+ console.log(chalk.red(`Unknown module "${mod}". Supported: ${SUPPORTED_MODULES.join(', ')}`));
90
+ process.exitCode = 1;
91
+ return;
92
+ }
93
+
94
+ const extConfig = (await import('../utils/config.js')).default;
95
+ const cfg = extConfig.get('ext.compliance') || {};
96
+ const enabled = cfg.enabledModules || [];
97
+
98
+ if (!enabled.includes(module)) {
99
+ console.log(chalk.yellow(`${module.toUpperCase()} is not enabled.`));
100
+ return;
101
+ }
102
+
103
+ extConfig.set('ext.compliance', {
104
+ ...cfg,
105
+ enabledModules: enabled.filter(m => m !== module),
106
+ });
107
+ console.log(chalk.green(`✔ Disabled ${module.toUpperCase()} compliance module`));
108
+ } catch (err) {
109
+ console.log(chalk.red(`Failed to disable module: ${err.message}`));
110
+ process.exitCode = 1;
111
+ }
112
+ });
113
+
114
+ const consent = cmd
115
+ .command('consent')
116
+ .description('manage GDPR consent records')
117
+ .action(() => {
118
+ consent.outputHelp();
119
+ });
120
+
121
+ consent
122
+ .command('grant <publicKey>')
123
+ .description('record GDPR consent for a user')
124
+ .action(async (publicKey) => {
125
+ try {
126
+ const extConfig = (await import('../utils/config.js')).default;
127
+ const store = extConfig.get('ext_store.compliance-gdpr') || {};
128
+
129
+ store[`consent:${publicKey}`] = {
130
+ status: 'granted',
131
+ timestamp: new Date().toISOString(),
132
+ };
133
+ extConfig.set('ext_store.compliance-gdpr', store);
134
+
135
+ console.log(chalk.green(`✔ Consent granted for ${chalk.white(publicKey.slice(0, 16))}...`));
136
+ } catch (err) {
137
+ console.log(chalk.red(`Failed to grant consent: ${err.message}`));
138
+ process.exitCode = 1;
139
+ }
140
+ });
141
+
142
+ consent
143
+ .command('revoke <publicKey>')
144
+ .description('revoke GDPR consent for a user')
145
+ .action(async (publicKey) => {
146
+ try {
147
+ const extConfig = (await import('../utils/config.js')).default;
148
+ const store = extConfig.get('ext_store.compliance-gdpr') || {};
149
+
150
+ store[`consent:${publicKey}`] = {
151
+ status: 'revoked',
152
+ timestamp: new Date().toISOString(),
153
+ };
154
+ extConfig.set('ext_store.compliance-gdpr', store);
155
+
156
+ console.log(chalk.green(`✔ Consent revoked for ${chalk.white(publicKey.slice(0, 16))}...`));
157
+ } catch (err) {
158
+ console.log(chalk.red(`Failed to revoke consent: ${err.message}`));
159
+ process.exitCode = 1;
160
+ }
161
+ });
162
+
163
+ cmd
164
+ .command('erasure <publicKey>')
165
+ .description('GDPR right to erasure — mark user data for deletion')
166
+ .action(async (publicKey) => {
167
+ try {
168
+ const extConfig = (await import('../utils/config.js')).default;
169
+ const store = extConfig.get('ext_store.compliance-gdpr') || {};
170
+
171
+ store[`erasure:${publicKey}`] = {
172
+ status: 'pending',
173
+ requestedAt: new Date().toISOString(),
174
+ };
175
+ store[`consent:${publicKey}`] = {
176
+ status: 'revoked',
177
+ timestamp: new Date().toISOString(),
178
+ };
179
+ extConfig.set('ext_store.compliance-gdpr', store);
180
+
181
+ console.log(chalk.green(`✔ Erasure request recorded for ${chalk.white(publicKey.slice(0, 16))}...`));
182
+ console.log(chalk.gray(' User data marked for deletion. Consent auto-revoked.'));
183
+ } catch (err) {
184
+ console.log(chalk.red(`Failed to record erasure: ${err.message}`));
185
+ process.exitCode = 1;
186
+ }
187
+ });
188
+
189
+ cmd
190
+ .command('export <publicKey>')
191
+ .description('GDPR data subject access request — export user data')
192
+ .action(async (publicKey) => {
193
+ try {
194
+ const extConfig = (await import('../utils/config.js')).default;
195
+ const store = extConfig.get('ext_store.compliance-gdpr') || {};
196
+
197
+ const consentRecord = store[`consent:${publicKey}`];
198
+ const erasureRecord = store[`erasure:${publicKey}`];
199
+
200
+ const exportData = {
201
+ publicKey,
202
+ exportedAt: new Date().toISOString(),
203
+ consent: consentRecord || null,
204
+ erasure: erasureRecord || null,
205
+ };
206
+
207
+ const filename = `gdpr-export-${publicKey.slice(0, 8)}-${Date.now()}.json`;
208
+ const fs = await import('fs');
209
+ fs.writeFileSync(filename, JSON.stringify(exportData, null, 2));
210
+
211
+ console.log(chalk.green(`✔ Data exported for ${chalk.white(publicKey.slice(0, 16))}...`));
212
+ console.log(` File: ${chalk.white(filename)}`);
213
+ } catch (err) {
214
+ console.log(chalk.red(`Failed to export data: ${err.message}`));
215
+ process.exitCode = 1;
216
+ }
217
+ });
218
+ }
@@ -0,0 +1,136 @@
1
+ import chalk from 'chalk';
2
+ import config from '../utils/config.js';
3
+
4
+ const KNOWN_KEYS = {
5
+ port: 'Local seeder port (default: auto)',
6
+ downloadDir: 'Download folder (default: ~/Downloads/Palexplorer)',
7
+ max_connections: 'Max P2P connections',
8
+ bandwidth_cap: 'Upload bandwidth cap in KB/s (0 = unlimited)',
9
+ bandwidth_up: 'Upload bandwidth limit in KB/s',
10
+ bandwidth_down: 'Download bandwidth limit in KB/s',
11
+ discovery_servers: 'Discovery server URLs (comma-separated list)',
12
+ announce_interval: 'DHT announce interval in seconds',
13
+ logLevel: 'Log level: debug, info, warn, error, silent',
14
+ };
15
+
16
+ export default function configCommand(program) {
17
+ const cmd = program
18
+ .command('config')
19
+ .description('view and manage Palexplorer configuration')
20
+ .addHelpText('after', `
21
+ Examples:
22
+ $ pe config list Show all settings
23
+ $ pe config get theme Get a specific setting
24
+ $ pe config set theme dark Set a value
25
+ $ pe config reset theme Reset to default
26
+ `);
27
+
28
+ cmd
29
+ .command('list')
30
+ .description('show all current settings')
31
+ .action(() => {
32
+ const settings = config.get('settings') || {};
33
+ if (program.opts().json) {
34
+ console.log(JSON.stringify({ settings, knownKeys: KNOWN_KEYS }, null, 2));
35
+ return;
36
+ }
37
+ console.log('');
38
+ console.log(chalk.cyan('Current Settings:'));
39
+ const SENSITIVE_KEYS = new Set(['apiKey', 'api_key', 'token', 'secret', 'password', 'privateKey']);
40
+ if (Object.keys(settings).length === 0) {
41
+ console.log(chalk.gray(' (no custom settings — all defaults active)'));
42
+ } else {
43
+ for (const [k, v] of Object.entries(settings)) {
44
+ const desc = KNOWN_KEYS[k] ? chalk.gray(` — ${KNOWN_KEYS[k]}`) : '';
45
+ const display = SENSITIVE_KEYS.has(k) ? '***' : v;
46
+ console.log(` ${chalk.white(k)} = ${chalk.yellow(display)}${desc}`);
47
+ }
48
+ }
49
+ console.log('');
50
+ console.log(chalk.gray('Known keys:'));
51
+ for (const [k, desc] of Object.entries(KNOWN_KEYS)) {
52
+ console.log(` ${chalk.white(k.padEnd(22))} ${chalk.gray(desc)}`);
53
+ }
54
+ });
55
+
56
+ cmd
57
+ .command('get <key>')
58
+ .description('get a single config value')
59
+ .action((key) => {
60
+ const settings = config.get('settings') || {};
61
+ if (!(key in settings)) {
62
+ if (program.opts().json) {
63
+ console.log(JSON.stringify({ key, value: null, isDefault: true }, null, 2));
64
+ return;
65
+ }
66
+ console.log(chalk.gray(`'${key}' is not set (using default).`));
67
+ return;
68
+ }
69
+ if (program.opts().json) {
70
+ console.log(JSON.stringify({ key, value: settings[key] }, null, 2));
71
+ return;
72
+ }
73
+ console.log(`${key} = ${chalk.yellow(settings[key])}`);
74
+ });
75
+
76
+ cmd
77
+ .command('set <key> <value>')
78
+ .description('set a config value')
79
+ .action((key, value) => {
80
+ const DANGEROUS_KEYS = ['__proto__', 'constructor', 'prototype'];
81
+ if (!key || !key.trim()) {
82
+ console.log(chalk.red(`Rejected: config key cannot be empty.`));
83
+ process.exitCode = 1;
84
+ return;
85
+ }
86
+ const keyParts = key.split('.');
87
+ if (DANGEROUS_KEYS.some(d => keyParts.includes(d)) || key.startsWith('__')) {
88
+ console.log(chalk.red(`Rejected: '${key}' is not a valid config key.`));
89
+ process.exitCode = 1;
90
+ return;
91
+ }
92
+ if (!(key in KNOWN_KEYS)) {
93
+ console.log(chalk.yellow(`Warning: unknown config key '${key}'. Known keys: ${Object.keys(KNOWN_KEYS).join(', ')}`));
94
+ }
95
+ const settings = config.get('settings') || {};
96
+ settings[key] = value;
97
+ config.set('settings', settings);
98
+ console.log(chalk.green(`✔ ${key} = ${value}`));
99
+ });
100
+
101
+ cmd
102
+ .command('reset [key]')
103
+ .description('reset a single key or all settings to defaults')
104
+ .action((key) => {
105
+ if (key && key.trim()) {
106
+ const settings = config.get('settings') || {};
107
+ if (!(key in settings)) {
108
+ console.log(chalk.gray(`'${key}' is not set.`));
109
+ return;
110
+ }
111
+ delete settings[key];
112
+ config.set('settings', settings);
113
+ console.log(chalk.green(`✔ '${key}' reset to default.`));
114
+ } else {
115
+ config.set('settings', {});
116
+ console.log(chalk.green('✔ All settings reset to defaults.'));
117
+ }
118
+ });
119
+
120
+ // Running `pe config` without subcommand shows list
121
+ cmd.action(() => {
122
+ const settings = config.get('settings') || {};
123
+ console.log('');
124
+ console.log(chalk.cyan('Current Settings:'));
125
+ if (Object.keys(settings).length === 0) {
126
+ console.log(chalk.gray(' (no custom settings — all defaults active)'));
127
+ } else {
128
+ for (const [k, v] of Object.entries(settings)) {
129
+ console.log(` ${chalk.white(k)} = ${chalk.yellow(v)}`);
130
+ }
131
+ }
132
+ console.log('');
133
+ console.log(chalk.gray('Use `pe config set <key> <value>` to change a setting.'));
134
+ console.log(chalk.gray('Use `pe config list` to see all known keys.'));
135
+ });
136
+ }
@@ -0,0 +1,44 @@
1
+ import chalk from 'chalk';
2
+ import { connect, disconnect, getConnectionState } from '../core/connectionManager.js';
3
+
4
+ export default function connectCommand(program) {
5
+ program
6
+ .command('connect')
7
+ .description('connect to the Palexplorer network')
8
+ .action(async () => {
9
+ const current = getConnectionState();
10
+ if (current.status === 'connected') {
11
+ console.log(chalk.yellow('Already connected.'));
12
+ return;
13
+ }
14
+
15
+ console.log(chalk.blue('Connecting to Palexplorer network...'));
16
+ const result = await connect({
17
+ onProgress: (step, progress) => {
18
+ console.log(chalk.gray(` [${progress}%] ${step}`));
19
+ },
20
+ });
21
+
22
+ if (result.connectedCount > 0) {
23
+ console.log(chalk.green(`\nConnected to ${result.connectedCount} server(s).`));
24
+ } else {
25
+ console.log(chalk.yellow('\nWarning: No servers reachable.'));
26
+ process.exitCode = 1;
27
+ }
28
+ });
29
+
30
+ program
31
+ .command('disconnect')
32
+ .description('disconnect from the Palexplorer network')
33
+ .action(async () => {
34
+ const current = getConnectionState();
35
+ if (current.status === 'disconnected') {
36
+ console.log(chalk.yellow('Already disconnected.'));
37
+ return;
38
+ }
39
+
40
+ console.log(chalk.blue('Disconnecting...'));
41
+ await disconnect();
42
+ console.log(chalk.green('Disconnected from network.'));
43
+ });
44
+ }