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,216 +1,216 @@
1
- import chalk from 'chalk';
2
- import { listShares, updateShare, getShareSummary, VISIBILITY_LEVELS } from '../core/shares.js';
3
- import { getGroups } from '../core/groups.js';
4
- import config from '../utils/config.js';
5
- import path from 'path';
6
-
7
- const VIS_COLORS = {
8
- public: 'green', global: 'green', private: 'red',
9
- group: 'blue', network: 'cyan', 'link-only': 'yellow',
10
- };
11
-
12
- function visLabel(v) {
13
- const color = VIS_COLORS[v] || 'white';
14
- return chalk[color](v.toUpperCase());
15
- }
16
-
17
- export default function permissionsCommand(program) {
18
- const cmd = program
19
- .command('permissions')
20
- .description('unified view of all shares and their permissions')
21
- .addHelpText('after', `
22
- Examples:
23
- $ pe permissions Show all shares with permissions
24
- $ pe permissions --compact Compact one-line-per-share view
25
- $ pe permissions set <id> --visibility group --group team
26
- $ pe permissions set <id> --streamable Mark share as streamable media
27
- $ pe permissions set <id> --add-pal alice
28
- $ pe permissions set <id> --remove-pal bob
29
- `)
30
- .option('--compact', 'One-line-per-share view')
31
- .option('--json', 'Output as JSON')
32
- .action((opts) => {
33
- const summary = getShareSummary();
34
- const groups = getGroups();
35
-
36
- if (opts.json) {
37
- console.log(JSON.stringify(summary, null, 2));
38
- return;
39
- }
40
-
41
- if (summary.length === 0) {
42
- console.log(chalk.gray('No active shares.'));
43
- return;
44
- }
45
-
46
- console.log('');
47
- console.log(chalk.bold(` Shares & Permissions (${summary.length} total)`));
48
- console.log(chalk.gray(' ─'.repeat(30)));
49
- console.log('');
50
-
51
- if (opts.compact) {
52
- // Compact table view
53
- const maxName = Math.max(12, ...summary.map(s => s.name.length));
54
- const header = ` ${'NAME'.padEnd(maxName)} ${'VISIBILITY'.padEnd(12)} ${'STREAM'.padEnd(6)} ${'RECIPIENTS'.padEnd(24)} ID`;
55
- console.log(chalk.gray(header));
56
- console.log(chalk.gray(' ' + '─'.repeat(header.length)));
57
-
58
- for (const s of summary) {
59
- const recips = [];
60
- if (s.recipients.length) recips.push(s.recipients.join(', '));
61
- if (s.groups.length) {
62
- const gNames = s.groups.map(gId => groups.find(g => g.id === gId)?.name || gId);
63
- recips.push(`[${gNames.join(', ')}]`);
64
- }
65
- if (s.networks.length) recips.push(`{${s.networks.join(', ')}}`);
66
- const recipStr = recips.join(', ') || chalk.gray('everyone');
67
-
68
- console.log(` ${chalk.white(s.name.padEnd(maxName))} ${visLabel(s.visibility).padEnd(12 + 10)} ${s.streamable ? chalk.magenta('yes') : chalk.gray('no ').padEnd(6)} ${recipStr.substring(0, 24).padEnd(24)} ${chalk.gray(s.id)}`);
69
- }
70
- } else {
71
- // Detailed view
72
- for (const s of summary) {
73
- const gNames = s.groups.map(gId => groups.find(g => g.id === gId)?.name || gId);
74
-
75
- console.log(` ${chalk.bold.white(s.name)} ${chalk.gray(`(${s.id})`)}`);
76
- console.log(` Path: ${chalk.gray(s.path)}`);
77
- console.log(` Visibility: ${visLabel(s.visibility)}`);
78
- console.log(` Streamable: ${s.streamable ? chalk.magenta('Yes — media streaming enabled') : chalk.gray('No')}`);
79
-
80
- if (s.visibility === 'public' || s.visibility === 'global') {
81
- console.log(` Access: ${chalk.green('Everyone')}`);
82
- } else if (s.visibility === 'group' && gNames.length) {
83
- console.log(` Groups: ${chalk.blue(gNames.join(', '))}`);
84
- } else if (s.visibility === 'network' && s.networks.length) {
85
- console.log(` Networks: ${chalk.cyan(s.networks.join(', '))}`);
86
- } else if (s.visibility === 'link-only') {
87
- console.log(` Access: ${chalk.yellow('Link-only (anyone with the link)')}`);
88
- }
89
-
90
- if (s.recipients.length) {
91
- console.log(` Recipients: ${chalk.white(s.recipients.join(', '))}`);
92
- }
93
-
94
- console.log(` Recursive: ${s.recursive ? 'Yes' : 'No'}`);
95
- if (s.hasMagnet) console.log(` Magnet: ${chalk.green('Active')}`);
96
- if (s.hasPassword) console.log(` Password: ${chalk.yellow('Protected')}`);
97
- console.log('');
98
- }
99
- }
100
- });
101
-
102
- cmd
103
- .command('set <id>')
104
- .description('edit share permissions')
105
- .option('--visibility <type>', `Set visibility: ${VISIBILITY_LEVELS.join(', ')}`)
106
- .option('--streamable', 'Enable media streaming for this share')
107
- .option('--no-streamable', 'Disable media streaming')
108
- .option('--add-pal <name>', 'Add a pal as recipient')
109
- .option('--remove-pal <name>', 'Remove a pal from recipients')
110
- .option('--add-group <name>', 'Add a group')
111
- .option('--remove-group <name>', 'Remove a group')
112
- .option('--add-network <id>', 'Add a network')
113
- .option('--remove-network <id>', 'Remove a network')
114
- .option('--recursive', 'Enable recursive sharing')
115
- .option('--no-recursive', 'Disable recursive sharing')
116
- .option('--writable', 'Allow recipients to write/modify files')
117
- .option('--read-only', 'Restrict recipients to read-only access')
118
- .action(async (id, opts) => {
119
- try {
120
- const shares = listShares();
121
- const share = shares.find(s => s.id === id || s.path === path.resolve(id));
122
- if (!share) {
123
- console.log(chalk.red('Share not found.'));
124
- process.exitCode = 1;
125
- return;
126
- }
127
-
128
- const updates = {};
129
-
130
- if (opts.visibility) {
131
- if (!VISIBILITY_LEVELS.includes(opts.visibility)) {
132
- console.log(chalk.red(`Invalid visibility. Options: ${VISIBILITY_LEVELS.join(', ')}`));
133
- process.exitCode = 1;
134
- return;
135
- }
136
- updates.visibility = opts.visibility;
137
- }
138
-
139
- if (opts.streamable !== undefined) {
140
- updates.streamable = opts.streamable;
141
- }
142
-
143
- if (opts.recursive !== undefined) {
144
- updates.recursive = opts.recursive;
145
- }
146
-
147
- if (opts.writable) {
148
- updates.permissions = { ...share.permissions, write: true };
149
- }
150
- if (opts.readOnly) {
151
- updates.permissions = { ...share.permissions, write: false };
152
- }
153
-
154
- if (opts.addPal) {
155
- const { getFriends } = await import('../core/users.js');
156
- const friends = getFriends();
157
- const pal = friends.find(f => f.name === opts.addPal || f.handle === opts.addPal);
158
- if (!pal) { console.log(chalk.red(`Pal '${opts.addPal}' not found.`)); process.exitCode = 1; return; }
159
- const recipients = share.recipients || [];
160
- if (!recipients.find(r => r.id === pal.id)) {
161
- recipients.push({ id: pal.id, name: pal.name, handle: pal.handle });
162
- updates.recipients = recipients;
163
- }
164
- }
165
-
166
- if (opts.removePal) {
167
- const recipients = (share.recipients || []).filter(
168
- r => r.name !== opts.removePal && r.handle !== opts.removePal
169
- );
170
- updates.recipients = recipients;
171
- }
172
-
173
- if (opts.addGroup) {
174
- const { getGroup } = await import('../core/groups.js');
175
- const group = getGroup(opts.addGroup);
176
- if (!group) { console.log(chalk.red(`Group '${opts.addGroup}' not found.`)); process.exitCode = 1; return; }
177
- const groups = share.sharedWithGroups || [];
178
- if (!groups.includes(group.id)) {
179
- groups.push(group.id);
180
- updates.sharedWithGroups = groups;
181
- }
182
- }
183
-
184
- if (opts.removeGroup) {
185
- const { getGroup } = await import('../core/groups.js');
186
- const group = getGroup(opts.removeGroup);
187
- updates.sharedWithGroups = (share.sharedWithGroups || []).filter(gId => gId !== group?.id);
188
- }
189
-
190
- if (opts.addNetwork) {
191
- const networks = share.sharedWithNetworks || [];
192
- if (!networks.includes(opts.addNetwork)) networks.push(opts.addNetwork);
193
- updates.sharedWithNetworks = networks;
194
- }
195
-
196
- if (opts.removeNetwork) {
197
- updates.sharedWithNetworks = (share.sharedWithNetworks || []).filter(n => n !== opts.removeNetwork);
198
- }
199
-
200
- if (Object.keys(updates).length === 0) {
201
- console.log(chalk.yellow('No changes specified.'));
202
- return;
203
- }
204
-
205
- const updated = updateShare(id, updates);
206
- console.log(chalk.green(`Share updated: ${updated.name || path.basename(updated.path)}`));
207
-
208
- for (const [key, value] of Object.entries(updates)) {
209
- console.log(chalk.gray(` ${key}: ${JSON.stringify(value)}`));
210
- }
211
- } catch (err) {
212
- console.log(chalk.red(`Error: ${err.message}`));
213
- process.exitCode = 1;
214
- }
215
- });
216
- }
1
+ import chalk from 'chalk';
2
+ import { listShares, updateShare, getShareSummary, VISIBILITY_LEVELS } from '../core/shares.js';
3
+ import { getGroups } from '../core/groups.js';
4
+ import config from '../utils/config.js';
5
+ import path from 'path';
6
+
7
+ const VIS_COLORS = {
8
+ public: 'green', global: 'green', private: 'red',
9
+ group: 'blue', network: 'cyan', 'link-only': 'yellow',
10
+ };
11
+
12
+ function visLabel(v) {
13
+ const color = VIS_COLORS[v] || 'white';
14
+ return chalk[color](v.toUpperCase());
15
+ }
16
+
17
+ export default function permissionsCommand(program) {
18
+ const cmd = program
19
+ .command('permissions')
20
+ .description('unified view of all shares and their permissions')
21
+ .addHelpText('after', `
22
+ Examples:
23
+ $ pal permissions Show all shares with permissions
24
+ $ pal permissions --compact Compact one-line-per-share view
25
+ $ pal permissions set <id> --visibility group --group team
26
+ $ pal permissions set <id> --streamable Mark share as streamable media
27
+ $ pal permissions set <id> --add-pal alice
28
+ $ pal permissions set <id> --remove-pal bob
29
+ `)
30
+ .option('--compact', 'One-line-per-share view')
31
+ .option('--json', 'Output as JSON')
32
+ .action((opts) => {
33
+ const summary = getShareSummary();
34
+ const groups = getGroups();
35
+
36
+ if (opts.json) {
37
+ console.log(JSON.stringify(summary, null, 2));
38
+ return;
39
+ }
40
+
41
+ if (summary.length === 0) {
42
+ console.log(chalk.gray('No active shares.'));
43
+ return;
44
+ }
45
+
46
+ console.log('');
47
+ console.log(chalk.bold(` Shares & Permissions (${summary.length} total)`));
48
+ console.log(chalk.gray(' ─'.repeat(30)));
49
+ console.log('');
50
+
51
+ if (opts.compact) {
52
+ // Compact table view
53
+ const maxName = Math.max(12, ...summary.map(s => s.name.length));
54
+ const header = ` ${'NAME'.padEnd(maxName)} ${'VISIBILITY'.padEnd(12)} ${'STREAM'.padEnd(6)} ${'RECIPIENTS'.padEnd(24)} ID`;
55
+ console.log(chalk.gray(header));
56
+ console.log(chalk.gray(' ' + '─'.repeat(header.length)));
57
+
58
+ for (const s of summary) {
59
+ const recips = [];
60
+ if (s.recipients.length) recips.push(s.recipients.join(', '));
61
+ if (s.groups.length) {
62
+ const gNames = s.groups.map(gId => groups.find(g => g.id === gId)?.name || gId);
63
+ recips.push(`[${gNames.join(', ')}]`);
64
+ }
65
+ if (s.networks.length) recips.push(`{${s.networks.join(', ')}}`);
66
+ const recipStr = recips.join(', ') || chalk.gray('everyone');
67
+
68
+ console.log(` ${chalk.white(s.name.padEnd(maxName))} ${visLabel(s.visibility).padEnd(12 + 10)} ${s.streamable ? chalk.magenta('yes') : chalk.gray('no ').padEnd(6)} ${recipStr.substring(0, 24).padEnd(24)} ${chalk.gray(s.id)}`);
69
+ }
70
+ } else {
71
+ // Detailed view
72
+ for (const s of summary) {
73
+ const gNames = s.groups.map(gId => groups.find(g => g.id === gId)?.name || gId);
74
+
75
+ console.log(` ${chalk.bold.white(s.name)} ${chalk.gray(`(${s.id})`)}`);
76
+ console.log(` Path: ${chalk.gray(s.path)}`);
77
+ console.log(` Visibility: ${visLabel(s.visibility)}`);
78
+ console.log(` Streamable: ${s.streamable ? chalk.magenta('Yes — media streaming enabled') : chalk.gray('No')}`);
79
+
80
+ if (s.visibility === 'public' || s.visibility === 'global') {
81
+ console.log(` Access: ${chalk.green('Everyone')}`);
82
+ } else if (s.visibility === 'group' && gNames.length) {
83
+ console.log(` Groups: ${chalk.blue(gNames.join(', '))}`);
84
+ } else if (s.visibility === 'network' && s.networks.length) {
85
+ console.log(` Networks: ${chalk.cyan(s.networks.join(', '))}`);
86
+ } else if (s.visibility === 'link-only') {
87
+ console.log(` Access: ${chalk.yellow('Link-only (anyone with the link)')}`);
88
+ }
89
+
90
+ if (s.recipients.length) {
91
+ console.log(` Recipients: ${chalk.white(s.recipients.join(', '))}`);
92
+ }
93
+
94
+ console.log(` Recursive: ${s.recursive ? 'Yes' : 'No'}`);
95
+ if (s.hasMagnet) console.log(` Magnet: ${chalk.green('Active')}`);
96
+ if (s.hasPassword) console.log(` Password: ${chalk.yellow('Protected')}`);
97
+ console.log('');
98
+ }
99
+ }
100
+ });
101
+
102
+ cmd
103
+ .command('set <id>')
104
+ .description('edit share permissions')
105
+ .option('--visibility <type>', `Set visibility: ${VISIBILITY_LEVELS.join(', ')}`)
106
+ .option('--streamable', 'Enable media streaming for this share')
107
+ .option('--no-streamable', 'Disable media streaming')
108
+ .option('--add-pal <name>', 'Add a pal as recipient')
109
+ .option('--remove-pal <name>', 'Remove a pal from recipients')
110
+ .option('--add-group <name>', 'Add a group')
111
+ .option('--remove-group <name>', 'Remove a group')
112
+ .option('--add-network <id>', 'Add a network')
113
+ .option('--remove-network <id>', 'Remove a network')
114
+ .option('--recursive', 'Enable recursive sharing')
115
+ .option('--no-recursive', 'Disable recursive sharing')
116
+ .option('--writable', 'Allow recipients to write/modify files')
117
+ .option('--read-only', 'Restrict recipients to read-only access')
118
+ .action(async (id, opts) => {
119
+ try {
120
+ const shares = listShares();
121
+ const share = shares.find(s => s.id === id || s.path === path.resolve(id));
122
+ if (!share) {
123
+ console.log(chalk.red('Share not found.'));
124
+ process.exitCode = 1;
125
+ return;
126
+ }
127
+
128
+ const updates = {};
129
+
130
+ if (opts.visibility) {
131
+ if (!VISIBILITY_LEVELS.includes(opts.visibility)) {
132
+ console.log(chalk.red(`Invalid visibility. Options: ${VISIBILITY_LEVELS.join(', ')}`));
133
+ process.exitCode = 1;
134
+ return;
135
+ }
136
+ updates.visibility = opts.visibility;
137
+ }
138
+
139
+ if (opts.streamable !== undefined) {
140
+ updates.streamable = opts.streamable;
141
+ }
142
+
143
+ if (opts.recursive !== undefined) {
144
+ updates.recursive = opts.recursive;
145
+ }
146
+
147
+ if (opts.writable) {
148
+ updates.permissions = { ...share.permissions, write: true };
149
+ }
150
+ if (opts.readOnly) {
151
+ updates.permissions = { ...share.permissions, write: false };
152
+ }
153
+
154
+ if (opts.addPal) {
155
+ const { getFriends } = await import('../core/users.js');
156
+ const friends = getFriends();
157
+ const pal = friends.find(f => f.name === opts.addPal || f.handle === opts.addPal);
158
+ if (!pal) { console.log(chalk.red(`Pal '${opts.addPal}' not found.`)); process.exitCode = 1; return; }
159
+ const recipients = share.recipients || [];
160
+ if (!recipients.find(r => r.id === pal.id)) {
161
+ recipients.push({ id: pal.id, name: pal.name, handle: pal.handle });
162
+ updates.recipients = recipients;
163
+ }
164
+ }
165
+
166
+ if (opts.removePal) {
167
+ const recipients = (share.recipients || []).filter(
168
+ r => r.name !== opts.removePal && r.handle !== opts.removePal
169
+ );
170
+ updates.recipients = recipients;
171
+ }
172
+
173
+ if (opts.addGroup) {
174
+ const { getGroup } = await import('../core/groups.js');
175
+ const group = getGroup(opts.addGroup);
176
+ if (!group) { console.log(chalk.red(`Group '${opts.addGroup}' not found.`)); process.exitCode = 1; return; }
177
+ const groups = share.sharedWithGroups || [];
178
+ if (!groups.includes(group.id)) {
179
+ groups.push(group.id);
180
+ updates.sharedWithGroups = groups;
181
+ }
182
+ }
183
+
184
+ if (opts.removeGroup) {
185
+ const { getGroup } = await import('../core/groups.js');
186
+ const group = getGroup(opts.removeGroup);
187
+ updates.sharedWithGroups = (share.sharedWithGroups || []).filter(gId => gId !== group?.id);
188
+ }
189
+
190
+ if (opts.addNetwork) {
191
+ const networks = share.sharedWithNetworks || [];
192
+ if (!networks.includes(opts.addNetwork)) networks.push(opts.addNetwork);
193
+ updates.sharedWithNetworks = networks;
194
+ }
195
+
196
+ if (opts.removeNetwork) {
197
+ updates.sharedWithNetworks = (share.sharedWithNetworks || []).filter(n => n !== opts.removeNetwork);
198
+ }
199
+
200
+ if (Object.keys(updates).length === 0) {
201
+ console.log(chalk.yellow('No changes specified.'));
202
+ return;
203
+ }
204
+
205
+ const updated = updateShare(id, updates);
206
+ console.log(chalk.green(`Share updated: ${updated.name || path.basename(updated.path)}`));
207
+
208
+ for (const [key, value] of Object.entries(updates)) {
209
+ console.log(chalk.gray(` ${key}: ${JSON.stringify(value)}`));
210
+ }
211
+ } catch (err) {
212
+ console.log(chalk.red(`Error: ${err.message}`));
213
+ process.exitCode = 1;
214
+ }
215
+ });
216
+ }
@@ -1,97 +1,97 @@
1
- import chalk from 'chalk';
2
- import crypto from 'crypto';
3
- import config from '../utils/config.js';
4
- import readline from 'readline';
5
-
6
- function promptPin(prompt) {
7
- return new Promise((resolve) => {
8
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
9
- rl.question(prompt, (answer) => {
10
- rl.close();
11
- resolve(answer.trim());
12
- });
13
- });
14
- }
15
-
16
- export default function pinCommand(program) {
17
- const cmd = program
18
- .command('pin')
19
- .description('manage PIN lock for app security')
20
- .addHelpText('after', `
21
- Examples:
22
- $ pe pin status Show if PIN is set
23
- $ pe pin set Set a new PIN
24
- $ pe pin remove Remove PIN lock
25
- `)
26
- .action(() => {
27
- showPinStatus();
28
- });
29
-
30
- cmd
31
- .command('status')
32
- .description('show if PIN lock is enabled')
33
- .action(() => {
34
- showPinStatus();
35
- });
36
-
37
- cmd
38
- .command('set')
39
- .description('set or update PIN lock')
40
- .action(async () => {
41
- const pin = await promptPin('Enter new PIN: ');
42
- if (!pin || pin.length < 4) {
43
- console.log(chalk.red('PIN must be at least 4 characters.'));
44
- process.exitCode = 1;
45
- return;
46
- }
47
- const confirm = await promptPin('Confirm PIN: ');
48
- if (pin !== confirm) {
49
- console.log(chalk.red('PINs do not match.'));
50
- process.exitCode = 1;
51
- return;
52
- }
53
- const salt = crypto.randomBytes(16).toString('hex');
54
- const hash = crypto.createHash('sha256').update(salt + pin).digest('hex');
55
- config.set('pinHash', JSON.stringify({ salt, hash }));
56
- console.log(chalk.green('\u2714 PIN lock enabled.'));
57
- });
58
-
59
- cmd
60
- .command('remove')
61
- .description('remove PIN lock')
62
- .action(async () => {
63
- const existing = config.get('pinHash');
64
- if (!existing) {
65
- console.log(chalk.gray('No PIN is set.'));
66
- return;
67
- }
68
- const pin = await promptPin('Enter current PIN to remove: ');
69
- let match = false;
70
- try {
71
- const parsed = JSON.parse(existing);
72
- if (parsed.salt && parsed.hash) {
73
- const hash = crypto.createHash('sha256').update(parsed.salt + pin).digest('hex');
74
- match = hash === parsed.hash;
75
- }
76
- } catch {
77
- const hash = crypto.createHash('sha256').update(pin).digest('hex');
78
- match = hash === existing;
79
- }
80
- if (!match) {
81
- console.log(chalk.red('Incorrect PIN.'));
82
- process.exitCode = 1;
83
- return;
84
- }
85
- config.delete('pinHash');
86
- console.log(chalk.green('\u2714 PIN lock removed.'));
87
- });
88
- }
89
-
90
- function showPinStatus() {
91
- const pinHash = config.get('pinHash');
92
- if (pinHash) {
93
- console.log(`PIN lock: ${chalk.green('Enabled')}`);
94
- } else {
95
- console.log(`PIN lock: ${chalk.gray('Not set')}`);
96
- }
97
- }
1
+ import chalk from 'chalk';
2
+ import crypto from 'crypto';
3
+ import config from '../utils/config.js';
4
+ import readline from 'readline';
5
+
6
+ function promptPin(prompt) {
7
+ return new Promise((resolve) => {
8
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
9
+ rl.question(prompt, (answer) => {
10
+ rl.close();
11
+ resolve(answer.trim());
12
+ });
13
+ });
14
+ }
15
+
16
+ export default function pinCommand(program) {
17
+ const cmd = program
18
+ .command('pin')
19
+ .description('manage PIN lock for app security')
20
+ .addHelpText('after', `
21
+ Examples:
22
+ $ pal pin status Show if PIN is set
23
+ $ pal pin set Set a new PIN
24
+ $ pal pin remove Remove PIN lock
25
+ `)
26
+ .action(() => {
27
+ showPinStatus();
28
+ });
29
+
30
+ cmd
31
+ .command('status')
32
+ .description('show if PIN lock is enabled')
33
+ .action(() => {
34
+ showPinStatus();
35
+ });
36
+
37
+ cmd
38
+ .command('set')
39
+ .description('set or update PIN lock')
40
+ .action(async () => {
41
+ const pin = await promptPin('Enter new PIN: ');
42
+ if (!pin || pin.length < 4) {
43
+ console.log(chalk.red('PIN must be at least 4 characters.'));
44
+ process.exitCode = 1;
45
+ return;
46
+ }
47
+ const confirm = await promptPin('Confirm PIN: ');
48
+ if (pin !== confirm) {
49
+ console.log(chalk.red('PINs do not match.'));
50
+ process.exitCode = 1;
51
+ return;
52
+ }
53
+ const salt = crypto.randomBytes(16).toString('hex');
54
+ const hash = crypto.createHash('sha256').update(salt + pin).digest('hex');
55
+ config.set('pinHash', JSON.stringify({ salt, hash }));
56
+ console.log(chalk.green('\u2714 PIN lock enabled.'));
57
+ });
58
+
59
+ cmd
60
+ .command('remove')
61
+ .description('remove PIN lock')
62
+ .action(async () => {
63
+ const existing = config.get('pinHash');
64
+ if (!existing) {
65
+ console.log(chalk.gray('No PIN is set.'));
66
+ return;
67
+ }
68
+ const pin = await promptPin('Enter current PIN to remove: ');
69
+ let match = false;
70
+ try {
71
+ const parsed = JSON.parse(existing);
72
+ if (parsed.salt && parsed.hash) {
73
+ const hash = crypto.createHash('sha256').update(parsed.salt + pin).digest('hex');
74
+ match = hash === parsed.hash;
75
+ }
76
+ } catch {
77
+ const hash = crypto.createHash('sha256').update(pin).digest('hex');
78
+ match = hash === existing;
79
+ }
80
+ if (!match) {
81
+ console.log(chalk.red('Incorrect PIN.'));
82
+ process.exitCode = 1;
83
+ return;
84
+ }
85
+ config.delete('pinHash');
86
+ console.log(chalk.green('\u2714 PIN lock removed.'));
87
+ });
88
+ }
89
+
90
+ function showPinStatus() {
91
+ const pinHash = config.get('pinHash');
92
+ if (pinHash) {
93
+ console.log(`PIN lock: ${chalk.green('Enabled')}`);
94
+ } else {
95
+ console.log(`PIN lock: ${chalk.gray('Not set')}`);
96
+ }
97
+ }