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 +14 -2
- package/lib/commands/completion.js +65 -3
- package/lib/utils/fuzzy.js +47 -0
- package/package.json +2 -2
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
|
-
//
|
|
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.
|
|
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": "
|
|
7
|
+
"pal": "bin/pal.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"bin/",
|