clever-tools 4.5.2 → 4.6.1

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 (69) hide show
  1. package/README.md +10 -0
  2. package/bin/clever.js +25 -22
  3. package/package.json +2 -2
  4. package/src/clever-client/auth-bridge.js +0 -25
  5. package/src/commands/README.md +1 -0
  6. package/src/commands/accesslogs/accesslogs.command.js +4 -1
  7. package/src/commands/accesslogs/accesslogs.docs.md +1 -1
  8. package/src/commands/addon/addon.create.command.js +23 -20
  9. package/src/commands/config/config.set.command.js +3 -3
  10. package/src/commands/config-provider/config-provider.args.js +8 -0
  11. package/src/commands/config-provider/config-provider.command.js +10 -0
  12. package/src/commands/config-provider/config-provider.docs.md +109 -0
  13. package/src/commands/config-provider/config-provider.get.command.js +36 -0
  14. package/src/commands/config-provider/config-provider.import.command.js +40 -0
  15. package/src/commands/config-provider/config-provider.list.command.js +45 -0
  16. package/src/commands/config-provider/config-provider.open.command.js +17 -0
  17. package/src/commands/config-provider/config-provider.rm.command.js +25 -0
  18. package/src/commands/config-provider/config-provider.set.command.js +37 -0
  19. package/src/commands/create/create.command.js +7 -6
  20. package/src/commands/curl/curl.command.js +18 -19
  21. package/src/commands/deploy/deploy.command.js +14 -8
  22. package/src/commands/deploy/deploy.docs.md +20 -0
  23. package/src/commands/diag/diag.command.js +52 -39
  24. package/src/commands/features/features.disable.command.js +1 -2
  25. package/src/commands/features/features.enable.command.js +1 -2
  26. package/src/commands/features/features.info.command.js +1 -1
  27. package/src/commands/features/features.list.command.js +1 -2
  28. package/src/commands/global.commands.js +22 -0
  29. package/src/commands/global.options.js +1 -1
  30. package/src/commands/login/login.command.js +119 -20
  31. package/src/commands/login/login.docs.md +9 -2
  32. package/src/commands/logout/logout.command.js +39 -7
  33. package/src/commands/logout/logout.docs.md +7 -1
  34. package/src/commands/logs/logs.command.js +7 -8
  35. package/src/commands/logs/logs.docs.md +1 -1
  36. package/src/commands/ng/ng.get-config.command.js +3 -3
  37. package/src/commands/profile/profile.command.js +13 -37
  38. package/src/commands/profile/profile.docs.md +28 -0
  39. package/src/commands/profile/profile.list.command.js +44 -0
  40. package/src/commands/profile/profile.switch.command.js +80 -0
  41. package/src/commands/restart/restart.command.js +3 -2
  42. package/src/commands/ssh/ssh.command.js +2 -2
  43. package/src/commands/tokens/tokens.command.js +3 -3
  44. package/src/commands/tokens/tokens.create.command.js +4 -4
  45. package/src/config/cache.js +60 -0
  46. package/src/config/config.js +233 -0
  47. package/src/config/features.js +167 -0
  48. package/src/config/paths.js +23 -0
  49. package/src/lib/date-utils.js +48 -0
  50. package/src/lib/fs.js +49 -0
  51. package/src/lib/operator-commands.js +4 -4
  52. package/src/lib/profile.js +116 -0
  53. package/src/logger.js +123 -69
  54. package/src/logger.types.d.ts +5 -0
  55. package/src/models/app_configuration.js +42 -39
  56. package/src/models/application_configuration.js +30 -30
  57. package/src/models/config-provider.js +34 -0
  58. package/src/models/git-isomorphic.js +153 -0
  59. package/src/models/git-system.js +182 -0
  60. package/src/models/git.js +150 -128
  61. package/src/models/ids-resolver.js +1 -1
  62. package/src/models/log.js +141 -90
  63. package/src/models/send-to-api.js +58 -33
  64. package/src/models/user.js +0 -11
  65. package/src/models/utils.js +2 -2
  66. package/src/experimental-features.js +0 -91
  67. package/src/lib/format-date.js +0 -3
  68. package/src/models/configuration.js +0 -140
  69. package/src/models/log-v4.js +0 -189
package/README.md CHANGED
@@ -154,6 +154,16 @@ clever <command> --help # Get specific help
154
154
  clever --format json <command> # JSON output for scripting
