@chabokan.net/cli 0.8.10 → 0.9.0

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.
Files changed (55) hide show
  1. package/README.md +520 -66
  2. package/dist/base.d.ts +34 -9
  3. package/dist/base.js +172 -20
  4. package/dist/commands/account/info.d.ts +12 -0
  5. package/dist/commands/account/info.js +40 -0
  6. package/dist/commands/account/list.d.ts +5 -1
  7. package/dist/commands/account/list.js +35 -16
  8. package/dist/commands/account/remove.d.ts +5 -1
  9. package/dist/commands/account/remove.js +30 -15
  10. package/dist/commands/account/use.d.ts +6 -2
  11. package/dist/commands/account/use.js +26 -15
  12. package/dist/commands/cloudserver/create.d.ts +40 -0
  13. package/dist/commands/cloudserver/create.js +242 -0
  14. package/dist/commands/cloudserver/delete.d.ts +14 -0
  15. package/dist/commands/cloudserver/delete.js +65 -0
  16. package/dist/commands/cloudserver/list.d.ts +12 -0
  17. package/dist/commands/cloudserver/list.js +46 -0
  18. package/dist/commands/cloudserver/restart.d.ts +13 -0
  19. package/dist/commands/cloudserver/restart.js +51 -0
  20. package/dist/commands/cloudserver/start.d.ts +13 -0
  21. package/dist/commands/cloudserver/start.js +51 -0
  22. package/dist/commands/cloudserver/stop.d.ts +13 -0
  23. package/dist/commands/cloudserver/stop.js +52 -0
  24. package/dist/commands/deploy.d.ts +16 -5
  25. package/dist/commands/deploy.js +182 -101
  26. package/dist/commands/login.d.ts +8 -4
  27. package/dist/commands/login.js +97 -49
  28. package/dist/commands/service/domain/add.d.ts +15 -0
  29. package/dist/commands/service/domain/add.js +74 -0
  30. package/dist/commands/service/domain/remove.d.ts +15 -0
  31. package/dist/commands/service/domain/remove.js +72 -0
  32. package/dist/commands/service/list.d.ts +5 -1
  33. package/dist/commands/service/list.js +41 -20
  34. package/dist/commands/service/logs.d.ts +7 -3
  35. package/dist/commands/service/logs.js +46 -34
  36. package/dist/commands/service/resize.d.ts +10 -6
  37. package/dist/commands/service/resize.js +94 -72
  38. package/dist/commands/service/restart.d.ts +7 -3
  39. package/dist/commands/service/restart.js +40 -31
  40. package/dist/commands/service/start.d.ts +7 -3
  41. package/dist/commands/service/start.js +41 -30
  42. package/dist/commands/service/stop.d.ts +7 -3
  43. package/dist/commands/service/stop.js +41 -30
  44. package/dist/commands/wallet/list.d.ts +12 -0
  45. package/dist/commands/wallet/list.js +41 -0
  46. package/dist/constants.d.ts +2 -0
  47. package/dist/constants.js +7 -2
  48. package/dist/helper.d.ts +32 -9
  49. package/dist/helper.js +426 -49
  50. package/dist/types.d.ts +77 -0
  51. package/dist/types.js +2 -0
  52. package/dist/ui.d.ts +23 -0
  53. package/dist/ui.js +70 -0
  54. package/oclif.manifest.json +635 -25
  55. package/package.json +39 -35
package/dist/base.d.ts CHANGED
@@ -1,15 +1,40 @@
1
1
  import { Command } from '@oclif/core';
