pal-explorer-cli 0.4.12 → 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/README.md +149 -149
- package/bin/pal.js +77 -4
- package/extensions/@palexplorer/analytics/extension.json +20 -1
- package/extensions/@palexplorer/analytics/index.js +19 -9
- package/extensions/@palexplorer/audit/extension.json +14 -0
- package/extensions/@palexplorer/auth-email/extension.json +15 -0
- package/extensions/@palexplorer/auth-oauth/extension.json +15 -0
- package/extensions/@palexplorer/chat/extension.json +14 -0
- package/extensions/@palexplorer/discovery/extension.json +17 -0
- package/extensions/@palexplorer/discovery/index.js +1 -1
- package/extensions/@palexplorer/email-notifications/extension.json +23 -0
- package/extensions/@palexplorer/groups/extension.json +15 -0
- package/extensions/@palexplorer/share-links/extension.json +15 -0
- package/extensions/@palexplorer/sync/extension.json +16 -0
- package/extensions/@palexplorer/user-mgmt/extension.json +15 -0
- package/lib/capabilities.js +24 -24
- package/lib/commands/analytics.js +175 -175
- package/lib/commands/api-keys.js +131 -131
- package/lib/commands/audit.js +235 -235
- package/lib/commands/auth.js +137 -137
- package/lib/commands/backup.js +76 -76
- package/lib/commands/billing.js +148 -148
- package/lib/commands/chat.js +217 -217
- package/lib/commands/cloud-backup.js +231 -231
- package/lib/commands/comment.js +99 -99
- package/lib/commands/completion.js +265 -203
- package/lib/commands/compliance.js +218 -218
- package/lib/commands/config.js +136 -136
- package/lib/commands/connect.js +44 -44
- package/lib/commands/dept.js +294 -294
- package/lib/commands/device.js +146 -146
- package/lib/commands/download.js +240 -226
- package/lib/commands/explorer.js +178 -178
- package/lib/commands/extension.js +1060 -970
- package/lib/commands/favorite.js +90 -90
- package/lib/commands/federation.js +270 -270
- package/lib/commands/file.js +533 -533
- package/lib/commands/group.js +271 -271
- package/lib/commands/gui-share.js +29 -29
- package/lib/commands/init.js +61 -61
- package/lib/commands/invite.js +59 -59
- package/lib/commands/list.js +58 -58
- package/lib/commands/log.js +116 -116
- package/lib/commands/nearby.js +108 -108
- package/lib/commands/network.js +251 -251
- package/lib/commands/notify.js +198 -198
- package/lib/commands/org.js +273 -273
- package/lib/commands/pal.js +403 -180
- package/lib/commands/permissions.js +216 -216
- package/lib/commands/pin.js +97 -97
- package/lib/commands/protocol.js +357 -357
- package/lib/commands/rbac.js +147 -147
- package/lib/commands/recover.js +36 -36
- package/lib/commands/register.js +171 -171
- package/lib/commands/relay.js +131 -131
- package/lib/commands/remote.js +368 -368
- package/lib/commands/revoke.js +50 -50
- package/lib/commands/scanner.js +280 -280
- package/lib/commands/schedule.js +344 -344
- package/lib/commands/scim.js +203 -203
- package/lib/commands/search.js +181 -181
- package/lib/commands/serve.js +438 -438
- package/lib/commands/server.js +350 -350
- package/lib/commands/share-link.js +199 -199
- package/lib/commands/share.js +336 -323
- package/lib/commands/sso.js +200 -200
- package/lib/commands/status.js +145 -145
- package/lib/commands/stream.js +562 -562
- package/lib/commands/su.js +187 -187
- package/lib/commands/sync.js +979 -979
- package/lib/commands/transfers.js +152 -152
- package/lib/commands/uninstall.js +188 -188
- package/lib/commands/update.js +204 -204
- package/lib/commands/user.js +276 -276
- package/lib/commands/vfs.js +84 -84
- package/lib/commands/web-login.js +79 -79
- package/lib/commands/web.js +52 -52
- package/lib/commands/webhook.js +180 -180
- package/lib/commands/whoami.js +59 -59
- package/lib/commands/workspace.js +121 -121
- package/lib/core/billing.js +16 -5
- package/lib/core/dhtDiscovery.js +9 -2
- package/lib/core/discoveryClient.js +13 -7
- package/lib/core/extensions.js +142 -1
- package/lib/core/identity.js +33 -2
- package/lib/core/imageProcessor.js +109 -0
- package/lib/core/imageTorrent.js +167 -0
- package/lib/core/permissions.js +1 -1
- package/lib/core/pro.js +11 -4
- package/lib/core/serverList.js +4 -1
- package/lib/core/shares.js +12 -1
- package/lib/core/signalingServer.js +14 -2
- package/lib/core/su.js +1 -1
- package/lib/core/users.js +1 -1
- package/lib/protocol/messages.js +12 -3
- package/lib/utils/explorer.js +1 -1
- package/lib/utils/fuzzy.js +47 -0
- package/lib/utils/help.js +357 -357
- package/lib/utils/torrent.js +1 -0
- package/package.json +4 -3
package/lib/commands/vfs.js
CHANGED
|
@@ -1,84 +1,84 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { vfs, getMountStatus } from '../core/vfs.js';
|
|
3
|
-
|
|
4
|
-
export default function vfsCommand(program) {
|
|
5
|
-
const cmd = program
|
|
6
|
-
.command('vfs')
|
|
7
|
-
.description('manage the virtual filesystem drive')
|
|
8
|
-
.addHelpText('after', `
|
|
9
|
-
Examples:
|
|
10
|
-
$
|
|
11
|
-
$
|
|
12
|
-
$
|
|
13
|
-
$
|
|
14
|
-
`);
|
|
15
|
-
|
|
16
|
-
cmd
|
|
17
|
-
.command('mount [letter]')
|
|
18
|
-
.description('mount virtual drive (default P:)')
|
|
19
|
-
.action(async (letter = 'P') => {
|
|
20
|
-
try {
|
|
21
|
-
console.log(chalk.blue('Starting VFS server...'));
|
|
22
|
-
await vfs.start();
|
|
23
|
-
await vfs.syncShares();
|
|
24
|
-
|
|
25
|
-
console.log(chalk.blue(`Mounting drive ${letter}:...`));
|
|
26
|
-
await vfs.mount(letter);
|
|
27
|
-
|
|
28
|
-
const status = getMountStatus();
|
|
29
|
-
if (status.mounted) {
|
|
30
|
-
console.log(chalk.green(`Virtual drive mounted at ${letter}: with ${status.shareCount} share(s).`));
|
|
31
|
-
} else {
|
|
32
|
-
console.log(chalk.yellow('VFS server running but drive mount was skipped (see warnings above).'));
|
|
33
|
-
console.log(chalk.gray(`WebDAV available at http://127.0.0.1:${status.port}`));
|
|
34
|
-
}
|
|
35
|
-
} catch (err) {
|
|
36
|
-
console.error(chalk.red('Failed to mount VFS:'), err.message);
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
cmd
|
|
41
|
-
.command('unmount')
|
|
42
|
-
.description('unmount virtual drive')
|
|
43
|
-
.action(async () => {
|
|
44
|
-
try {
|
|
45
|
-
const status = getMountStatus();
|
|
46
|
-
if (!status?.mounted || !status?.driveLetter) {
|
|
47
|
-
console.log(chalk.yellow('No virtual drive is currently mounted.'));
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
await vfs.unmount(status.driveLetter);
|
|
51
|
-
await vfs.stop();
|
|
52
|
-
console.log(chalk.green('Virtual drive unmounted.'));
|
|
53
|
-
} catch (err) {
|
|
54
|
-
console.error(chalk.red('Failed to unmount VFS:'), err.message);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
cmd
|
|
59
|
-
.command('status')
|
|
60
|
-
.description('show mount status and listed shares')
|
|
61
|
-
.action(() => {
|
|
62
|
-
try {
|
|
63
|
-
const status = getMountStatus();
|
|
64
|
-
console.log('');
|
|
65
|
-
console.log(chalk.cyan('VFS Status:'));
|
|
66
|
-
console.log(` Server: ${status.running ? chalk.green('Running') : chalk.red('Stopped')}`);
|
|
67
|
-
console.log(` Mounted: ${status.mounted ? chalk.green(`Yes (${status.driveLetter}:)`) : chalk.yellow('No')}`);
|
|
68
|
-
console.log(` Port: ${chalk.white(status.port)}`);
|
|
69
|
-
console.log(` Shares: ${chalk.white(status.shareCount)}`);
|
|
70
|
-
|
|
71
|
-
if (status.shares.length > 0) {
|
|
72
|
-
console.log('');
|
|
73
|
-
console.log(chalk.cyan(' Mounted Shares:'));
|
|
74
|
-
for (const s of status.shares) {
|
|
75
|
-
console.log(` /${s.name} -> ${chalk.gray(s.localPath)}`);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
console.log('');
|
|
79
|
-
} catch (err) {
|
|
80
|
-
console.error(chalk.red('VFS status error:'), err.message);
|
|
81
|
-
process.exitCode = 1;
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
}
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { vfs, getMountStatus } from '../core/vfs.js';
|
|
3
|
+
|
|
4
|
+
export default function vfsCommand(program) {
|
|
5
|
+
const cmd = program
|
|
6
|
+
.command('vfs')
|
|
7
|
+
.description('manage the virtual filesystem drive')
|
|
8
|
+
.addHelpText('after', `
|
|
9
|
+
Examples:
|
|
10
|
+
$ pal vfs mount Mount virtual drive at P:
|
|
11
|
+
$ pal vfs mount S Mount at S:
|
|
12
|
+
$ pal vfs unmount Unmount virtual drive
|
|
13
|
+
$ pal vfs status Show mount status
|
|
14
|
+
`);
|
|
15
|
+
|
|
16
|
+
cmd
|
|
17
|
+
.command('mount [letter]')
|
|
18
|
+
.description('mount virtual drive (default P:)')
|
|
19
|
+
.action(async (letter = 'P') => {
|
|
20
|
+
try {
|
|
21
|
+
console.log(chalk.blue('Starting VFS server...'));
|
|
22
|
+
await vfs.start();
|
|
23
|
+
await vfs.syncShares();
|
|
24
|
+
|
|
25
|
+
console.log(chalk.blue(`Mounting drive ${letter}:...`));
|
|
26
|
+
await vfs.mount(letter);
|
|
27
|
+
|
|
28
|
+
const status = getMountStatus();
|
|
29
|
+
if (status.mounted) {
|
|
30
|
+
console.log(chalk.green(`Virtual drive mounted at ${letter}: with ${status.shareCount} share(s).`));
|
|
31
|
+
} else {
|
|
32
|
+
console.log(chalk.yellow('VFS server running but drive mount was skipped (see warnings above).'));
|
|
33
|
+
console.log(chalk.gray(`WebDAV available at http://127.0.0.1:${status.port}`));
|
|
34
|
+
}
|
|
35
|
+
} catch (err) {
|
|
36
|
+
console.error(chalk.red('Failed to mount VFS:'), err.message);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
cmd
|
|
41
|
+
.command('unmount')
|
|
42
|
+
.description('unmount virtual drive')
|
|
43
|
+
.action(async () => {
|
|
44
|
+
try {
|
|
45
|
+
const status = getMountStatus();
|
|
46
|
+
if (!status?.mounted || !status?.driveLetter) {
|
|
47
|
+
console.log(chalk.yellow('No virtual drive is currently mounted.'));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
await vfs.unmount(status.driveLetter);
|
|
51
|
+
await vfs.stop();
|
|
52
|
+
console.log(chalk.green('Virtual drive unmounted.'));
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.error(chalk.red('Failed to unmount VFS:'), err.message);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
cmd
|
|
59
|
+
.command('status')
|
|
60
|
+
.description('show mount status and listed shares')
|
|
61
|
+
.action(() => {
|
|
62
|
+
try {
|
|
63
|
+
const status = getMountStatus();
|
|
64
|
+
console.log('');
|
|
65
|
+
console.log(chalk.cyan('VFS Status:'));
|
|
66
|
+
console.log(` Server: ${status.running ? chalk.green('Running') : chalk.red('Stopped')}`);
|
|
67
|
+
console.log(` Mounted: ${status.mounted ? chalk.green(`Yes (${status.driveLetter}:)`) : chalk.yellow('No')}`);
|
|
68
|
+
console.log(` Port: ${chalk.white(status.port)}`);
|
|
69
|
+
console.log(` Shares: ${chalk.white(status.shareCount)}`);
|
|
70
|
+
|
|
71
|
+
if (status.shares.length > 0) {
|
|
72
|
+
console.log('');
|
|
73
|
+
console.log(chalk.cyan(' Mounted Shares:'));
|
|
74
|
+
for (const s of status.shares) {
|
|
75
|
+
console.log(` /${s.name} -> ${chalk.gray(s.localPath)}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
console.log('');
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.error(chalk.red('VFS status error:'), err.message);
|
|
81
|
+
process.exitCode = 1;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
@@ -1,79 +1,79 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { getIdentity } from '../core/identity.js';
|
|
3
|
-
import config from '../utils/config.js';
|
|
4
|
-
|
|
5
|
-
export default function webLoginCommand(program) {
|
|
6
|
-
program
|
|
7
|
-
.command('web-login <code>')
|
|
8
|
-
.description('sign in to palexplorer.com using your desktop identity')
|
|
9
|
-
.option('--host <host>', 'Web server host', 'palexplorer.com')
|
|
10
|
-
.addHelpText('after', `
|
|
11
|
-
Examples:
|
|
12
|
-
$
|
|
13
|
-
$
|
|
14
|
-
`)
|
|
15
|
-
.action(async (code, opts) => {
|
|
16
|
-
try {
|
|
17
|
-
const identity = await getIdentity();
|
|
18
|
-
if (!identity) {
|
|
19
|
-
console.log(chalk.red('No identity found. Run:
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const host = opts.host;
|
|
24
|
-
const protocol = host.startsWith('localhost') || host.startsWith('127.0.0.1') ? 'http' : 'https';
|
|
25
|
-
const baseUrl = `${protocol}://${host}`;
|
|
26
|
-
|
|
27
|
-
console.log(chalk.gray(`Resolving code ${code}...`));
|
|
28
|
-
|
|
29
|
-
const resolveRes = await fetch(`${baseUrl}/api/auth/qr/resolve`, {
|
|
30
|
-
method: 'POST',
|
|
31
|
-
headers: { 'Content-Type': 'application/json' },
|
|
32
|
-
body: JSON.stringify({ code }),
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
if (!resolveRes.ok) {
|
|
36
|
-
const data = await resolveRes.json().catch(() => ({}));
|
|
37
|
-
console.log(chalk.red(`Failed: ${data.error || 'Code not found or expired'}`));
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const { sessionId, challenge } = await resolveRes.json();
|
|
42
|
-
|
|
43
|
-
console.log(chalk.gray('Signing challenge...'));
|
|
44
|
-
|
|
45
|
-
let sodium;
|
|
46
|
-
try {
|
|
47
|
-
sodium = (await import('sodium-native')).default;
|
|
48
|
-
} catch {
|
|
49
|
-
sodium = await import('sodium-native');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const sig = Buffer.alloc(sodium.crypto_sign_BYTES);
|
|
53
|
-
const challengeBuf = Buffer.from(challenge, 'hex');
|
|
54
|
-
const privateKey = Buffer.from(identity.privateKey, 'hex');
|
|
55
|
-
sodium.crypto_sign_detached(sig, challengeBuf, privateKey);
|
|
56
|
-
|
|
57
|
-
const verifyRes = await fetch(`${baseUrl}/api/auth/qr/verify`, {
|
|
58
|
-
method: 'POST',
|
|
59
|
-
headers: { 'Content-Type': 'application/json' },
|
|
60
|
-
body: JSON.stringify({
|
|
61
|
-
sessionId,
|
|
62
|
-
publicKey: identity.publicKey,
|
|
63
|
-
signature: sig.toString('hex'),
|
|
64
|
-
}),
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
if (!verifyRes.ok) {
|
|
68
|
-
const data = await verifyRes.json().catch(() => ({}));
|
|
69
|
-
console.log(chalk.red(`Verification failed: ${data.error || 'Unknown error'}`));
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
console.log(chalk.green('Signed in to web successfully!'));
|
|
74
|
-
console.log(chalk.gray('Your browser should update automatically.'));
|
|
75
|
-
} catch (err) {
|
|
76
|
-
console.log(chalk.red(`Error: ${err.message}`));
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
}
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { getIdentity } from '../core/identity.js';
|
|
3
|
+
import config from '../utils/config.js';
|
|
4
|
+
|
|
5
|
+
export default function webLoginCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('web-login <code>')
|
|
8
|
+
.description('sign in to palexplorer.com using your desktop identity')
|
|
9
|
+
.option('--host <host>', 'Web server host', 'palexplorer.com')
|
|
10
|
+
.addHelpText('after', `
|
|
11
|
+
Examples:
|
|
12
|
+
$ pal web-login A7X-3K9 Sign in to palexplorer.com
|
|
13
|
+
$ pal web-login A7X-3K9 --host localhost:4000 Use local dev server
|
|
14
|
+
`)
|
|
15
|
+
.action(async (code, opts) => {
|
|
16
|
+
try {
|
|
17
|
+
const identity = await getIdentity();
|
|
18
|
+
if (!identity) {
|
|
19
|
+
console.log(chalk.red('No identity found. Run: pal init'));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const host = opts.host;
|
|
24
|
+
const protocol = host.startsWith('localhost') || host.startsWith('127.0.0.1') ? 'http' : 'https';
|
|
25
|
+
const baseUrl = `${protocol}://${host}`;
|
|
26
|
+
|
|
27
|
+
console.log(chalk.gray(`Resolving code ${code}...`));
|
|
28
|
+
|
|
29
|
+
const resolveRes = await fetch(`${baseUrl}/api/auth/qr/resolve`, {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
headers: { 'Content-Type': 'application/json' },
|
|
32
|
+
body: JSON.stringify({ code }),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (!resolveRes.ok) {
|
|
36
|
+
const data = await resolveRes.json().catch(() => ({}));
|
|
37
|
+
console.log(chalk.red(`Failed: ${data.error || 'Code not found or expired'}`));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const { sessionId, challenge } = await resolveRes.json();
|
|
42
|
+
|
|
43
|
+
console.log(chalk.gray('Signing challenge...'));
|
|
44
|
+
|
|
45
|
+
let sodium;
|
|
46
|
+
try {
|
|
47
|
+
sodium = (await import('sodium-native')).default;
|
|
48
|
+
} catch {
|
|
49
|
+
sodium = await import('sodium-native');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const sig = Buffer.alloc(sodium.crypto_sign_BYTES);
|
|
53
|
+
const challengeBuf = Buffer.from(challenge, 'hex');
|
|
54
|
+
const privateKey = Buffer.from(identity.privateKey, 'hex');
|
|
55
|
+
sodium.crypto_sign_detached(sig, challengeBuf, privateKey);
|
|
56
|
+
|
|
57
|
+
const verifyRes = await fetch(`${baseUrl}/api/auth/qr/verify`, {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
headers: { 'Content-Type': 'application/json' },
|
|
60
|
+
body: JSON.stringify({
|
|
61
|
+
sessionId,
|
|
62
|
+
publicKey: identity.publicKey,
|
|
63
|
+
signature: sig.toString('hex'),
|
|
64
|
+
}),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (!verifyRes.ok) {
|
|
68
|
+
const data = await verifyRes.json().catch(() => ({}));
|
|
69
|
+
console.log(chalk.red(`Verification failed: ${data.error || 'Unknown error'}`));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log(chalk.green('Signed in to web successfully!'));
|
|
74
|
+
console.log(chalk.gray('Your browser should update automatically.'));
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.log(chalk.red(`Error: ${err.message}`));
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
package/lib/commands/web.js
CHANGED
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { exec } from 'child_process';
|
|
3
|
-
import config from '../utils/config.js';
|
|
4
|
-
|
|
5
|
-
export default function webCommand(program) {
|
|
6
|
-
program
|
|
7
|
-
.command('web')
|
|
8
|
-
.description('open the web dashboard in your browser')
|
|
9
|
-
.option('--port <port>', 'Web dashboard port', '8585')
|
|
10
|
-
.option('--url-only', 'Just print the URL, do not open browser')
|
|
11
|
-
.addHelpText('after', `
|
|
12
|
-
Examples:
|
|
13
|
-
$
|
|
14
|
-
$
|
|
15
|
-
$
|
|
16
|
-
`)
|
|
17
|
-
.action(async (opts) => {
|
|
18
|
-
try {
|
|
19
|
-
const token = config.get('webDashboardToken');
|
|
20
|
-
if (!token) {
|
|
21
|
-
console.log(chalk.yellow('No web dashboard token found. Start the daemon with --web first:'));
|
|
22
|
-
console.log(chalk.cyan('
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const port = opts.port;
|
|
27
|
-
const url = `http://localhost:${port}?token=${token}`;
|
|
28
|
-
|
|
29
|
-
if (opts.urlOnly) {
|
|
30
|
-
console.log(url);
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
console.log(chalk.blue('Opening web dashboard...'));
|
|
35
|
-
console.log(chalk.cyan(` URL: ${url}`));
|
|
36
|
-
console.log(chalk.gray(` Token: ${token}`));
|
|
37
|
-
|
|
38
|
-
const platform = process.platform;
|
|
39
|
-
const cmd = platform === 'win32' ? `start "" "${url}"` :
|
|
40
|
-
platform === 'darwin' ? `open "${url}"` : `xdg-open "${url}"`;
|
|
41
|
-
|
|
42
|
-
exec(cmd, (err) => {
|
|
43
|
-
if (err) {
|
|
44
|
-
console.log(chalk.yellow('Could not open browser automatically. Visit the URL above.'));
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
} catch (err) {
|
|
48
|
-
console.error(chalk.red('Web dashboard error:'), err.message);
|
|
49
|
-
process.exitCode = 1;
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
}
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { exec } from 'child_process';
|
|
3
|
+
import config from '../utils/config.js';
|
|
4
|
+
|
|
5
|
+
export default function webCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('web')
|
|
8
|
+
.description('open the web dashboard in your browser')
|
|
9
|
+
.option('--port <port>', 'Web dashboard port', '8585')
|
|
10
|
+
.option('--url-only', 'Just print the URL, do not open browser')
|
|
11
|
+
.addHelpText('after', `
|
|
12
|
+
Examples:
|
|
13
|
+
$ pal web Open web dashboard in browser
|
|
14
|
+
$ pal web --url-only Just print the dashboard URL
|
|
15
|
+
$ pal web --port 9090 Use custom port
|
|
16
|
+
`)
|
|
17
|
+
.action(async (opts) => {
|
|
18
|
+
try {
|
|
19
|
+
const token = config.get('webDashboardToken');
|
|
20
|
+
if (!token) {
|
|
21
|
+
console.log(chalk.yellow('No web dashboard token found. Start the daemon with --web first:'));
|
|
22
|
+
console.log(chalk.cyan(' pal serve --web'));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const port = opts.port;
|
|
27
|
+
const url = `http://localhost:${port}?token=${token}`;
|
|
28
|
+
|
|
29
|
+
if (opts.urlOnly) {
|
|
30
|
+
console.log(url);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log(chalk.blue('Opening web dashboard...'));
|
|
35
|
+
console.log(chalk.cyan(` URL: ${url}`));
|
|
36
|
+
console.log(chalk.gray(` Token: ${token}`));
|
|
37
|
+
|
|
38
|
+
const platform = process.platform;
|
|
39
|
+
const cmd = platform === 'win32' ? `start "" "${url}"` :
|
|
40
|
+
platform === 'darwin' ? `open "${url}"` : `xdg-open "${url}"`;
|
|
41
|
+
|
|
42
|
+
exec(cmd, (err) => {
|
|
43
|
+
if (err) {
|
|
44
|
+
console.log(chalk.yellow('Could not open browser automatically. Visit the URL above.'));
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
} catch (err) {
|
|
48
|
+
console.error(chalk.red('Web dashboard error:'), err.message);
|
|
49
|
+
process.exitCode = 1;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|