mage-remote-run 0.15.1 → 0.17.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.
package/README.md CHANGED
@@ -11,6 +11,8 @@ This tool is in an early stage and not yet stable. Expect breaking changes as th
11
11
  - **Connection Management**: Easily switch between multiple Magento instances (SaaS/PaaS).
12
12
  - **Interactive Prompts**: User-friendly wizard for configuration and command inputs.
13
13
  - **Rich Output**: Formatted tables and structured data display.
14
+ - **Interactive Console (REPL)**: Run CLI commands or JavaScript in a live session.
15
+ - **MCP Server**: Expose commands as MCP tools over stdio or HTTP (SSE).
14
16
  - **Comprehensive API Support**:
15
17
  - **Stores**: Manage websites, stores, and store views.
16
18
  - **Customers**: List, search, show, and delete customers.
@@ -83,6 +85,27 @@ The CLI supports multiple profiles. You can configure them interactively.
83
85
  node bin/mage-remote-run.js order show <increment_id>
84
86
  ```
85
87
 
88
+ ### Interactive Console (REPL)
89
+
90
+ ```bash
91
+ node bin/mage-remote-run.js console
92
+ # or
93
+ node bin/mage-remote-run.js repl
94
+ ```
95
+
96
+ Inside the console, you can run commands directly (for example, `website list`) or evaluate JavaScript with top-level `await`.
97
+
98
+ ### MCP Server
99
+
100
+ Expose the CLI as an MCP server:
101
+
102
+ ```bash
103
+ node bin/mage-remote-run.js mcp --transport stdio
104
+ node bin/mage-remote-run.js mcp --transport http --host 127.0.0.1 --port 18098
105
+ ```
106
+
107
+ Commands are registered as tools (for example, `website list` becomes `website_list`), and tool inputs map to the same CLI arguments and options.
108
+
86
109
  ## Development
87
110
 
88
111
  ### Testing
@@ -10,6 +10,7 @@ import { registerTaxCommands } from './commands/tax.js';
10
10
  import { registerInventoryCommands } from './commands/inventory.js';
11
11
  import { registerAdobeIoEventsCommands } from './commands/adobe-io-events.js';
12
12
  import { registerWebhooksCommands } from './commands/webhooks.js';
13
+ import { registerPurchaseOrderCartCommands } from './commands/purchase-order-cart.js';
13
14
  import { registerConsoleCommand } from './commands/console.js';
14
15
 
15
16
  export { registerConnectionCommands, registerConsoleCommand };
@@ -29,6 +30,7 @@ export function registerCoreCommands(program) {
29
30
  export function registerCloudCommands(program) {
30
31
  registerAdobeIoEventsCommands(program);
31
32
  registerCompanyCommands(program);
33
+ registerPurchaseOrderCartCommands(program);
32
34
  registerWebhooksCommands(program);
33
35
  }
34
36
 
@@ -132,4 +132,437 @@ Examples:
132
132
 
133
133
  } catch (e) { handleError(e); }
134
134
  });
135
+
136
+ //-------------------------------------------------------
137
+ // "company create" Command
138
+ //-------------------------------------------------------
139
+ company.command('create')
140
+ .description('Create a new company')
141
+ .addHelpText('after', `
142
+ Examples:
143
+ $ mage-remote-run company create
144
+ `)
145
+ .action(async () => {
146
+ try {
147
+ const client = await createClient();
148
+ const { default: inquirer } = await import('inquirer');
149
+
150
+ // Prompt for basic info
151
+ const answers = await inquirer.prompt([
152
+ { name: 'company_name', message: 'Company Name:' },
153
+ { name: 'company_email', message: 'Company Email:' },
154
+ { name: 'legal_name', message: 'Legal Name (optional):' },
155
+ { name: 'vat_tax_id', message: 'VAT/Tax ID (optional):' },
156
+ { name: 'reseller_id', message: 'Reseller ID (optional):' },
157
+ { name: 'customer_group_id', message: 'Customer Group ID:', default: '1' },
158
+ { name: 'sales_representative_id', message: 'Sales Representative ID (Admin User ID):', default: '1' },
159
+ { name: 'email', message: 'Super Admin Email:' },
160
+ { name: 'firstname', message: 'Super Admin First Name:' },
161
+ { name: 'lastname', message: 'Super Admin Last Name:' }
162
+ ]);
163
+
164
+ // Address is required usually
165
+ const address = await inquirer.prompt([
166
+ { name: 'street', message: 'Street:' },
167
+ { name: 'city', message: 'City:' },
168
+ { name: 'country_id', message: 'Country ID:', default: 'US' },
169
+ { name: 'region', message: 'Region/State:' },
170
+ { name: 'postcode', message: 'Postcode:' },
171
+ { name: 'telephone', message: 'Telephone:' }
172
+ ]);
173
+
174
+ const payload = {
175
+ company: {
176
+ company_name: answers.company_name,
177
+ company_email: answers.company_email,
178
+ legal_name: answers.legal_name,
179
+ vat_tax_id: answers.vat_tax_id,
180
+ reseller_id: answers.reseller_id,
181
+ customer_group_id: answers.customer_group_id,
182
+ sales_representative_id: answers.sales_representative_id,
183
+ super_user_id: 0, // 0 for new user
184
+ street: [address.street],
185
+ city: address.city,
186
+ country_id: address.country_id,
187
+ region: { region: address.region }, // Simplified
188
+ postcode: address.postcode,
189
+ telephone: address.telephone
190
+ },
191
+ company_admin: {
192
+ email: answers.email,
193
+ firstname: answers.firstname,
194
+ lastname: answers.lastname
195
+ }
196
+ };
197
+
198
+ // POST /V1/company/
199
+ const data = await client.post('V1/company', payload);
200
+ console.log(chalk.green(`✅ Company created with ID: ${data.id}`));
201
+ } catch (e) { handleError(e); }
202
+ });
203
+
204
+ //-------------------------------------------------------
205
+ // "company update" Command
206
+ //-------------------------------------------------------
207
+ company.command('update <companyId>')
208
+ .description('Update company details')
209
+ .addHelpText('after', `
210
+ Examples:
211
+ $ mage-remote-run company update 123
212
+ `)
213
+ .action(async (companyId) => {
214
+ try {
215
+ const client = await createClient();
216
+ const { default: inquirer } = await import('inquirer');
217
+
218
+ // First fetch data
219
+ let current;
220
+ try {
221
+ current = await client.get(`V1/company/${companyId}`);
222
+ } catch (e) {
223
+ throw new Error(`Company ${companyId} not found.`);
224
+ }
225
+
226
+ const answers = await inquirer.prompt([
227
+ { name: 'company_name', message: 'Company Name:', default: current.company_name },
228
+ { name: 'company_email', message: 'Company Email:', default: current.company_email },
229
+ { name: 'sales_representative_id', message: 'Sales Rep ID:', default: current.sales_representative_id },
230
+ { name: 'customer_group_id', message: 'Customer Group ID:', default: current.customer_group_id }
231
+ ]);
232
+
233
+ // Merge
234
+ const payload = {
235
+ ...current,
236
+ ...answers
237
+ };
238
+
239
+ await client.put(`V1/company/${companyId}`, payload);
240
+ console.log(chalk.green(`✅ Company ${companyId} updated.`));
241
+ } catch (e) { handleError(e); }
242
+ });
243
+
244
+ //-------------------------------------------------------
245
+ // "company delete" Command
246
+ //-------------------------------------------------------
247
+ company.command('delete <companyId>')
248
+ .description('Delete a company')
249
+ .option('--force', 'Force delete without confirmation')
250
+ .action(async (companyId, options) => {
251
+ try {
252
+ const client = await createClient();
253
+ const { default: inquirer } = await import('inquirer');
254
+
255
+ if (!options.force) {
256
+ const { confirm } = await inquirer.prompt([{
257
+ type: 'confirm',
258
+ name: 'confirm',
259
+ message: `Delete company ${companyId}?`,
260
+ default: false
261
+ }]);
262
+ if (!confirm) return;
263
+ }
264
+
265
+ await client.delete(`V1/company/${companyId}`);
266
+ console.log(chalk.green(`✅ Company ${companyId} deleted.`));
267
+ } catch (e) { handleError(e); }
268
+ });
269
+
270
+ //-------------------------------------------------------
271
+ // "company structure" Command
272
+ //-------------------------------------------------------
273
+ company.command('structure <companyId>')
274
+ .description('Show company structure (hierarchy)')
275
+ .action(async (companyId) => {
276
+ try {
277
+ const client = await createClient();
278
+ // GET /V1/company/relations usually returns the user structure.
279
+ // However, without a specific hierarchy endpoint that is easy to visualize, we might just dump data.
280
+ const data = await client.get(`V1/company/${companyId}`);
281
+ console.log(chalk.bold(`Company Structure for ${data.company_name} (${data.id})`));
282
+ console.log('Hierarchy visualization requires more complex data. Showing basic info.');
283
+ console.log(`Parent ID: ${data.parent_id || 'None'}`);
284
+ console.log(`Legal Name: ${data.legal_name || '-'}`);
285
+ } catch (e) { handleError(e); }
286
+ });
287
+
288
+ //-------------------------------------------------------
289
+ // "company role" Group
290
+ //-------------------------------------------------------
291
+ const role = company.command('role').description('Manage company roles');
292
+
293
+ role.command('list')
294
+ .description('List roles')
295
+ .action(async () => {
296
+ try {
297
+ const client = await createClient();
298
+ // Assuming lists all roles visible to admin context
299
+ const data = await client.get('V1/company/role', { 'searchCriteria[pageSize]': 20 });
300
+ const items = data.items || [];
301
+ const rows = items.map(r => [r.role_id, r.role_name, r.company_id]);
302
+ console.log(chalk.bold(`Total Roles: ${data.total_count}`));
303
+ printTable(['ID', 'Name', 'Company ID'], rows);
304
+ } catch (e) { handleError(e); }
305
+ });
306
+
307
+ role.command('show <roleId>')
308
+ .description('Show role details')
309
+ .action(async (roleId) => {
310
+ try {
311
+ const client = await createClient();
312
+ const data = await client.get(`V1/company/role/${roleId}`);
313
+ console.log(chalk.bold(`\nRole: ${data.role_name} (ID: ${data.role_id})`));
314
+ if (data.permissions) {
315
+ console.log(chalk.bold('\nPermissions:'));
316
+ data.permissions.forEach(p => console.log(` - ${p.resource_id}: ${p.permission}`));
317
+ }
318
+ } catch (e) { handleError(e); }
319
+ });
320
+
321
+ //-------------------------------------------------------
322
+ // "company credit" Group
323
+ //-------------------------------------------------------
324
+ const credit = company.command('credit').description('Manage company credits');
325
+
326
+ credit.command('show <companyId>')
327
+ .description('Show credit for company')
328
+ .action(async (companyId) => {
329
+ try {
330
+ const client = await createClient();
331
+ const data = await client.get(`V1/companyCredits/company/${companyId}`);
332
+ console.log(chalk.bold(`\nCredit ID: ${data.id}`));
333
+ console.log(`Balance: ${data.balance} ${data.currency_code}`);
334
+ console.log(`Limit: ${data.credit_limit} ${data.currency_code}`);
335
+ console.log(`Avail: ${data.available_limit} ${data.currency_code}`);
336
+ } catch (e) { handleError(e); }
337
+ });
338
+
339
+ credit.command('history <companyId>')
340
+ .description('Show credit history')
341
+ .option('-p, --page <number>', 'Page number', '1')
342
+ .action(async (companyId, options) => {
343
+ try {
344
+ const client = await createClient();
345
+
346
+ // 1. Get Credit ID for the company
347
+ let creditData;
348
+ try {
349
+ creditData = await client.get(`V1/companyCredits/company/${companyId}`);
350
+ } catch (e) {
351
+ throw new Error(`Could not find credit profile for company ${companyId}`);
352
+ }
353
+ const creditId = creditData.id;
354
+
355
+ // 2. Search History by company_credit_id
356
+ const params = {
357
+ 'searchCriteria[filterGroups][0][filters][0][field]': 'company_credit_id',
358
+ 'searchCriteria[filterGroups][0][filters][0][value]': creditId,
359
+ 'searchCriteria[currentPage]': options.page,
360
+ 'searchCriteria[pageSize]': 20
361
+ };
362
+ const data = await client.get('V1/companyCredits/history', params);
363
+
364
+ if (data.total_count === 0) {
365
+ console.log(chalk.yellow('No history found.'));
366
+ return;
367
+ }
368
+
369
+ const typeMap = {
370
+ 1: 'Allocate',
371
+ 2: 'Reimburse',
372
+ 3: 'Purchase',
373
+ 4: 'Refund',
374
+ 5: 'Revert',
375
+ 6: 'Currency Update'
376
+ };
377
+ const rows = (data.items || []).map(h => [
378
+ h.datetime,
379
+ `${typeMap[h.type] || 'Unknown'} (${h.type})`,
380
+ h.amount,
381
+ h.balance,
382
+ h.comment
383
+ ]);
384
+ console.log(chalk.bold(`Total Entries: ${data.total_count}`));
385
+ printTable(['Date', 'Type', 'Amount', 'Balance', 'Comment'], rows);
386
+ } catch (e) { handleError(e); }
387
+ });
388
+
389
+ credit.command('increase [creditId] [amount]')
390
+ .description('Increase balance')
391
+ .option('--currency <code >', 'Currency code')
392
+ .option('--type <number>', 'Operation Type (1=Allocate, 2=Reimburse, 4=Refund, 5=Revert)')
393
+ .option('--comment <text>', 'Comment', 'Manual increase')
394
+ .option('--po <number>', 'PO Number', '')
395
+ .action(async (creditId, amount, options) => {
396
+ try {
397
+ const client = await createClient();
398
+ const inquirer = (await import('inquirer')).default;
399
+ const { select } = await import('@inquirer/prompts');
400
+
401
+ let isInteractive = false;
402
+ if (!creditId) {
403
+ isInteractive = true;
404
+ const ans = await inquirer.prompt([{
405
+ type: 'input',
406
+ name: 'creditId',
407
+ message: 'Enter Credit ID:'
408
+ }]);
409
+ creditId = ans.creditId;
410
+ }
411
+
412
+ if (!amount) {
413
+ isInteractive = true;
414
+ const ans = await inquirer.prompt([{
415
+ type: 'input',
416
+ name: 'amount',
417
+ message: 'Enter Amount:'
418
+ }]);
419
+ amount = ans.amount;
420
+ }
421
+
422
+ // Operation Type Selection (if not provided)
423
+ let operationType = options.type;
424
+ if (!operationType) {
425
+ if (isInteractive) {
426
+ operationType = await select({
427
+ message: 'Select Operation Type:',
428
+ choices: [
429
+ { name: 'Reimburse (2)', value: 2 },
430
+ { name: 'Allocate (1)', value: 1 },
431
+ { name: 'Refund (4)', value: 4 },
432
+ { name: 'Revert (5)', value: 5 }
433
+ ],
434
+ default: 2
435
+ });
436
+ } else {
437
+ operationType = 2;
438
+ }
439
+ }
440
+
441
+ // 1. Get current credit details to find currency
442
+ let currency = options.currency;
443
+ if (!currency) {
444
+ try {
445
+ const creditData = await client.get(`V1/companyCredits/${creditId}`);
446
+ currency = creditData.currency_code;
447
+ } catch (e) {
448
+ console.warn(chalk.yellow('Could not fetch credit details to determine currency. Using interactive prompt.'));
449
+ }
450
+ }
451
+
452
+ if (!currency) {
453
+ const ans = await inquirer.prompt([{
454
+ type: 'input',
455
+ name: 'currency',
456
+ message: 'Enter currency code:',
457
+ default: 'USD'
458
+ }]);
459
+ currency = ans.currency;
460
+ }
461
+
462
+ const payload = {
463
+ value: parseFloat(amount),
464
+ currency: currency,
465
+ operationType: parseInt(operationType), // 1=Allocate, 2=Reimburse, 4=Refund, 5=Revert
466
+ comment: options.comment,
467
+ options: {
468
+ purchase_order: options.po,
469
+ order_increment: '',
470
+ currency_display: currency,
471
+ currency_base: currency
472
+ }
473
+ };
474
+
475
+ await client.post(`V1/companyCredits/${creditId}/increaseBalance`, payload);
476
+ console.log(chalk.green(`✅ Credit ${creditId} increased by ${amount} ${currency}`));
477
+ } catch (e) { handleError(e); }
478
+ });
479
+
480
+ credit.command('decrease [creditId] [amount]')
481
+ .description('Decrease balance')
482
+ .option('--currency <code>', 'Currency code')
483
+ .option('--type <number>', 'Operation Type (3=Purchase)')
484
+ .option('--comment <text>', 'Comment', 'Manual decrease')
485
+ .option('--po <number>', 'PO Number', '')
486
+ .action(async (creditId, amount, options) => {
487
+ try {
488
+ const client = await createClient();
489
+ const inquirer = (await import('inquirer')).default;
490
+ const { select } = await import('@inquirer/prompts');
491
+
492
+ let isInteractive = false;
493
+ if (!creditId) {
494
+ isInteractive = true;
495
+ const ans = await inquirer.prompt([{
496
+ type: 'input',
497
+ name: 'creditId',
498
+ message: 'Enter Credit ID:'
499
+ }]);
500
+ creditId = ans.creditId;
501
+ }
502
+
503
+ if (!amount) {
504
+ isInteractive = true;
505
+ const ans = await inquirer.prompt([{
506
+ type: 'input',
507
+ name: 'amount',
508
+ message: 'Enter Amount:'
509
+ }]);
510
+ amount = ans.amount;
511
+ }
512
+
513
+ // Operation Type Selection (if not provided)
514
+ let operationType = options.type;
515
+ if (!operationType) {
516
+ if (isInteractive) {
517
+ operationType = await select({
518
+ message: 'Select Operation Type:',
519
+ choices: [
520
+ { name: 'Purchase (3)', value: 3 },
521
+ { name: 'Reimburse (2)', value: 2 }
522
+ ],
523
+ default: 3
524
+ });
525
+ } else {
526
+ operationType = 3;
527
+ }
528
+ }
529
+
530
+ // 1. Get current credit details to find currency
531
+ let currency = options.currency;
532
+ if (!currency) {
533
+ try {
534
+ const creditData = await client.get(`V1/companyCredits/${creditId}`);
535
+ currency = creditData.currency_code;
536
+ } catch (e) {
537
+ console.warn(chalk.yellow('Could not fetch credit details to determine currency. Using interactive prompt.'));
538
+ }
539
+ }
540
+
541
+ if (!currency) {
542
+ const ans = await inquirer.prompt([{
543
+ type: 'input',
544
+ name: 'currency',
545
+ message: 'Enter currency code:',
546
+ default: 'USD'
547
+ }]);
548
+ currency = ans.currency;
549
+ }
550
+
551
+ const payload = {
552
+ value: parseFloat(amount),
553
+ currency: currency,
554
+ operationType: parseInt(operationType),
555
+ comment: options.comment,
556
+ options: {
557
+ purchase_order: options.po,
558
+ order_increment: '',
559
+ currency_display: currency,
560
+ currency_base: currency
561
+ }
562
+ };
563
+
564
+ await client.post(`V1/companyCredits/${creditId}/decreaseBalance`, payload);
565
+ console.log(chalk.green(`✅ Credit ${creditId} decreased by ${amount} ${currency}`));
566
+ } catch (e) { handleError(e); }
567
+ });
135
568
  }
@@ -73,6 +73,39 @@ Examples:
73
73
  } catch (e) { handleError(e); }
74
74
  });
75
75
 
76
+ //-------------------------------------------------------
77
+ // "customer create" Command
78
+ //-------------------------------------------------------
79
+ customers.command('create')
80
+ .description('Create a new customer')
81
+ .addHelpText('after', `
82
+ Examples:
83
+ $ mage-remote-run customer create
84
+ `)
85
+ .action(async () => {
86
+ try {
87
+ const client = await createClient();
88
+ const answers = await inquirer.prompt([
89
+ { name: 'firstname', message: 'First Name:' },
90
+ { name: 'lastname', message: 'Last Name:' },
91
+ { name: 'email', message: 'Email:' },
92
+ { name: 'password', message: 'Password:', type: 'password' }
93
+ ]);
94
+
95
+ const payload = {
96
+ customer: {
97
+ email: answers.email,
98
+ firstname: answers.firstname,
99
+ lastname: answers.lastname
100
+ },
101
+ password: answers.password
102
+ };
103
+
104
+ const data = await client.post('V1/customers', payload);
105
+ console.log(chalk.green(`✅ Customer created with ID: ${data.id}`));
106
+ } catch (e) { handleError(e); }
107
+ });
108
+
76
109
 
77
110
  //-------------------------------------------------------
78
111
  // "customer edit" Command
@@ -241,12 +274,8 @@ Examples:
241
274
  websiteId = answers.websiteId;
242
275
  }
243
276
 
244
- if (!customerId && !options.redirectUrl) {
245
- const { redirectUrl } = await inquirer.prompt([
246
- { name: 'redirectUrl', message: 'Redirect URL (optional):' }
247
- ]);
248
- options.redirectUrl = redirectUrl;
249
- }
277
+ // Redirect URL is optional and only used if provided via flag
278
+ // User requested to remove usage of interactive prompt for it.
250
279
 
251
280
  const payload = {
252
281
  email: email,
@@ -105,6 +105,57 @@ Examples:
105
105
  } catch (e) { handleError(e); }
106
106
  });
107
107
 
108
+ //-------------------------------------------------------
109
+ // "order cancel" Command
110
+ //-------------------------------------------------------
111
+ orders.command('cancel <id>')
112
+ .description('Cancel an order')
113
+ .addHelpText('after', `
114
+ Examples:
115
+ $ mage-remote-run order cancel 123
116
+ `)
117
+ .action(async (id) => {
118
+ try {
119
+ const client = await createClient();
120
+ await client.post(`V1/orders/${id}/cancel`);
121
+ console.log(chalk.green(`Order ${id} cancelled.`));
122
+ } catch (e) { handleError(e); }
123
+ });
124
+
125
+ //-------------------------------------------------------
126
+ // "order hold" Command
127
+ //-------------------------------------------------------
128
+ orders.command('hold <id>')
129
+ .description('Hold an order')
130
+ .addHelpText('after', `
131
+ Examples:
132
+ $ mage-remote-run order hold 123
133
+ `)
134
+ .action(async (id) => {
135
+ try {
136
+ const client = await createClient();
137
+ await client.post(`V1/orders/${id}/hold`);
138
+ console.log(chalk.green(`Order ${id} put on hold.`));
139
+ } catch (e) { handleError(e); }
140
+ });
141
+
142
+ //-------------------------------------------------------
143
+ // "order unhold" Command
144
+ //-------------------------------------------------------
145
+ orders.command('unhold <id>')
146
+ .description('Unhold an order')
147
+ .addHelpText('after', `
148
+ Examples:
149
+ $ mage-remote-run order unhold 123
150
+ `)
151
+ .action(async (id) => {
152
+ try {
153
+ const client = await createClient();
154
+ await client.post(`V1/orders/${id}/unhold`);
155
+ console.log(chalk.green(`Order ${id} released from hold.`));
156
+ } catch (e) { handleError(e); }
157
+ });
158
+
108
159
  //-------------------------------------------------------
109
160
  // "order show" Command
110
161
  //-------------------------------------------------------
@@ -171,6 +171,32 @@ Examples:
171
171
  } catch (e) { handleError(e); }
172
172
  });
173
173
 
174
+ const media = products.command('media').description('Manage product media');
175
+
176
+ //-------------------------------------------------------
177
+ // "product media list" Command
178
+ //-------------------------------------------------------
179
+ media.command('list <sku>')
180
+ .description('List media for a product')
181
+ .addHelpText('after', `
182
+ Examples:
183
+ $ mage-remote-run product media list SKU123
184
+ `)
185
+ .action(async (sku) => {
186
+ try {
187
+ const client = await createClient();
188
+ const data = await client.get(`V1/products/${encodeURIComponent(sku)}/media`);
189
+
190
+ if (!data || data.length === 0) {
191
+ console.log('No media found.');
192
+ return;
193
+ }
194
+
195
+ const rows = data.map(m => [m.id, m.media_type, m.file, m.label || '', m.position, m.disabled ? 'Yes' : 'No']);
196
+ printTable(['ID', 'Type', 'File', 'Label', 'Pos', 'Disabled'], rows);
197
+ } catch (e) { handleError(e); }
198
+ });
199
+
174
200
  const types = products.command('type').description('Manage product types');
175
201
 
176
202
 
@@ -0,0 +1,63 @@
1
+ import { createClient } from '../api/factory.js';
2
+ import { printTable, handleError } from '../utils.js';
3
+ import chalk from 'chalk';
4
+
5
+ export function registerPurchaseOrderCartCommands(program) {
6
+ const poCart = program.command('po-cart').description('Manage Purchase Order Carts');
7
+
8
+ //-------------------------------------------------------
9
+ // "po-cart totals" Command
10
+ //-------------------------------------------------------
11
+ poCart.command('totals <cartId>')
12
+ .description('Get purchase order cart totals')
13
+ .action(async (cartId) => {
14
+ try {
15
+ const client = await createClient();
16
+ const data = await client.get(`V1/purchase-order-carts/${cartId}/totals`);
17
+
18
+ console.log(chalk.bold('Totals:'));
19
+ const rows = (data.total_segments || []).map(t => [t.title, t.value]);
20
+ printTable(['Title', 'Value'], rows);
21
+
22
+ console.log(chalk.bold(`\nGrand Total: ${data.grand_total} ${data.quote_currency_code}`));
23
+ } catch (e) { handleError(e); }
24
+ });
25
+
26
+ //-------------------------------------------------------
27
+ // "po-cart shipping-methods" Command
28
+ //-------------------------------------------------------
29
+ poCart.command('shipping-methods <cartId>')
30
+ .description('Estimate shipping methods (requires address ID)')
31
+ .requiredOption('--address-id <id>', 'Address ID to estimate for')
32
+ .action(async (cartId, options) => {
33
+ try {
34
+ const client = await createClient();
35
+ const payload = { addressId: options.addressId };
36
+ const data = await client.post(`V1/purchase-order-carts/${cartId}/estimate-shipping-methods-by-address-id`, payload);
37
+
38
+ console.log(chalk.bold('Shipping Methods:'));
39
+ const rows = data.map(m => [m.carrier_title, m.method_title, m.amount, m.price_excl_tax]);
40
+ printTable(['Carrier', 'Method', 'Amount', 'Price Excl Tax'], rows);
41
+ } catch (e) { handleError(e); }
42
+ });
43
+
44
+ //-------------------------------------------------------
45
+ // "po-cart payment-info" Command
46
+ //-------------------------------------------------------
47
+ poCart.command('payment-info <cartId>')
48
+ .description('Get payment information')
49
+ .action(async (cartId) => {
50
+ try {
51
+ const client = await createClient();
52
+ const data = await client.get(`V1/purchase-order-carts/${cartId}/payment-information`);
53
+
54
+ console.log(chalk.bold('Payment Methods:'));
55
+ const methods = data.payment_methods || [];
56
+ methods.forEach(m => console.log(`- ${m.title} (${m.code})`));
57
+
58
+ console.log(chalk.bold('\nTotals:'));
59
+ const totals = data.totals || {};
60
+ console.log(`Grand Total: ${totals.grand_total} ${totals.quote_currency_code}`);
61
+ } catch (e) { handleError(e); }
62
+ });
63
+ }
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "mage-remote-run",
3
- "version": "0.15.1",
3
+ "version": "0.17.0",
4
4
  "description": "The remote swiss army knife for Magento Open Source, Mage-OS, Adobe Commerce",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "test": "NODE_OPTIONS=\"--experimental-vm-modules --localstorage-file=./.localstorage\" jest",
8
8
  "start": "node bin/mage-remote-run.js",
9
+ "dev:api-discover": "node scripts/parse_swagger.cjs",
9
10
  "release": "release-it"
10
11
  },
11
12
  "keywords": [],