mage-remote-run 0.2.6 → 0.4.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.
@@ -25,6 +25,7 @@ import { registerOrdersCommands } from '../lib/commands/orders.js';
25
25
  import { registerEavCommands } from '../lib/commands/eav.js';
26
26
  import { registerProductsCommands } from '../lib/commands/products.js';
27
27
  import { registerTaxCommands } from '../lib/commands/tax.js';
28
+ import { registerInventoryCommands } from '../lib/commands/inventory.js';
28
29
 
29
30
  registerConnectionCommands(program);
30
31
  registerWebsitesCommands(program);
@@ -34,6 +35,7 @@ registerOrdersCommands(program);
34
35
  registerEavCommands(program);
35
36
  registerProductsCommands(program);
36
37
  registerTaxCommands(program);
38
+ registerInventoryCommands(program);
37
39
 
38
40
  function resolveCommandMatch(parent, token) {
39
41
  const tokenLower = token.toLowerCase();
@@ -7,19 +7,35 @@ export function registerCustomersCommands(program) {
7
7
  const customers = program.command('customer').description('Manage customers');
8
8
 
9
9
  customers.command('list')
10
+ .description('List customers')
10
11
  .option('-p, --page <number>', 'Page number', '1')
11
12
  .option('-s, --size <number>', 'Page size', '20')
13
+ .option('-f, --format <type>', 'Output format (text, json, xml)', 'text')
12
14
  .action(async (options) => {
13
15
  try {
14
16
  const client = await createClient();
17
+ const headers = {};
18
+ if (options.format === 'json') headers['Accept'] = 'application/json';
19
+ else if (options.format === 'xml') headers['Accept'] = 'application/xml';
20
+
15
21
  const params = {
16
22
  'searchCriteria[currentPage]': options.page,
17
23
  'searchCriteria[pageSize]': options.size
18
24
  };
19
- const data = await client.get('V1/customers/search', params);
25
+ const data = await client.get('V1/customers/search', params, { headers });
26
+
27
+ if (options.format === 'json') {
28
+ console.log(JSON.stringify(data, null, 2));
29
+ return;
30
+ }
31
+ if (options.format === 'xml') {
32
+ console.log(data);
33
+ return;
34
+ }
35
+
20
36
  const rows = (data.items || []).map(c => [c.id, c.email, c.firstname, c.lastname, c.group_id]);
21
37
  console.log(chalk.bold(`Total: ${data.total_count}, Page: ${options.page}, Size: ${options.size}`));
22
- printTable(['ID', 'Email', 'First Name', 'Last Name', 'Group'], rows);
38
+ printTable(['ID', 'Email', 'First Name', 'Last Name', 'Group ID'], rows);
23
39
  } catch (e) { handleError(e); }
24
40
  });
25
41
 
@@ -193,4 +209,23 @@ export function registerCustomersCommands(program) {
193
209
  }
194
210
  }
195
211
  });
212
+ const groups = customers.command('group').description('Manage customer groups');
213
+
214
+ groups.command('list')
215
+ .description('List customer groups')
216
+ .option('-p, --page <number>', 'Page number', '1')
217
+ .option('-s, --size <number>', 'Page size', '20')
218
+ .action(async (options) => {
219
+ try {
220
+ const client = await createClient();
221
+ const params = {
222
+ 'searchCriteria[currentPage]': options.page,
223
+ 'searchCriteria[pageSize]': options.size
224
+ };
225
+ const data = await client.get('V1/customerGroups/search', params);
226
+ const rows = (data.items || []).map(g => [g.id, g.code, g.tax_class_id]);
227
+ console.log(chalk.bold(`Total: ${data.total_count}, Page: ${options.page}, Size: ${options.size}`));
228
+ printTable(['ID', 'Code', 'Tax Class ID'], rows);
229
+ } catch (e) { handleError(e); }
230
+ });
196
231
  }
