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.
- package/README.md +149 -149
- package/bin/pal.js +63 -2
- 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 +203 -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/help.js +357 -357
- package/lib/utils/torrent.js +1 -0
- package/package.json +4 -3
package/lib/commands/init.js
CHANGED
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
import { createIdentity } from '../core/identity.js';
|
|
2
|
-
import { addProfile, getProfiles, migrateToMultiUser } from '../core/users.js';
|
|
3
|
-
import config from '../utils/config.js';
|
|
4
|
-
import chalk from 'chalk';
|
|
5
|
-
import ora from 'ora';
|
|
6
|
-
|
|
7
|
-
export default function initCommand(program) {
|
|
8
|
-
program
|
|
9
|
-
.command('init <name>')
|
|
10
|
-
.description('create a new Pal Explorer identity with a name')
|
|
11
|
-
.option('-f, --force', 'Overwrite existing identity')
|
|
12
|
-
.addHelpText('after', `
|
|
13
|
-
Examples:
|
|
14
|
-
$
|
|
15
|
-
$
|
|
16
|
-
`)
|
|
17
|
-
.action(async (name, options) => {
|
|
18
|
-
const spinner = ora('Initializing Identity...').start();
|
|
19
|
-
try {
|
|
20
|
-
const id = await createIdentity(name, options);
|
|
21
|
-
|
|
22
|
-
// Create user profile (owner if first user, otherwise member)
|
|
23
|
-
migrateToMultiUser();
|
|
24
|
-
const profiles = getProfiles();
|
|
25
|
-
const isFirst = profiles.length === 0 || (profiles.length === 1 && profiles[0].publicKey === id.publicKey);
|
|
26
|
-
const role = isFirst ? 'owner' : 'user';
|
|
27
|
-
try {
|
|
28
|
-
addProfile(id.publicKey, null, name, role);
|
|
29
|
-
} catch {
|
|
30
|
-
// Profile may already exist from migration
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
spinner.succeed(chalk.green(`Identity Created for '${name}'!`));
|
|
34
|
-
console.log(chalk.cyan(`Your Public Key (ID): ${id.publicKey}`));
|
|
35
|
-
if (role === 'owner') {
|
|
36
|
-
console.log(chalk.yellow(`Role: Owner (full control)`));
|
|
37
|
-
}
|
|
38
|
-
console.log(chalk.gray('Next: Run `
|
|
39
|
-
console.log('');
|
|
40
|
-
console.log(chalk.bgYellow.black(' RECOVERY PHRASE — WRITE THIS DOWN AND KEEP IT SAFE '));
|
|
41
|
-
console.log(chalk.yellow(id.mnemonic));
|
|
42
|
-
console.log(chalk.red('This will NOT be shown again. Use `
|
|
43
|
-
|
|
44
|
-
// Opt-in to error reporting (first-time setup)
|
|
45
|
-
const settings = config.get('settings') || {};
|
|
46
|
-
if (settings.errorReportingEnabled === undefined) {
|
|
47
|
-
console.log('');
|
|
48
|
-
console.log(chalk.cyan('Help improve Palexplorer?'));
|
|
49
|
-
console.log(chalk.gray('Automatically send anonymous crash reports and errors to help us fix bugs.'));
|
|
50
|
-
console.log(chalk.gray('No personal data, files, or encryption keys are ever sent.'));
|
|
51
|
-
console.log(chalk.gray('You can change this anytime:
|
|
52
|
-
settings.errorReportingEnabled = true;
|
|
53
|
-
config.set('settings', settings);
|
|
54
|
-
console.log(chalk.green('✔ Error reporting enabled (opt-out:
|
|
55
|
-
}
|
|
56
|
-
} catch (err) {
|
|
57
|
-
spinner.fail(chalk.red('Initialization Failed'));
|
|
58
|
-
console.error(chalk.red(`Error: ${err.message}`));
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
}
|
|
1
|
+
import { createIdentity } from '../core/identity.js';
|
|
2
|
+
import { addProfile, getProfiles, migrateToMultiUser } from '../core/users.js';
|
|
3
|
+
import config from '../utils/config.js';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
|
|
7
|
+
export default function initCommand(program) {
|
|
8
|
+
program
|
|
9
|
+
.command('init <name>')
|
|
10
|
+
.description('create a new Pal Explorer identity with a name')
|
|
11
|
+
.option('-f, --force', 'Overwrite existing identity')
|
|
12
|
+
.addHelpText('after', `
|
|
13
|
+
Examples:
|
|
14
|
+
$ pal init alice Create identity as "alice"
|
|
15
|
+
$ pal init bob --force Overwrite existing identity
|
|
16
|
+
`)
|
|
17
|
+
.action(async (name, options) => {
|
|
18
|
+
const spinner = ora('Initializing Identity...').start();
|
|
19
|
+
try {
|
|
20
|
+
const id = await createIdentity(name, options);
|
|
21
|
+
|
|
22
|
+
// Create user profile (owner if first user, otherwise member)
|
|
23
|
+
migrateToMultiUser();
|
|
24
|
+
const profiles = getProfiles();
|
|
25
|
+
const isFirst = profiles.length === 0 || (profiles.length === 1 && profiles[0].publicKey === id.publicKey);
|
|
26
|
+
const role = isFirst ? 'owner' : 'user';
|
|
27
|
+
try {
|
|
28
|
+
addProfile(id.publicKey, null, name, role);
|
|
29
|
+
} catch {
|
|
30
|
+
// Profile may already exist from migration
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
spinner.succeed(chalk.green(`Identity Created for '${name}'!`));
|
|
34
|
+
console.log(chalk.cyan(`Your Public Key (ID): ${id.publicKey}`));
|
|
35
|
+
if (role === 'owner') {
|
|
36
|
+
console.log(chalk.yellow(`Role: Owner (full control)`));
|
|
37
|
+
}
|
|
38
|
+
console.log(chalk.gray('Next: Run `pal register <handle>` to claim your handle.'));
|
|
39
|
+
console.log('');
|
|
40
|
+
console.log(chalk.bgYellow.black(' RECOVERY PHRASE — WRITE THIS DOWN AND KEEP IT SAFE '));
|
|
41
|
+
console.log(chalk.yellow(id.mnemonic));
|
|
42
|
+
console.log(chalk.red('This will NOT be shown again. Use `pal recover` to restore your identity.'));
|
|
43
|
+
|
|
44
|
+
// Opt-in to error reporting (first-time setup)
|
|
45
|
+
const settings = config.get('settings') || {};
|
|
46
|
+
if (settings.errorReportingEnabled === undefined) {
|
|
47
|
+
console.log('');
|
|
48
|
+
console.log(chalk.cyan('Help improve Palexplorer?'));
|
|
49
|
+
console.log(chalk.gray('Automatically send anonymous crash reports and errors to help us fix bugs.'));
|
|
50
|
+
console.log(chalk.gray('No personal data, files, or encryption keys are ever sent.'));
|
|
51
|
+
console.log(chalk.gray('You can change this anytime: pal config set errorReportingEnabled false'));
|
|
52
|
+
settings.errorReportingEnabled = true;
|
|
53
|
+
config.set('settings', settings);
|
|
54
|
+
console.log(chalk.green('✔ Error reporting enabled (opt-out: pal config set errorReportingEnabled false)'));
|
|
55
|
+
}
|
|
56
|
+
} catch (err) {
|
|
57
|
+
spinner.fail(chalk.red('Initialization Failed'));
|
|
58
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
package/lib/commands/invite.js
CHANGED
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import qrcode from 'qrcode-terminal';
|
|
3
|
-
import { getIdentity } from '../core/identity.js';
|
|
4
|
-
|
|
5
|
-
export function encodeInvite(identity) {
|
|
6
|
-
const payload = JSON.stringify({
|
|
7
|
-
pk: identity.publicKey,
|
|
8
|
-
h: identity.handle || null,
|
|
9
|
-
n: identity.name || null
|
|
10
|
-
});
|
|
11
|
-
return 'pal://' + Buffer.from(payload).toString('base64url');
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function decodeInvite(url) {
|
|
15
|
-
if (!url.startsWith('pal://')) return null;
|
|
16
|
-
try {
|
|
17
|
-
const raw = Buffer.from(url.slice(6), 'base64url').toString('utf8');
|
|
18
|
-
const parsed = JSON.parse(raw);
|
|
19
|
-
if (!parsed.pk || typeof parsed.pk !== 'string') return null;
|
|
20
|
-
return parsed;
|
|
21
|
-
} catch {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export default function inviteCommand(program) {
|
|
27
|
-
program
|
|
28
|
-
.command('invite')
|
|
29
|
-
.description('generate a magic invite link so others can add you as a pal')
|
|
30
|
-
.option('--qr', 'Also render the invite as a QR code in the terminal')
|
|
31
|
-
.addHelpText('after', `
|
|
32
|
-
Examples:
|
|
33
|
-
$
|
|
34
|
-
$
|
|
35
|
-
`)
|
|
36
|
-
.action(async (opts) => {
|
|
37
|
-
const identity = await getIdentity();
|
|
38
|
-
if (!identity) {
|
|
39
|
-
console.log(chalk.red('No identity found. Run `
|
|
40
|
-
process.exitCode = 1;
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const url = encodeInvite(identity);
|
|
45
|
-
|
|
46
|
-
console.log('');
|
|
47
|
-
console.log(chalk.cyan('Your invite link:'));
|
|
48
|
-
console.log(chalk.white(url));
|
|
49
|
-
console.log('');
|
|
50
|
-
console.log(chalk.gray('Share this over any channel. Recipient runs:'));
|
|
51
|
-
console.log(chalk.white(`
|
|
52
|
-
|
|
53
|
-
if (opts.qr) {
|
|
54
|
-
console.log('');
|
|
55
|
-
console.log(chalk.cyan('QR Code:'));
|
|
56
|
-
qrcode.generate(url, { small: true });
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
}
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import qrcode from 'qrcode-terminal';
|
|
3
|
+
import { getIdentity } from '../core/identity.js';
|
|
4
|
+
|
|
5
|
+
export function encodeInvite(identity) {
|
|
6
|
+
const payload = JSON.stringify({
|
|
7
|
+
pk: identity.publicKey,
|
|
8
|
+
h: identity.handle || null,
|
|
9
|
+
n: identity.name || null
|
|
10
|
+
});
|
|
11
|
+
return 'pal://' + Buffer.from(payload).toString('base64url');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function decodeInvite(url) {
|
|
15
|
+
if (!url.startsWith('pal://')) return null;
|
|
16
|
+
try {
|
|
17
|
+
const raw = Buffer.from(url.slice(6), 'base64url').toString('utf8');
|
|
18
|
+
const parsed = JSON.parse(raw);
|
|
19
|
+
if (!parsed.pk || typeof parsed.pk !== 'string') return null;
|
|
20
|
+
return parsed;
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default function inviteCommand(program) {
|
|
27
|
+
program
|
|
28
|
+
.command('invite')
|
|
29
|
+
.description('generate a magic invite link so others can add you as a pal')
|
|
30
|
+
.option('--qr', 'Also render the invite as a QR code in the terminal')
|
|
31
|
+
.addHelpText('after', `
|
|
32
|
+
Examples:
|
|
33
|
+
$ pal invite Generate invite link
|
|
34
|
+
$ pal invite --qr Show invite as QR code
|
|
35
|
+
`)
|
|
36
|
+
.action(async (opts) => {
|
|
37
|
+
const identity = await getIdentity();
|
|
38
|
+
if (!identity) {
|
|
39
|
+
console.log(chalk.red('No identity found. Run `pal init <name>` first.'));
|
|
40
|
+
process.exitCode = 1;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const url = encodeInvite(identity);
|
|
45
|
+
|
|
46
|
+
console.log('');
|
|
47
|
+
console.log(chalk.cyan('Your invite link:'));
|
|
48
|
+
console.log(chalk.white(url));
|
|
49
|
+
console.log('');
|
|
50
|
+
console.log(chalk.gray('Share this over any channel. Recipient runs:'));
|
|
51
|
+
console.log(chalk.white(` pal pal add ${url}`));
|
|
52
|
+
|
|
53
|
+
if (opts.qr) {
|
|
54
|
+
console.log('');
|
|
55
|
+
console.log(chalk.cyan('QR Code:'));
|
|
56
|
+
qrcode.generate(url, { small: true });
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
package/lib/commands/list.js
CHANGED
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { listShares, getSharesByDrive } from '../core/shares.js';
|
|
3
|
-
|
|
4
|
-
export default function listCommand(program) {
|
|
5
|
-
program
|
|
6
|
-
.command('list')
|
|
7
|
-
.description('list shared resources')
|
|
8
|
-
.option('--by-drive', 'Group shares by drive')
|
|
9
|
-
.addHelpText('after', `
|
|
10
|
-
Examples:
|
|
11
|
-
$
|
|
12
|
-
`)
|
|
13
|
-
.action((options) => {
|
|
14
|
-
const shares = listShares();
|
|
15
|
-
if (program.opts().json) {
|
|
16
|
-
const safe = shares.map(s => {
|
|
17
|
-
const { password, encryptedShareKeys, ...rest } = s;
|
|
18
|
-
return { ...rest, password: password ? '***' : undefined };
|
|
19
|
-
});
|
|
20
|
-
console.log(JSON.stringify(safe, null, 2));
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
if (shares.length === 0) {
|
|
24
|
-
console.log(chalk.gray('No shared resources found. Use `
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (options.byDrive) {
|
|
29
|
-
const byDrive = getSharesByDrive();
|
|
30
|
-
Object.keys(byDrive).forEach(drive => {
|
|
31
|
-
console.log(''); // New line
|
|
32
|
-
console.log(chalk.yellow(`Drive: ${drive}`));
|
|
33
|
-
byDrive[drive].forEach(share => {
|
|
34
|
-
const vis = share.visibility || 'global';
|
|
35
|
-
const visColors = { global: 'green', public: 'green', private: 'red', group: 'blue', network: 'cyan', 'link-only': 'yellow' };
|
|
36
|
-
const visibilityStr = chalk[visColors[vis] || 'white'](`[${vis.toUpperCase()}]`);
|
|
37
|
-
const streamTag = share.streamable ? chalk.magenta(' [STREAM]') : '';
|
|
38
|
-
const expiredTag = share.expired ? chalk.red(' [EXPIRED]') : '';
|
|
39
|
-
console.log(` - ${visibilityStr}${streamTag}${expiredTag} [${chalk.cyan(share.id)}] ${share.path} (${chalk.gray(share.type)})`);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
} else {
|
|
43
|
-
console.log(''); // New line
|
|
44
|
-
console.log(chalk.green('Active Shares:'));
|
|
45
|
-
shares.forEach(share => {
|
|
46
|
-
const vis = share.visibility || 'global';
|
|
47
|
-
const visColors = { global: 'green', public: 'green', private: 'red', group: 'blue', network: 'cyan', 'link-only': 'yellow' };
|
|
48
|
-
const visibilityStr = chalk[visColors[vis] || 'white'](`[${vis.toUpperCase()}]`);
|
|
49
|
-
const streamTag = share.streamable ? chalk.magenta(' [STREAM]') : '';
|
|
50
|
-
const recipCount = (share.recipients || []).length;
|
|
51
|
-
const recipStr = recipCount > 0 ? chalk.gray(` → ${recipCount} recipient${recipCount > 1 ? 's' : ''}`) : '';
|
|
52
|
-
const expiredTag = share.expired ? chalk.red(' [EXPIRED]') : '';
|
|
53
|
-
const expiryStr = share.expiresAt && !share.expired ? chalk.gray(` expires ${new Date(share.expiresAt).toLocaleDateString()}`) : '';
|
|
54
|
-
const dlStr = share.maxDownloads ? chalk.gray(` ${share.downloads || 0}/${share.maxDownloads} downloads`) : '';
|
|
55
|
-
console.log(`- ${visibilityStr}${streamTag}${expiredTag} [${chalk.cyan(share.id)}] ${share.path} (${chalk.gray(share.type)})${recipStr}${expiryStr}${dlStr}`);
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
});
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { listShares, getSharesByDrive } from '../core/shares.js';
|
|
3
|
+
|
|
4
|
+
export default function listCommand(program) {
|
|
5
|
+
program
|
|
6
|
+
.command('list')
|
|
7
|
+
.description('list shared resources')
|
|
8
|
+
.option('--by-drive', 'Group shares by drive')
|
|
9
|
+
.addHelpText('after', `
|
|
10
|
+
Examples:
|
|
11
|
+
$ pal list List all shared resources
|
|
12
|
+
`)
|
|
13
|
+
.action((options) => {
|
|
14
|
+
const shares = listShares();
|
|
15
|
+
if (program.opts().json) {
|
|
16
|
+
const safe = shares.map(s => {
|
|
17
|
+
const { password, encryptedShareKeys, ...rest } = s;
|
|
18
|
+
return { ...rest, password: password ? '***' : undefined };
|
|
19
|
+
});
|
|
20
|
+
console.log(JSON.stringify(safe, null, 2));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (shares.length === 0) {
|
|
24
|
+
console.log(chalk.gray('No shared resources found. Use `pal share <path>` to add one.'));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (options.byDrive) {
|
|
29
|
+
const byDrive = getSharesByDrive();
|
|
30
|
+
Object.keys(byDrive).forEach(drive => {
|
|
31
|
+
console.log(''); // New line
|
|
32
|
+
console.log(chalk.yellow(`Drive: ${drive}`));
|
|
33
|
+
byDrive[drive].forEach(share => {
|
|
34
|
+
const vis = share.visibility || 'global';
|
|
35
|
+
const visColors = { global: 'green', public: 'green', private: 'red', group: 'blue', network: 'cyan', 'link-only': 'yellow' };
|
|
36
|
+
const visibilityStr = chalk[visColors[vis] || 'white'](`[${vis.toUpperCase()}]`);
|
|
37
|
+
const streamTag = share.streamable ? chalk.magenta(' [STREAM]') : '';
|
|
38
|
+
const expiredTag = share.expired ? chalk.red(' [EXPIRED]') : '';
|
|
39
|
+
console.log(` - ${visibilityStr}${streamTag}${expiredTag} [${chalk.cyan(share.id)}] ${share.path} (${chalk.gray(share.type)})`);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
} else {
|
|
43
|
+
console.log(''); // New line
|
|
44
|
+
console.log(chalk.green('Active Shares:'));
|
|
45
|
+
shares.forEach(share => {
|
|
46
|
+
const vis = share.visibility || 'global';
|
|
47
|
+
const visColors = { global: 'green', public: 'green', private: 'red', group: 'blue', network: 'cyan', 'link-only': 'yellow' };
|
|
48
|
+
const visibilityStr = chalk[visColors[vis] || 'white'](`[${vis.toUpperCase()}]`);
|
|
49
|
+
const streamTag = share.streamable ? chalk.magenta(' [STREAM]') : '';
|
|
50
|
+
const recipCount = (share.recipients || []).length;
|
|
51
|
+
const recipStr = recipCount > 0 ? chalk.gray(` → ${recipCount} recipient${recipCount > 1 ? 's' : ''}`) : '';
|
|
52
|
+
const expiredTag = share.expired ? chalk.red(' [EXPIRED]') : '';
|
|
53
|
+
const expiryStr = share.expiresAt && !share.expired ? chalk.gray(` expires ${new Date(share.expiresAt).toLocaleDateString()}`) : '';
|
|
54
|
+
const dlStr = share.maxDownloads ? chalk.gray(` ${share.downloads || 0}/${share.maxDownloads} downloads`) : '';
|
|
55
|
+
console.log(`- ${visibilityStr}${streamTag}${expiredTag} [${chalk.cyan(share.id)}] ${share.path} (${chalk.gray(share.type)})${recipStr}${expiryStr}${dlStr}`);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
59
|
}
|
package/lib/commands/log.js
CHANGED
|
@@ -1,116 +1,116 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import os from 'os';
|
|
5
|
-
|
|
6
|
-
const LOG_DIR = path.join(os.homedir(), '.pal', 'logs');
|
|
7
|
-
const LOG_FILE = path.join(LOG_DIR, 'pal.log');
|
|
8
|
-
|
|
9
|
-
function colorLevel(level) {
|
|
10
|
-
switch (level) {
|
|
11
|
-
case 'error': return chalk.red(level);
|
|
12
|
-
case 'warn': return chalk.yellow(level);
|
|
13
|
-
case 'info': return chalk.blue(level);
|
|
14
|
-
case 'debug': return chalk.gray(level);
|
|
15
|
-
default: return level;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function formatEntry(line) {
|
|
20
|
-
try {
|
|
21
|
-
const entry = JSON.parse(line);
|
|
22
|
-
const ts = entry.ts?.slice(11, 19) || '';
|
|
23
|
-
return `${chalk.gray(ts)} ${colorLevel(entry.level).padEnd(15)} ${entry.msg}`;
|
|
24
|
-
} catch {
|
|
25
|
-
return line;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export default function logCommand(program) {
|
|
30
|
-
program
|
|
31
|
-
.command('log')
|
|
32
|
-
.description('view application logs')
|
|
33
|
-
.option('--tail', 'Follow new log entries')
|
|
34
|
-
.option('--level <level>', 'Filter by level (debug/info/warn/error)')
|
|
35
|
-
.option('-n, --lines <count>', 'Number of recent lines to show', '20')
|
|
36
|
-
.option('--clear', 'Clear the log file')
|
|
37
|
-
.addHelpText('after', `
|
|
38
|
-
Examples:
|
|
39
|
-
$
|
|
40
|
-
$
|
|
41
|
-
$
|
|
42
|
-
$
|
|
43
|
-
`)
|
|
44
|
-
.action(async (opts) => {
|
|
45
|
-
try {
|
|
46
|
-
if (opts.clear) {
|
|
47
|
-
if (fs.existsSync(LOG_FILE)) {
|
|
48
|
-
fs.writeFileSync(LOG_FILE, '');
|
|
49
|
-
console.log(chalk.green('✔ Log file cleared.'));
|
|
50
|
-
} else {
|
|
51
|
-
console.log(chalk.gray('No log file to clear.'));
|
|
52
|
-
}
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (!fs.existsSync(LOG_FILE)) {
|
|
57
|
-
console.log(chalk.gray('No log file found. Logs will appear after using commands.'));
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const content = fs.readFileSync(LOG_FILE, 'utf8').trim();
|
|
62
|
-
if (!content) {
|
|
63
|
-
console.log(chalk.gray('Log file is empty.'));
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
let lines = content.split('\n');
|
|
68
|
-
|
|
69
|
-
if (opts.level) {
|
|
70
|
-
lines = lines.filter(line => {
|
|
71
|
-
try {
|
|
72
|
-
const entry = JSON.parse(line);
|
|
73
|
-
return entry.level === opts.level;
|
|
74
|
-
} catch { return true; }
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const count = parseInt(opts.lines, 10) || 20;
|
|
79
|
-
lines = lines.slice(-count);
|
|
80
|
-
|
|
81
|
-
for (const line of lines) {
|
|
82
|
-
console.log(formatEntry(line));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (opts.tail) {
|
|
86
|
-
console.log(chalk.gray('--- tailing (Ctrl+C to stop) ---'));
|
|
87
|
-
let size = fs.statSync(LOG_FILE).size;
|
|
88
|
-
const watcher = fs.watch(LOG_FILE, () => {
|
|
89
|
-
try {
|
|
90
|
-
const newSize = fs.statSync(LOG_FILE).size;
|
|
91
|
-
if (newSize <= size) { size = newSize; return; }
|
|
92
|
-
const fd = fs.openSync(LOG_FILE, 'r');
|
|
93
|
-
const buf = Buffer.alloc(newSize - size);
|
|
94
|
-
fs.readSync(fd, buf, 0, buf.length, size);
|
|
95
|
-
fs.closeSync(fd);
|
|
96
|
-
size = newSize;
|
|
97
|
-
const newLines = buf.toString().trim().split('\n');
|
|
98
|
-
for (const line of newLines) {
|
|
99
|
-
if (opts.level) {
|
|
100
|
-
try {
|
|
101
|
-
const entry = JSON.parse(line);
|
|
102
|
-
if (entry.level !== opts.level) continue;
|
|
103
|
-
} catch { /* show anyway */ }
|
|
104
|
-
}
|
|
105
|
-
console.log(formatEntry(line));
|
|
106
|
-
}
|
|
107
|
-
} catch { /* ignore */ }
|
|
108
|
-
});
|
|
109
|
-
await new Promise(() => {}); // block forever
|
|
110
|
-
}
|
|
111
|
-
} catch (err) {
|
|
112
|
-
console.error(chalk.red('Log viewer error:'), err.message);
|
|
113
|
-
process.exitCode = 1;
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
}
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
|
|
6
|
+
const LOG_DIR = path.join(os.homedir(), '.pal', 'logs');
|
|
7
|
+
const LOG_FILE = path.join(LOG_DIR, 'pal.log');
|
|
8
|
+
|
|
9
|
+
function colorLevel(level) {
|
|
10
|
+
switch (level) {
|
|
11
|
+
case 'error': return chalk.red(level);
|
|
12
|
+
case 'warn': return chalk.yellow(level);
|
|
13
|
+
case 'info': return chalk.blue(level);
|
|
14
|
+
case 'debug': return chalk.gray(level);
|
|
15
|
+
default: return level;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function formatEntry(line) {
|
|
20
|
+
try {
|
|
21
|
+
const entry = JSON.parse(line);
|
|
22
|
+
const ts = entry.ts?.slice(11, 19) || '';
|
|
23
|
+
return `${chalk.gray(ts)} ${colorLevel(entry.level).padEnd(15)} ${entry.msg}`;
|
|
24
|
+
} catch {
|
|
25
|
+
return line;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default function logCommand(program) {
|
|
30
|
+
program
|
|
31
|
+
.command('log')
|
|
32
|
+
.description('view application logs')
|
|
33
|
+
.option('--tail', 'Follow new log entries')
|
|
34
|
+
.option('--level <level>', 'Filter by level (debug/info/warn/error)')
|
|
35
|
+
.option('-n, --lines <count>', 'Number of recent lines to show', '20')
|
|
36
|
+
.option('--clear', 'Clear the log file')
|
|
37
|
+
.addHelpText('after', `
|
|
38
|
+
Examples:
|
|
39
|
+
$ pal log Show recent log entries
|
|
40
|
+
$ pal log --tail Follow log in real-time
|
|
41
|
+
$ pal log --level error Filter by log level
|
|
42
|
+
$ pal log -n 50 Show last 50 entries
|
|
43
|
+
`)
|
|
44
|
+
.action(async (opts) => {
|
|
45
|
+
try {
|
|
46
|
+
if (opts.clear) {
|
|
47
|
+
if (fs.existsSync(LOG_FILE)) {
|
|
48
|
+
fs.writeFileSync(LOG_FILE, '');
|
|
49
|
+
console.log(chalk.green('✔ Log file cleared.'));
|
|
50
|
+
} else {
|
|
51
|
+
console.log(chalk.gray('No log file to clear.'));
|
|
52
|
+
}
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!fs.existsSync(LOG_FILE)) {
|
|
57
|
+
console.log(chalk.gray('No log file found. Logs will appear after using commands.'));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const content = fs.readFileSync(LOG_FILE, 'utf8').trim();
|
|
62
|
+
if (!content) {
|
|
63
|
+
console.log(chalk.gray('Log file is empty.'));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let lines = content.split('\n');
|
|
68
|
+
|
|
69
|
+
if (opts.level) {
|
|
70
|
+
lines = lines.filter(line => {
|
|
71
|
+
try {
|
|
72
|
+
const entry = JSON.parse(line);
|
|
73
|
+
return entry.level === opts.level;
|
|
74
|
+
} catch { return true; }
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const count = parseInt(opts.lines, 10) || 20;
|
|
79
|
+
lines = lines.slice(-count);
|
|
80
|
+
|
|
81
|
+
for (const line of lines) {
|
|
82
|
+
console.log(formatEntry(line));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (opts.tail) {
|
|
86
|
+
console.log(chalk.gray('--- tailing (Ctrl+C to stop) ---'));
|
|
87
|
+
let size = fs.statSync(LOG_FILE).size;
|
|
88
|
+
const watcher = fs.watch(LOG_FILE, () => {
|
|
89
|
+
try {
|
|
90
|
+
const newSize = fs.statSync(LOG_FILE).size;
|
|
91
|
+
if (newSize <= size) { size = newSize; return; }
|
|
92
|
+
const fd = fs.openSync(LOG_FILE, 'r');
|
|
93
|
+
const buf = Buffer.alloc(newSize - size);
|
|
94
|
+
fs.readSync(fd, buf, 0, buf.length, size);
|
|
95
|
+
fs.closeSync(fd);
|
|
96
|
+
size = newSize;
|
|
97
|
+
const newLines = buf.toString().trim().split('\n');
|
|
98
|
+
for (const line of newLines) {
|
|
99
|
+
if (opts.level) {
|
|
100
|
+
try {
|
|
101
|
+
const entry = JSON.parse(line);
|
|
102
|
+
if (entry.level !== opts.level) continue;
|
|
103
|
+
} catch { /* show anyway */ }
|
|
104
|
+
}
|
|
105
|
+
console.log(formatEntry(line));
|
|
106
|
+
}
|
|
107
|
+
} catch { /* ignore */ }
|
|
108
|
+
});
|
|
109
|
+
await new Promise(() => {}); // block forever
|
|
110
|
+
}
|
|
111
|
+
} catch (err) {
|
|
112
|
+
console.error(chalk.red('Log viewer error:'), err.message);
|
|
113
|
+
process.exitCode = 1;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|