@super-protocol/sp-cli 0.0.7 → 0.0.9

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.
@@ -1,12 +1,14 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import path from 'node:path';
3
- import { confirm, isCancel, text } from '@clack/prompts';
4
3
  import { Flags } from '@oclif/core';
4
+ import { Value } from 'typebox/value';
5
5
  import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
6
+ import { cliConfigSchema } from '../../config/config.schema.js';
6
7
  import { PROVIDER_URL } from '../../constants.js';
7
8
  import { AuthService } from '../../services/auth.service.js';
8
9
  import { StorageService } from '../../services/storage.service.js';
9
- import { getConfigNameFromCredentials } from '../../utils/helper.js';
10
+ import { getConfigName, getConfigNameFromCredentials, preparePath, readJsonFile, } from '../../utils/helper.js';
11
+ import { promptService } from '../../utils/prompt.service.js';
10
12
  import { BaseAccountCommand } from './base.js';
11
13
  export default class AccountLoginCommand extends BaseAccountCommand {
12
14
  static aliases = ['login'];
@@ -23,6 +25,9 @@ export default class AccountLoginCommand extends BaseAccountCommand {
23
25
  privateKey: Flags.string({
24
26
  description: 'Account private key used for authentication',
25
27
  }),
28
+ path: Flags.string({
29
+ description: 'Path to account(configuration) file to import',
30
+ }),
26
31
  url: Flags.string({
27
32
  description: 'Provider base URL',
28
33
  hidden: true,
@@ -36,8 +41,7 @@ export default class AccountLoginCommand extends BaseAccountCommand {
36
41
  configFileManager;
37
42
  shouldSkipLogin = false;
38
43
  async inputPrompt(options) {
39
- const result = await text(options);
40
- return this.ensurePromptValue(result);
44
+ return promptService.text(options);
41
45
  }
42
46
  async checkStorage() {
43
47
  await this.container
@@ -91,18 +95,14 @@ export default class AccountLoginCommand extends BaseAccountCommand {
91
95
  }
92
96
  async askName(fallback) {
93
97
  const value = await this.inputPrompt({
94
- message: 'Configuration name:',
98
+ message: 'Account name:',
95
99
  placeholder: fallback,
96
100
  });
97
101
  const trimmed = value.trim();
98
102
  return trimmed || fallback;
99
103
  }
100
104
  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);
105
+ return promptService.confirm({ initialValue, message });
106
106
  }
107
107
  createAccountFromKey(privateKey) {
108
108
  try {
@@ -120,10 +120,22 @@ export default class AccountLoginCommand extends BaseAccountCommand {
120
120
  async createConfigIfMissing(configFile, displayName, providerUrl, account) {
121
121
  const configPath = path.join(this.configFileManager.getConfigDir(), configFile);
122
122
  if (existsSync(configPath)) {
123
+ await this.configFileManager.updateConfigName(configFile, displayName);
123
124
  return;
124
125
  }
125
126
  await this.configFileManager.createConfig(configFile, displayName, providerUrl, account);
126
- this.log(`Created configuration: ${displayName}`);
127
+ this.log(`Created account: ${displayName}`);
128
+ }
129
+ async updateConfigDisplayName(configFile, displayName) {
130
+ if (!displayName) {
131
+ return;
132
+ }
133
+ const updated = await this.configFileManager.updateConfigName(configFile, displayName);
134
+ if (updated) {
135
+ this.log(`Updated account name to "${displayName}"`);
136
+ return;
137
+ }
138
+ this.log(`Account name: "${displayName}"`);
127
139
  }
128
140
  findConfigsByPrivateKey(privateKey, providerUrl, matchProviderUrl) {
129
141
  const normalizedKey = privateKey.trim().toLowerCase();
@@ -164,7 +176,7 @@ export default class AccountLoginCommand extends BaseAccountCommand {
164
176
  }
165
177
  let shouldCreate = true;
166
178
  if (!this.flags.yes) {
167
- shouldCreate = await this.confirmPrompt(`Configuration "${name}" not found. Create it?`, true);
179
+ shouldCreate = await this.confirmPrompt(`Account with "${name}" not found. Create it?`, true);
168
180
  }
169
181
  if (!shouldCreate) {
170
182
  this.shouldSkipLogin = true;
@@ -176,7 +188,7 @@ export default class AccountLoginCommand extends BaseAccountCommand {
176
188
  await this.createConfigIfMissing(configFile, name, providerUrl, account);
177
189
  let shouldSwitch = true;
178
190
  if (!this.flags.yes) {
179
- shouldSwitch = await this.confirmPrompt(`Switch to configuration "${name}" now?`, true);
191
+ shouldSwitch = await this.confirmPrompt(`Switch to account "${name}" now?`, true);
180
192
  }
181
193
  if (!shouldSwitch) {
182
194
  this.shouldSkipLogin = true;
@@ -184,41 +196,120 @@ export default class AccountLoginCommand extends BaseAccountCommand {
184
196
  }
185
197
  await this.configFileManager.setCurrentConfig(configFile);
186
198
  }
187
- async resolveByPrivateKey(privateKey, providerUrl, matchProviderUrl) {
199
+ assertConfigHasAccount(config) {
200
+ if (!config.account?.privateKey) {
201
+ throw new Error('Account private key is required for import.');
202
+ }
203
+ }
204
+ async loadConfigFromPath(configPath) {
205
+ try {
206
+ const config = await readJsonFile({ path: configPath });
207
+ if (!Value.Check(cliConfigSchema, config)) {
208
+ throw new Error("Account doesn't match required schema");
209
+ }
210
+ this.assertConfigHasAccount(config);
211
+ return config;
212
+ }
213
+ catch (error) {
214
+ this.error(`Invalid account file: ${error instanceof Error ? error.message : String(error)}`, { exit: 1 });
215
+ }
216
+ }
217
+ resolveConfigFileName(configPath, config, name) {
218
+ if (name) {
219
+ return getConfigName(name);
220
+ }
221
+ if (config.name) {
222
+ return getConfigName(config.name);
223
+ }
224
+ const baseName = path.basename(configPath, path.extname(configPath));
225
+ return getConfigName(baseName);
226
+ }
227
+ async resolveByPath(rawPath, name) {
228
+ const sourcePath = preparePath(rawPath);
229
+ const config = await this.loadConfigFromPath(sourcePath);
230
+ const targetFile = this.resolveConfigFileName(sourcePath, config, name);
231
+ const targetPath = path.join(this.configFileManager.getConfigDir(), targetFile);
232
+ const desiredName = name?.trim();
233
+ const privateKey = config.account.privateKey;
234
+ const providerUrl = (config.providerUrl ?? PROVIDER_URL).trim();
235
+ const matches = this.findConfigsByPrivateKey(privateKey, providerUrl, true);
236
+ if (matches.length > 0) {
237
+ let selectedFile = matches[0].file;
238
+ if (matches.length > 1) {
239
+ if (this.flags.yes) {
240
+ this.log(`Too many matched accounts: ${matches.map((conf) => conf.name).join(',')} Selected first: ${matches[0].name}`);
241
+ }
242
+ else {
243
+ selectedFile = await this.selectPrompt({
244
+ message: 'Multiple accounts found for this private key. Select one:',
245
+ options: matches.map((match) => ({ label: match.name, value: match.file })),
246
+ });
247
+ }
248
+ }
249
+ this.log('Account already exists. Nothing to import.');
250
+ await this.updateConfigDisplayName(selectedFile, desiredName);
251
+ await this.configFileManager.setCurrentConfig(selectedFile);
252
+ return;
253
+ }
254
+ if (existsSync(targetPath)) {
255
+ const existingConfig = this.configFileManager.getConfigData(targetFile);
256
+ const existingKey = existingConfig?.account?.privateKey?.trim().toLowerCase();
257
+ const existingUrl = (existingConfig?.providerUrl ?? PROVIDER_URL).trim();
258
+ const importedKey = privateKey.trim().toLowerCase();
259
+ if (!existingKey || existingKey !== importedKey || existingUrl !== providerUrl) {
260
+ this.error(`Account file "${targetFile}" already exists with different credentials. Use a different name or remove the existing file before importing.`, { exit: 1 });
261
+ }
262
+ this.log(`Account already exists. Switching to ${name || targetFile}`);
263
+ await this.configFileManager.setCurrentConfig(targetFile);
264
+ return;
265
+ }
266
+ try {
267
+ const importedFile = await this.configFileManager.importConfig(sourcePath, name);
268
+ await this.configFileManager.setCurrentConfig(importedFile);
269
+ }
270
+ catch (error) {
271
+ this.error(error instanceof Error ? error.message : String(error), { exit: 1 });
272
+ }
273
+ }
274
+ async resolveByPrivateKey(privateKey, providerUrl, matchProviderUrl, name) {
188
275
  const matches = this.findConfigsByPrivateKey(privateKey, providerUrl, matchProviderUrl);
276
+ const desiredName = name?.trim();
189
277
  if (matches.length === 1) {
278
+ await this.updateConfigDisplayName(matches[0].file, desiredName);
190
279
  await this.configFileManager.setCurrentConfig(matches[0].file);
191
280
  return;
192
281
  }
193
282
  if (matches.length > 1) {
194
283
  if (this.flags.yes) {
195
- this.log(`Too many matched configs: ${matches.map((conf) => conf.name).join(',')} Selected first: ${matches[0].name}`);
284
+ this.log(`Too many matched accounts: ${matches.map((conf) => conf.name).join(',')} Selected first: ${matches[0].name}`);
285
+ await this.updateConfigDisplayName(matches[0].file, desiredName);
196
286
  await this.configFileManager.setCurrentConfig(matches[0].file);
197
287
  return;
198
288
  }
199
289
  const selected = await this.selectPrompt({
200
- message: 'Multiple configurations found for this private key. Select one:',
290
+ message: 'Multiple accounts found for this private key. Select one:',
201
291
  options: matches.map((match) => ({ label: match.name, value: match.file })),
202
292
  });
293
+ await this.updateConfigDisplayName(selected, desiredName);
203
294
  await this.configFileManager.setCurrentConfig(selected);
204
295
  return;
205
296
  }
206
297
  let shouldCreate = true;
207
298
  if (!this.flags.yes) {
208
- shouldCreate = await this.confirmPrompt('No configuration found for this private key. Create a new one?', true);
299
+ shouldCreate = await this.confirmPrompt('No accounts found for this private key. Create a new one?', true);
209
300
  }
210
301
  if (!shouldCreate) {
211
302
  this.shouldSkipLogin = true;
212
303
  return;
213
304
  }
214
305
  const account = this.createAccountFromKey(privateKey);
215
- const suggestedName = this.flags.name?.trim() || account.address;
306
+ const suggestedName = desiredName || account.address;
216
307
  const displayName = this.flags.yes ? suggestedName : await this.askName(suggestedName);
217
308
  const configFile = getConfigNameFromCredentials(privateKey, providerUrl);
218
309
  await this.createConfigIfMissing(configFile, displayName, providerUrl, account);
219
310
  let shouldSwitch = true;
220
311
  if (!this.flags.yes) {
221
- shouldSwitch = await this.confirmPrompt(`Switch to configuration "${displayName}" now?`, true);
312
+ shouldSwitch = await this.confirmPrompt(`Switch to account "${displayName}" now?`, true);
222
313
  }
223
314
  if (!shouldSwitch) {
224
315
  this.shouldSkipLogin = true;
@@ -228,18 +319,27 @@ export default class AccountLoginCommand extends BaseAccountCommand {
228
319
  }
229
320
  async resolveConfiguration() {
230
321
  const name = this.flags.name?.trim();
322
+ const configPath = this.flags.path?.trim();
323
+ if (configPath !== undefined && !configPath) {
324
+ this.error('Account path cannot be empty.', { exit: 1 });
325
+ }
231
326
  const privateKey = this.flags.privateKey?.trim();
232
327
  const url = this.flags.url?.trim();
233
328
  if (url !== undefined && !url) {
234
329
  this.error('Provider URL cannot be empty.', { exit: 1 });
235
330
  }
236
331
  const providerUrl = (url || PROVIDER_URL).trim();
237
- if (name) {
238
- await this.resolveByName(name, privateKey, providerUrl);
332
+ if (configPath) {
333
+ await this.resolveByPath(configPath, name);
239
334
  return;
240
335
  }
241
336
  if (privateKey) {
242
- await this.resolveByPrivateKey(privateKey, providerUrl, Boolean(url));
337
+ await this.resolveByPrivateKey(privateKey, providerUrl, Boolean(url), name);
338
+ return;
339
+ }
340
+ if (name) {
341
+ await this.resolveByName(name, privateKey, providerUrl);
342
+ return;
243
343
  }
244
344
  }
245
345
  static maskPrivateKey(privateKey) {
@@ -6,6 +6,9 @@ export default class AccountSwitchCommand extends BaseAccountCommand<typeof Acco
6
6
  static examples: string[];
7
7
  protected configFileManager: ConfigFileManager;
8
8
  protected configManager: ConfigManager;
9
+ static flags: {
10
+ name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ };
9
12
  init(): Promise<void>;
10
13
  run(): Promise<void>;
11
14
  private switchConfig;
@@ -1,3 +1,4 @@
1
+ import { Flags } from '@oclif/core';
1
2
  import { BaseAccountCommand } from './base.js';
2
3
  export default class AccountSwitchCommand extends BaseAccountCommand {
3
4
  static authenticate = false;
@@ -5,6 +6,12 @@ export default class AccountSwitchCommand extends BaseAccountCommand {
5
6
  static examples = ['<%= config.bin %> <%= command.id %>'];
6
7
  configFileManager;
7
8
  configManager;
9
+ static flags = {
10
+ name: Flags.string({
11
+ char: 'n',
12
+ description: 'Account name to switch',
13
+ }),
14
+ };
8
15
  async init() {
9
16
  await super.init();
10
17
  await this.container.initConfigFileManager().build();
@@ -12,28 +19,41 @@ export default class AccountSwitchCommand extends BaseAccountCommand {
12
19
  await this.container.initConfigManager().build();
13
20
  }
14
21
  async run() {
22
+ const { name } = this.flags;
15
23
  const configs = this.configFileManager.getConfigsWithNames();
16
24
  const currentConfig = this.configFileManager.getCurrentConfigFile();
17
25
  if (configs.length === 0) {
18
26
  this.log('No accounts found');
19
- this.log('Create a new account with: sp account login --privateKey "<PRIVATE_KEY>" --name "Account Name"');
27
+ this.log('Create a new account with: sp account login --privateKey "<PRIVATE_KEY>" --name "Account Name" or sp account login');
20
28
  return;
21
29
  }
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
+ let selected;
31
+ if (name) {
32
+ try {
33
+ const config = this.configFileManager.getConfigWithName(name);
34
+ selected = config.file;
35
+ }
36
+ catch {
37
+ this.error(`Account with name ${name} not found`);
38
+ }
39
+ }
40
+ else {
41
+ selected = await this.selectPrompt({
42
+ initialValue: currentConfig,
43
+ message: 'Select an account:',
44
+ options: configs.map((config) => ({
45
+ label: config.name ?? config.file,
46
+ value: config.file,
47
+ })),
48
+ });
49
+ }
30
50
  const selectedConfig = configs.find((c) => c.file === selected);
31
51
  await this.switchConfig(selected, selectedConfig?.name || selected);
32
52
  }
33
53
  async switchConfig(configFile, displayName) {
34
54
  await this.configFileManager.setCurrentConfig(configFile);
35
55
  this.log(`Switched to account: ${displayName || configFile}`);
36
- await this.initAccountContext({ enableAuth: false, enableCookies: true, rebuild: true });
56
+ await this.container.initConfigManager(true).build();
37
57
  this.configFileManager = this.container.configFileManager;
38
58
  this.configManager = this.container.configManager;
39
59
  }
@@ -1,7 +1,21 @@
1
- import { isCancel } from '@clack/prompts';
2
1
  import { Command, Flags } from '@oclif/core';
3
2
  import { AppContainer } from '../lib/container.js';
4
3
  import logger from '../logger.js';
4
+ import { findMissingRequiredArgs, findMissingRequiredFlags, formatMissingArgsError, formatMissingFlagsError, promptForArg, promptForFlag, } from '../utils/prompt-flags.js';
5
+ import { promptService } from '../utils/prompt.service.js';
6
+ import { isInteractiveMode } from '../utils/tty.js';
7
+ const relaxRequired = (definitions) => {
8
+ if (!definitions) {
9
+ return definitions;
10
+ }
11
+ return Object.fromEntries(Object.entries(definitions).map(([name, definition]) => [
12
+ name,
13
+ {
14
+ ...definition,
15
+ required: false,
16
+ },
17
+ ]));
18
+ };
5
19
  export class BaseCommand extends Command {
6
20
  static authenticate = true;
7
21
  static baseFlags = {
@@ -11,6 +25,12 @@ export class BaseCommand extends Command {
11
25
  required: false,
12
26
  summary: 'Specify config file.',
13
27
  }),
28
+ tty: Flags.boolean({
29
+ allowNo: true,
30
+ helpGroup: 'GLOBAL',
31
+ required: false,
32
+ summary: 'Force or disable interactive mode (use --no-tty to disable).',
33
+ }),
14
34
  };
15
35
  static enableJsonFlag = true;
16
36
  args;
@@ -22,24 +42,89 @@ export class BaseCommand extends Command {
22
42
  this.logger = logger.getPino();
23
43
  }
24
44
  ensurePromptValue(value, errorMessage = 'Operation cancelled.') {
25
- if (isCancel(value)) {
26
- this.error(errorMessage);
45
+ try {
46
+ return promptService.ensureValue(value, errorMessage);
47
+ }
48
+ catch (error) {
49
+ this.error(error instanceof Error ? error.message : String(error));
27
50
  }
28
- return value;
29
51
  }
30
52
  async init() {
31
53
  await super.init();
32
- const { args, flags } = await this.parse({
33
- args: this.ctor.args,
34
- baseFlags: super.ctor.baseFlags,
54
+ const allFlags = { ...this.ctor.flags, ...super.ctor.baseFlags };
55
+ const relaxedArgs = relaxRequired(this.ctor.args);
56
+ const relaxedFlags = relaxRequired(this.ctor.flags);
57
+ const relaxedBaseFlags = relaxRequired(super.ctor.baseFlags);
58
+ let parsedResult = await this.parse({
59
+ args: relaxedArgs,
60
+ baseFlags: relaxedBaseFlags,
35
61
  enableJsonFlag: this.ctor.enableJsonFlag,
36
- flags: this.ctor.flags,
37
- strict: this.ctor.strict,
62
+ flags: relaxedFlags,
63
+ strict: false,
38
64
  });
39
- this.flags = flags;
40
- this.args = args;
65
+ const isInteractive = isInteractiveMode(parsedResult.flags.tty);
66
+ promptService.setInteractiveMode(isInteractive);
67
+ const missingFlags = await findMissingRequiredFlags(parsedResult.flags, allFlags);
68
+ const missingArgs = await findMissingRequiredArgs(parsedResult.args, this.ctor.args);
69
+ if (missingFlags.length > 0 || missingArgs.length > 0) {
70
+ if (!isInteractive) {
71
+ const errors = [];
72
+ if (missingFlags.length > 0) {
73
+ errors.push(formatMissingFlagsError(missingFlags));
74
+ }
75
+ if (missingArgs.length > 0) {
76
+ errors.push(formatMissingArgsError(missingArgs));
77
+ }
78
+ this.error(errors.join(' '), { exit: 1 });
79
+ }
80
+ const argValues = [];
81
+ for (const missingArg of missingArgs) {
82
+ const value = await promptForArg(missingArg);
83
+ argValues.push(String(value));
84
+ }
85
+ const flagPairs = [];
86
+ for (const missingFlag of missingFlags) {
87
+ const value = await promptForFlag(missingFlag);
88
+ if (missingFlag.type === 'boolean') {
89
+ const boolValue = Boolean(value);
90
+ if (boolValue) {
91
+ flagPairs.push(`--${missingFlag.name}`);
92
+ }
93
+ else if ('allowNo' in missingFlag.definition &&
94
+ Boolean(missingFlag.definition.allowNo)) {
95
+ flagPairs.push(`--no-${missingFlag.name}`);
96
+ }
97
+ else {
98
+ this.error(`Flag --${missingFlag.name} is required and only supports "true".`);
99
+ }
100
+ }
101
+ else {
102
+ flagPairs.push(`--${missingFlag.name}`, String(value));
103
+ }
104
+ }
105
+ const updatedArgv = [...this.argv, ...argValues, ...flagPairs];
106
+ this.argv = updatedArgv;
107
+ parsedResult = await this.parse({
108
+ args: this.ctor.args,
109
+ baseFlags: super.ctor.baseFlags,
110
+ enableJsonFlag: this.ctor.enableJsonFlag,
111
+ flags: this.ctor.flags,
112
+ strict: this.ctor.strict,
113
+ });
114
+ }
115
+ else if (this.ctor.strict) {
116
+ parsedResult = await this.parse({
117
+ args: this.ctor.args,
118
+ baseFlags: super.ctor.baseFlags,
119
+ enableJsonFlag: this.ctor.enableJsonFlag,
120
+ flags: this.ctor.flags,
121
+ strict: true,
122
+ });
123
+ }
124
+ this.flags = parsedResult.flags;
125
+ this.args = parsedResult.args;
41
126
  this.container = AppContainer.container.setupRuntimeConfig({
42
- configFile: flags.config,
127
+ configFile: parsedResult.flags.config,
43
128
  });
44
129
  }
45
130
  }
@@ -1,8 +1,8 @@
1
- import { password, select, text } from '@clack/prompts';
2
1
  import { StorageType } from '@super-protocol/provider-client';
3
2
  import { BaseCommand } from '../../commands/base.js';
4
3
  import { S3_REGION } from '../../constants.js';
5
4
  import { StorageService } from '../../services/storage.service.js';
5
+ import { promptService } from '../../utils/prompt.service.js';
6
6
  export class BaseStorageCommand extends BaseCommand {
7
7
  currentDir = process.cwd();
8
8
  storageService;
@@ -14,25 +14,25 @@ export class BaseStorageCommand extends BaseCommand {
14
14
  }
15
15
  async promptS3Credentials() {
16
16
  const region = S3_REGION;
17
- const readAccessKeyId = this.ensurePromptValue(await text({
17
+ const readAccessKeyId = (await promptService.text({
18
18
  message: 'Read access key ID',
19
19
  validate(value) {
20
20
  return value ? undefined : 'Read access key ID is required';
21
21
  },
22
22
  })).trim();
23
- const readSecretAccessKey = this.ensurePromptValue(await password({
23
+ const readSecretAccessKey = (await promptService.password({
24
24
  message: 'Read secret access key',
25
25
  validate(value) {
26
26
  return value ? undefined : 'Read secret access key is required';
27
27
  },
28
28
  })).trim();
29
- const writeAccessKeyId = this.ensurePromptValue(await text({
29
+ const writeAccessKeyId = (await promptService.text({
30
30
  message: 'Write access key ID',
31
31
  validate(value) {
32
32
  return value ? undefined : 'Write access key ID is required';
33
33
  },
34
34
  })).trim();
35
- const writeSecretAccessKey = this.ensurePromptValue(await password({
35
+ const writeSecretAccessKey = (await promptService.password({
36
36
  message: 'Write secret access key',
37
37
  validate(value) {
38
38
  return value ? undefined : 'Write secret access key is required';
@@ -47,20 +47,20 @@ export class BaseStorageCommand extends BaseCommand {
47
47
  };
48
48
  }
49
49
  async promptStoragePayload() {
50
- const storageType = this.ensurePromptValue(await select({
50
+ const storageType = await promptService.select({
51
51
  message: 'Select storage type',
52
52
  options: [
53
53
  { label: 'S3', value: StorageType.S3 },
54
54
  { label: 'StorJ', value: StorageType.StorJ },
55
55
  ],
56
- }));
57
- const bucket = this.ensurePromptValue(await text({
56
+ });
57
+ const bucket = (await promptService.text({
58
58
  message: 'Bucket name',
59
59
  validate(value) {
60
60
  return value ? undefined : 'Bucket is required';
61
61
  },
62
62
  })).trim();
63
- const prefix = this.ensurePromptValue(await text({
63
+ const prefix = (await promptService.text({
64
64
  defaultValue: '/',
65
65
  initialValue: '/',
66
66
  message: 'Prefix',
@@ -82,13 +82,13 @@ export class BaseStorageCommand extends BaseCommand {
82
82
  return storage;
83
83
  }
84
84
  async promptStorJCredentials() {
85
- const readAccessToken = this.ensurePromptValue(await password({
85
+ const readAccessToken = (await promptService.password({
86
86
  message: 'Read access token',
87
87
  validate(value) {
88
88
  return value ? undefined : 'Read access token is required';
89
89
  },
90
90
  })).trim();
91
- const writeAccessToken = this.ensurePromptValue(await password({
91
+ const writeAccessToken = (await promptService.password({
92
92
  message: 'Write access token',
93
93
  validate(value) {
94
94
  return value ? undefined : 'Write access token is required';
@@ -1,5 +1,5 @@
1
- import { select } from '@clack/prompts';
2
1
  import { StoragesUndefinedError } from '../../services/storage.service.js';
2
+ import { promptService } from '../../utils/prompt.service.js';
3
3
  import { BaseStorageCommand } from './base.js';
4
4
  export default class StorageSelect extends BaseStorageCommand {
5
5
  static description = 'Select a storage that will be used by subsequent commands.';
@@ -7,13 +7,13 @@ export default class StorageSelect extends BaseStorageCommand {
7
7
  async run() {
8
8
  try {
9
9
  const storages = await this.storageService.requestStorages();
10
- const storageId = this.ensurePromptValue(await select({
10
+ const storageId = await promptService.select({
11
11
  message: 'Select storage',
12
12
  options: storages.map((storage) => ({
13
13
  label: this.storageService.getLabel(storage),
14
14
  value: storage.id,
15
15
  })),
16
- }));
16
+ });
17
17
  if (!storageId) {
18
18
  this.error('Storage ID is required');
19
19
  }
@@ -1,9 +1,9 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { confirm, select, text } from '@clack/prompts';
4
3
  import { Flags } from '@oclif/core';
5
4
  import { StorageType, } from '@super-protocol/provider-client';
6
5
  import { StoragesUndefinedError } from '../../services/storage.service.js';
6
+ import { promptService } from '../../utils/prompt.service.js';
7
7
  import { BaseStorageCommand } from './base.js';
8
8
  export default class StorageUpdate extends BaseStorageCommand {
9
9
  static description = 'Update the configuration of an existing storage.';
@@ -39,10 +39,10 @@ export default class StorageUpdate extends BaseStorageCommand {
39
39
  if (!options || options.length === 0) {
40
40
  throw new StoragesUndefinedError('No storages available to update');
41
41
  }
42
- storageId = this.ensurePromptValue(await select({
42
+ storageId = await promptService.select({
43
43
  message: 'Select storage to update',
44
44
  options,
45
- }));
45
+ });
46
46
  }
47
47
  if (!storageId) {
48
48
  this.error('Storage ID is required');
@@ -148,14 +148,14 @@ export default class StorageUpdate extends BaseStorageCommand {
148
148
  }
149
149
  async promptStorageUpdate(existingStorage) {
150
150
  const payload = {};
151
- const bucket = this.ensurePromptValue(await text({
151
+ const bucket = (await promptService.text({
152
152
  defaultValue: existingStorage?.bucket ?? '',
153
153
  message: 'Bucket name (leave empty to keep current)',
154
154
  })).trim();
155
155
  if (bucket) {
156
156
  payload.bucket = bucket;
157
157
  }
158
- const prefix = this.ensurePromptValue(await text({
158
+ const prefix = (await promptService.text({
159
159
  defaultValue: existingStorage?.prefix ?? '',
160
160
  message: 'Prefix (leave empty to keep current)',
161
161
  })).trim();
@@ -163,27 +163,27 @@ export default class StorageUpdate extends BaseStorageCommand {
163
163
  payload.prefix = prefix;
164
164
  }
165
165
  let newStorageType;
166
- const shouldChangeType = this.ensurePromptValue(await confirm({
166
+ const shouldChangeType = await promptService.confirm({
167
167
  initialValue: false,
168
168
  message: existingStorage
169
169
  ? `Change storage type? (current: ${existingStorage.storageType})`
170
170
  : 'Change storage type?',
171
- }));
171
+ });
172
172
  if (shouldChangeType) {
173
- newStorageType = this.ensurePromptValue(await select({
173
+ newStorageType = await promptService.select({
174
174
  message: 'Select new storage type',
175
175
  options: [
176
176
  { label: 'Amazon S3', value: StorageType.S3 },
177
177
  { label: 'StorJ', value: StorageType.StorJ },
178
178
  ],
179
- }));
179
+ });
180
180
  payload.storageType = newStorageType;
181
181
  }
182
182
  const effectiveType = newStorageType ?? existingStorage?.storageType;
183
183
  const credentialsRequired = Boolean(newStorageType);
184
184
  if (effectiveType) {
185
185
  const updateCredentials = credentialsRequired ||
186
- this.ensurePromptValue(await confirm({
186
+ (await promptService.confirm({
187
187
  initialValue: false,
188
188
  message: `Update ${effectiveType} credentials?`,
189
189
  }));
@@ -1,4 +1,4 @@
1
- import { type SelectOptions } from '@clack/prompts';
1
+ import { type SelectOptions } from '../../utils/prompt.service.js';
2
2
  import { BaseCommand } from '../base.js';
3
3
  export default class WorkflowsExtendLease extends BaseCommand<typeof WorkflowsExtendLease> {
4
4
  static args: {