@@ -12,14 +12,29 @@ export function registerEavCommands(program) {
12
12
  .description('List all attribute sets')
13
13
  .option('-p, --page <number>', 'Page number', '1')
14
14
  .option('-s, --size <number>', 'Page size', '20')
15
+ .option('-f, --format <type>', 'Output format (text, json, xml)', 'text')
15
16
  .action(async (options) => {
16
17
  try {
17
18
  const client = await createClient();
19
+ const headers = {};
20
+ if (options.format === 'json') headers['Accept'] = 'application/json';
21
+ else if (options.format === 'xml') headers['Accept'] = 'application/xml';
22
+
18
23
  const params = {
19
24
  'searchCriteria[currentPage]': options.page,
20
25
  'searchCriteria[pageSize]': options.size
21
26
  };
22
- const data = await client.get('V1/eav/attribute-sets/list', params);
27
+ const data = await client.get('V1/eav/attribute-sets/list', params, { headers });
28
+
29
+ if (options.format === 'json') {
30
+ console.log(JSON.stringify(data, null, 2));
31
+ return;
32
+ }
33
+ if (options.format === 'xml') {
34
+ console.log(data);
35
+ return;
36
+ }
37
+
23
38
  const rows = (data.items || []).map(set => [
24
39
  set.attribute_set_id,
25
40
  set.attribute_set_name,
@@ -0,0 +1,105 @@
1
+
2
+ import { createClient } from '../api/factory.js';
3
+ import { printTable, handleError } from '../utils.js';
4
+ import chalk from 'chalk';
5
+
6
+ export function registerInventoryCommands(program) {
7
+ const inventory = program.command('inventory').description('Manage inventory');
8
+
9
+ const stock = inventory.command('stock').description('Manage inventory stocks');
10
+
11
+ stock.command('list')
12
+ .description('List inventory stocks')
13
+ .action(async () => {
14
+ try {
15
+ const client = await createClient();
16
+ const data = await client.get('V1/inventory/stocks', {
17
+ 'searchCriteria[currentPage]': 1,
18
+ 'searchCriteria[pageSize]': 50
19
+ });
20
+ // Check if result is items array or object with items
21
+ const items = data.items ? data.items : data;
22
+ const rows = (Array.isArray(items) ? items : []).map(s => [s.stock_id, s.name]);
23
+ printTable(['ID', 'Name'], rows);
24
+ } catch (e) { handleError(e); }
25
+ });
26
+
27
+ stock.command('show <stockId>')
28
+ .description('Show stock details')
29
+ .action(async (stockId) => {
30
+ try {
31
+ const client = await createClient();
32
+ const data = await client.get(`V1/inventory/stocks/${stockId}`);
33
+ console.log(chalk.bold.blue('\nšŸ“¦ Stock Information'));
34
+ console.log(chalk.gray('━'.repeat(60)));
35
+ console.log(` ${chalk.bold('ID:')} ${data.stock_id}`);
36
+ console.log(` ${chalk.bold('Name:')} ${data.name}`);
37
+ if (data.extension_attributes && data.extension_attributes.sales_channels) {
38
+ console.log(chalk.bold('\nšŸ“¢ Sales Channels'));
39
+ data.extension_attributes.sales_channels.forEach(sc => {
40
+ console.log(` Type: ${sc.type}, Code: ${sc.code}`);
41
+ });
42
+ }
43
+ console.log('');
44
+ } catch (e) { handleError(e); }
45
+ });
46
+
47
+ inventory.command('resolve-stock <type> <code>')
48
+ .description('Resolve stock for a sales channel')
49
+ .action(async (type, code) => {
50
+ try {
51
+ const client = await createClient();
52
+ const stock = await client.get(`V1/inventory/stock-resolver/${type}/${code}`);
53
+ console.log(chalk.bold(`Resolved Stock ID: ${stock.stock_id}`));
54
+ } catch (e) { handleError(e); }
55
+ });
56
+
57
+ const source = inventory.command('source').description('Manage inventory sources');
58
+
59
+ source.command('list')
60
+ .description('List inventory sources')
61
+ .option('-p, --page <number>', 'Page number', '1')
62
+ .option('-s, --size <number>', 'Page size', '20')
63
+ .option('-f, --format <type>', 'Output format (text, json, xml)', 'text')
64
+ .action(async (options) => {
65
+ try {
66
+ const client = await createClient();
67
+ const headers = {};
68
+ if (options.format === 'json') headers['Accept'] = 'application/json';
69
+ else if (options.format === 'xml') headers['Accept'] = 'application/xml';
70
+
71
+ const params = {
72
+ 'searchCriteria[currentPage]': options.page,
73
+ 'searchCriteria[pageSize]': options.size
74
+ };
75
+ const data = await client.get('V1/inventory/sources', params, { headers });
76
+
77
+ if (options.format === 'json') {
78
+ console.log(JSON.stringify(data, null, 2));
79
+ return;
80
+ }
81
+ if (options.format === 'xml') {
82
+ console.log(data);
83
+ return;
84
+ }
85
+
86
+ const rows = (data.items || []).map(s => [s.source_code, s.name, s.enabled ? 'Yes' : 'No', s.postcode]);
87
+ console.log(chalk.bold(`Total: ${data.total_count}, Page: ${options.page}, Size: ${options.size}`));
88
+ printTable(['Code', 'Name', 'Enabled', 'Postcode'], rows);
89
+ } catch (e) { handleError(e); }
90
+ });
91
+
92
+ const ssa = source.command('selection-algorithm').description('Manage source selection algorithms');
93
+
94
+ ssa.command('list')
95
+ .description('List available source selection algorithms')
96
+ .action(async () => {
97
+ try {
98
+ const client = await createClient();
99
+ const data = await client.get('V1/inventory/source-selection-algorithm-list');
100
+ const items = data.items ? data.items : data;
101
+ const rows = (Array.isArray(items) ? items : []).map(a => [a.code, a.title, a.description]);
102
+ printTable(['Code', 'Title', 'Description'], rows);
103
+ } catch (e) { handleError(e); }
104
+ });
105
+ }
@@ -9,14 +9,29 @@ export function registerOrdersCommands(program) {
9
9
  orders.command('list')
10
10
  .option('-p, --page <number>', 'Page number', '1')
11
11
  .option('-s, --size <number>', 'Page size', '20')
12
+ .option('-f, --format <type>', 'Output format (text, json, xml)', 'text')
12
13
  .action(async (options) => {
13
14
  try {
14
15
  const client = await createClient();
16
+ const headers = {};
17
+ if (options.format === 'json') headers['Accept'] = 'application/json';
18
+ else if (options.format === 'xml') headers['Accept'] = 'application/xml';
19
+
15
20
  const params = {
16
21
  'searchCriteria[currentPage]': options.page,
17
22
  'searchCriteria[pageSize]': options.size
18
23
  };
19
- const data = await client.get('V1/orders', params);
24
+ const data = await client.get('V1/orders', params, { headers });
25
+
26
+ if (options.format === 'json') {
27
+ console.log(JSON.stringify(data, null, 2));
28
+ return;
29
+ }
30
+ if (options.format === 'xml') {
31
+ console.log(data);
32
+ return;
33
+ }
34
+
20
35
  const rows = (data.items || []).map(o => [o.entity_id, o.increment_id, o.status, o.grand_total, o.customer_email]);
21
36
  console.log(chalk.bold(`Total: ${data.total_count}, Page: ${options.page}, Size: ${options.size}`));
22
37
  printTable(['ID', 'Increment ID', 'Status', 'Total', 'Email'], rows);
@@ -5,7 +5,156 @@ import chalk from 'chalk';
5
5
  export function registerProductsCommands(program) {
6
6
  const products = program.command('product').description('Manage products');
7
7
 
8
- products.command('types')
8
+ products.command('list')
9
+ .description('List products')
10
+ .option('-p, --page <number>', 'Page number', '1')
11
+ .option('-s, --size <number>', 'Page size', '20')
12
+ .option('--sort-by <field>', 'Field to sort by', 'entity_id')
13
+ .option('--sort-order <order>', 'Sort order (ASC, DESC)', 'ASC')
14
+ .option('-f, --format <type>', 'Output format (text, json, xml)', 'text')
15
+ .action(async (options) => {
16
+ try {
17
+ const client = await createClient();
18
+ const headers = {};
19
+ if (options.format === 'json') headers['Accept'] = 'application/json';
20
+ else if (options.format === 'xml') headers['Accept'] = 'application/xml';
21
+
22
+ const params = {
23
+ 'searchCriteria[currentPage]': options.page,
24
+ 'searchCriteria[pageSize]': options.size,
25
+ 'searchCriteria[sortOrders][0][field]': options.sortBy,
26
+ 'searchCriteria[sortOrders][0][direction]': options.sortOrder
27
+ };
28
+ const data = await client.get('V1/products', params, { headers });
29
+
30
+ if (options.format === 'json') {
31
+ console.log(JSON.stringify(data, null, 2));
32
+ return;
33
+ }
34
+ if (options.format === 'xml') {
35
+ console.log(data);
36
+ return;
37
+ }
38
+
39
+ const rows = (data.items || []).map(p => [p.id, p.sku, p.name, p.type_id, p.price]);
40
+ console.log(chalk.bold(`Total: ${data.total_count}, Page: ${options.page}, Size: ${options.size}`));
41
+ printTable(['ID', 'SKU', 'Name', 'Type', 'Price'], rows);
42
+ } catch (e) { handleError(e); }
43
+ });
44
+
45
+ products.command('show <sku>')
46
+ .description('Show product details')
47
+ .option('-f, --format <type>', 'Output format (text, json, xml)', 'text')
48
+ .action(async (sku, options) => {
49
+ try {
50
+ const client = await createClient();
51
+ let headers = {};
52
+ if (options.format === 'json') headers['Accept'] = 'application/json';
53
+ else if (options.format === 'xml') headers['Accept'] = 'application/xml';
54
+
55
+ let data;
56
+ try {
57
+ // SKU in URL needs to be encoded, but typically client libraries handle it if properly used.
58
+ // However, we are constructing URL manually. encodeURIComponent is safer.
59
+ data = await client.get(`V1/products/${encodeURIComponent(sku)}`, {}, { headers });
60
+ } catch (e) {
61
+ throw new Error(`Product '${sku}' not found.`);
62
+ }
63
+
64
+ if (options.format === 'json') {
65
+ console.log(JSON.stringify(data, null, 2));
66
+ return;
67
+ }
68
+ if (options.format === 'xml') {
69
+ console.log(data);
70
+ return;
71
+ }
72
+
73
+ console.log(chalk.bold.blue('\nšŸ“¦ Product Information'));
74
+ console.log(chalk.gray('━'.repeat(60)));
75
+
76
+ // General
77
+ console.log(chalk.bold('\nā„¹ļø General Information'));
78
+ console.log(` ${chalk.bold('ID:')} ${data.id}`);
79
+ console.log(` ${chalk.bold('SKU:')} ${data.sku}`);
80
+ console.log(` ${chalk.bold('Name:')} ${data.name}`);
81
+ console.log(` ${chalk.bold('Type:')} ${data.type_id}`);
82
+ console.log(` ${chalk.bold('Set ID:')} ${data.attribute_set_id}`);
83
+ console.log(` ${chalk.bold('Created At:')} ${data.created_at}`);
84
+ console.log(` ${chalk.bold('Updated At:')} ${data.updated_at}`);
85
+
86
+ // Pricing
87
+ console.log(chalk.bold('\nšŸ’° Pricing'));
88
+ console.log(` ${chalk.bold('Price:')} ${data.price}`);
89
+ // Simple checks for special price if available in standard fields or attributes
90
+ const specialPrice = data.custom_attributes && data.custom_attributes.find(a => a.attribute_code === 'special_price');
91
+ if (specialPrice) {
92
+ console.log(` ${chalk.bold('Special Price:')} ${specialPrice.value}`);
93
+ }
94
+ if (data.tier_prices && data.tier_prices.length > 0) {
95
+ console.log(` ${chalk.bold('Tier Prices:')} ${data.tier_prices.length} defined`);
96
+ }
97
+
98
+ // Stock (Simple check via extension attributes usually)
99
+ if (data.extension_attributes && data.extension_attributes.stock_item) {
100
+ const stock = data.extension_attributes.stock_item;
101
+ console.log(chalk.bold('\nšŸ“¦ Stock'));
102
+ console.log(` ${chalk.bold('In Stock:')} ${stock.is_in_stock ? chalk.green('Yes') : chalk.red('No')}`);
103
+ console.log(` ${chalk.bold('Quantity:')} ${stock.qty}`);
104
+ }
105
+
106
+ // Content
107
+ const { convert } = await import('html-to-text');
108
+ const description = data.custom_attributes && data.custom_attributes.find(a => a.attribute_code === 'description');
109
+ const shortDescription = data.custom_attributes && data.custom_attributes.find(a => a.attribute_code === 'short_description');
110
+
111
+ if (description || shortDescription) {
112
+ console.log(chalk.bold('\nšŸ“ Content'));
113
+ if (shortDescription) {
114
+ console.log(chalk.bold.underline('Short Description:'));
115
+ console.log(convert(shortDescription.value, { wordwrap: 80 }));
116
+ console.log('');
117
+ }
118
+ if (description) {
119
+ console.log(chalk.bold.underline('Description:'));
120
+ console.log(convert(description.value, { wordwrap: 80 }));
121
+ console.log('');
122
+ }
123
+ }
124
+
125
+ // Custom Attributes
126
+ if (data.custom_attributes && data.custom_attributes.length > 0) {
127
+ const ignoredAttributes = ['description', 'short_description', 'special_price', 'category_ids', 'url_key'];
128
+ const visibleAttributes = data.custom_attributes.filter(a => !ignoredAttributes.includes(a.attribute_code));
129
+
130
+ if (visibleAttributes.length > 0) {
131
+ console.log(chalk.bold('\nšŸ“‹ Additional Attributes'));
132
+ const attrRows = visibleAttributes
133
+ .filter(a => typeof a.value !== 'object') // Skip complex objects for now
134
+ .map(a => [a.attribute_code, a.value]);
135
+
136
+ if (attrRows.length > 0) {
137
+ printTable(['Attribute', 'Value'], attrRows);
138
+ }
139
+ }
140
+ }
141
+
142
+ // Media
143
+ if (data.media_gallery_entries && data.media_gallery_entries.length > 0) {
144
+ console.log(chalk.bold('\nšŸ–¼ļø Media'));
145
+ data.media_gallery_entries.forEach(entry => {
146
+ console.log(` [${entry.media_type}] ${entry.file} (${entry.label || 'No Label'})`);
147
+ });
148
+ }
149
+
150
+ console.log(chalk.gray('━'.repeat(60)));
151
+
152
+ } catch (e) { handleError(e); }
153
+ });
154
+
155
+ const types = products.command('type').description('Manage product types');
156
+
157
+ types.command('list')
9
158
  .description('List available product types')
10
159
  .action(async () => {
11
160
  try {
@@ -11,14 +11,29 @@ export function registerTaxCommands(program) {
11
11
  .description('List tax classes')
12
12
  .option('-p, --page <number>', 'Page number', '1')
13
13
  .option('-s, --size <number>', 'Page size', '20')
14
+ .option('-f, --format <type>', 'Output format (text, json, xml)', 'text')
14
15
  .action(async (options) => {
15
16
  try {
16
17
  const client = await createClient();
18
+ const headers = {};
19
+ if (options.format === 'json') headers['Accept'] = 'application/json';
20
+ else if (options.format === 'xml') headers['Accept'] = 'application/xml';
21
+
17
22
  const params = {
18
23
  'searchCriteria[currentPage]': options.page,
19
24
  'searchCriteria[pageSize]': options.size
20
25
  };
21
- const data = await client.get('V1/taxClasses/search', params);
26
+ const data = await client.get('V1/taxClasses/search', params, { headers });
27
+
28
+ if (options.format === 'json') {
29
+ console.log(JSON.stringify(data, null, 2));
30
+ return;
31
+ }
32
+ if (options.format === 'xml') {
33
+ console.log(data);
34
+ return;
35
+ }
36
+
22
37
  const rows = (data.items || []).map(tc => [
23
38
  tc.class_id,
24
39
  tc.class_name,
package/lib/config.js CHANGED
@@ -5,8 +5,9 @@ import { mkdirp } from 'mkdirp';
5
5
 
6
6
  const paths = envPaths('mage-remote-run', { suffix: '' });
7
7
  const CONFIG_DIR = paths.config;
8
+ const CACHE_DIR = paths.cache;
8
9
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
9
- const TOKEN_CACHE_FILE = path.join(CONFIG_DIR, 'token-cache.json');
10
+ const TOKEN_CACHE_FILE = path.join(CACHE_DIR, 'token-cache.json');
10
11
 
11
12
  export async function loadConfig() {
12
13
  try {
@@ -69,7 +70,7 @@ export async function loadTokenCache() {
69
70
 
70
71
  export async function saveTokenCache(cache) {
71
72
  try {
72
- await mkdirp(CONFIG_DIR);
73
+ await mkdirp(CACHE_DIR);
73
74
  fs.writeFileSync(TOKEN_CACHE_FILE, JSON.stringify(cache, null, 2));
74
75
  } catch (e) {
75
76
  console.error("Error saving token cache:", e.message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mage-remote-run",
3
- "version": "0.2.6",
3
+ "version": "0.4.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": {
@@ -36,6 +36,7 @@
36
36
  "cli-table3": "^0.6.5",
37
37
  "commander": "^14.0.2",
38
38
  "env-paths": "^3.0.0",
39
+ "html-to-text": "^9.0.5",
39
40
  "inquirer": "^13.1.0",
40
41
  "mkdirp": "^3.0.1",
41
42
  "oauth-1.0a": "^2.2.6",