155
155
  ```
156
156
 
157
+ ## AI Agent Integration
158
+
159
+ Clever Tools can be used with AI coding assistants (Claude Code, Cursor, Codex, GitHub Copilot, etc.):
160
+
161
+ ```bash
162
+ npx add-skill CleverCloud/clever-tools
163
+ ```
164
+
165
+ This installs the Clever Tools skill, giving your AI assistant knowledge of all commands and Clever Cloud concepts.
166
+
157
167
  ## Support & Contributing
158
168
 
159
169
  - **Issues & Questions**: [GitHub Issues](https://github.com/CleverCloud/clever-tools/issues)
package/bin/clever.js CHANGED
@@ -14,11 +14,10 @@ import {
14
14
  verboseOption,
15
15
  versionOption,
16
16
  } from '../src/commands/global.options.js';
17
- import { EXPERIMENTAL_FEATURES } from '../src/experimental-features.js';
17
+ import { EXPERIMENTAL_FEATURES, getFeatures } from '../src/config/features.js';
18
18
  import { cliparse } from '../src/lib/cliparse-patched.js';
19
19
  import { styleText } from '../src/lib/style-text.js';
20
20
  import { getDefault, isBoolean, isRequired } from '../src/lib/zod-utils.js';
21
- import { getFeatures } from '../src/models/configuration.js';
22
21
 
23
22
  /**
24
23
  * @typedef {import('../src/lib/define-command.types.js').CommandDefinition} CommandDefinition
@@ -150,25 +149,25 @@ function buildCommand(name, commandEntry, featuresFromConf) {
150
149
  * @returns {Object} cliparse command
151
150
  */
152
151
  function convertCommand(name, commandDef, subcommands = []) {
153
- const config = {
152
+ const cliparseConfig = {
154
153
  description: commandDef.description,
155
154
  };
156
155
 
157
156
  if (commandDef.options != null) {
158
- config.privateOptions = Object.values(commandDef.options).map(convertOption);
157
+ cliparseConfig.privateOptions = Object.values(commandDef.options).map(convertOption);
159
158
  }
160
159
 
161
160
  // Convert arguments
162
161
  if (commandDef.args != null && commandDef.args.length > 0) {
163
- config.args = commandDef.args.map(convertArgument);
162
+ cliparseConfig.args = commandDef.args.map(convertArgument);
164
163
  }
165
164
 
166
165
  // Add subcommands
167
166
  if (subcommands.length > 0) {
168
- config.commands = subcommands;
167
+ cliparseConfig.commands = subcommands;
169
168
  }
170
169
 
171
- const command = cliparse.command(name, config, commandDef.handler);
170
+ const command = cliparse.command(name, cliparseConfig, commandDef.handler);
172
171
 
173
172
  // Attach the original definition so cliparse-patched.js can use it for --help display
174
173
  command._definition = commandDef;
@@ -182,26 +181,28 @@ function convertCommand(name, commandDef, subcommands = []) {
182
181
  * @returns {Object} cliparse option
183
182
  */
184
183
  function convertOption(option) {
185
- const config = {
184
+ const cliparseConfig = {
186
185
  description: option.description,
187
186
  };
188
187
 
189
188
  if (option.aliases != null) {
190
- config.aliases = option.aliases;
189
+ cliparseConfig.aliases = option.aliases;
191
190
  }
192
191
  const optionDefault = getDefault(option.schema);
193
192
  if (optionDefault != null) {
194
- config.default = optionDefault;
193
+ // Parse the default value through the schema to apply transforms
194
+ const parsed = option.schema.safeParse(optionDefault);
195
+ cliparseConfig.default = parsed.success ? parsed.data : optionDefault;
195
196
  }
196
197
  if (option.placeholder != null) {
197
- config.metavar = option.placeholder;
198
+ cliparseConfig.metavar = option.placeholder;
198
199
  }
199
200
  if (option.complete != null) {
200
- config.complete = option.complete;
201
+ cliparseConfig.complete = option.complete;
201
202
  }
202
203
 
203
204
  // Use Zod's safeParse for validation (handles coercion, enums, refinements, etc.)
204
- config.parser = (value) => {
205
+ cliparseConfig.parser = (value) => {
205
206
  // Log deprecation warning if option is deprecated
206
207
  if (option.deprecated) {
207
208
  const message = typeof option.deprecated === 'string' ? `, ${option.deprecated}.` : '';
@@ -217,16 +218,16 @@ function convertOption(option) {
217
218
 
218
219
  // Mark as required if schema has no default and is not optional
219
220
  if (isRequired(option.schema)) {
220
- config.required = true;
221
+ cliparseConfig.required = true;
221
222
  }
222
223
 
223
224
  // Boolean schemas: use cliparse.option with expects_value: false (like cliparse.flag does)
224
225
  if (isBoolean(option.schema)) {
225
226
  // eslint-disable-next-line camelcase -- cliparse API uses snake_case
226
- config.expects_value = false;
227
+ cliparseConfig.expects_value = false;
227
228
  }
228
229
 
229
- return cliparse.option(option.name, config);
230
+ return cliparse.option(option.name, cliparseConfig);
230
231
  }
231
232
 
232
233
  /**
@@ -235,16 +236,16 @@ function convertOption(option) {
235
236
  * @returns {Object} cliparse argument
236
237
  */
237
238
  function convertArgument(arg) {
238
- const config = {
239
+ const cliparseConfig = {
239
240
  description: arg.description,
240
241
  };
241
242
 
242
243
  if (arg.complete) {
243
- config.complete = arg.complete;
244
+ cliparseConfig.complete = arg.complete;
244
245
  }
245
246
 
246
247
  // Use Zod's safeParse for validation (handles coercion, enums, refinements, etc.)
247
- config.parser = (value) => {
248
+ cliparseConfig.parser = (value) => {
248
249
  const result = arg.schema.safeParse(value);
249
250
  if (!result.success) {
250
251
  throw new Error(result.error.issues.map((i) => i.message).join(', '));
@@ -254,10 +255,12 @@ function convertArgument(arg) {
254
255
 
255
256
  const argDefault = getDefault(arg.schema);
256
257
  if (argDefault != null) {
257
- config.default = argDefault;
258
+ // Parse the default value through the schema to apply transforms
259
+ const parsed = arg.schema.safeParse(argDefault);
260
+ cliparseConfig.default = parsed.success ? parsed.data : argDefault;
258
261
  } else if (!isRequired(arg.schema)) {
259
- config.default = '';
262
+ cliparseConfig.default = '';
260
263
  }
261
264
 
262
- return cliparse.argument(arg.placeholder, config);
265
+ return cliparse.argument(arg.placeholder, cliparseConfig);
263
266
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clever-tools",
3
- "version": "4.5.2",
3
+ "version": "4.6.1",
4
4
  "description": "Command Line Interface for Clever Cloud.",
5
5
  "keywords": [
6
6
  "cli",
@@ -63,6 +63,7 @@
63
63
  "lodash": "4.17.21",
64
64
  "open": "10.2.0",
65
65
  "semver": "7.7.3",
66
+ "simple-git": "3.30.0",
66
67
  "slugify": "1.6.6",
67
68
  "tldts": "7.0.19",
68
69
  "update-notifier": "7.3.1",
@@ -89,7 +90,6 @@
89
90
  "remark-parse": "11.0.0",
90
91
  "remark-stringify": "11.0.0",
91
92
  "rollup": "4.53.3",
92
- "simple-git": "3.30.0",
93
93
  "tinyglobby": "0.2.15",
94
94
  "typescript": "5.9.3",
95
95
  "unified": "11.0.5"
@@ -22,28 +22,3 @@ export function deleteApiToken(apiTokenId) {
22
22
  headers: { Accept: 'application/json' },
23
23
  });
24
24
  }
25
-
26
- export function addOauthHeaderPlaintext(tokens) {
27
- return async function (requestParams) {
28
- const authorizationHeader =
29
- 'OAuth ' +
30
- [
31
- `oauth_consumer_key="${tokens.OAUTH_CONSUMER_KEY}"`,
32
- `oauth_token="${tokens.API_OAUTH_TOKEN}"`,
33
- // %26 is URL escaped character "&"
34
- `oauth_signature="${tokens.OAUTH_CONSUMER_SECRET}%26${tokens.API_OAUTH_TOKEN_SECRET}"`,
35
- // oauth_nonce is not mandatory
36
- // oauth_signature_method is not mandatory, it defaults to PLAINTEXT
37
- // oauth_timestamp is not mandatory
38
- // oauth_version is not mandatory, it defaults to 1.0
39
- ].join(', ');
40
-
41
- return {
42
- ...requestParams,
43
- headers: {
44
- authorization: authorizationHeader,
45
- ...requestParams.headers,
46
- },
47
- };
48
- };
49
- }
@@ -22,6 +22,7 @@ These options are available for all commands:
22
22
  |[`clever applications`](./applications/applications.docs.md)|List linked applications|
23
23
  |[`clever cancel-deploy`](./cancel-deploy/cancel-deploy.docs.md)|Cancel an ongoing deployment|
24
24
  |[`clever config`](./config/config.docs.md)|Display or edit the configuration of your application|
25
+ |[`clever config-provider`](./config-provider/config-provider.docs.md)|Manage configuration providers|
25
26
  |[`clever console`](./console/console.docs.md)|Open an application in the Console|
26
27
  |[`clever create`](./create/create.docs.md)|Create an application|
27
28
  |[`clever curl`](./curl/curl.docs.md)|Query Clever Cloud's API using Clever Tools credentials|
@@ -145,7 +145,10 @@ export const accesslogsCommand = defineCommand({
145
145
  });
146
146
 
147
147
  // Properly close the stream
148
- process.once('SIGINT', (signal) => stream.close(signal));
148
+ process.once('SIGINT', (signal) => {
149
+ stream.close(signal);
150
+ process.kill(process.pid, 'SIGINT');
151
+ });
149
152
 
150
153
  const closeReason = await stream.start();
151
154
 
@@ -12,7 +12,7 @@ clever accesslogs [options]
12
12
 
13
13
  |Name|Description|
14
14
  |---|---|
15
- |`--addon` `<addon-id>`|Add-on ID|
15
+ |`--addon` `<addon-id>`|Add-on ID or real ID|
16
16
  |`--after`, `--since` `<after>`|Fetch logs after this date/time (ISO8601 date, positive number in seconds or duration, e.g.: 1h)|
17
17
  |`-a`, `--alias` `<alias>`|Short name for the application|
18
18
  |`--app` `<app-id\|app-name>`|Application to manage by its ID (or name, if unambiguous)|
@@ -1,6 +1,7 @@
1
1
  import dedent from 'dedent';
2
2
  import { z } from 'zod';
3
3
  import { getOperator } from '../../clever-client/operators.js';
4
+ import { config } from '../../config/config.js';
4
5
  import { defineCommand } from '../../lib/define-command.js';
5
6
  import { defineOption } from '../../lib/define-option.js';
6
7
  import { styleText } from '../../lib/style-text.js';
@@ -9,7 +10,6 @@ import * as Addon from '../../models/addon.js';
9
10
  import { completePlan, completeRegion, parseAddonOptions } from '../../models/addon.js';
10
11
  import * as AppConfig from '../../models/app_configuration.js';
11
12
  import { listAvailableAliases } from '../../models/application.js';
12
- import { conf } from '../../models/configuration.js';
13
13
  import * as Organisation from '../../models/organisation.js';
14
14
  import { sendToApi } from '../../models/send-to-api.js';
15
15
  import * as User from '../../models/user.js';
@@ -20,39 +20,37 @@ import { addonNameArg, addonProviderArg } from './addon.args.js';
20
20
  const ADDON_PROVIDERS = {
21
21
  keycloak: {
22
22
  name: 'Keycloak',
23
- isOperator: true,
24
- postCreateInstructions: `Learn more about Keycloak on Clever Cloud: ${conf.DOC_URL}/addons/keycloak/`,
23
+ operatorProvider: 'keycloak',
24
+ postCreateInstructions: `Learn more about Keycloak on Clever Cloud: ${config.DOC_URL}/addons/keycloak/`,
25
25
  },
26
26
  kv: {
27
27
  name: 'Materia KV',
28
- isOperator: false,
29
28
  status: 'beta',
30
29
  postCreateInstructions: (addonId) => dedent`