2
- export default abstract class extends Command {
2
+ import { AxiosRequestConfig } from 'axios';
3
+ import type { CloudServerRef, ConfigFile, Service, WalletRef } from './types.js';
4
+ export default abstract class BaseCommand extends Command {
3
5
  static flags: {
4
- help: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<void>;
6
+ help: import("@oclif/core/interfaces").BooleanFlag<void>;
5
7
  };
6
- axiosConfig: any;
8
+ axiosConfig: AxiosRequestConfig;
7
9
  got: import("got").Got<never>;
8
- read_config(): Promise<{
9
- users: {};
10
- default_user: string;
11
- }>;
12
- write_config(object: any): Promise<void>;
13
- get_services(filter?: {}): Promise<any>;
10
+ read_config(): Promise<ConfigFile>;
11
+ write_config(object: ConfigFile): Promise<void>;
12
+ get_services(filter?: Record<string, string>): Promise<Service[]>;
13
+ /**
14
+ * Returns true when an account is logged in. Otherwise prints a consistent
15
+ * message telling the user how to fix it and returns false, so callers can
16
+ * simply `if (!(await this.require_login())) return`.
17
+ */
18
+ require_login(): Promise<boolean>;
19
+ /**
20
+ * Resolves which service to act on. Returns `flagValue` untouched when the
21
+ * user passed --service, otherwise fetches the account's services and
22
+ * prompts. Returns undefined (having already logged why) when the service
23
+ * cannot be determined, so callers should bail out on a falsy result.
24
+ */
25
+ select_service(flagValue: string | undefined, filter?: Record<string, string>, operation?: string): Promise<string | undefined>;
26
+ /**
27
+ * Resolves which cloud server (VPS) to act on, by hostname. The API needs
28
+ * an internal `cloud_server_id` for every action, but that id is plumbing
29
+ * only — it is never shown to, or asked of, the user. Both the flag path
30
+ * and the interactive path fetch the list and match on hostname, so a
31
+ * bogus `--hostname` is reported clearly instead of silently sent as-is.
32
+ */
33
+ select_cloud_server(flagValue: string | undefined, operation?: string): Promise<CloudServerRef | undefined>;
34
+ /**
35
+ * Prompts the user to pick a wallet, by name — mirrors `select_cloud_server`
36
+ * in keeping the internal id out of both the flag and the prompt.
37
+ */
38
+ select_wallet(flagValue: string | undefined): Promise<undefined | WalletRef>;
14
39
  init_run(): Promise<void>;
15
40
  }
package/dist/base.js CHANGED
@@ -1,57 +1,209 @@
1
1
  import { Command, Flags } from '@oclif/core';
2
2
  import axios from 'axios';
3
- import { BASE_API_URL, GLOBAL_CONF_PATH } from './constants.js';
4
- import { checkUpdate, get_all_services, isEmptyObject, read_config_file } from './helper.js';
5
- import HttpsProxyAgent from 'https-proxy-agent';
6
- import * as fs from 'fs';
3
+ import { BASE_API_URL, REQUEST_TIMEOUT, UPLOAD_TIMEOUT } from './constants.js';
4
+ import { checkUpdate, extractRecords, get_all_services, handleApiError, isEmptyObject, logErrorDetails, read_config_file, write_config_file, } from './helper.js';
5
+ import { HttpsProxyAgent } from 'https-proxy-agent';
6
+ import * as ui from './ui.js';
7
+ import inquirer from 'inquirer';
7
8
  import got from 'got';
8
- import * as https from 'https';
9
- export default class default_1 extends Command {
9
+ import * as https from 'node:https';
10
+ export default class BaseCommand extends Command {
10
11
  static flags = {
11
12
  help: Flags.help({ char: 'h' }),
12
13
  };
13
14
  axiosConfig = {
14
- ...axios.defaults
15
+ ...axios.defaults,
16
+ headers: {},
17
+ timeout: REQUEST_TIMEOUT,
15
18
  };
16
19
  got = got.extend();
17
20
  async read_config() {
18
21
  return read_config_file();
19
22
  }
20
23
  async write_config(object) {
21
- fs.writeFileSync(GLOBAL_CONF_PATH, JSON.stringify(object));
24
+ write_config_file(object);
22
25
  }
23
26
  async get_services(filter = {}) {
24
- return await get_all_services(filter, this.axiosConfig);
27
+ return get_all_services(filter, this.axiosConfig);
28
+ }
29
+ /**
30
+ * Returns true when an account is logged in. Otherwise prints a consistent
31
+ * message telling the user how to fix it and returns false, so callers can
32
+ * simply `if (!(await this.require_login())) return`.
33
+ */
34
+ async require_login() {
35
+ const config_json = await this.read_config();
36
+ if (isEmptyObject(config_json.users) || Object.keys(config_json.users).length === 0) {
37
+ this.log(ui.error('You are not logged in.'));
38
+ this.log(ui.hint(`Run ${ui.cmd('chabok login')} to sign in with your email, or ${ui.cmd('chabok login -t <token>')} to use an API token`));
39
+ return false;
40
+ }
41
+ return true;
42
+ }
43
+ /**
44
+ * Resolves which service to act on. Returns `flagValue` untouched when the
45
+ * user passed --service, otherwise fetches the account's services and
46
+ * prompts. Returns undefined (having already logged why) when the service
47
+ * cannot be determined, so callers should bail out on a falsy result.
48
+ */
49
+ async select_service(flagValue, filter = {}, operation = 'Fetch Services') {
50
+ if (flagValue) {
51
+ return flagValue;
52
+ }
53
+ let all_services;
54
+ try {
55
+ all_services = await this.get_services(filter);
56
+ }
57
+ catch (error) {
58
+ const errorInfo = handleApiError(error, 'Failed to fetch services list.', {
59
+ endpoint: 'services/',
60
+ operation,
61
+ });
62
+ this.log(ui.error(errorInfo.message));
63
+ logErrorDetails(errorInfo, this);
64
+ return undefined;
65
+ }
66
+ if (all_services.length === 0) {
67
+ this.log(ui.info('No services available for this action.'));
68
+ this.log(ui.hint(`See everything on your account with ${ui.cmd('chabok service list')}`));
69
+ return undefined;
70
+ }
71
+ const { service } = await inquirer.prompt({
72
+ type: 'select',
73
+ message: 'Please select a service:',
74
+ name: 'service',
75
+ choices: all_services,
76
+ });
77
+ return service;
78
+ }
79
+ /**
80
+ * Resolves which cloud server (VPS) to act on, by hostname. The API needs
81
+ * an internal `cloud_server_id` for every action, but that id is plumbing
82
+ * only — it is never shown to, or asked of, the user. Both the flag path
83
+ * and the interactive path fetch the list and match on hostname, so a
84
+ * bogus `--hostname` is reported clearly instead of silently sent as-is.
85
+ */
86
+ async select_cloud_server(flagValue, operation = 'Fetch Cloudservers') {
87
+ let records;
88
+ try {
89
+ const { data } = await axios.get('cloudservers/', this.axiosConfig);
90
+ records = extractRecords(data, ['user_cloud_servers', 'cloudservers', 'results', 'data']);
91
+ }
92
+ catch (error) {
93
+ const errorInfo = handleApiError(error, 'Failed to fetch cloud servers list.', {
94
+ endpoint: 'cloudservers/',
95
+ operation,
96
+ });
97
+ this.log(ui.error(errorInfo.message));
98
+ logErrorDetails(errorInfo, this);
99
+ return undefined;
100
+ }
101
+ if (records.length === 0) {
102
+ this.log(ui.info('No cloud servers available for this action.'));
103
+ this.log(ui.hint(`See everything on your account with ${ui.cmd('chabok cloudserver list')}`));
104
+ return undefined;
105
+ }
106
+ const refs = records
107
+ .filter((record) => record.id !== undefined && record.hostname !== undefined)
108
+ .map((record) => ({ id: String(record.id), hostname: String(record.hostname) }));
109
+ if (flagValue) {
110
+ const match = refs.find((ref) => ref.hostname === flagValue);
111
+ if (!match) {
112
+ this.log(ui.error(`Cloud server ${ui.value(flagValue)} was not found.`));
113
+ this.log(ui.hint(`Check the available hostnames with ${ui.cmd('chabok cloudserver list')}`));
114
+ return undefined;
115
+ }
116
+ return match;
117
+ }
118
+ const { hostname } = await inquirer.prompt({
119
+ type: 'select',
120
+ message: 'Please select a cloud server:',
121
+ name: 'hostname',
122
+ choices: refs.map((ref) => ({ value: ref.hostname, name: ref.hostname })),
123
+ });
124
+ return refs.find((ref) => ref.hostname === hostname);
125
+ }
126
+ /**
127
+ * Prompts the user to pick a wallet, by name — mirrors `select_cloud_server`
128
+ * in keeping the internal id out of both the flag and the prompt.
129
+ */
130
+ async select_wallet(flagValue) {
131
+ let wallets;
132
+ try {
133
+ const { data } = await axios.get('wallets/', this.axiosConfig);
134
+ wallets = extractRecords(data, ['wallets', 'results', 'data']);
135
+ }
136
+ catch (error) {
137
+ const errorInfo = handleApiError(error, 'Failed to fetch wallets.', {
138
+ endpoint: 'wallets/',
139
+ operation: 'List Wallets',
140
+ });
141
+ this.log(ui.error(errorInfo.message));
142
+ logErrorDetails(errorInfo, this);
143
+ return undefined;
144
+ }
145
+ if (wallets.length === 0) {
146
+ this.log(ui.info('You have no wallets to pay from.'));
147
+ return undefined;
148
+ }
149
+ const refs = wallets
150
+ .filter((record) => record.id !== undefined && record.name !== undefined)
151
+ .map((record) => ({ id: String(record.id), name: String(record.name) }));
152
+ if (flagValue) {
153
+ const match = refs.find((ref) => ref.name === flagValue);
154
+ if (!match) {
155
+ this.log(ui.error(`Wallet ${ui.value(flagValue)} was not found.`));
156
+ return undefined;
157
+ }
158
+ return match;
159
+ }
160
+ const { name } = await inquirer.prompt({
161
+ type: 'select',
162
+ message: 'Pay from which wallet?',
163
+ name: 'name',
164
+ choices: refs.map((ref) => ({ value: ref.name, name: ref.name })),
165
+ });
166
+ return refs.find((ref) => ref.name === name);
25
167
  }
26
168
  async init_run() {
27
169
  await checkUpdate(this.config.version);
170
+ // Certificate verification stays on by default. CHABOK_INSECURE_TLS is an
171
+ // explicit escape hatch for self-signed endpoints, and it warns loudly.
172
+ const insecure = process.env.CHABOK_INSECURE_TLS === 'true';
173
+ if (insecure) {
174
+ this.warn('TLS certificate verification is disabled (CHABOK_INSECURE_TLS=true). Your credentials are not protected against interception.');
175
+ }
28
176
  const main_agent = new https.Agent({
29
- rejectUnauthorized: false
177
+ rejectUnauthorized: !insecure
30
178
  });
31
- // @ts-ignore
32
- const gotConfig = {};
179
+ const gotConfig = {
180
+ prefixUrl: BASE_API_URL,
181
+ agent: { https: main_agent },
182
+ timeout: { request: UPLOAD_TIMEOUT },
183
+ };
33
184
  this.axiosConfig.httpsAgent = main_agent;
34
185
  this.axiosConfig.baseURL = BASE_API_URL;
35
- gotConfig.prefixUrl = this.axiosConfig.baseURL;
36
- gotConfig.agent = { https: main_agent };
37
186
  const proxy = process.env.http_proxy || process.env.https_proxy;
38
187
  if (proxy) {
39
188
  this.log(`Using proxy server ${proxy}`);
40
- // @ts-ignore
41
- const agent = new HttpsProxyAgent(proxy);
189
+ const agent = new HttpsProxyAgent(proxy, { rejectUnauthorized: !insecure });
42
190
  this.axiosConfig.httpsAgent = agent;
43
191
  this.axiosConfig.proxy = false;
44
- // @ts-ignore
192
+ // HttpsProxyAgent is a drop-in https.Agent at runtime, but its
193
+ // getName() signature is not structurally compatible with @types/node.
45
194
  gotConfig.agent = { https: agent };
46
195
  }
47
196
  const config_json = read_config_file();
48
- this.got = got.extend(gotConfig);
49
197
  if (isEmptyObject(config_json) || isEmptyObject(config_json.users)) {
198
+ this.got = got.extend(gotConfig);
50
199
  return;
51
200
  }
52
- // @ts-ignore
53
- const token = config_json.users[config_json.default_user]["token"] || null;
201
+ const defaultUser = config_json.default_user;
202
+ const token = defaultUser && config_json.users[defaultUser]?.token;
54
203
  if (token) {
204
+ if (!this.axiosConfig.headers) {
205
+ this.axiosConfig.headers = {};
206
+ }
55
207
  this.axiosConfig.headers.Authorization = `Token ${token}`;
56
208
  gotConfig.headers = { Authorization: `Token ${token}` };
57
209
  }
@@ -0,0 +1,12 @@
1
+ import Command from "../../base.js";
2
+ export default class AccountInfo extends Command {
3
+ static description: string;
4
+ static examples: {
5
+ description: string;
6
+ command: string;
7
+ }[];
8
+ static flags: {
9
+ help: import("@oclif/core/interfaces").BooleanFlag<void>;
10
+ };
11
+ run(): Promise<void>;
12
+ }
@@ -0,0 +1,40 @@
1
+ import Command from "../../base.js";
2
+ import * as ui from "../../ui.js";
3
+ import { handleApiError, logErrorDetails, isObject } from "../../helper.js";
4
+ import axios from "axios";
5
+ export default class AccountInfo extends Command {
6
+ static description = "show information about the active account";
7
+ static examples = [
8
+ {
9
+ description: 'Show the active account\'s details',
10
+ command: '<%= config.bin %> account info',
11
+ },
12
+ ];
13
+ static flags = {
14
+ ...Command.flags,
15
+ };
16
+ async run() {
17
+ await this.parse(AccountInfo);
18
+ const cli = this;
19
+ await this.init_run();
20
+ if (!(await this.require_login())) {
21
+ return;
22
+ }
23
+ try {
24
+ const { data } = await axios.get('accounts/info/', this.axiosConfig);
25
+ if (!isObject(data.user)) {
26
+ cli.log(ui.info('No account information was returned.'));
27
+ return;
28
+ }
29
+ ui.printRecord(data.user);
30
+ }
31
+ catch (error) {
32
+ const errorInfo = handleApiError(error, 'Failed to fetch account information.', {
33
+ endpoint: 'accounts/info/',
34
+ operation: 'Account Info'
35
+ });
36
+ cli.log(ui.error(errorInfo.message));
37
+ logErrorDetails(errorInfo, cli);
38
+ }
39
+ }
40
+ }
@@ -1,8 +1,12 @@
1
1
  import Command from "../../base.js";
2
2
  export default class AccountList extends Command {
3
3
  static description: string;
4
+ static examples: {
5
+ description: string;
6
+ command: string;
7
+ }[];
4
8
  static flags: {
5
- help: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<void>;
9
+ help: import("@oclif/core/interfaces").BooleanFlag<void>;
6
10
  };
7
11
  run(): Promise<void>;
8
12
  }
@@ -1,28 +1,47 @@
1
1
  import Command from "../../base.js";
2
- import { ux } from "@oclif/core";
2
+ import { printTable } from "@oclif/table";
3
+ import * as ui from "../../ui.js";
4
+ import { isDebug } from "../../helper.js";
3
5
  export default class AccountList extends Command {
4
- static description = "show accounts list";
6
+ static description = "list the accounts you are logged in to";
7
+ static examples = [
8
+ {
9
+ description: 'Show every logged-in account and which one is active',
10
+ command: '<%= config.bin %> account list',
11
+ },
12
+ ];
5
13
  static flags = {
6
14
  ...Command.flags,
7
15
  };
8
16
  async run() {
9
- const { args, flags } = await this.parse(AccountList);
17
+ await this.parse(AccountList);
10
18
  const cli = this;
11
- await this.init_run();
12
- const config_json = await this.read_config();
13
- if (Object.keys(config_json.users).length > 0) {
14
- const accounts_data = Object.keys(config_json.users).map((account) => {
15
- return {
16
- Name: account,
17
- };
19
+ try {
20
+ await this.init_run();
21
+ const config_json = await this.read_config();
22
+ const accounts = Object.keys(config_json.users);
23
+ if (accounts.length === 0) {
24
+ cli.log(ui.info('You are not logged in to any account.'));
25
+ cli.log(ui.hint(`Run ${ui.cmd('chabok login')} to get started`));
26
+ return;
27
+ }
28
+ const accounts_data = accounts.map((account) => ({
29
+ Account: account,
30
+ Active: account === config_json.default_user ? "yes" : "",
31
+ }));
32
+ printTable({
33
+ data: accounts_data,
34
+ columns: ["Account", "Active"],
18
35
  });
19
- ux.table(accounts_data, {
20
- Name: {},
21
- }, flags);
36
+ if (accounts.length > 1) {
37
+ cli.log(ui.hint(`Switch with ${ui.cmd('chabok account use')}`));
38
+ }
22
39
  }
23
- else {
24
- cli.log("first you should login");
25
- return;
40
+ catch (error) {
41
+ cli.log(ui.error('Could not read your accounts.'));
42
+ if (isDebug()) {
43
+ cli.log(String(error));
44
+ }
26
45
  }
27
46
  }
28
47
  }
@@ -1,8 +1,12 @@
1
1
  import Command from "../../base.js";
2
2
  export default class AccountRemove extends Command {
3
3
  static description: string;
4
+ static examples: {
5
+ description: string;
6
+ command: string;
7
+ }[];
4
8
  static flags: {
5
- help: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<void>;
9
+ help: import("@oclif/core/interfaces").BooleanFlag<void>;
6
10
  };
7
11
  run(): Promise<void>;
8
12
  }
@@ -1,34 +1,49 @@
1
1
  import inquirer from "inquirer";
2
- import chalk from "chalk";
2
+ import * as ui from "../../ui.js";
3
3
  import { isEmptyObject, read_config_file } from "../../helper.js";
4
4
  import Command from "../../base.js";
5
- import { GLOBAL_CONF_PATH } from "../../constants.js";
6
- import fs from "fs";
7
5
  export default class AccountRemove extends Command {
8
- static description = "remove account from list";
6
+ static description = "sign out of an account and forget its stored token";
7
+ static examples = [
8
+ {
9
+ description: 'Pick the account to sign out of',
10
+ command: '<%= config.bin %> account remove',
11
+ },
12
+ ];
9
13
  static flags = {
10
14
  ...Command.flags,
11
15
  };
12
16
  async run() {
13
17
  const cli = this;
14
- const { args, flags } = await this.parse(AccountRemove);
18
+ await this.parse(AccountRemove);
15
19
  const config_json = read_config_file();
16
20
  if (isEmptyObject(config_json.users)) {
17
- cli.log(`${chalk.red("[Error]")} first you should login!`);
21
+ cli.log(ui.info('There are no logged-in accounts to remove.'));
18
22
  return;
19
23
  }
20
- let { user } = await inquirer.prompt({
21
- type: "list",
22
- message: "Please select a user:",
24
+ const { user } = await inquirer.prompt({
25
+ type: "select",
26
+ message: "Sign out of which account?",
23
27
  name: "user",
24
28
  choices: Object.keys(config_json.users),
25
29
  });
26
- // @ts-ignore
27
- delete config_json.users[user];
28
- if (config_json["default_user"] == user) {
29
- config_json["default_user"] = "";
30
+ if (user in config_json.users) {
31
+ delete config_json.users[user];
32
+ if (config_json.default_user === user) {
33
+ // Fall back to any remaining account so the CLI stays usable.
34
+ config_json.default_user = Object.keys(config_json.users)[0] ?? "";
35
+ }
36
+ await this.write_config(config_json);
37
+ cli.log(ui.success(`Signed out of ${ui.value(user)}.`));
38
+ if (config_json.default_user) {
39
+ cli.log(ui.hint(`Now using ${ui.value(config_json.default_user)}`));
40
+ }
41
+ else {
42
+ cli.log(ui.hint(`No accounts left — run ${ui.cmd('chabok login')} to sign in again`));
43
+ }
44
+ }
45
+ else {
46
+ cli.log(ui.error(`No account named ${ui.value(user)} is logged in.`));
30
47
  }
31
- fs.writeFileSync(GLOBAL_CONF_PATH, JSON.stringify(config_json));
32
- cli.log(`${chalk.green("[Success]")} user deleted successfully.`);
33
48
  }
34
49
  }
@@ -1,9 +1,13 @@
1
1
  import Command from "../../base.js";
2
2
  export default class AccountUse extends Command {
3
3
  static description: string;
4
+ static examples: {
5
+ description: string;
6
+ command: string;
7
+ }[];
4
8
  static flags: {
5
- user: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
6
- help: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<void>;
9
+ user: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ help: import("@oclif/core/interfaces").BooleanFlag<void>;
7
11
  };
8
12
  run(): Promise<void>;
9
13
  }
@@ -1,41 +1,52 @@
1
1
  import { Flags } from "@oclif/core";
2
2
  import inquirer from "inquirer";
3
- import * as fs from "fs";
4
- import chalk from "chalk";
3
+ import * as ui from "../../ui.js";
5
4
  import { isEmptyObject, read_config_file } from "../../helper.js";
6
5
  import Command from "../../base.js";
7
- import { GLOBAL_CONF_PATH } from "../../constants.js";
8
6
  export default class AccountUse extends Command {
9
- static description = "switch your default user between logged in users";
7
+ static description = "switch the active account";
8
+ static examples = [
9
+ {
10
+ description: 'Pick the account to switch to from a list',
11
+ command: '<%= config.bin %> account use',
12
+ },
13
+ {
14
+ description: 'Switch to a specific account without prompting',
15
+ command: '<%= config.bin %> account use --user me@example.com',
16
+ },
17
+ ];
10
18
  static flags = {
11
19
  ...Command.flags,
12
- user: Flags.string({ char: "u", description: "default user" }),
20
+ user: Flags.string({ char: "u", description: "email of the account to switch to" }),
13
21
  };
14
22
  async run() {
15
23
  const cli = this;
16
- const { args, flags } = await this.parse(AccountUse);
24
+ const { flags } = await this.parse(AccountUse);
17
25
  const config_json = read_config_file();
18
26
  let default_user = flags.user;
19
27
  if (isEmptyObject(config_json.users)) {
20
- cli.log(`${chalk.red("[Error]")} first you should login!`);
28
+ cli.log(ui.error('You are not logged in to any account.'));
29
+ cli.log(ui.hint(`Run ${ui.cmd('chabok login')} first`));
21
30
  return;
22
31
  }
23
32
  if (!flags.user) {
24
- let { user } = await inquirer.prompt({
25
- type: "list",
26
- message: "Please select a user:",
33
+ const { user } = await inquirer.prompt({
34
+ type: "select",
35
+ message: "Switch to which account?",
27
36
  name: "user",
37
+ default: config_json.default_user,
28
38
  choices: Object.keys(config_json.users),
29
39
  });
30
40
  default_user = user;
31
41
  }
32
- if (default_user in config_json.users) {
33
- config_json["default_user"] = default_user;
34
- fs.writeFileSync(GLOBAL_CONF_PATH, JSON.stringify(config_json));
35
- cli.log(`${chalk.green("[Success]")} user changed successfully.`);
42
+ if (default_user && default_user in config_json.users) {
43
+ config_json.default_user = default_user;
44
+ await this.write_config(config_json);
45
+ cli.log(ui.success(`Now using ${ui.value(default_user)}.`));
36
46
  }
37
47
  else {
38
- cli.log(`${chalk.red("[Error]")} selected user not exist!`);
48
+ cli.log(ui.error(`No account named ${ui.value(String(default_user))} is logged in.`));
49
+ cli.log(ui.hint(`See your accounts with ${ui.cmd('chabok account list')}`));
39
50
  }
40
51
  }
41
52
  }
@@ -0,0 +1,40 @@
1
+ import Command from "../../base.js";
2
+ interface Choice {
3
+ value: string;
4
+ name: string;
5
+ }
6
+ export default class CloudserverCreate extends Command {
7
+ static description: string;
8
+ static examples: {
9
+ description: string;
10
+ command: string;
11
+ }[];
12
+ static flags: {
13
+ hostname: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ wallet: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ help: import("@oclif/core/interfaces").BooleanFlag<void>;
16
+ };
17
+ run(): Promise<void>;
18
+ /**
19
+ * Fetches locations for `cloudId` and lets the user pick one. Some
20
+ * locations (notably Iranian ones) come with a legal disclaimer under
21
+ * `extra_data.description` that the user must read and accept before the
22
+ * server can be created — this isn't documented in the OpenAPI schema at
23
+ * all, only discovered by hitting the create endpoint's rejection message.
24
+ */
25
+ pick_location(cloudId: string): Promise<undefined | {
26
+ location: Choice;
27
+ descriptionConfirmed: boolean | undefined;
28
+ }>;
29
+ pick_hostname(flagValue: string | undefined): Promise<string | undefined>;
30
+ /**
31
+ * Fetches a list from `endpoint`, lets the user pick one, and returns its
32
+ * {value, name}. `toChoice` defaults to a generic id/name mapper, but most
33
+ * of these endpoints have a known shape (learned by hand against the live
34
+ * API), so callers pass an explicit mapper for a clearer label. `value` is
35
+ * whatever the create request needs (an id) — the choice list itself only
36
+ * ever shows `name`.
37
+ */
38
+ pick(label: string, method: 'get' | 'post', endpoint: string, body: Record<string, unknown> | undefined, wrapperKeys: string[], toChoice?: (record: Record<string, unknown>) => Choice): Promise<Choice | undefined>;
39
+ }
40
+ export {};