puter-cli 1.8.5 → 2.0.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.
@@ -9,6 +9,7 @@ import { deleteSite } from './sites.js';
9
9
  import { copyFile, createFile, listRemoteFiles, pathExists, removeFileOrDirectory } from './files.js';
10
10
  import { getCurrentDirectory } from './auth.js';
11
11
  import crypto from '../crypto.js';
12
+ import { getPuter } from '../modules/PuterModule.js';
12
13
 
13
14
  /**
14
15
  * List all apps
@@ -23,22 +24,13 @@ import crypto from '../crypto.js';
23
24
  */
24
25
  export async function listApps({ statsPeriod = 'all', iconSize = 64 } = {}) {
25
26
  console.log(chalk.green(`Listing of apps during period "${chalk.cyan(statsPeriod)}" (try also: today, yesterday, 7d, 30d, this_month, last_month):\n`));
27
+ const puter = getPuter();
26
28
  try {
27
- const response = await fetch(`${API_BASE}/drivers/call`, {
28
- method: 'POST',
29
- headers: getHeaders(),
30
- body: JSON.stringify({
31
- interface: "puter-apps",
32
- method: "select",
33
- args: {
34
- params: { icon_size: iconSize },
35
- predicate: ["user-can-edit"],
36
- stats_period: statsPeriod,
37
- }
38
- })
29
+ const result = await puter.apps.list({
30
+ icon_size: iconSize,
31
+ stats_period: statsPeriod
39
32
  });
40
- const data = await response.json();
41
- if (data && data['result']) {
33
+ if (result) {
42
34
  // Create a new table instance
43
35
  const table = new Table({
44
36
  head: [
@@ -57,7 +49,7 @@ export async function listApps({ statsPeriod = 'all', iconSize = 64 } = {}) {
57
49
 
58
50
  // Populate the table with app data
59
51
  let i = 0;
60
- for (const app of data['result']) {
52
+ for (const app of result) {
61
53
  table.push([
62
54
  i++,
63
55
  app['title'],
@@ -72,7 +64,7 @@ export async function listApps({ statsPeriod = 'all', iconSize = 64 } = {}) {
72
64
 
73
65
  // Display the table
74
66
  console.log(table.toString());
75
- console.log(chalk.green(`You have in total: ${chalk.cyan(data['result'].length)} application(s).`));
67
+ console.log(chalk.green(`You have in total: ${chalk.cyan(result.length)} application(s).`));
76
68
  } else {
77
69
  console.error(chalk.red('Unable to list your apps. Please check your credentials.'));
78
70
  }
@@ -97,24 +89,12 @@ export async function appInfo(args = []) {
97
89
  }
98
90
  const appName = args[0].trim()
99
91
  console.log(chalk.green(`Looking for "${chalk.dim(appName)}" app informations:\n`));
92
+ const puter = getPuter();
100
93
  try {
101
- const response = await fetch(`${API_BASE}/drivers/call`, {
102
- method: 'POST',
103
- headers: getHeaders(),
104
- body: JSON.stringify({
105
- interface: "puter-apps",
106
- method: "read",
107
- args: {
108
- id: {
109
- name: appName
110
- }
111
- }
112
- })
113
- });
114
- const data = await response.json();
115
- if (data && data['result']) {
94
+ const result = await puter.apps.get(appName);
95
+ if (result) {
116
96
  // Display the informations
117
- displayNonNullValues(data['result']);
97
+ displayNonNullValues(result);
118
98
  } else {
119
99
  console.error(chalk.red('Could not find this app.'));
120
100
  }
@@ -150,40 +130,24 @@ export async function createApp(args) {
150
130
  console.log(chalk.dim(`Description: ${description}`));
151
131
  console.log(chalk.dim(`URL: ${url}`));
152
132
 
133
+ const puter = getPuter();
153
134
  try {
154
135
  // Step 1: Create the app
155
- const createAppResponse = await fetch(`${API_BASE}/drivers/call`, {
156
- method: 'POST',
157
- headers: getHeaders(),
158
- body: JSON.stringify({
159
- interface: "puter-apps",
160
- method: "create",
161
- args: {
162
- object: {
163
- name: name,
164
- index_url: url,
165
- title: name,
166
- description: description,
167
- maximize_on_start: false,
168
- background: false,
169
- metadata: {
170
- window_resizable: true
171
- }
172
- },
173
- options: {
174
- dedupe_name: true
175
- }
176
- }
177
- })
136
+ const createAppData = await puter.apps.create({
137
+ name: name,
138
+ indexURL: url,
139
+ title: name,
140
+ description: description,
141
+ maximizeOnStart: false,
142
+ dedupeName: true
178
143
  });
179
- const createAppData = await createAppResponse.json();
180
- if (!createAppData || !createAppData.success) {
144
+ if (!createAppData) {
181
145
  console.error(chalk.red(`Failed to create app "${name}"`));
182
146
  return;
183
147
  }
184
- const appUid = createAppData.result.uid;
185
- const appName = createAppData.result.name;
186
- const username = createAppData.result.owner.username;
148
+ const appUid = createAppData.uid;
149
+ const appName = createAppData.name;
150
+ const username = createAppData.owner.username;
187
151
  console.log(chalk.green(`App "${chalk.dim(name)}" created successfully!`));
188
152
  console.log(chalk.cyan(`AppName: ${chalk.dim(appName)}\nUID: ${chalk.dim(appUid)}\nUsername: ${chalk.dim(username)}`));
189
153
 
@@ -191,18 +155,11 @@ export async function createApp(args) {
191
155
  const uid = crypto.randomUUID();
192
156
  const appDir = `/${username}/AppData/${appUid}`;
193
157
  console.log(chalk.green(`Creating directory...\nPath: ${chalk.dim(appDir)}\nApp: ${chalk.dim(name)}\nUID: ${chalk.dim(uid)}\n`));
194
- const createDirResponse = await fetch(`${API_BASE}/mkdir`, {
195
- method: 'POST',
196
- headers: getHeaders(),
197
- body: JSON.stringify({
198
- parent: appDir,
199
- path: `app-${uid}`,
200
- overwrite: true,
201
- dedupe_name: false,
202
- create_missing_parents: true
203
- })
204
- });
205
- const createDirData = await createDirResponse.json();
158
+ const createDirData = await puter.fs.mkdir(`${appDir}/app-${uid}`, {
159
+ overwrite: true,
160
+ dedupeName: false,
161
+ createMissingParents: true,
162
+ })
206
163
  if (!createDirData || !createDirData.uid) {
207
164
  console.error(chalk.red(`Failed to create directory for app "${name}"`));
208
165
  return;
@@ -246,23 +203,11 @@ export async function createApp(args) {
246
203
 
247
204
  // Step 5: Update the app's index_url to point to the subdomain
248
205
  console.log(chalk.green(`Set "${chalk.dim(subdomainName)}" as a subdomain for app: "${chalk.dim(appName)}"...\n`));
249
- const updateAppResponse = await fetch(`${API_BASE}/drivers/call`, {
250
- method: 'POST',
251
- headers: getHeaders(),
252
- body: JSON.stringify({
253
- interface: "puter-apps",
254
- method: "update",
255
- args: {
256
- id: { name: appName },
257
- object: {
258
- index_url: `https://${subdomainName}.puter.site`,
259
- title: name
260
- }
261
- }
262
- })
263
- });
264
- const updateAppData = await updateAppResponse.json();
265
- if (!updateAppData || !updateAppData.success) {
206
+ const updateAppData = await puter.apps.update(appName, {
207
+ indexURL: `https://${subdomainName}.puter.site`,
208
+ title: name
209
+ })
210
+ if (!updateAppData) {
266
211
  console.error(chalk.red(`Failed to update app "${name}" with new subdomain`));
267
212
  return;
268
213
  }
@@ -301,35 +246,25 @@ export async function updateApp(args = []) {
301
246
  return;
302
247
  }
303
248
 
249
+ const puter = getPuter();
304
250
  console.log(chalk.green(`Updating app: "${chalk.dim(name)}" from directory: ${chalk.dim(remoteDir)}\n`));
305
251
  try {
306
252
  // Step 1: Get the app info
307
- const appResponse = await fetch(`${API_BASE}/drivers/call`, {
308
- method: 'POST',
309
- headers: getHeaders(),
310
- body: JSON.stringify({
311
- interface: "puter-apps",
312
- method: "read",
313
- args: {
314
- id: { name }
315
- }
316
- })
317
- });
318
- const data = await appResponse.json();
319
- if (!data || !data.success) {
253
+ const data = await puter.apps.get(name);
254
+ if (!data) {
320
255
  console.error(chalk.red(`Failed to find app: "${name}"`));
321
256
  return;
322
257
  }
323
- const appUid = data.result.uid;
324
- const appName = data.result.name;
325
- const username = data.result.owner.username;
326
- const indexUrl = data.result.index_url;
258
+ const appUid = data.uid;
259
+ const appName = data.name;
260
+ const username = data.owner.username;
261
+ const indexUrl = data.index_url;
327
262
  const appDir = `/${username}/AppData/${appUid}`;
328
263
  console.log(chalk.cyan(`AppName: ${chalk.dim(appName)}\nUID: ${chalk.dim(appUid)}\nUsername: ${chalk.dim(username)}`));
329
264
 
330
265
  // Step 2: Find the path from subdomain
331
266
  const subdomains = await getSubdomains();
332
- const appSubdomain = subdomains.result.find(sd => sd.root_dir?.dirname?.endsWith(appUid));
267
+ const appSubdomain = subdomains.find(sd => sd.root_dir?.dirname?.endsWith(appUid));
333
268
  if (!appSubdomain){
334
269
  console.error(chalk.red(`Sorry! We could not find the subdomain for ${chalk.cyan(name)} application.`));
335
270
  return;
@@ -361,6 +296,7 @@ export async function updateApp(args = []) {
361
296
  console.log(chalk.dim(indexUrl));
362
297
  } catch (error) {
363
298
  console.error(chalk.red(`Failed to update app "${name}".\nError: ${error.message}`));
299
+ console.error(error);
364
300
  }
365
301
  }
366
302
 
@@ -374,24 +310,13 @@ export async function deleteApp(name) {
374
310
  console.log(chalk.red('Usage: app:delete <name>'));
375
311
  return false;
376
312
  }
313
+ const puter = getPuter();
377
314
  console.log(chalk.green(`Checking app "${name}"...\n`));
378
315
  try {
379
316
  // Step 1: Read app details
380
- const readResponse = await fetch(`${API_BASE}/drivers/call`, {
381
- method: 'POST',
382
- headers: getHeaders(),
383
- body: JSON.stringify({
384
- interface: "puter-apps",
385
- method: "read",
386
- args: {
387
- id: { name }
388
- }
389
- })
390
- });
391
-
392
- const readData = await readResponse.json();
317
+ const readData = await puter.apps.get(name);
393
318
 
394
- if (!readData.success || !readData.result) {
319
+ if (!readData) {
395
320
  console.log(chalk.red(`App "${chalk.bold(name)}" not found.`));
396
321
  return false;
397
322
  }
@@ -399,35 +324,24 @@ export async function deleteApp(name) {
399
324
  // Show app details and confirm deletion
400
325
  console.log(chalk.cyan('\nApp Details:'));
401
326
  console.log(chalk.dim('----------------------------------------'));
402
- console.log(chalk.dim(`Name: ${chalk.cyan(readData.result.name)}`));
403
- console.log(chalk.dim(`Title: ${chalk.cyan(readData.result.title)}`));
404
- console.log(chalk.dim(`Created: ${chalk.cyan(formatDate(readData.result.created_at))}`));
405
- console.log(chalk.dim(`URL: ${readData.result.index_url}`));
327
+ console.log(chalk.dim(`Name: ${chalk.cyan(readData.name)}`));
328
+ console.log(chalk.dim(`Title: ${chalk.cyan(readData.title)}`));
329
+ console.log(chalk.dim(`Created: ${chalk.cyan(formatDate(readData.created_at))}`));
330
+ console.log(chalk.dim(`URL: ${readData.index_url}`));
406
331
  console.log(chalk.dim('----------------------------------------'));
407
332
 
408
333
  // Step 2: Delete the app
409
334
  console.log(chalk.green(`Deleting app "${chalk.red(name)}"...`));
410
- const deleteResponse = await fetch(`${API_BASE}/drivers/call`, {
411
- method: 'POST',
412
- headers: getHeaders(),
413
- body: JSON.stringify({
414
- interface: "puter-apps",
415
- method: "delete",
416
- args: {
417
- id: { name }
418
- }
419
- })
420
- });path
421
335
 
422
- const deleteData = await deleteResponse.json();
423
- if (!deleteData.success) {
336
+ const deleteData = await puter.apps.delete(name);
337
+ if (!deleteData) {
424
338
  console.error(chalk.red(`Failed to delete app "${name}".\nP.S. Make sure to provide the 'name' attribute not the 'title'.`));
425
339
  return false;
426
340
  }
427
341
 
428
342
  // Lookup subdomainUID then delete it
429
343
  const subdomains = await getSubdomains();
430
- const appSubdomain = subdomains.result.find(sd => sd.root_dir?.dirname?.endsWith(readData.result.uid));
344
+ const appSubdomain = subdomains.find(sd => sd.root_dir?.dirname?.endsWith(readData.uid));
431
345
  const subdomainDeleted = await deleteSite([appSubdomain.uid]);
432
346
  if (subdomainDeleted){
433
347
  console.log(chalk.green(`Subdomain: ${chalk.dim(appSubdomain.uid)} deleted.`));
@@ -1,21 +1,23 @@
1
- import fs from 'node:fs';
2
- import inquirer from 'inquirer';
3
1
  import chalk from 'chalk';
4
2
  import Conf from 'conf';
5
3
  import ora from 'ora';
6
- import fetch from 'node-fetch';
7
- import { PROJECT_NAME, API_BASE, getHeaders, BASE_URL } from '../commons.js'
8
- import { ProfileAPI } from '../modules/ProfileModule.js';
9
- import { get_context } from '../temporary/context_helpers.js';
4
+ import { PROJECT_NAME, } from '../commons.js'
5
+ import { getProfileModule } from '../modules/ProfileModule.js';
6
+ import { getPuter } from '../modules/PuterModule.js';
10
7
  const config = new Conf({ projectName: PROJECT_NAME });
11
8
 
12
9
  /**
13
10
  * Login user
11
+ * @param {Object} options - Login options
12
+ * @param {boolean} options.save - Save token to .env file
13
+ * @param {boolean} options.web - Use browser-based login (default)
14
+ * @param {boolean} options.withCredentials - Use username/password login
15
+ * @param {string} options.host - Puter host URL
14
16
  * @returns void
15
17
  */
16
- export async function login(args = {}, context) {
17
- const profileAPI = context[ProfileAPI];
18
- await profileAPI.switchProfileWizard();
18
+ export async function login(options = {}) {
19
+ const profileAPI = getProfileModule();
20
+ await profileAPI.switchProfileWizard(options);
19
21
  }
20
22
 
21
23
  /**
@@ -23,20 +25,31 @@ export async function login(args = {}, context) {
23
25
  * @returns void
24
26
  */
25
27
  export async function logout() {
26
-
28
+
27
29
  let spinner;
28
30
  try {
29
31
  spinner = ora('Logging out from Puter...').start();
30
32
  const token = config.get('auth_token');
31
- if (!token) {
33
+ const selected_profile = config.get('selected_profile');
34
+
35
+ if (token) {
36
+ // legacy auth
37
+ config.clear();
38
+ spinner.succeed(chalk.green('Successfully logged out from Puter!'));
39
+ } else if (selected_profile) {
40
+ // multi profile auth
41
+ config.delete('selected_profile');
42
+ config.delete('username');
43
+ config.delete('cwd');
44
+
45
+ const profiles = config.get('profiles');
46
+ config.set('profiles', profiles.filter(profile => profile.uuid != selected_profile));
47
+ spinner.succeed(chalk.green('Successfully logged out from Puter!'));
48
+ } else {
32
49
  spinner.info(chalk.yellow('Already logged out'));
33
- return;
34
50
  }
35
-
36
- config.clear(); // Remove all stored data
37
- spinner.succeed(chalk.green('Successfully logged out from Puter!'));
38
51
  } catch (error) {
39
- if (spinner){
52
+ if (spinner) {
40
53
  spinner.fail(chalk.red('Failed to logout'));
41
54
  }
42
55
  console.error(chalk.red(`Error: ${error.message}`));
@@ -45,12 +58,9 @@ export async function logout() {
45
58
 
46
59
  export async function getUserInfo() {
47
60
  console.log(chalk.green('Getting user info...\n'));
61
+ const puter = getPuter();
48
62
  try {
49
- const response = await fetch(`${API_BASE}/whoami`, {
50
- method: 'GET',
51
- headers: getHeaders()
52
- });
53
- const data = await response.json();
63
+ const data = await puter.auth.getUser();
54
64
  if (data) {
55
65
  console.log(chalk.cyan('User Information:'));
56
66
  console.log(chalk.dim('----------------------------------------'));
@@ -80,14 +90,12 @@ export function isAuthenticated() {
80
90
  }
81
91
 
82
92
  export function getAuthToken() {
83
- const context = get_context();
84
- const profileAPI = context[ProfileAPI];
93
+ const profileAPI = getProfileModule();;
85
94
  return profileAPI.getAuthToken();
86
95
  }
87
96
 
88
97
  export function getCurrentUserName() {
89
- const context = get_context();
90
- const profileAPI = context[ProfileAPI];
98
+ const profileAPI = getProfileModule();;
91
99
  return profileAPI.getCurrentProfile()?.username;
92
100
  }
93
101
 
@@ -100,98 +108,88 @@ export function getCurrentDirectory() {
100
108
  */
101
109
  export async function getUsageInfo() {
102
110
  console.log(chalk.green('Fetching usage information...\n'));
111
+ const puter = getPuter();
103
112
  try {
104
- const response = await fetch(`${API_BASE}/drivers/usage`, {
105
- method: 'GET',
106
- headers: getHeaders()
107
- });
108
-
109
- const data = await response.json();
110
- if (data) {
111
- console.log(chalk.cyan('Usage Information:'));
112
- console.log(chalk.dim('========================================'));
113
-
114
- // Display user usage in a table
115
- if (data.user && data.user.length > 0) {
116
- console.log(chalk.cyan('User Usage:'));
117
- console.log(chalk.dim('----------------------------------------'));
118
- console.log(
119
- chalk.bold('Service'.padEnd(30)) +
120
- chalk.bold('Implementation'.padEnd(20)) +
121
- chalk.bold('Month'.padEnd(10)) +
122
- chalk.bold('Usage'.padEnd(10)) +
123
- chalk.bold('Limit'.padEnd(10)) +
124
- chalk.bold('Rate Limit')
125
- );
126
- console.log(chalk.dim('----------------------------------------'));
127
- data.user.forEach(usage => {
128
- const service = `${usage.service['driver.interface']}.${usage.service['driver.method']}`;
129
- const implementation = usage.service['driver.implementation'];
130
- const month = `${usage.month}/${usage.year}`;
131
- const monthlyUsage = usage.monthly_usage?.toString();
132
- const monthlyLimit = usage.monthly_limit ? usage.monthly_limit.toString() : 'No Limit';
133
- const rateLimit = usage.policy ? `${usage.policy['rate-limit'].max} req/${usage.policy['rate-limit'].period / 1000}s` : 'N/A';
134
-
135
- console.log(
136
- service.padEnd(30) +
137
- implementation.padEnd(20) +
138
- month.padEnd(10) +
139
- monthlyUsage.padEnd(10) +
140
- monthlyLimit.padEnd(10) +
141
- rateLimit
142
- );
143
- });
144
- console.log(chalk.dim('----------------------------------------'));
145
- }
146
-
147
- // Display app usage in a table (if available)
148
- if (data.apps && Object.keys(data.apps).length > 0) {
149
- console.log(chalk.cyan('\nApp Usage:'));
150
- console.log(chalk.dim('----------------------------------------'));
151
- console.log(
152
- chalk.bold('App'.padEnd(30)) +
153
- chalk.bold('Usage'.padEnd(10)) +
154
- chalk.bold('Limit'.padEnd(10))
155
- );
156
- console.log(chalk.dim('----------------------------------------'));
157
- for (const [app, usage] of Object.entries(data.apps)) {
158
- console.log(
159
- app.padEnd(30) +
160
- usage.used.toString().padEnd(10) +
161
- usage.available.toString().padEnd(10)
162
- );
163
- }
164
- console.log(chalk.dim('----------------------------------------'));
165
- }
166
-
167
- // Display general usages in a table (if available)
168
- if (data.usages && data.usages.length > 0) {
169
- console.log(chalk.cyan('\nGeneral Usages:'));
170
- console.log(chalk.dim('----------------------------------------'));
171
- console.log(
172
- chalk.bold('Name'.padEnd(30)) +
173
- chalk.bold('Used'.padEnd(10)) +
174
- chalk.bold('Available'.padEnd(10)) +
175
- chalk.bold('Refill')
176
- );
177
- console.log(chalk.dim('----------------------------------------'));
178
- data.usages.forEach(usage => {
179
- console.log(
180
- usage.name.padEnd(30) +
181
- usage.used.toString().padEnd(10) +
182
- usage.available.toString().padEnd(10) +
183
- usage.refill
184
- );
185
- });
186
- console.log(chalk.dim('----------------------------------------'));
187
- }
188
-
189
- console.log(chalk.dim('========================================'));
190
- console.log(chalk.green('Done.'));
191
- } else {
192
- console.error(chalk.red('Unable to fetch usage information.'));
113
+ const data = await puter.auth.getMonthlyUsage();
114
+ if (data) {
115
+ // Display allowance information
116
+ if (data.allowanceInfo) {
117
+ console.log(chalk.cyan('Allowance Information:'));
118
+ console.log(chalk.dim('='.repeat(100)));
119
+ console.log(chalk.cyan(`Month Usage Allowance: `) + chalk.white(data.allowanceInfo.monthUsageAllowance.toLocaleString()));
120
+ console.log(chalk.cyan(`Remaining: `) + chalk.white(data.allowanceInfo.remaining.toLocaleString()));
121
+ const usedPercentage = ((data.allowanceInfo.monthUsageAllowance - data.allowanceInfo.remaining) / data.allowanceInfo.monthUsageAllowance * 100).toFixed(2);
122
+ console.log(chalk.cyan(`Used: `) + chalk.white(`${usedPercentage}%`));
123
+ console.log(chalk.dim('='.repeat(100)));
124
+ }
125
+
126
+ // Display usage information per API
127
+ if (data.usage) {
128
+ console.log(chalk.cyan('\nAPI Usage:'));
129
+ console.log(chalk.dim('='.repeat(100)));
130
+ console.log(
131
+ chalk.bold('API'.padEnd(50)) +
132
+ chalk.bold('Count'.padEnd(15)) +
133
+ chalk.bold('Cost'.padEnd(20)) +
134
+ chalk.bold('Units')
135
+ );
136
+ console.log(chalk.dim('='.repeat(100)));
137
+
138
+ // Filter out 'total' and sort entries by cost (descending)
139
+ const usageEntries = Object.entries(data.usage)
140
+ .filter(([key]) => key !== 'total')
141
+ .sort(([, a], [, b]) => b.cost - a.cost);
142
+
143
+ usageEntries.forEach(([api, details]) => {
144
+ console.log(
145
+ api.padEnd(50) +
146
+ details.count.toString().padEnd(15) +
147
+ details.cost.toLocaleString().padEnd(20) +
148
+ details.units.toLocaleString()
149
+ );
150
+ });
151
+
152
+ // Display total if available
153
+ if (data.usage.total !== undefined) {
154
+ console.log(chalk.dim('='.repeat(100)));
155
+ console.log(
156
+ chalk.bold('TOTAL'.padEnd(50)) +
157
+ ''.padEnd(15) +
158
+ chalk.bold(data.usage.total.toLocaleString())
159
+ );
160
+ }
161
+ console.log(chalk.dim('='.repeat(100)));
193
162
  }
163
+
164
+ // Display app totals
165
+ if (data.appTotals && Object.keys(data.appTotals).length > 0) {
166
+ console.log(chalk.cyan('\nApp Totals:'));
167
+ console.log(chalk.dim('='.repeat(100)));
168
+ console.log(
169
+ chalk.bold('App'.padEnd(50)) +
170
+ chalk.bold('Count'.padEnd(15)) +
171
+ chalk.bold('Total')
172
+ );
173
+ console.log(chalk.dim('='.repeat(100)));
174
+
175
+ // Sort by total (descending)
176
+ const appEntries = Object.entries(data.appTotals)
177
+ .sort(([, a], [, b]) => b.total - a.total);
178
+
179
+ appEntries.forEach(([app, details]) => {
180
+ console.log(
181
+ app.padEnd(50) +
182
+ details.count.toString().padEnd(15) +
183
+ details.total.toLocaleString()
184
+ );
185
+ });
186
+ console.log(chalk.dim('='.repeat(100)));
187
+ }
188
+ console.log(chalk.green('Done.'));
189
+ } else {
190
+ console.error(chalk.red('Unable to fetch usage information.'));
191
+ }
194
192
  } catch (error) {
195
- console.error(chalk.red(`Failed to fetch usage information.\nError: ${error.message}`));
193
+ console.error(chalk.red(`Failed to fetch usage information.\nError: ${error.message}`));
196
194
  }
197
195
  }