31
30
  ${styleText('yellow', "You can easily use Materia KV with 'redis-cli', with such commands:")}
32
31
  ${styleText('blue', `source <(clever addon env ${addonId} -F shell)`)}
33
32
  ${styleText('blue', 'redis-cli -h $KV_HOST -p $KV_PORT --tls')}
34
- Learn more about Materia KV on Clever Cloud: ${conf.DOC_URL}/addons/materia-kv/
33
+ Learn more about Materia KV on Clever Cloud: ${config.DOC_URL}/addons/materia-kv/
35
34
  `,
36
35
  },
37
36
  'addon-matomo': {
38
37
  name: 'Matomo',
39
- isOperator: true,
40
- postCreateInstructions: `Learn more about Matomo on Clever Cloud: ${conf.DOC_URL}/addons/matomo/`,
38
+ operatorProvider: 'matomo',
39
+ postCreateInstructions: `Learn more about Matomo on Clever Cloud: ${config.DOC_URL}/addons/matomo/`,
41
40
  },
42
41
  metabase: {
43
42
  name: 'Metabase',
44
- isOperator: true,
45
- postCreateInstructions: `Learn more about Metabase on Clever Cloud: ${conf.DOC_URL}/addons/metabase/`,
43
+ operatorProvider: 'metabase',
44
+ postCreateInstructions: `Learn more about Metabase on Clever Cloud: ${config.DOC_URL}/addons/metabase/`,
46
45
  },
47
46
  otoroshi: {
48
47
  name: 'Otoroshi with LLM',
49
- isOperator: true,
50
- postCreateInstructions: `Learn more about Otoroshi with LLM on Clever Cloud: ${conf.DOC_URL}/addons/otoroshi/`,
48
+ operatorProvider: 'otoroshi',
49
+ postCreateInstructions: `Learn more about Otoroshi with LLM on Clever Cloud: ${config.DOC_URL}/addons/otoroshi/`,
51
50
  },
52
51
  'addon-pulsar': {
53
52
  name: 'Pulsar',
54
- isOperator: false,
55
- postCreateInstructions: `Learn more about Pulsar on Clever Cloud: ${conf.DOC_URL}/addons/pulsar/`,
53
+ postCreateInstructions: `Learn more about Pulsar on Clever Cloud: ${config.DOC_URL}/addons/pulsar/`,
56
54
  },
57
55
  };
58
56
 
@@ -98,7 +96,10 @@ export const addonCreateCommand = defineCommand({
98
96
  }),
99
97
  option: defineOption({
100
98
  name: 'option',
101
- schema: z.string().transform(addonOptions).optional(),
99
+ schema: z
100
+ .union([z.string(), z.array(z.string())])
101
+ .transform(addonOptions)
102
+ .optional(),
102
103
  description:
103
104
  'Option to enable for the add-on. Multiple --option argument can be passed to enable multiple options',
104
105
  placeholder: 'option',
@@ -187,16 +188,18 @@ export const addonCreateCommand = defineCommand({
187
188
  Logger.println(`Real ID: ${newAddon.realId}`);
188
189
  Logger.println(`Name: ${newAddon.name}`);
189
190
 
190
- const operator = ADDON_PROVIDERS[providerName]?.isOperator
191
- ? await getOperator({ provider: providerName, realId: newAddon.realId }).then(sendToApi)
192
- : null;
191
+ const operatorProvider = ADDON_PROVIDERS[providerName]?.operatorProvider;
192
+ const operator =
193
+ operatorProvider != null
194
+ ? await getOperator({ provider: operatorProvider, realId: newAddon.realId }).then(sendToApi)
195
+ : null;
193
196
 
194
- if (operator) {
195
- if (operator.accessUrl) {
197
+ if (operator != null) {
198
+ if (operator.accessUrl != null) {
196
199
  Logger.println();
197
200
  Logger.println(`Your ${ADDON_PROVIDERS[providerName].name} is starting:`);
198
201
  Logger.println(` - Access it: ${operator.accessUrl}`);
199
- Logger.println(` - Manage it: ${conf.GOTO_URL}/${newAddon.id}`);
202
+ Logger.println(` - Manage it: ${config.GOTO_URL}/${newAddon.id}`);
200
203
  }
201
204
 
202
205
  if (operator.initialCredentials) {
@@ -26,13 +26,13 @@ export const configSetCommand = defineCommand({
26
26
  async handler(options, configurationName, configurationValue) {
27
27
  const { alias, app: appIdOrName } = options;
28
28
  const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
29
- const config = ApplicationConfiguration.getById(configurationName);
29
+ const appConfig = ApplicationConfiguration.getById(configurationName);
30
30
  const newOptions = {
31
- [config.name]: ApplicationConfiguration.parse(config, configurationValue),
31
+ [appConfig.name]: ApplicationConfiguration.parse(appConfig, configurationValue),
32
32
  };
33
33
  const app = await Application.updateOptions(ownerId, appId, newOptions);
34
34
  Logger.printSuccess(
35
- `Config ${styleText('green', config.id)} successfully updated to ${styleText('green', ApplicationConfiguration.formatValue(config, app[config.name]).toString())}!`,
35
+ `Config ${styleText('green', appConfig.id)} successfully updated to ${styleText('green', ApplicationConfiguration.formatValue(appConfig, app[appConfig.name]).toString())}!`,
36
36
  );
37
37
  },
38
38
  });
@@ -0,0 +1,8 @@
1
+ import { z } from 'zod';
2
+ import { defineArgument } from '../../lib/define-argument.js';
3
+
4
+ export const configProviderIdOrNameArg = defineArgument({
5
+ schema: z.string(),
6
+ description: 'Add-on ID, real ID (config_xxx) or name (if unambiguous)',
7
+ placeholder: 'addon-id|config-provider-id|addon-name',
8
+ });
@@ -0,0 +1,10 @@
1
+ import { defineCommand } from '../../lib/define-command.js';
2
+
3
+ export const configProviderCommand = defineCommand({
4
+ description: 'Manage configuration providers',
5
+ since: '4.6.0',
6
+ options: {},
7
+ args: [],
8
+ // Parent command - no handler, only contains subcommands
9
+ handler: null,
10
+ });
@@ -0,0 +1,109 @@
1
+ # 📖 `clever config-provider` command reference
2
+
3
+ ## ➡️ `clever config-provider` <kbd>Since 4.6.0</kbd>
4
+
5
+ Manage configuration providers
6
+
7
+ ```bash
8
+ clever config-provider
9
+ ```
10
+
11
+ ## ➡️ `clever config-provider get` <kbd>Since 4.6.0</kbd>
12
+
13
+ List environment variables of a configuration provider
14
+
15
+ ```bash
16
+ clever config-provider get <addon-id|config-provider-id|addon-name> [options]
17
+ ```
18
+
19
+ ### 📥 Arguments
20
+
21
+ |Name|Description|
22
+ |---|---|
23
+ |`addon-id|config-provider-id|addon-name`|Add-on ID, real ID (config_xxx) or name (if unambiguous)|
24
+
25
+ ### ⚙️ Options
26
+
27
+ |Name|Description|
28
+ |---|---|
29
+ |`-F`, `--format` `<format>`|Output format (human, json, shell) (default: human)|
30
+
31
+ ## ➡️ `clever config-provider import` <kbd>Since 4.6.0</kbd>
32
+
33
+ Load environment variables from STDIN
34
+ (WARNING: this deletes all current variables and replaces them with the new list loaded from STDIN)
35
+
36
+ ```bash
37
+ clever config-provider import <addon-id|config-provider-id|addon-name> [options]
38
+ ```
39
+
40
+ ### 📥 Arguments
41
+
42
+ |Name|Description|
43
+ |---|---|
44
+ |`addon-id|config-provider-id|addon-name`|Add-on ID, real ID (config_xxx) or name (if unambiguous)|
45
+
46
+ ### ⚙️ Options
47
+
48
+ |Name|Description|
49
+ |---|---|
50
+ |`-F`, `--format` `<format>`|Input format (name-equals-value, json) (default: name-equals-value)|
51
+
52
+ ## ➡️ `clever config-provider list` <kbd>Since 4.6.0</kbd>
53
+
54
+ List configuration providers
55
+
56
+ ```bash
57
+ clever config-provider list [options]
58
+ ```
59
+
60
+ ### ⚙️ Options
61
+
62
+ |Name|Description|
63
+ |---|---|
64
+ |`-F`, `--format` `<format>`|Output format (human, json) (default: human)|
65
+
66
+ ## ➡️ `clever config-provider open` <kbd>Since 4.6.0</kbd>
67
+
68
+ Open the configuration provider in Clever Cloud Console
69
+
70
+ ```bash
71
+ clever config-provider open <addon-id|config-provider-id|addon-name>
72
+ ```
73
+
74
+ ### 📥 Arguments
75
+
76
+ |Name|Description|
77
+ |---|---|
78
+ |`addon-id|config-provider-id|addon-name`|Add-on ID, real ID (config_xxx) or name (if unambiguous)|
79
+
80
+ ## ➡️ `clever config-provider rm` <kbd>Since 4.6.0</kbd>
81
+
82
+ Remove an environment variable from a configuration provider
83
+
84
+ ```bash
85
+ clever config-provider rm <addon-id|config-provider-id|addon-name> <variable-name>
86
+ ```
87
+
88
+ ### 📥 Arguments
89
+
90
+ |Name|Description|
91
+ |---|---|
92
+ |`addon-id|config-provider-id|addon-name`|Add-on ID, real ID (config_xxx) or name (if unambiguous)|
93
+ |`variable-name`|Name of the environment variable|
94
+
95
+ ## ➡️ `clever config-provider set` <kbd>Since 4.6.0</kbd>
96
+
97
+ Add or update an environment variable named <variable-name> with the value <variable-value>
98
+
99
+ ```bash
100
+ clever config-provider set <addon-id|config-provider-id|addon-name> <variable-name> <variable-value>
101
+ ```
102
+
103
+ ### 📥 Arguments
104
+
105
+ |Name|Description|
106
+ |---|---|
107
+ |`addon-id|config-provider-id|addon-name`|Add-on ID, real ID (config_xxx) or name (if unambiguous)|
108
+ |`variable-name`|Name of the environment variable|
109
+ |`variable-value`|Value of the environment variable|
@@ -0,0 +1,36 @@
1
+ import { getConfigProviderEnv } from '@clevercloud/client/esm/api/v4/addon.js';
2
+ import { toNameEqualsValueString } from '@clevercloud/client/esm/utils/env-vars.js';
3
+ import { defineCommand } from '../../lib/define-command.js';
4
+ import { Logger } from '../../logger.js';
5
+ import { resolveConfigProviderId } from '../../models/config-provider.js';
6
+ import { sendToApi } from '../../models/send-to-api.js';
7
+ import { envFormatOption } from '../global.options.js';
8
+ import { configProviderIdOrNameArg } from './config-provider.args.js';
9
+
10
+ export const configProviderGetCommand = defineCommand({
11
+ description: 'List environment variables of a configuration provider',
12
+ since: '4.6.0',
13
+ options: {
14
+ format: envFormatOption,
15
+ },
16
+ args: [configProviderIdOrNameArg],
17
+ async handler(options, addonIdOrRealIdOrName) {
18
+ const { format } = options;
19
+ const { realId } = await resolveConfigProviderId(addonIdOrRealIdOrName);
20
+
21
+ // API returns an array of { name, value } objects
22
+ const envVars = await getConfigProviderEnv({ configurationProviderId: realId }).then(sendToApi);
23
+
24
+ switch (format) {
25
+ case 'json':
26
+ Logger.printJson(envVars);
27
+ break;
28
+ case 'shell':
29
+ Logger.println(toNameEqualsValueString(envVars, { addExports: true }));
30
+ break;
31
+ case 'human':
32
+ default:
33
+ Logger.println(toNameEqualsValueString(envVars, { addExports: false }));
34
+ }
35
+ },
36
+ });
@@ -0,0 +1,40 @@
1
+ import { updateConfigProviderEnv } from '@clevercloud/client/esm/api/v4/addon.js';
2
+ import { z } from 'zod';
3
+ import { defineCommand } from '../../lib/define-command.js';
4
+ import { defineOption } from '../../lib/define-option.js';
5
+ import { Logger } from '../../logger.js';
6
+ import { resolveConfigProviderId } from '../../models/config-provider.js';
7
+ import { sendToApi } from '../../models/send-to-api.js';
8
+ import * as variables from '../../models/variables.js';
9
+ import { configProviderIdOrNameArg } from './config-provider.args.js';
10
+
11
+ const importFormatOption = defineOption({
12
+ name: 'format',
13
+ schema: z.enum(['name-equals-value', 'json']).default('name-equals-value'),
14
+ description: 'Input format (name-equals-value, json)',
15
+ aliases: ['F'],
16
+ placeholder: 'format',
17
+ });
18
+
19
+ export const configProviderImportCommand = defineCommand({
20
+ description:
21
+ 'Load environment variables from STDIN\n(WARNING: this deletes all current variables and replaces them with the new list loaded from STDIN)',
22
+ since: '4.6.0',
23
+ options: {
24
+ format: importFormatOption,
25
+ },
26
+ args: [configProviderIdOrNameArg],
27
+ async handler(options, addonIdOrRealIdOrName) {
28
+ const { format } = options;
29
+ const { realId } = await resolveConfigProviderId(addonIdOrRealIdOrName);
30
+
31
+ // readVariablesFromStdin returns { NAME: "value" } format
32
+ // but the API expects [{ name, value }] format
33
+ const envVarsObject = await variables.readVariablesFromStdin(format);
34
+ const envVarsArray = Object.entries(envVarsObject).map(([name, value]) => ({ name, value }));
35
+
36
+ await updateConfigProviderEnv({ configurationProviderId: realId }, envVarsArray).then(sendToApi);
37
+
38
+ Logger.println('Environment variables have been set');
39
+ },
40
+ });
@@ -0,0 +1,45 @@
1
+ import { defineCommand } from '../../lib/define-command.js';
2
+ import { styleText } from '../../lib/style-text.js';
3
+ import { Logger } from '../../logger.js';
4
+ import { findAddonsByAddonProvider } from '../../models/ids-resolver.js';
5
+ import { humanJsonOutputFormatOption } from '../global.options.js';
6
+
7
+ export const configProviderListCommand = defineCommand({
8
+ description: 'List configuration providers',
9
+ since: '4.6.0',
10
+ options: {
11
+ format: humanJsonOutputFormatOption,
12
+ },
13
+ args: [],
14
+ async handler(options) {
15
+ const { format } = options;
16
+ const deployed = await findAddonsByAddonProvider('config-provider');
17
+ const providersPerOwner = Object.groupBy(deployed, (provider) => provider.ownerId);
18
+
19
+ switch (format) {
20
+ case 'json':
21
+ Logger.printJson(providersPerOwner);
22
+ break;
23
+ case 'human':
24
+ default:
25
+ if (deployed.length === 0) {
26
+ Logger.println(
27
+ `No configuration provider found, create one with ${styleText('blue', 'clever addon create config-provider')} command`,
28
+ );
29
+ return;
30
+ }
31
+
32
+ Logger.println(`Found ${deployed.length} configuration provider${deployed.length > 1 ? 's' : ''}:`);
33
+ Logger.println();
34
+
35
+ Object.values(providersPerOwner).forEach((providers) => {
36
+ Logger.println(`${styleText('bold', `${providers[0].ownerId} (${providers[0].ownerName})`)}`);
37
+ providers.forEach((provider) => {
38
+ Logger.println(` ${provider.name} ${styleText('grey', `(${provider.realId})`)}`);
39
+ });
40
+ Logger.println();
41
+ });
42
+ break;
43
+ }
44
+ },
45
+ });
@@ -0,0 +1,17 @@
1
+ import { config } from '../../config/config.js';
2
+ import { defineCommand } from '../../lib/define-command.js';
3
+ import { styleText } from '../../lib/style-text.js';
4
+ import { resolveConfigProviderId } from '../../models/config-provider.js';
5
+ import { openBrowser } from '../../models/utils.js';
6
+ import { configProviderIdOrNameArg } from './config-provider.args.js';
7
+
8
+ export const configProviderOpenCommand = defineCommand({
9
+ description: 'Open the configuration provider in Clever Cloud Console',
10
+ since: '4.6.0',
11
+ options: {},
12
+ args: [configProviderIdOrNameArg],
13
+ async handler(_options, addonIdOrRealIdOrName) {
14
+ const { addonId } = await resolveConfigProviderId(addonIdOrRealIdOrName);
15
+ await openBrowser(`${config.GOTO_URL}/${addonId}`, `Opening ${styleText('blue', addonId)} in the browser...`);
16
+ },
17
+ });
@@ -0,0 +1,25 @@
1
+ import { getConfigProviderEnv, updateConfigProviderEnv } from '@clevercloud/client/esm/api/v4/addon.js';
2
+ import { defineCommand } from '../../lib/define-command.js';
3
+ import { Logger } from '../../logger.js';
4
+ import { resolveConfigProviderId } from '../../models/config-provider.js';
5
+ import { sendToApi } from '../../models/send-to-api.js';
6
+ import { envVariableNameArg } from '../global.args.js';
7
+ import { configProviderIdOrNameArg } from './config-provider.args.js';
8
+
9
+ export const configProviderRmCommand = defineCommand({
10
+ description: 'Remove an environment variable from a configuration provider',
11
+ since: '4.6.0',
12
+ options: {},
13
+ args: [configProviderIdOrNameArg, envVariableNameArg],
14
+ async handler(_options, addonIdOrRealIdOrName, varName) {
15
+ const { realId } = await resolveConfigProviderId(addonIdOrRealIdOrName);
16
+
17
+ // API returns an array of { name, value } objects
18
+ const envVars = await getConfigProviderEnv({ configurationProviderId: realId }).then(sendToApi);
19
+ const filteredEnvVars = envVars.filter((v) => v.name !== varName);
20
+
21
+ await updateConfigProviderEnv({ configurationProviderId: realId }, filteredEnvVars).then(sendToApi);
22
+
23
+ Logger.println('Environment variable has been removed');
24
+ },
25
+ });