@api-now/cli 1.0.4 → 1.1.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.
@@ -1,314 +0,0 @@
1
- import { Argument } from 'commander';
2
- import http from 'node:http';
3
- import open from 'open';
4
- import { config } from '../utils/config.js';
5
- import { getSdkClient, handleApiError } from '../utils/api.js';
6
- import { Formatter, style, debug } from '../utils/formatter.js';
7
- import { setupFirstOrganizationIfNeeded } from '../utils/org-setup.js';
8
- import { registerTokensCommands } from './tokens.js';
9
- /**
10
- * Registers authentication management commands (login, status, logout) with the main Commander program.
11
- *
12
- * @param program - The root Commander program.
13
- */
14
- export function registerAuthCommands(program) {
15
- const authCmd = program.command('auth').description('Manage CLI authentication');
16
- registerTokensCommands(authCmd);
17
- authCmd
18
- .command('login')
19
- .description('Authenticate with the API platform using OAuth2 (google, github, or linkedin)')
20
- .addArgument(new Argument('provider', 'Authentication provider').choices(['google', 'github', 'linkedin']))
21
- .action(async (provider) => {
22
- const formatter = new Formatter({ format: config.resolved.format });
23
- const allowedProviders = ['google', 'github', 'linkedin'];
24
- if (!allowedProviders.includes(provider)) {
25
- formatter.error({
26
- message: `Invalid provider '${provider}'. Allowed: ${allowedProviders.join(', ')}`,
27
- });
28
- process.exit(1);
29
- }
30
- const apiUrl = config.resolved.apiUrl;
31
- if (config.resolved.format !== 'json') {
32
- console.log(`Starting authentication flow using provider '${provider}'...`);
33
- }
34
- try {
35
- await runLoginServer(apiUrl, provider, formatter);
36
- formatter.success('Successfully authenticated!');
37
- const client = getSdkClient();
38
- await setupFirstOrganizationIfNeeded(client, formatter);
39
- }
40
- catch (err) {
41
- if (err instanceof Error) {
42
- formatter.error({ message: err.message });
43
- }
44
- else {
45
- formatter.error({ message: 'Authentication failed.' });
46
- }
47
- process.exit(1);
48
- }
49
- });
50
- authCmd
51
- .command('logout')
52
- .description('Log out and clear the stored token')
53
- .action(() => {
54
- const formatter = new Formatter({ format: config.resolved.format });
55
- config.clearConfigKey('token');
56
- formatter.success('Logged out successfully.');
57
- });
58
- authCmd
59
- .command('status')
60
- .description('Check current authentication status')
61
- .action(async () => {
62
- const formatter = new Formatter({ format: config.resolved.format });
63
- const resolved = config.resolved;
64
- if (!resolved.token) {
65
- formatter.error({ message: 'Not logged in. Use `auth login <provider>` to authenticate.' });
66
- process.exit(1);
67
- }
68
- try {
69
- const client = getSdkClient();
70
- const user = await client.users.me({ token: client.token });
71
- if (resolved.format === 'json') {
72
- formatter.object({ authenticated: true, user });
73
- }
74
- else {
75
- console.log(style.green('✔ Authenticated successfully!'));
76
- console.log(`User: ${user.name} (${user.email})`);
77
- console.log(`Default Org: ${resolved.org || 'None'}`);
78
- }
79
- await setupFirstOrganizationIfNeeded(client, formatter);
80
- }
81
- catch (err) {
82
- const apiErr = handleApiError(err);
83
- formatter.error({
84
- message: 'Authentication token is invalid or expired.',
85
- detail: apiErr.message,
86
- });
87
- process.exit(1);
88
- }
89
- });
90
- }
91
- /**
92
- * Spins up a temporary local HTTP server to receive the OAuth authentication token
93
- * from the browser-based callback page.
94
- *
95
- * @param apiUrl - The target backend API server base URL.
96
- * @param provider - The OAuth login provider.
97
- * @param formatter - Output formatter.
98
- * @returns A promise resolving to the retrieved authentication token.
99
- */
100
- function runLoginServer(apiUrl, provider, formatter) {
101
- return new Promise((resolve, reject) => {
102
- const server = http.createServer((req, res) => {
103
- if (req.method === 'GET' && req.url?.startsWith('/callback')) {
104
- debug('auth', `Received request for callback: ${req.url}`);
105
- res.writeHead(200, { 'Content-Type': 'text/html' });
106
- const html = `
107
- <!DOCTYPE html>
108
- <html>
109
- <head>
110
- <title>API NOW! CLI Login</title>
111
- <style>
112
- body {
113
- font-family: system-ui, -apple-system, sans-serif;
114
- display: flex;
115
- align-items: center;
116
- justify-content: center;
117
- height: 100vh;
118
- margin: 0;
119
- background: #121214;
120
- color: #e1e1e6;
121
- }
122
- .card {
123
- background: #202024;
124
- padding: 2rem;
125
- border-radius: 8px;
126
- box-shadow: 0 4px 12px rgba(0,0,0,0.5);
127
- text-align: center;
128
- max-width: 450px;
129
- width: 100%;
130
- }
131
- h1 { color: #04d361; margin-top: 0; }
132
- .status { font-weight: bold; margin: 1rem 0; line-height: 1.4; }
133
- .diagnostics {
134
- display: none;
135
- text-align: left;
136
- margin-top: 1.5rem;
137
- padding: 1rem;
138
- background: #2f2f33;
139
- border-radius: 4px;
140
- font-size: 0.8rem;
141
- font-family: monospace;
142
- white-space: pre-wrap;
143
- word-break: break-all;
144
- border-left: 4px solid #f75a68;
145
- }
146
- </style>
147
- </head>
148
- <body>
149
- <div class="card">
150
- <h1>API NOW!</h1>
151
- <p class="status" id="status">Retrieving token from server...</p>
152
- <div class="diagnostics" id="diagnostics">
153
- <strong>Diagnostic Details:</strong>
154
- <div id="diagnostics-content" style="margin-top: 0.5rem;"></div>
155
- </div>
156
- </div>
157
- <script>
158
- fetch('${apiUrl}/auth/token', { credentials: 'include' })
159
- .then(r => {
160
- if (!r.ok) {
161
- throw new Error('Token request failed: ' + r.statusText + ' (status: ' + r.status + ')');
162
- }
163
- return r.json();
164
- })
165
- .then(data => {
166
- console.log("[DEBUG] Parse response JSON:", data);
167
- const token = data.token || data.data?.token || data.data;
168
- if (!token) {
169
- throw new Error('Token not found in API response structure');
170
- }
171
-
172
- console.log("[DEBUG] POSTing token to local server callback...");
173
- return fetch('/token', {
174
- method: 'POST',
175
- headers: { 'Content-Type': 'application/json' },
176
- body: JSON.stringify({ token })
177
- });
178
- })
179
- .then(r => {
180
- if (!r.ok) {
181
- throw new Error('Failed to save token in CLI server (status: ' + r.status + ')');
182
- }
183
- document.getElementById('status').innerText = 'Authentication successful! You can close this tab now.';
184
- document.getElementById('status').style.color = '#04d361';
185
- })
186
- .catch(err => {
187
- console.error("[ERROR]", err);
188
- document.getElementById('status').innerText = 'Authentication failed.';
189
- document.getElementById('status').style.color = '#f75a68';
190
-
191
- const diag = document.getElementById('diagnostics');
192
- const content = document.getElementById('diagnostics-content');
193
- diag.style.display = 'block';
194
- content.innerText =
195
- 'Error Message: ' + err.message + '\\n\\n' +
196
- 'Originating URL: ' + window.location.href + '\\n' +
197
- 'Target API URL: ${apiUrl}/auth/token\\n\\n' +
198
- 'TIP: If you get "Unauthorized (status: 401)", make sure that:\\n' +
199
- '1. You completed the login flow on the browser window that opened.\\n' +
200
- '2. The host of the page you are on (' + window.location.hostname + ') matches the host of the API (' + new URL('${apiUrl}').hostname + ') to satisfy SameSite cookie policy.\\n' +
201
- '3. Check browser developer console (F12) for detailed CORS/Network errors.';
202
-
203
- fetch('/error', {
204
- method: 'POST',
205
- headers: { 'Content-Type': 'application/json' },
206
- body: JSON.stringify({ message: err.message })
207
- }).catch(console.error);
208
- });
209
- </script>
210
- </body>
211
- </html>
212
- `;
213
- res.end(html);
214
- return;
215
- }
216
- if (req.method === 'POST' && req.url === '/token') {
217
- let body = '';
218
- req.on('data', (chunk) => {
219
- body += chunk;
220
- });
221
- req.on('end', () => {
222
- try {
223
- const data = JSON.parse(body);
224
- if (data.token) {
225
- debug('auth', 'Storing retrieved token to CLI configuration...');
226
- config.writeProperty('token', data.token);
227
- res.writeHead(200, { 'Content-Type': 'application/json' });
228
- res.end(JSON.stringify({ success: true }));
229
- resolve(data.token);
230
- }
231
- else {
232
- res.writeHead(400, { 'Content-Type': 'application/json' });
233
- res.end(JSON.stringify({ error: 'Token missing' }));
234
- reject(new Error('Token missing from browser callback.'));
235
- }
236
- }
237
- catch (e) {
238
- if (e instanceof Error) {
239
- res.writeHead(400, { 'Content-Type': 'application/json' });
240
- res.end(JSON.stringify({ error: e.message }));
241
- reject(new Error('Invalid body received: ' + e.message));
242
- }
243
- else {
244
- res.writeHead(400, { 'Content-Type': 'application/json' });
245
- res.end(JSON.stringify({ error: 'Invalid body received' }));
246
- reject(new Error('Invalid body received'));
247
- }
248
- }
249
- finally {
250
- cleanup();
251
- }
252
- });
253
- return;
254
- }
255
- if (req.method === 'POST' && req.url === '/error') {
256
- let body = '';
257
- req.on('data', (chunk) => {
258
- body += chunk;
259
- });
260
- req.on('end', () => {
261
- try {
262
- const data = JSON.parse(body);
263
- debug('auth', `Received login error from browser callback: ${data.message}`);
264
- reject(new Error(data.message || 'Browser login failed'));
265
- }
266
- catch {
267
- reject(new Error('Browser login failed'));
268
- }
269
- finally {
270
- cleanup();
271
- }
272
- });
273
- return;
274
- }
275
- res.writeHead(404);
276
- res.end();
277
- });
278
- const handleSigInt = () => {
279
- cleanup();
280
- process.exit(130);
281
- };
282
- process.on('SIGINT', handleSigInt);
283
- process.on('SIGTERM', handleSigInt);
284
- const timeout = setTimeout(() => {
285
- reject(new Error('Authentication timed out.'));
286
- cleanup();
287
- }, 300000);
288
- function cleanup() {
289
- clearTimeout(timeout);
290
- process.off('SIGINT', handleSigInt);
291
- process.off('SIGTERM', handleSigInt);
292
- server.close();
293
- }
294
- server.listen(0, '127.0.0.1', async () => {
295
- const port = server.address().port;
296
- // Smart hostname matching: resolve 127.0.0.1 vs localhost context dynamically to enable cookie transport
297
- const apiHost = new URL(apiUrl).hostname;
298
- const redirectHost = apiHost === 'localhost' ? 'localhost' : '127.0.0.1';
299
- const loginUrl = `${apiUrl}/auth/${provider}/redirect?r=http://${redirectHost}:${port}/callback`;
300
- debug('auth', `Ephemeral loopback server listening on http://127.0.0.1:${port}`);
301
- debug('auth', `Browser callback origin configured to http://${redirectHost}:${port}`);
302
- if (formatter.format !== 'json') {
303
- console.log(`Opening browser to log in...`);
304
- console.log(`If it doesn't open automatically, navigate to:\n ${loginUrl}`);
305
- }
306
- try {
307
- await open(loginUrl);
308
- }
309
- catch (_err) {
310
- // Ignore open failure
311
- }
312
- });
313
- });
314
- }
@@ -1,289 +0,0 @@
1
- import { getSdkClient, handleApiError } from '../utils/api.js';
2
- import { config } from '../utils/config.js';
3
- import { Formatter, CliErrorCode } from '../utils/formatter.js';
4
- import { DomainFileKind } from '@api-now/core/models/kinds.js';
5
- import { askInput, askSelect } from '../utils/prompt.js';
6
- import { DataCatalogKind, DataCatalogVersion, } from '@api-now/core/models/index.js';
7
- /**
8
- * Registers catalog management commands (publish, list) with the main Commander program.
9
- *
10
- * @param program - The root Commander program.
11
- */
12
- export function registerCatalogCommands(program) {
13
- const catalogCmd = program.command('catalog').description('Manage Data Catalog items');
14
- catalogCmd
15
- .command('publish')
16
- .description('Publish a data domain file to the data catalog')
17
- .option('--file <fid>', 'File ID of the data domain')
18
- .option('--name <name>', 'Name for the catalog entry')
19
- .option('--description <desc>', 'Description of the catalog entry')
20
- .option('--scope <scope>', 'Publish scope: public, organization, private')
21
- .option('-v, --catalog-version <ver>', 'Semantic version (e.g. 1.0.0)')
22
- .option('--org <oid>', 'Organization ID (optional, defaults to configured default org)')
23
- .option('--lifecycle <lifecycle>', 'Version lifecycle status (dev, beta, stable)', 'stable')
24
- .option('--changelog <changelog>', 'Changelog description for this version')
25
- .action(async (options) => {
26
- const formatter = new Formatter({ format: config.resolved.format });
27
- const resolved = config.resolved;
28
- const orgId = options.org || resolved.org;
29
- if (!orgId) {
30
- formatter.error({
31
- code: CliErrorCode.MISSING_REQUIRED,
32
- message: 'Organization ID is required.',
33
- detail: 'Pass --org <oid> or set a default organization using `apinow orgs set-default <oid>`.',
34
- });
35
- process.exit(1);
36
- }
37
- // Empathic interactive fallbacks
38
- let fileId = options.file;
39
- if (!fileId && process.stdout.isTTY) {
40
- try {
41
- const client = getSdkClient();
42
- const res = await client.files.list(orgId, {
43
- filter: {
44
- field: 'kind',
45
- operator: 'eq',
46
- value: DomainFileKind,
47
- },
48
- }, { token: client.token });
49
- const files = res.data || [];
50
- if (files.length > 0) {
51
- fileId = await askSelect('Select a data domain file to publish:', files.map((f) => ({
52
- name: `${f.info?.name || 'Unnamed'} (${f.key})`,
53
- value: f.key,
54
- })));
55
- }
56
- }
57
- catch (_err) {
58
- // Ignore list error and fail/prompt below
59
- }
60
- }
61
- if (!fileId) {
62
- formatter.error({
63
- code: CliErrorCode.MISSING_REQUIRED,
64
- message: 'File ID is required.',
65
- detail: 'Specify --file <fid>.',
66
- });
67
- process.exit(1);
68
- }
69
- let name = options.name;
70
- if (!name && process.stdout.isTTY) {
71
- name = await askInput('Enter catalog entry name:');
72
- }
73
- if (!name) {
74
- formatter.error({
75
- code: CliErrorCode.MISSING_REQUIRED,
76
- message: 'Name is required.',
77
- detail: 'Specify --name <name>.',
78
- });
79
- process.exit(1);
80
- }
81
- let description = options.description;
82
- if (!description && process.stdout.isTTY) {
83
- description = await askInput('Enter catalog entry description:');
84
- }
85
- if (!description) {
86
- formatter.error({
87
- code: CliErrorCode.MISSING_REQUIRED,
88
- message: 'Description is required.',
89
- detail: 'Specify --description <desc>.',
90
- });
91
- process.exit(1);
92
- }
93
- let scope = options.scope;
94
- if (!scope && process.stdout.isTTY) {
95
- scope = await askSelect('Select publish scope:', [
96
- { name: 'Public (public)', value: 'public' },
97
- { name: 'Organization (organization)', value: 'organization' },
98
- { name: 'Private (private)', value: 'private' },
99
- ]);
100
- }
101
- if (!scope) {
102
- formatter.error({
103
- code: CliErrorCode.MISSING_REQUIRED,
104
- message: 'Scope is required.',
105
- detail: 'Specify --scope <scope> (allowed: public, organization, private).',
106
- });
107
- process.exit(1);
108
- }
109
- const allowedScopes = ['public', 'organization', 'private'];
110
- if (!allowedScopes.includes(scope)) {
111
- formatter.error({
112
- code: CliErrorCode.INVALID_ARGUMENT,
113
- message: `Invalid scope '${scope}'.`,
114
- detail: `Allowed: ${allowedScopes.join(', ')}`,
115
- });
116
- process.exit(1);
117
- }
118
- let version = options.catalogVersion;
119
- if (!version && process.stdout.isTTY) {
120
- version = await askInput('Enter semantic version:', '1.0.0');
121
- }
122
- if (!version) {
123
- formatter.error({
124
- code: CliErrorCode.MISSING_REQUIRED,
125
- message: 'Version is required.',
126
- detail: 'Specify --catalog-version <ver> or -v <ver>.',
127
- });
128
- process.exit(1);
129
- }
130
- const allowedLifecycles = ['dev', 'beta', 'stable'];
131
- if (!allowedLifecycles.includes(options.lifecycle)) {
132
- formatter.error({
133
- code: CliErrorCode.INVALID_ARGUMENT,
134
- message: `Invalid lifecycle '${options.lifecycle}'.`,
135
- detail: `Allowed: ${allowedLifecycles.join(', ')}`,
136
- });
137
- process.exit(1);
138
- }
139
- try {
140
- const client = getSdkClient();
141
- let catalogId = null;
142
- try {
143
- const status = await client.dataCatalog.checkPublicationStatus(fileId, orgId, {
144
- token: client.token,
145
- });
146
- catalogId = status.key;
147
- }
148
- catch (err) {
149
- const apiErr = handleApiError(err);
150
- if (apiErr.status !== 404) {
151
- throw err;
152
- }
153
- }
154
- if (!catalogId) {
155
- if (config.resolved.format !== 'json') {
156
- console.log('Catalog entry does not exist. Creating new catalog entry...');
157
- }
158
- const entry = {
159
- key: '',
160
- kind: DataCatalogKind,
161
- organization: orgId,
162
- file: fileId,
163
- scope: scope,
164
- name: name,
165
- description: description,
166
- publishedBy: '',
167
- publishedAt: 0,
168
- createdAt: 0,
169
- updatedAt: 0,
170
- tags: [],
171
- };
172
- const createdRecord = await client.dataCatalog.publish(entry, { token: client.token });
173
- catalogId = createdRecord.key;
174
- }
175
- else {
176
- if (config.resolved.format !== 'json') {
177
- console.log(`Catalog entry already exists (ID: ${catalogId}). Publishing new version...`);
178
- }
179
- }
180
- const versionPayload = DataCatalogVersion.createSchema({
181
- scope: scope,
182
- lifecycle: options.lifecycle,
183
- version: version,
184
- changelog: options.changelog || undefined,
185
- });
186
- const versionData = await client.dataCatalog.publishVersion(catalogId, versionPayload, {
187
- token: client.token,
188
- });
189
- formatter.success(`Successfully published version '${version}' to the data catalog!`, versionData);
190
- }
191
- catch (err) {
192
- const apiErr = handleApiError(err);
193
- formatter.error(apiErr);
194
- process.exit(1);
195
- }
196
- });
197
- catalogCmd
198
- .command('list')
199
- .description('List published data catalog items or versions of a specific catalog entry')
200
- .option('--scope <scope>', 'Filter by scope: all, public, organization, private', 'all')
201
- .option('--org <oid>', 'Organization ID (optional, defaults to configured default org)')
202
- .option('--key <key>', 'Data catalog item key to list all versions of a specific data domain')
203
- .action(async (options) => {
204
- const formatter = new Formatter({ format: config.resolved.format });
205
- const resolved = config.resolved;
206
- const orgId = options.org || resolved.org;
207
- const allowedScopes = ['all', 'public', 'organization', 'private'];
208
- if (!allowedScopes.includes(options.scope)) {
209
- formatter.error({
210
- message: `Invalid scope '${options.scope}'. Allowed: ${allowedScopes.join(', ')}`,
211
- });
212
- process.exit(1);
213
- }
214
- if (['organization', 'private'].includes(options.scope) && !orgId) {
215
- formatter.error({
216
- message: `Organization ID (--org <oid>) is required when filtering by scope '${options.scope}'.`,
217
- });
218
- process.exit(1);
219
- }
220
- try {
221
- const client = getSdkClient();
222
- if (options.key) {
223
- const res = await client.dataCatalog.listVersions(options.key, { token: client.token });
224
- const versions = res.data || [];
225
- const rows = versions.map((v) => ({
226
- key: v.key,
227
- version: v.version,
228
- published: new Date(v.published).toLocaleString(),
229
- }));
230
- formatter.list(rows, ['key', 'version', 'published'], {
231
- headers: ['VERSION KEY', 'VERSION', 'PUBLISHED AT'],
232
- });
233
- }
234
- else {
235
- const results = [];
236
- if (options.scope === 'all') {
237
- const resPublic = await client.dataCatalog.list({ scope: 'public', oid: orgId }, { token: client.token });
238
- results.push(...(resPublic.data || []));
239
- if (orgId) {
240
- try {
241
- const resOrg = await client.dataCatalog.list({ scope: 'organization', oid: orgId }, { token: client.token });
242
- results.push(...(resOrg.data || []));
243
- }
244
- catch {
245
- // Ignore if organization query fails
246
- }
247
- try {
248
- const resPrivate = await client.dataCatalog.list({ scope: 'private', oid: orgId }, { token: client.token });
249
- results.push(...(resPrivate.data || []));
250
- }
251
- catch {
252
- // Ignore if private query fails
253
- }
254
- }
255
- }
256
- else {
257
- const res = await client.dataCatalog.list({ scope: options.scope, oid: orgId }, { token: client.token });
258
- results.push(...(res.data || []));
259
- }
260
- const seen = new Set();
261
- const uniqueResults = results.filter((item) => {
262
- const key = item.key;
263
- if (seen.has(key))
264
- return false;
265
- seen.add(key);
266
- return true;
267
- });
268
- const rows = uniqueResults.map((item) => {
269
- const lastVersion = item.versions && item.versions[0] ? item.versions[0].version : 'N/A';
270
- return {
271
- key: item.key,
272
- name: item.name,
273
- scope: item.scope,
274
- lastVersion,
275
- organization: item.organization || 'N/A',
276
- };
277
- });
278
- formatter.list(rows, ['key', 'name', 'scope', 'lastVersion', 'organization'], {
279
- headers: ['KEY', 'NAME', 'SCOPE', 'LAST VERSION', 'ORGANIZATION'],
280
- });
281
- }
282
- }
283
- catch (err) {
284
- const apiErr = handleApiError(err);
285
- formatter.error(apiErr);
286
- process.exit(1);
287
- }
288
- });
289
- }
@@ -1,59 +0,0 @@
1
- import { config } from '../utils/config.js';
2
- import { Formatter, CliErrorCode } from '../utils/formatter.js';
3
- /**
4
- * Registers configuration management commands with the main Commander program.
5
- *
6
- * @param program - The root Commander program.
7
- */
8
- export function registerConfigCommands(program) {
9
- const configCmd = program.command('config').description('Manage CLI configuration settings');
10
- configCmd
11
- .command('get <key>')
12
- .description('Get a configuration value')
13
- .action((key) => {
14
- const formatter = new Formatter({ format: config.resolved.format });
15
- const configObj = config.resolved;
16
- if (key in configObj) {
17
- const val = configObj[key];
18
- if (config.resolved.format === 'json') {
19
- formatter.object({ [key]: val });
20
- }
21
- else {
22
- console.log(val);
23
- }
24
- }
25
- else {
26
- formatter.error({
27
- code: CliErrorCode.INVALID_ARGUMENT,
28
- message: `Configuration key '${key}' not found.`,
29
- detail: `Run \`apinow config set ${key} <value>\` to define it.`,
30
- });
31
- process.exit(1);
32
- }
33
- });
34
- configCmd
35
- .command('set <key> <value>')
36
- .description('Set a configuration value')
37
- .action((key, value) => {
38
- const formatter = new Formatter({ format: config.resolved.format });
39
- const allowedKeys = ['apiUrl', 'token', 'org'];
40
- if (!allowedKeys.includes(key)) {
41
- formatter.error({
42
- code: CliErrorCode.INVALID_ARGUMENT,
43
- message: `Invalid configuration key '${key}'.`,
44
- detail: `Allowed keys: ${allowedKeys.join(', ')}`,
45
- });
46
- process.exit(1);
47
- }
48
- config.writeProperty(key, value);
49
- formatter.success(`Configuration '${key}' set to '${value}'.`);
50
- });
51
- configCmd
52
- .command('reset')
53
- .description('Reset all configuration settings and remove stored files')
54
- .action(() => {
55
- const formatter = new Formatter({ format: config.resolved.format });
56
- config.reset();
57
- formatter.success('Configuration has been reset successfully.');
58
- });
59
- }