pal-explorer-cli 0.4.13 → 0.4.14

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
@@ -146,9 +146,21 @@ if (cmdMatch) {
146
146
  } else if (isVersionOrHelp) {
147
147
  // Help/version — load all commands for full help text, skip migrations
148
148
  await Promise.all(commands.map(c => lazy(c[1])(program)));
149
+ } else if (currentCmd && !currentCmd.startsWith('-')) {
150
+ // Unknown command — suggest similar commands
151
+ const { suggest } = await import('../lib/utils/fuzzy.js');
152
+ const allNames = commands.map(c => c[0]);
153
+ const suggestions = suggest(currentCmd, allNames);
154
+ if (suggestions.length) {
155
+ console.error(chalk.red(`Unknown command: ${currentCmd}`));
156
+ console.error(chalk.yellow(`\nDid you mean?\n${suggestions.map(s => ` pal ${s}`).join('\n')}`));
157
+ } else {
158
+ console.error(chalk.red(`Unknown command: ${currentCmd}`));
159
+ console.error(chalk.gray('Run "pal --help" to see available commands.'));
160
+ }
161
+ process.exitCode = 1;
149
162
  } else {
150
- // Unknown command load all for Commander to handle
151
- await runMigrations();
163
+ // Flag-only args (--help, --version handled above) load all
152
164
  await Promise.all(commands.map(c => lazy(c[1])(program)));
153
165
  }
154
166
 
@@ -8,10 +8,21 @@ _pal_completions() {
8
8
 
9
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
10
 
11
+ local global_flags="--help --version --json --log-level"
12
+
13
+ # Complete flags anywhere
14
+ if [[ "\$cur" == -* ]]; then
15
+ COMPREPLY=( $(compgen -W "\$global_flags" -- "\$cur") )
16
+ return
17
+ fi
18
+
19
+ # Handle top-level vs subcommand completion
20
+ if [[ \$COMP_CWORD -eq 1 ]]; then
21
+ COMPREPLY=( $(compgen -W "\$commands" -- "\$cur") )
22
+ return
23
+ fi
24
+
11
25
  case "\$prev" in
12
- pal)
13
- COMPREPLY=( $(compgen -W "\$commands" -- "\$cur") )
14
- ;;
15
26
  pal)
16
27
  COMPREPLY=( $(compgen -W "add list remove" -- "\$cur") )
17
28
  ;;
@@ -66,6 +77,39 @@ _pal_completions() {
66
77
  explorer)
67
78
  COMPREPLY=( $(compgen -W "install uninstall" -- "\$cur") )
68
79
  ;;
80
+ network)
81
+ COMPREPLY=( $(compgen -W "create list info invite join members groups connect disconnect" -- "\$cur") )
82
+ ;;
83
+ stream)
84
+ COMPREPLY=( $(compgen -W "local remote stop status broadcast join transport" -- "\$cur") )
85
+ ;;
86
+ extension|ext)
87
+ COMPREPLY=( $(compgen -W "list install remove enable disable info config create" -- "\$cur") )
88
+ ;;
89
+ billing)
90
+ COMPREPLY=( $(compgen -W "status plans activate deactivate checkout" -- "\$cur") )
91
+ ;;
92
+ org)
93
+ COMPREPLY=( $(compgen -W "create list info invite remove subscribe unsubscribe billing" -- "\$cur") )
94
+ ;;
95
+ protocol)
96
+ COMPREPLY=( $(compgen -W "info policy route envelope keys" -- "\$cur") )
97
+ ;;
98
+ workspace)
99
+ COMPREPLY=( $(compgen -W "list create delete add remove" -- "\$cur") )
100
+ ;;
101
+ pin)
102
+ COMPREPLY=( $(compgen -W "set remove status" -- "\$cur") )
103
+ ;;
104
+ auth)
105
+ COMPREPLY=( $(compgen -W "login logout status" -- "\$cur") )
106
+ ;;
107
+ cloud-backup)
108
+ COMPREPLY=( $(compgen -W "create restore list status" -- "\$cur") )
109
+ ;;
110
+ --log-level)
111
+ COMPREPLY=( $(compgen -W "debug info warn error silent" -- "\$cur") )
112
+ ;;
69
113
  *)
70
114
  COMPREPLY=()
71
115
  ;;
@@ -148,6 +192,14 @@ _pe() {
148
192
  'dept:Department management'
149
193
  )
150
194
 
195
+ # Complete flags
196
+ if [[ "\$words[CURRENT]" == -* ]]; then
197
+ local -a flags
198
+ flags=('--help:Show help' '--version:Show version' '--json:Output as JSON' '--log-level:Set log level')
199
+ _describe 'flag' flags
200
+ return
201
+ fi
202
+
151
203
  if (( CURRENT == 2 )); then
152
204
  _describe 'command' commands
153
205
  elif (( CURRENT == 3 )); then
@@ -171,6 +223,16 @@ _pe() {
171
223
  api-keys) subcommands=('create:Create key' 'revoke:Revoke key'); _describe 'subcommand' subcommands ;;
