@super-protocol/sp-cli 0.0.5 → 0.0.7
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 +97 -229
- package/dist/commands/account/base.d.ts +20 -0
- package/dist/commands/account/base.js +49 -0
- package/dist/commands/{config/delete.d.ts → account/forget.d.ts} +5 -2
- package/dist/commands/{config/delete.js → account/forget.js} +12 -5
- package/dist/commands/account/get-sppi.d.ts +2 -2
- package/dist/commands/account/get-sppi.js +8 -13
- package/dist/commands/account/info.d.ts +5 -2
- package/dist/commands/account/info.js +53 -3
- package/dist/commands/account/list.d.ts +9 -0
- package/dist/commands/{config → account}/list.js +16 -9
- package/dist/commands/account/login.d.ts +35 -0
- package/dist/commands/account/login.js +254 -0
- package/dist/commands/account/switch.d.ts +12 -0
- package/dist/commands/account/switch.js +40 -0
- package/dist/commands/base.d.ts +4 -3
- package/dist/commands/base.js +3 -1
- package/dist/commands/files/download.js +13 -5
- package/dist/commands/files/upload.js +11 -7
- package/dist/commands/storage/base.d.ts +2 -2
- package/dist/commands/storage/create.js +1 -1
- package/dist/commands/storage/select.js +3 -5
- package/dist/commands/storage/show.js +2 -4
- package/dist/commands/storage/update.js +31 -14
- package/dist/commands/workflows/extend-lease.d.ts +1 -1
- package/dist/commands/workflows/extend-lease.js +19 -10
- package/dist/config/config-file.schema.d.ts +1 -2
- package/dist/config/config-file.schema.js +0 -1
- package/dist/config/config.schema.d.ts +1 -1
- package/dist/errors.d.ts +2 -0
- package/dist/errors.js +2 -0
- package/dist/hooks/finally/shutdown-blockchain.d.ts +1 -1
- package/dist/hooks/finally/shutdown-blockchain.js +1 -1
- package/dist/hooks/finally/unlock-config.d.ts +3 -0
- package/dist/hooks/finally/unlock-config.js +5 -0
- package/dist/hooks/init/init-container.d.ts +1 -1
- package/dist/hooks/init/init-container.js +2 -3
- package/dist/hooks/init/lock-config.d.ts +3 -0
- package/dist/hooks/init/lock-config.js +19 -0
- package/dist/hooks/prerun/auth.d.ts +3 -0
- package/dist/hooks/prerun/auth.js +70 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/interfaces/account-manager.interface.d.ts +1 -1
- package/dist/interfaces/config-manager.interface.d.ts +2 -2
- package/dist/lib/cli-instance-lock.d.ts +2 -0
- package/dist/lib/cli-instance-lock.js +37 -0
- package/dist/lib/container.d.ts +6 -4
- package/dist/lib/container.js +108 -38
- package/dist/logger.d.ts +1 -1
- package/dist/logger.js +4 -1
- package/dist/managers/account-manager.d.ts +7 -8
- package/dist/managers/account-manager.js +20 -45
- package/dist/managers/config-file-manager.d.ts +6 -7
- package/dist/managers/config-file-manager.js +35 -32
- package/dist/managers/config-manager.d.ts +5 -5
- package/dist/managers/config-manager.js +5 -2
- package/dist/middlewares/auth-middleware.d.ts +1 -1
- package/dist/services/auth.service.d.ts +10 -15
- package/dist/services/auth.service.js +71 -5
- package/dist/services/storage.service.d.ts +3 -3
- package/dist/services/storage.service.js +46 -19
- package/dist/utils/helper.d.ts +1 -0
- package/dist/utils/helper.js +15 -1
- package/oclif.manifest.json +258 -399
- package/package.json +18 -25
- package/dist/commands/auth/login.d.ts +0 -12
- package/dist/commands/auth/login.js +0 -50
- package/dist/commands/auth/me.d.ts +0 -5
- package/dist/commands/auth/me.js +0 -16
- package/dist/commands/config/add.d.ts +0 -13
- package/dist/commands/config/add.js +0 -73
- package/dist/commands/config/base.d.ts +0 -16
- package/dist/commands/config/base.js +0 -78
- package/dist/commands/config/create.d.ts +0 -11
- package/dist/commands/config/create.js +0 -41
- package/dist/commands/config/index.d.ts +0 -6
- package/dist/commands/config/index.js +0 -22
- package/dist/commands/config/list.d.ts +0 -6
- package/dist/commands/config/show.d.ts +0 -6
- package/dist/commands/config/show.js +0 -10
- package/dist/commands/config/use.d.ts +0 -6
- package/dist/commands/config/use.js +0 -27
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
|
-
import {
|
|
3
|
-
export default class
|
|
4
|
-
static
|
|
2
|
+
import { BaseAccountCommand } from './base.js';
|
|
3
|
+
export default class AccountForget extends BaseAccountCommand {
|
|
4
|
+
static authenticate = false;
|
|
5
|
+
static description = 'Purge all account details';
|
|
5
6
|
static examples = [
|
|
6
7
|
'<%= config.bin %> <%= command.id %>',
|
|
7
|
-
'<%= config.bin %> <%= command.id %> --name "My
|
|
8
|
-
'<%= config.bin %> <%= command.id %> --name "My
|
|
8
|
+
'<%= config.bin %> <%= command.id %> --name "My Account"',
|
|
9
|
+
'<%= config.bin %> <%= command.id %> --name "My Account" --force',
|
|
9
10
|
];
|
|
10
11
|
static flags = {
|
|
11
12
|
force: Flags.boolean({
|
|
@@ -18,6 +19,12 @@ export default class ConfigDelete extends BaseConfigCommand {
|
|
|
18
19
|
description: 'Configuration name to delete',
|
|
19
20
|
}),
|
|
20
21
|
};
|
|
22
|
+
configFileManager;
|
|
23
|
+
async init() {
|
|
24
|
+
await super.init();
|
|
25
|
+
await this.container.initConfigFileManager().build();
|
|
26
|
+
this.configFileManager = this.container.configFileManager;
|
|
27
|
+
}
|
|
21
28
|
async run() {
|
|
22
29
|
const { force, name } = this.flags;
|
|
23
30
|
await this.configFileManager.deleteConfigByName(name, force);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export default class AccountGetSppi extends
|
|
1
|
+
import { BaseAccountCommand } from './base.js';
|
|
2
|
+
export default class AccountGetSppi extends BaseAccountCommand<typeof AccountGetSppi> {
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
5
|
run(): Promise<{
|
|
@@ -1,23 +1,18 @@
|
|
|
1
1
|
import { formatEther } from 'viem';
|
|
2
|
-
import {
|
|
3
|
-
export default class AccountGetSppi extends
|
|
2
|
+
import { BaseAccountCommand } from './base.js';
|
|
3
|
+
export default class AccountGetSppi extends BaseAccountCommand {
|
|
4
4
|
static description = 'Get SPPI (Super Protocol incentive tokens) ';
|
|
5
|
-
static examples = [
|
|
6
|
-
'<%= config.bin %> <%= command.id %>',
|
|
7
|
-
];
|
|
5
|
+
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
8
6
|
async run() {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
.initConfigManager()
|
|
12
|
-
.initAccountManager()
|
|
13
|
-
.initProviderClient()
|
|
14
|
-
.build();
|
|
7
|
+
await this.initAccountContext();
|
|
8
|
+
const { providerClient } = this.container;
|
|
15
9
|
const { data, error } = await providerClient.POST('/api/faucet/request-tokens');
|
|
16
10
|
if (error) {
|
|
17
|
-
this.error(`
|
|
11
|
+
this.error(`Getting SPPI failed. Reason: ${error.message}`);
|
|
18
12
|
}
|
|
19
13
|
const tokens = formatEther(BigInt(data.amount || '0'));
|
|
20
|
-
this.log(
|
|
14
|
+
this.log('Tokens replenished');
|
|
15
|
+
this.log(`Current balance: ${tokens} SPPI`);
|
|
21
16
|
return { tokens };
|
|
22
17
|
}
|
|
23
18
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare class InfoCommand extends
|
|
1
|
+
import { BaseAccountCommand } from './base.js';
|
|
2
|
+
export declare class InfoCommand extends BaseAccountCommand<typeof InfoCommand> {
|
|
3
3
|
static description: string;
|
|
4
|
+
static flags: {
|
|
5
|
+
detail: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
6
|
+
};
|
|
4
7
|
init(): Promise<void>;
|
|
5
8
|
run(): Promise<{
|
|
6
9
|
address: string;
|
|
@@ -1,13 +1,22 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
1
2
|
import { formatEther } from 'viem';
|
|
3
|
+
import { PROVIDER_URL } from '../../constants.js';
|
|
2
4
|
import { StorageService } from '../../services/storage.service.js';
|
|
3
|
-
import {
|
|
4
|
-
export class InfoCommand extends
|
|
5
|
+
import { BaseAccountCommand } from './base.js';
|
|
6
|
+
export class InfoCommand extends BaseAccountCommand {
|
|
5
7
|
static description = 'Information about current authorized user';
|
|
8
|
+
static flags = {
|
|
9
|
+
detail: Flags.boolean({
|
|
10
|
+
default: false,
|
|
11
|
+
description: 'Detailed information about Account',
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
6
14
|
async init() {
|
|
7
15
|
await super.init();
|
|
8
|
-
await this.
|
|
16
|
+
await this.initAccountContext();
|
|
9
17
|
}
|
|
10
18
|
async run() {
|
|
19
|
+
const { flags } = await this.parse(InfoCommand);
|
|
11
20
|
const { providerClient } = this.container;
|
|
12
21
|
const storageService = new StorageService(providerClient, this.logger);
|
|
13
22
|
const { data, error } = await providerClient.GET('/api/auth/me');
|
|
@@ -36,6 +45,47 @@ export class InfoCommand extends BaseCommand {
|
|
|
36
45
|
this.log(` prefix: ${storage.prefix}`);
|
|
37
46
|
}
|
|
38
47
|
}
|
|
48
|
+
if (flags.detail) {
|
|
49
|
+
const { configFileManager } = this.container;
|
|
50
|
+
const current = configFileManager.getCurrentConfigFile();
|
|
51
|
+
const configDir = configFileManager.getConfigDir();
|
|
52
|
+
this.log('\n-----------------(DETAILS)-----------------------');
|
|
53
|
+
if (current) {
|
|
54
|
+
const configs = configFileManager.getConfigsWithNames();
|
|
55
|
+
const currentConfig = configs.find((c) => c.file === current);
|
|
56
|
+
const displayName = currentConfig?.name || current;
|
|
57
|
+
const configData = configFileManager.getConfigData(current);
|
|
58
|
+
this.log(`Current configuration: ${displayName}`);
|
|
59
|
+
this.log(`Configuration file: ${current}`);
|
|
60
|
+
this.log(`Configuration directory: ${configDir}`);
|
|
61
|
+
if (configData) {
|
|
62
|
+
this.log('\nConfiguration details:');
|
|
63
|
+
if (configData.providerUrl) {
|
|
64
|
+
this.log(` Provider URL: ${configData.providerUrl}`);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
this.log(` Provider URL: ${PROVIDER_URL} (using default)`);
|
|
68
|
+
}
|
|
69
|
+
if (configData.auth?.accessKey) {
|
|
70
|
+
this.log(' Authorization: Configured');
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
this.log(' Authorization: Unauthorized');
|
|
74
|
+
}
|
|
75
|
+
if (configData.account?.address) {
|
|
76
|
+
this.log(` Account: ${configData.account.address}`);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
this.log(' Account: Not configured');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
this.log('No configuration is currently set');
|
|
85
|
+
this.log(`Configuration directory: ${configDir}`);
|
|
86
|
+
this.log('Create a new configuration with: sp account login --privateKey "<PRIVATE_KEY>" --name "Account Name"');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
39
89
|
return {
|
|
40
90
|
address: data.address,
|
|
41
91
|
balance,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseCommand } from '../base.js';
|
|
2
|
+
export default class AccountListCommand extends BaseCommand<typeof AccountListCommand> {
|
|
3
|
+
static authenticate: boolean;
|
|
4
|
+
static description: string;
|
|
5
|
+
static examples: string[];
|
|
6
|
+
private configFileManager;
|
|
7
|
+
init(): Promise<void>;
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
import { printTable } from 'console-table-printer';
|
|
2
2
|
import { PROVIDER_URL } from '../../constants.js';
|
|
3
|
-
import {
|
|
4
|
-
export default class
|
|
5
|
-
static
|
|
6
|
-
static
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
import { BaseCommand } from '../base.js';
|
|
4
|
+
export default class AccountListCommand extends BaseCommand {
|
|
5
|
+
static authenticate = false;
|
|
6
|
+
static description = 'List all Accounts';
|
|
7
|
+
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
8
|
+
configFileManager;
|
|
9
|
+
async init() {
|
|
10
|
+
await super.init();
|
|
11
|
+
await this.container.initConfigFileManager().build();
|
|
12
|
+
this.configFileManager = this.container.configFileManager;
|
|
13
|
+
await this.container.initConfigManager().build();
|
|
14
|
+
}
|
|
9
15
|
async run() {
|
|
10
16
|
const configs = this.configFileManager.getConfigsWithNames();
|
|
11
17
|
const current = this.configFileManager.getCurrentConfigFile();
|
|
12
18
|
if (configs.length === 0) {
|
|
13
|
-
this.log('No
|
|
14
|
-
this.log('Create a new
|
|
19
|
+
this.log('No Accounts found');
|
|
20
|
+
this.log('Create a new account with: sp account login --privateKey "<PRIVATE_KEY>" --url "<PROVIDER_URL>" --name "Account Name"');
|
|
15
21
|
return;
|
|
16
22
|
}
|
|
17
23
|
const rows = [];
|
|
@@ -34,7 +40,8 @@ export default class ConfigList extends BaseConfigCommand {
|
|
|
34
40
|
{ name: 'account', title: 'Account' },
|
|
35
41
|
{ name: 'url', title: 'Provider Url' },
|
|
36
42
|
{ name: 'file', title: 'Configuration File' },
|
|
37
|
-
],
|
|
43
|
+
],
|
|
44
|
+
title: 'Available Accounts',
|
|
38
45
|
});
|
|
39
46
|
}
|
|
40
47
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { BaseAccountCommand } from './base.js';
|
|
2
|
+
export default class AccountLoginCommand extends BaseAccountCommand<typeof AccountLoginCommand> {
|
|
3
|
+
static aliases: string[];
|
|
4
|
+
static authenticate: boolean;
|
|
5
|
+
static description: string;
|
|
6
|
+
static examples: string[];
|
|
7
|
+
static flags: {
|
|
8
|
+
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
privateKey: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
url: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
};
|
|
13
|
+
private configFileManager;
|
|
14
|
+
private shouldSkipLogin;
|
|
15
|
+
private inputPrompt;
|
|
16
|
+
private checkStorage;
|
|
17
|
+
init(): Promise<void>;
|
|
18
|
+
run(): Promise<{
|
|
19
|
+
account: undefined;
|
|
20
|
+
success: boolean;
|
|
21
|
+
} | {
|
|
22
|
+
account: string;
|
|
23
|
+
success: boolean;
|
|
24
|
+
}>;
|
|
25
|
+
private askName;
|
|
26
|
+
private confirmPrompt;
|
|
27
|
+
private createAccountFromKey;
|
|
28
|
+
private createConfigIfMissing;
|
|
29
|
+
private findConfigsByPrivateKey;
|
|
30
|
+
private getPrivateKeyOrGenerate;
|
|
31
|
+
private resolveByName;
|
|
32
|
+
private resolveByPrivateKey;
|
|
33
|
+
private resolveConfiguration;
|
|
34
|
+
private static maskPrivateKey;
|
|
35
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { confirm, isCancel, text } from '@clack/prompts';
|
|
4
|
+
import { Flags } from '@oclif/core';
|
|
5
|
+
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
|
|
6
|
+
import { PROVIDER_URL } from '../../constants.js';
|
|
7
|
+
import { AuthService } from '../../services/auth.service.js';
|
|
8
|
+
import { StorageService } from '../../services/storage.service.js';
|
|
9
|
+
import { getConfigNameFromCredentials } from '../../utils/helper.js';
|
|
10
|
+
import { BaseAccountCommand } from './base.js';
|
|
11
|
+
export default class AccountLoginCommand extends BaseAccountCommand {
|
|
12
|
+
static aliases = ['login'];
|
|
13
|
+
static authenticate = false;
|
|
14
|
+
static description = 'Login to SuperProtocol (sign up or sign in).';
|
|
15
|
+
static examples = [
|
|
16
|
+
`<%= config.bin %> <%= command.id %>
|
|
17
|
+
`,
|
|
18
|
+
];
|
|
19
|
+
static flags = {
|
|
20
|
+
name: Flags.string({
|
|
21
|
+
description: 'Account readable name',
|
|
22
|
+
}),
|
|
23
|
+
privateKey: Flags.string({
|
|
24
|
+
description: 'Account private key used for authentication',
|
|
25
|
+
}),
|
|
26
|
+
url: Flags.string({
|
|
27
|
+
description: 'Provider base URL',
|
|
28
|
+
hidden: true,
|
|
29
|
+
}),
|
|
30
|
+
yes: Flags.boolean({
|
|
31
|
+
char: 'y',
|
|
32
|
+
default: false,
|
|
33
|
+
description: 'Skip questions (generate keys when needed).',
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
configFileManager;
|
|
37
|
+
shouldSkipLogin = false;
|
|
38
|
+
async inputPrompt(options) {
|
|
39
|
+
const result = await text(options);
|
|
40
|
+
return this.ensurePromptValue(result);
|
|
41
|
+
}
|
|
42
|
+
async checkStorage() {
|
|
43
|
+
await this.container
|
|
44
|
+
.initProviderClient({ enableAuth: true, enableCookies: true, rebuild: true })
|
|
45
|
+
.build();
|
|
46
|
+
const { providerClient } = this.container;
|
|
47
|
+
const storageService = new StorageService(providerClient, this.logger);
|
|
48
|
+
if (!(await storageService.hasStorage())) {
|
|
49
|
+
this.logger.info('Requesting default storage');
|
|
50
|
+
const { id, isCentralized, storageType } = await storageService.getCentralizedStorage();
|
|
51
|
+
this.logger.info({ id, isCentralized, storageType }, 'Requested new storage');
|
|
52
|
+
await storageService.saveStorage(id);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async init() {
|
|
56
|
+
await super.init();
|
|
57
|
+
await this.container.initConfigFileManager().build();
|
|
58
|
+
this.configFileManager = this.container.configFileManager;
|
|
59
|
+
await this.resolveConfiguration();
|
|
60
|
+
if (this.shouldSkipLogin) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
await this.initAccountContext({
|
|
64
|
+
assumeYes: this.flags.yes,
|
|
65
|
+
enableAuth: false,
|
|
66
|
+
enableCookies: true,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async run() {
|
|
70
|
+
if (this.shouldSkipLogin) {
|
|
71
|
+
return { account: undefined, success: false };
|
|
72
|
+
}
|
|
73
|
+
const { accountManager, configManager, providerClient } = this.container;
|
|
74
|
+
const authService = new AuthService(accountManager, configManager, providerClient, this.logger);
|
|
75
|
+
try {
|
|
76
|
+
await authService.auth({ force: true });
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
this.error(error, { exit: 1 });
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
await this.checkStorage();
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
this.logger.error({ err: error }, 'Storage initialization failed');
|
|
86
|
+
this.warn('Storage initialization failed. You can set up storage later using "sp storage create" or "sp storage select".');
|
|
87
|
+
}
|
|
88
|
+
const account = accountManager.getAddress();
|
|
89
|
+
this.log(`Logged in with account ${account}`);
|
|
90
|
+
return { account, success: true };
|
|
91
|
+
}
|
|
92
|
+
async askName(fallback) {
|
|
93
|
+
const value = await this.inputPrompt({
|
|
94
|
+
message: 'Configuration name:',
|
|
95
|
+
placeholder: fallback,
|
|
96
|
+
});
|
|
97
|
+
const trimmed = value.trim();
|
|
98
|
+
return trimmed || fallback;
|
|
99
|
+
}
|
|
100
|
+
async confirmPrompt(message, initialValue = true) {
|
|
101
|
+
const result = await confirm({ initialValue, message });
|
|
102
|
+
if (isCancel(result)) {
|
|
103
|
+
this.error('Operation cancelled.', { exit: 1 });
|
|
104
|
+
}
|
|
105
|
+
return Boolean(result);
|
|
106
|
+
}
|
|
107
|
+
createAccountFromKey(privateKey) {
|
|
108
|
+
try {
|
|
109
|
+
const account = privateKeyToAccount(privateKey);
|
|
110
|
+
return {
|
|
111
|
+
address: account.address,
|
|
112
|
+
privateKey: privateKey,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
this.logger.error({ err: error }, 'Invalid private key provided.');
|
|
117
|
+
this.error('Invalid private key provided.');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async createConfigIfMissing(configFile, displayName, providerUrl, account) {
|
|
121
|
+
const configPath = path.join(this.configFileManager.getConfigDir(), configFile);
|
|
122
|
+
if (existsSync(configPath)) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
await this.configFileManager.createConfig(configFile, displayName, providerUrl, account);
|
|
126
|
+
this.log(`Created configuration: ${displayName}`);
|
|
127
|
+
}
|
|
128
|
+
findConfigsByPrivateKey(privateKey, providerUrl, matchProviderUrl) {
|
|
129
|
+
const normalizedKey = privateKey.trim().toLowerCase();
|
|
130
|
+
const configs = this.configFileManager.getConfigsWithNames();
|
|
131
|
+
const matches = [];
|
|
132
|
+
for (const config of configs) {
|
|
133
|
+
const configData = this.configFileManager.getConfigData(config.file);
|
|
134
|
+
const storedKey = configData?.account?.privateKey?.toLowerCase();
|
|
135
|
+
const storedUrl = (configData?.providerUrl ?? PROVIDER_URL).trim();
|
|
136
|
+
const keyMatches = storedKey === normalizedKey;
|
|
137
|
+
const urlMatches = !matchProviderUrl || storedUrl === providerUrl;
|
|
138
|
+
if (keyMatches && urlMatches) {
|
|
139
|
+
matches.push(config);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return matches;
|
|
143
|
+
}
|
|
144
|
+
async getPrivateKeyOrGenerate() {
|
|
145
|
+
const generatedKey = generatePrivateKey();
|
|
146
|
+
if (this.flags.yes) {
|
|
147
|
+
return generatedKey;
|
|
148
|
+
}
|
|
149
|
+
const maskedKey = AccountLoginCommand.maskPrivateKey(generatedKey);
|
|
150
|
+
const value = await this.inputPrompt({
|
|
151
|
+
message: `Enter private key (or press Enter to use generated ${maskedKey}):`,
|
|
152
|
+
placeholder: maskedKey,
|
|
153
|
+
});
|
|
154
|
+
const trimmed = value.trim();
|
|
155
|
+
return trimmed || generatedKey;
|
|
156
|
+
}
|
|
157
|
+
async resolveByName(name, privateKey, providerUrl) {
|
|
158
|
+
const configs = this.configFileManager.getConfigsWithNames();
|
|
159
|
+
const normalized = name.toLowerCase();
|
|
160
|
+
const existingByName = configs.find((config) => config.name.toLowerCase() === normalized || config.file.toLowerCase() === normalized);
|
|
161
|
+
if (existingByName) {
|
|
162
|
+
await this.configFileManager.setCurrentConfig(existingByName.file);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
let shouldCreate = true;
|
|
166
|
+
if (!this.flags.yes) {
|
|
167
|
+
shouldCreate = await this.confirmPrompt(`Configuration "${name}" not found. Create it?`, true);
|
|
168
|
+
}
|
|
169
|
+
if (!shouldCreate) {
|
|
170
|
+
this.shouldSkipLogin = true;
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const resolvedKey = privateKey ?? (await this.getPrivateKeyOrGenerate());
|
|
174
|
+
const account = this.createAccountFromKey(resolvedKey);
|
|
175
|
+
const configFile = getConfigNameFromCredentials(resolvedKey, providerUrl);
|
|
176
|
+
await this.createConfigIfMissing(configFile, name, providerUrl, account);
|
|
177
|
+
let shouldSwitch = true;
|
|
178
|
+
if (!this.flags.yes) {
|
|
179
|
+
shouldSwitch = await this.confirmPrompt(`Switch to configuration "${name}" now?`, true);
|
|
180
|
+
}
|
|
181
|
+
if (!shouldSwitch) {
|
|
182
|
+
this.shouldSkipLogin = true;
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
await this.configFileManager.setCurrentConfig(configFile);
|
|
186
|
+
}
|
|
187
|
+
async resolveByPrivateKey(privateKey, providerUrl, matchProviderUrl) {
|
|
188
|
+
const matches = this.findConfigsByPrivateKey(privateKey, providerUrl, matchProviderUrl);
|
|
189
|
+
if (matches.length === 1) {
|
|
190
|
+
await this.configFileManager.setCurrentConfig(matches[0].file);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (matches.length > 1) {
|
|
194
|
+
if (this.flags.yes) {
|
|
195
|
+
this.log(`Too many matched configs: ${matches.map((conf) => conf.name).join(',')} Selected first: ${matches[0].name}`);
|
|
196
|
+
await this.configFileManager.setCurrentConfig(matches[0].file);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const selected = await this.selectPrompt({
|
|
200
|
+
message: 'Multiple configurations found for this private key. Select one:',
|
|
201
|
+
options: matches.map((match) => ({ label: match.name, value: match.file })),
|
|
202
|
+
});
|
|
203
|
+
await this.configFileManager.setCurrentConfig(selected);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
let shouldCreate = true;
|
|
207
|
+
if (!this.flags.yes) {
|
|
208
|
+
shouldCreate = await this.confirmPrompt('No configuration found for this private key. Create a new one?', true);
|
|
209
|
+
}
|
|
210
|
+
if (!shouldCreate) {
|
|
211
|
+
this.shouldSkipLogin = true;
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const account = this.createAccountFromKey(privateKey);
|
|
215
|
+
const suggestedName = this.flags.name?.trim() || account.address;
|
|
216
|
+
const displayName = this.flags.yes ? suggestedName : await this.askName(suggestedName);
|
|
217
|
+
const configFile = getConfigNameFromCredentials(privateKey, providerUrl);
|
|
218
|
+
await this.createConfigIfMissing(configFile, displayName, providerUrl, account);
|
|
219
|
+
let shouldSwitch = true;
|
|
220
|
+
if (!this.flags.yes) {
|
|
221
|
+
shouldSwitch = await this.confirmPrompt(`Switch to configuration "${displayName}" now?`, true);
|
|
222
|
+
}
|
|
223
|
+
if (!shouldSwitch) {
|
|
224
|
+
this.shouldSkipLogin = true;
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
await this.configFileManager.setCurrentConfig(configFile);
|
|
228
|
+
}
|
|
229
|
+
async resolveConfiguration() {
|
|
230
|
+
const name = this.flags.name?.trim();
|
|
231
|
+
const privateKey = this.flags.privateKey?.trim();
|
|
232
|
+
const url = this.flags.url?.trim();
|
|
233
|
+
if (url !== undefined && !url) {
|
|
234
|
+
this.error('Provider URL cannot be empty.', { exit: 1 });
|
|
235
|
+
}
|
|
236
|
+
const providerUrl = (url || PROVIDER_URL).trim();
|
|
237
|
+
if (name) {
|
|
238
|
+
await this.resolveByName(name, privateKey, providerUrl);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (privateKey) {
|
|
242
|
+
await this.resolveByPrivateKey(privateKey, providerUrl, Boolean(url));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
static maskPrivateKey(privateKey) {
|
|
246
|
+
const trimmed = privateKey.trim();
|
|
247
|
+
if (trimmed.length <= 14) {
|
|
248
|
+
return trimmed;
|
|
249
|
+
}
|
|
250
|
+
const start = trimmed.slice(0, 6);
|
|
251
|
+
const end = trimmed.slice(-4);
|
|
252
|
+
return `${start}…${end}`;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ConfigFileManager, ConfigManager } from '../../managers/index.js';
|
|
2
|
+
import { BaseAccountCommand } from './base.js';
|
|
3
|
+
export default class AccountSwitchCommand extends BaseAccountCommand<typeof AccountSwitchCommand> {
|
|
4
|
+
static authenticate: boolean;
|
|
5
|
+
static description: string;
|
|
6
|
+
static examples: string[];
|
|
7
|
+
protected configFileManager: ConfigFileManager;
|
|
8
|
+
protected configManager: ConfigManager;
|
|
9
|
+
init(): Promise<void>;
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
private switchConfig;
|
|
12
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { BaseAccountCommand } from './base.js';
|
|
2
|
+
export default class AccountSwitchCommand extends BaseAccountCommand {
|
|
3
|
+
static authenticate = false;
|
|
4
|
+
static description = 'Switch to a different account';
|
|
5
|
+
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
6
|
+
configFileManager;
|
|
7
|
+
configManager;
|
|
8
|
+
async init() {
|
|
9
|
+
await super.init();
|
|
10
|
+
await this.container.initConfigFileManager().build();
|
|
11
|
+
this.configFileManager = this.container.configFileManager;
|
|
12
|
+
await this.container.initConfigManager().build();
|
|
13
|
+
}
|
|
14
|
+
async run() {
|
|
15
|
+
const configs = this.configFileManager.getConfigsWithNames();
|
|
16
|
+
const currentConfig = this.configFileManager.getCurrentConfigFile();
|
|
17
|
+
if (configs.length === 0) {
|
|
18
|
+
this.log('No accounts found');
|
|
19
|
+
this.log('Create a new account with: sp account login --privateKey "<PRIVATE_KEY>" --name "Account Name"');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const selected = await this.selectPrompt({
|
|
23
|
+
initialValue: currentConfig,
|
|
24
|
+
message: 'Select an account:',
|
|
25
|
+
options: configs.map((config) => ({
|
|
26
|
+
label: config.name ?? config.file,
|
|
27
|
+
value: config.file,
|
|
28
|
+
})),
|
|
29
|
+
});
|
|
30
|
+
const selectedConfig = configs.find((c) => c.file === selected);
|
|
31
|
+
await this.switchConfig(selected, selectedConfig?.name || selected);
|
|
32
|
+
}
|
|
33
|
+
async switchConfig(configFile, displayName) {
|
|
34
|
+
await this.configFileManager.setCurrentConfig(configFile);
|
|
35
|
+
this.log(`Switched to account: ${displayName || configFile}`);
|
|
36
|
+
await this.initAccountContext({ enableAuth: false, enableCookies: true, rebuild: true });
|
|
37
|
+
this.configFileManager = this.container.configFileManager;
|
|
38
|
+
this.configManager = this.container.configManager;
|
|
39
|
+
}
|
|
40
|
+
}
|
package/dist/commands/base.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Command, Config, Interfaces } from '@oclif/core';
|
|
2
|
-
import pino from 'pino';
|
|
1
|
+
import { Command, type Config, type Interfaces } from '@oclif/core';
|
|
2
|
+
import type pino from 'pino';
|
|
3
3
|
import { AppContainer } from '../lib/container.js';
|
|
4
|
-
export type CommandFlags<T extends typeof Command> = Interfaces.InferredFlags<T['flags'] & typeof BaseCommand['baseFlags']>;
|
|
4
|
+
export type CommandFlags<T extends typeof Command> = Interfaces.InferredFlags<T['flags'] & (typeof BaseCommand)['baseFlags']>;
|
|
5
5
|
export type CommandArgs<T extends typeof Command> = Interfaces.InferredArgs<T['args']>;
|
|
6
6
|
export declare abstract class BaseCommand<T extends typeof Command> extends Command {
|
|
7
|
+
static authenticate: boolean;
|
|
7
8
|
static baseFlags: typeof Command.baseFlags;
|
|
8
9
|
static enableJsonFlag: boolean;
|
|
9
10
|
protected args: CommandArgs<T>;
|
package/dist/commands/base.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { isCancel } from '@clack/prompts';
|
|
2
|
-
import { Command, Flags
|
|
2
|
+
import { Command, Flags } from '@oclif/core';
|
|
3
3
|
import { AppContainer } from '../lib/container.js';
|
|
4
4
|
import logger from '../logger.js';
|
|
5
5
|
export class BaseCommand extends Command {
|
|
6
|
+
static authenticate = true;
|
|
6
7
|
static baseFlags = {
|
|
7
8
|
config: Flags.string({
|
|
8
9
|
helpGroup: 'GLOBAL',
|
|
10
|
+
hidden: true,
|
|
9
11
|
required: false,
|
|
10
12
|
summary: 'Specify config file.',
|
|
11
13
|
}),
|
|
@@ -4,9 +4,14 @@ import { createProgressPrinter } from '../../utils/progress.js';
|
|
|
4
4
|
import { BaseCommand } from '../base.js';
|
|
5
5
|
export default class FilesDownload extends BaseCommand {
|
|
6
6
|
static args = {
|
|
7
|
-
resourceFile: Args.string({
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
resourceFile: Args.string({
|
|
8
|
+
description: 'Path to a resource file',
|
|
9
|
+
required: true,
|
|
10
|
+
}),
|
|
11
|
+
localDirectory: Args.string({
|
|
12
|
+
description: 'Path to save downloaded file',
|
|
13
|
+
required: true,
|
|
14
|
+
}),
|
|
10
15
|
};
|
|
11
16
|
static description = 'Download file or directory described in resource file';
|
|
12
17
|
static examples = [
|
|
@@ -14,7 +19,10 @@ export default class FilesDownload extends BaseCommand {
|
|
|
14
19
|
];
|
|
15
20
|
static flags = {
|
|
16
21
|
'maximum-concurrent': Flags.integer({
|
|
17
|
-
default: 1,
|
|
22
|
+
default: 1,
|
|
23
|
+
description: 'Maximum concurrent pieces to download at once per transfer',
|
|
24
|
+
max: 1000,
|
|
25
|
+
min: 1,
|
|
18
26
|
}),
|
|
19
27
|
};
|
|
20
28
|
static help = 'Download and decrypt a file from the remote storage to <localPath> using resource file <resourcePath>';
|
|
@@ -36,7 +44,7 @@ export default class FilesDownload extends BaseCommand {
|
|
|
36
44
|
maximumConcurrent: flags['maximum-concurrent'],
|
|
37
45
|
resourcePath: args.resourceFile,
|
|
38
46
|
}, ({ current, key, total }) => {
|
|
39
|
-
progress.advance(key, current / total * 100, `Downloading: ${key} [${current}/${total}]`);
|
|
47
|
+
progress.advance(key, (current / total) * 100, `Downloading: ${key} [${current}/${total}]`);
|
|
40
48
|
});
|
|
41
49
|
}
|
|
42
50
|
catch (error) {
|