172
224
  share-link) subcommands=('create:Create link' 'list:List links'); _describe 'subcommand' subcommands ;;
173
225
  update) subcommands=('check:Check for updates'); _describe 'subcommand' subcommands ;;
226
+ network) subcommands=('create:Create network' 'list:List networks' 'info:Network info' 'invite:Invite to network' 'join:Join network' 'members:List members' 'groups:List groups' 'connect:Connect' 'disconnect:Disconnect'); _describe 'subcommand' subcommands ;;
227
+ stream) subcommands=('local:Stream local file' 'remote:Stream remote file' 'stop:Stop stream' 'status:Stream status' 'broadcast:Broadcast stream' 'join:Join broadcast' 'transport:Transport settings'); _describe 'subcommand' subcommands ;;
228
+ extension|ext) subcommands=('list:List extensions' 'install:Install extension' 'remove:Remove extension' 'enable:Enable extension' 'disable:Disable extension' 'info:Extension info' 'config:Extension config' 'create:Create extension'); _describe 'subcommand' subcommands ;;
229
+ billing) subcommands=('status:Billing status' 'plans:View plans' 'activate:Activate plan' 'deactivate:Cancel plan' 'checkout:Open checkout'); _describe 'subcommand' subcommands ;;
230
+ org) subcommands=('create:Create org' 'list:List orgs' 'info:Org info' 'invite:Invite member' 'remove:Remove member' 'subscribe:Subscribe' 'unsubscribe:Unsubscribe' 'billing:Org billing'); _describe 'subcommand' subcommands ;;
231
+ protocol) subcommands=('info:Protocol info' 'policy:Policy rules' 'route:Routing table' 'envelope:Envelope tools' 'keys:Key management'); _describe 'subcommand' subcommands ;;
232
+ workspace) subcommands=('list:List workspaces' 'create:Create workspace' 'delete:Delete workspace' 'add:Add to workspace' 'remove:Remove from workspace'); _describe 'subcommand' subcommands ;;
233
+ pin) subcommands=('set:Pin a share' 'remove:Unpin a share' 'status:Pin status'); _describe 'subcommand' subcommands ;;
234
+ auth) subcommands=('login:Log in' 'logout:Log out' 'status:Auth status'); _describe 'subcommand' subcommands ;;
235
+ cloud-backup) subcommands=('create:Create backup' 'restore:Restore backup' 'list:List backups' 'status:Backup status'); _describe 'subcommand' subcommands ;;
174
236
  esac
175
237
  fi
176
238
  }
@@ -0,0 +1,47 @@
1
+ function levenshtein(a, b) {
2
+ const m = a.length, n = b.length;
3
+ const dp = Array.from({ length: m + 1 }, (_, i) => {
4
+ const row = new Array(n + 1);
5
+ row[0] = i;
6
+ return row;
7
+ });
8
+ for (let j = 1; j <= n; j++) dp[0][j] = j;
9
+
10
+ for (let i = 1; i <= m; i++) {
11
+ for (let j = 1; j <= n; j++) {
12
+ dp[i][j] = a[i - 1] === b[j - 1]
13
+ ? dp[i - 1][j - 1]
14
+ : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
15
+ }
16
+ }
17
+ return dp[m][n];
18
+ }
19
+
20
+ export function suggest(input, candidates, { maxDistance = 3, maxResults = 3 } = {}) {
21
+ if (!input) return [];
22
+
23
+ const scored = candidates
24
+ .map(cmd => ({ cmd, dist: levenshtein(input.toLowerCase(), cmd.toLowerCase()) }))
25
+ .filter(({ dist, cmd }) => {
26
+ if (dist > maxDistance) return false;
27
+ // Also allow prefix matches (e.g., "ser" → "serve", "server", "search")
28
+ return true;
29
+ });
30
+
31
+ // Also add prefix matches not already included
32
+ const prefixMatches = candidates
33
+ .filter(cmd => cmd.toLowerCase().startsWith(input.toLowerCase()))
34
+ .map(cmd => ({ cmd, dist: 0.5 })); // give prefix matches high priority
35
+
36
+ const merged = new Map();
37
+ for (const entry of [...prefixMatches, ...scored]) {
38
+ if (!merged.has(entry.cmd) || entry.dist < merged.get(entry.cmd)) {
39
+ merged.set(entry.cmd, entry.dist);
40
+ }
41
+ }
42
+
43
+ return [...merged.entries()]
44
+ .sort((a, b) => a[1] - b[1])
45
+ .slice(0, maxResults)
46
+ .map(([cmd]) => cmd);
47
+ }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "pal-explorer-cli",
3
- "version": "0.4.13",
3
+ "version": "0.4.14",
4
4
  "description": "P2P encrypted file sharing CLI — share files directly with friends, not with the cloud",
5
5
  "main": "bin/pal.js",
6
6
  "bin": {
7
- "pal": "./bin/pal.js"
7
+ "pal": "bin/pal.js"
8
8
  },
9
9
  "files": [
10
10
  "bin/",