@jacebenson/jsn 0.0.3

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.
@@ -0,0 +1,313 @@
1
+ import chalk from 'chalk';
2
+ import { getStringField } from '../../helpers.js';
3
+
4
+ // ─── Helpers ───
5
+
6
+ function getDisplayValue(record, key) {
7
+ if (!record || typeof record !== 'object') return '';
8
+ const val = record[key];
9
+ if (val == null) return '';
10
+ if (typeof val === 'string') return val;
11
+ if (typeof val === 'object') {
12
+ if (val.display_value != null && val.display_value !== '') return String(val.display_value);
13
+ if (val.value != null) return String(val.value);
14
+ }
15
+ return String(val);
16
+ }
17
+
18
+ function getIntValue(record, key) {
19
+ if (!record || typeof record !== 'object') return 0;
20
+ const val = record[key];
21
+ if (val == null) return 0;
22
+ if (typeof val === 'number') return Math.floor(val);
23
+ if (typeof val === 'string') {
24
+ const n = parseInt(val, 10);
25
+ return isNaN(n) ? 0 : n;
26
+ }
27
+ if (typeof val === 'object' && val.value != null) {
28
+ const n = parseInt(val.value, 10);
29
+ return isNaN(n) ? 0 : n;
30
+ }
31
+ return 0;
32
+ }
33
+
34
+ function getBoolValue(record, key) {
35
+ if (!record || typeof record !== 'object') return false;
36
+ const val = record[key];
37
+ if (typeof val === 'boolean') return val;
38
+ if (typeof val === 'string') return val === 'true';
39
+ if (typeof val === 'object' && val.value != null) return String(val.value) === 'true';
40
+ return false;
41
+ }
42
+
43
+ // ─── Commands ───
44
+
45
+ export function formsCmd(wrap) {
46
+ return {
47
+ command: 'forms [subcommand]',
48
+ aliases: ['form', 'f'],
49
+ describe: 'Manage UI Forms',
50
+ builder: (yargs) => {
51
+ return yargs
52
+ .command({
53
+ command: 'list <table>',
54
+ aliases: ['ls'],
55
+ describe: 'List form views for a table',
56
+ builder: (y) => y
57
+ .option('limit', { alias: 'l', type: 'number', default: 50, describe: 'Max records' }),
58
+ handler: wrap(async (argv, app) => {
59
+ const table = argv.table;
60
+ const limit = argv.limit || 50;
61
+
62
+ const params = new URLSearchParams();
63
+ params.set('sysparm_limit', String(limit));
64
+ params.set('sysparm_fields', 'view');
65
+ params.set('sysparm_group_by', 'view');
66
+ params.set('sysparm_display_value', 'all');
67
+ const sysparmQuery = table ? `name=${table}^ORDERBYview` : 'ORDERBYview';
68
+ params.set('sysparm_query', sysparmQuery);
69
+
70
+ const records = await app.sdk.list('sys_ui_section', params);
71
+
72
+ const viewMap = new Map();
73
+ for (const record of records) {
74
+ const view = getDisplayValue(record, 'view');
75
+ if (view && !viewMap.has(view)) {
76
+ viewMap.set(view, true);
77
+ }
78
+ }
79
+
80
+ const views = Array.from(viewMap.keys()).sort();
81
+ const defaultViews = views.filter(v => v === 'Default view');
82
+ const workspaceViews = views.filter(v => v.toLowerCase().includes('workspace'));
83
+ const otherViews = views.filter(v => !defaultViews.includes(v) && !workspaceViews.includes(v));
84
+
85
+ const recordsOut = views.map(v => ({ view: v, table }));
86
+
87
+ app.ok({
88
+ table,
89
+ count: views.length,
90
+ default: defaultViews,
91
+ workspaces: workspaceViews,
92
+ other: otherViews,
93
+ records: recordsOut,
94
+ instance_url: app.getEffectiveInstance(),
95
+ }, {
96
+ summary: `${views.length} views for ${table}`,
97
+ breadcrumbs: [
98
+ { action: 'show', cmd: `jsn dev forms show ${table} --view "Default view"`, description: 'Show Default view layout' },
99
+ ],
100
+ });
101
+ }),
102
+ })
103
+ .command({
104
+ command: 'show <table>',
105
+ aliases: ['get'],
106
+ describe: 'Show form layout',
107
+ builder: (y) => y
108
+ .option('view', { type: 'string', default: 'Default view', describe: 'View name' }),
109
+ handler: wrap(async (argv, app) => {
110
+ const table = argv.table;
111
+ const viewName = argv.view;
112
+
113
+ // Look up view sys_id
114
+ let viewSysID = '';
115
+ const viewParams = new URLSearchParams();
116
+ viewParams.set('sysparm_limit', '1');
117
+ viewParams.set('sysparm_fields', 'sys_id');
118
+ viewParams.set('sysparm_query', `name=${viewName}`);
119
+ try {
120
+ const viewRecords = await app.sdk.list('sys_ui_view', viewParams);
121
+ if (viewRecords.length > 0) {
122
+ viewSysID = getStringField(viewRecords[0], 'sys_id');
123
+ }
124
+ } catch {
125
+ // ignore
126
+ }
127
+
128
+ if (!viewSysID) {
129
+ viewParams.set('sysparm_query', `title=${viewName}`);
130
+ try {
131
+ const viewRecords = await app.sdk.list('sys_ui_view', viewParams);
132
+ if (viewRecords.length > 0) {
133
+ viewSysID = getStringField(viewRecords[0], 'sys_id');
134
+ }
135
+ } catch {
136
+ // ignore
137
+ }
138
+ }
139
+
140
+ if (!viewSysID) {
141
+ viewSysID = viewName;
142
+ }
143
+
144
+ // Fetch sections
145
+ const params = new URLSearchParams();
146
+ params.set('sysparm_limit', '100');
147
+ params.set('sysparm_fields', 'sys_id,name,view,caption,header,order,active,sys_created_on,sys_updated_on');
148
+ params.set('sysparm_display_value', 'all');
149
+
150
+ const parts = [];
151
+ if (table) parts.push(`name=${table}`);
152
+ if (viewSysID) parts.push(`view=${viewSysID}`);
153
+ const sysparmQuery = parts.length > 0 ? `${parts.join('^')}^ORDERBYorder` : 'ORDERBYorder';
154
+ params.set('sysparm_query', sysparmQuery);
155
+
156
+ const records = await app.sdk.list('sys_ui_section', params);
157
+ if (records.length === 0) {
158
+ throw new Error(`no form sections found for ${table} with view "${viewName}"`);
159
+ }
160
+
161
+ // Parse sections
162
+ const sections = records.map(record => ({
163
+ sys_id: getStringField(record, 'sys_id'),
164
+ name: getDisplayValue(record, 'name'),
165
+ view: getDisplayValue(record, 'view'),
166
+ caption: getDisplayValue(record, 'caption'),
167
+ header: getDisplayValue(record, 'header'),
168
+ order: getIntValue(record, 'order'),
169
+ active: getBoolValue(record, 'active'),
170
+ createdOn: getDisplayValue(record, 'sys_created_on'),
171
+ updatedOn: getDisplayValue(record, 'sys_updated_on'),
172
+ }));
173
+
174
+ // Fetch elements for each section
175
+ const sectionElements = new Map();
176
+ for (const section of sections) {
177
+ const elemParams = new URLSearchParams();
178
+ elemParams.set('sysparm_limit', '100');
179
+ elemParams.set('sysparm_fields', 'sys_id,sys_ui_section,element,label,type,position,row,col,mandatory,read_only,visible');
180
+ elemParams.set('sysparm_display_value', 'all');
181
+ elemParams.set('sysparm_query', `sys_ui_section=${section.sys_id}^ORDERBYposition`);
182
+
183
+ try {
184
+ const elemRecords = await app.sdk.list('sys_ui_element', elemParams);
185
+ const elements = elemRecords.map(record => ({
186
+ sys_id: getStringField(record, 'sys_id'),
187
+ section: getStringField(record, 'sys_ui_section'),
188
+ name: getDisplayValue(record, 'element'),
189
+ label: getDisplayValue(record, 'label'),
190
+ elementType: getDisplayValue(record, 'type'),
191
+ position: getIntValue(record, 'position'),
192
+ row: getIntValue(record, 'row'),
193
+ column: getIntValue(record, 'col'),
194
+ mandatory: getBoolValue(record, 'mandatory'),
195
+ readOnly: getBoolValue(record, 'read_only'),
196
+ visible: getBoolValue(record, 'visible'),
197
+ }));
198
+ sectionElements.set(section.sys_id, elements);
199
+ } catch {
200
+ sectionElements.set(section.sys_id, []);
201
+ }
202
+ }
203
+
204
+ // Build formatted output
205
+ const lines = [];
206
+ lines.push('');
207
+ lines.push(chalk.bold(chalk.hex('#e8a217')(`${table} (${viewName})`)));
208
+ lines.push('');
209
+
210
+ for (let i = 0; i < sections.length; i++) {
211
+ const section = sections[i];
212
+ const elements = sectionElements.get(section.sys_id) || [];
213
+
214
+ let sectionTitle = section.caption;
215
+ if (!sectionTitle && section.header && section.header !== 'false') {
216
+ sectionTitle = section.header;
217
+ }
218
+ if (!sectionTitle) {
219
+ sectionTitle = `Section ${i + 1}`;
220
+ }
221
+
222
+ lines.push(chalk.bold(chalk.hex('#666666')(`─ ${sectionTitle} ─`)));
223
+
224
+ if (elements.length === 0) {
225
+ lines.push(chalk.hex('#888888')(' (no fields)'));
226
+ lines.push('');
227
+ continue;
228
+ }
229
+
230
+ elements.sort((a, b) => a.position - b.position);
231
+
232
+ for (const elem of elements) {
233
+ if (elem.elementType && elem.elementType !== 'field') {
234
+ continue;
235
+ }
236
+
237
+ let displayName = elem.name;
238
+ if (!displayName) displayName = elem.label;
239
+ if (!displayName) displayName = elem.elementType;
240
+
241
+ const indicators = [];
242
+ if (elem.mandatory) indicators.push('*');
243
+ if (elem.readOnly) indicators.push('(RO)');
244
+ const indicatorStr = indicators.length > 0 ? ` ${indicators.join(' ')}` : '';
245
+
246
+ lines.push(` ${chalk.hex('#cccccc')(displayName)}${indicatorStr ? chalk.hex('#888888')(indicatorStr) : ''}`);
247
+ }
248
+
249
+ lines.push('');
250
+ }
251
+
252
+ lines.push('─────');
253
+ lines.push('');
254
+ lines.push(chalk.bold(chalk.hex('#e8a217')('Hints:')));
255
+ lines.push(` ${`jsn dev forms list ${table}`.padEnd(50)} ${chalk.hex('#888888')('List all views')}`);
256
+ lines.push(` ${`jsn dev columns --table ${table}`.padEnd(50)} ${chalk.hex('#888888')('View table columns')}`);
257
+ lines.push('');
258
+
259
+ const formatted = lines.join('\n');
260
+
261
+ // Build structured data for JSON/quiet mode
262
+ const sectionsData = sections.map(section => {
263
+ const elements = (sectionElements.get(section.sys_id) || [])
264
+ .filter(elem => !elem.elementType || elem.elementType === 'field')
265
+ .sort((a, b) => a.position - b.position)
266
+ .map(elem => ({
267
+ sys_id: elem.sys_id,
268
+ name: elem.name,
269
+ label: elem.label,
270
+ type: elem.elementType,
271
+ position: elem.position,
272
+ mandatory: elem.mandatory,
273
+ read_only: elem.readOnly,
274
+ }));
275
+
276
+ let sectionTitle = section.caption;
277
+ if (!sectionTitle && section.header && section.header !== 'false') {
278
+ sectionTitle = section.header;
279
+ }
280
+ if (!sectionTitle) {
281
+ sectionTitle = 'Section';
282
+ }
283
+
284
+ return {
285
+ sys_id: section.sys_id,
286
+ caption: sectionTitle,
287
+ order: section.order,
288
+ elements,
289
+ };
290
+ });
291
+
292
+ app.ok({
293
+ table,
294
+ view: viewName,
295
+ sections: sectionsData,
296
+ _formatted: formatted,
297
+ _context: {
298
+ instance_url: app.getEffectiveInstance(),
299
+ table: 'sys_ui_section',
300
+ },
301
+ }, {
302
+ summary: `Form: ${table} (${viewName}) - ${sections.length} sections`,
303
+ breadcrumbs: [
304
+ { action: 'list', cmd: `jsn dev forms list ${table}`, description: 'List all views' },
305
+ { action: 'columns', cmd: `jsn dev columns --table ${table}`, description: 'View table columns' },
306
+ ],
307
+ });
308
+ }),
309
+ });
310
+ },
311
+ handler: () => {},
312
+ };
313
+ }
@@ -0,0 +1,233 @@
1
+ import chalk from 'chalk';
2
+ import { getStringField } from '../../helpers.js';
3
+
4
+ // ─── Helpers ───
5
+
6
+ function getDisplayValue(record, key) {
7
+ if (!record || typeof record !== 'object') return '';
8
+ const val = record[key];
9
+ if (val == null) return '';
10
+ if (typeof val === 'string') return val;
11
+ if (typeof val === 'object') {
12
+ if (val.display_value != null && val.display_value !== '') return String(val.display_value);
13
+ if (val.value != null) return String(val.value);
14
+ }
15
+ return String(val);
16
+ }
17
+
18
+ function getIntValue(record, key) {
19
+ if (!record || typeof record !== 'object') return 0;
20
+ const val = record[key];
21
+ if (val == null) return 0;
22
+ if (typeof val === 'number') return Math.floor(val);
23
+ if (typeof val === 'string') {
24
+ const n = parseInt(val, 10);
25
+ return isNaN(n) ? 0 : n;
26
+ }
27
+ if (typeof val === 'object' && val.value != null) {
28
+ const n = parseInt(val.value, 10);
29
+ return isNaN(n) ? 0 : n;
30
+ }
31
+ return 0;
32
+ }
33
+
34
+ // ─── Commands ───
35
+
36
+ export function listsCmd(wrap) {
37
+ return {
38
+ command: 'lists [subcommand]',
39
+ aliases: ['list-layout', 'l'],
40
+ describe: 'Manage UI List layouts',
41
+ builder: (yargs) => {
42
+ return yargs
43
+ .command({
44
+ command: 'list <table>',
45
+ aliases: ['ls'],
46
+ describe: 'List list views for a table',
47
+ builder: (y) => y
48
+ .option('limit', { alias: 'l', type: 'number', default: 50, describe: 'Max records' }),
49
+ handler: wrap(async (argv, app) => {
50
+ const table = argv.table;
51
+ const limit = argv.limit || 50;
52
+
53
+ const params = new URLSearchParams();
54
+ params.set('sysparm_limit', String(limit));
55
+ params.set('sysparm_fields', 'view');
56
+ params.set('sysparm_group_by', 'view');
57
+ params.set('sysparm_display_value', 'all');
58
+ const sysparmQuery = table ? `name=${table}^ORDERBYview` : 'ORDERBYview';
59
+ params.set('sysparm_query', sysparmQuery);
60
+
61
+ const records = await app.sdk.list('sys_ui_list', params);
62
+
63
+ const viewMap = new Map();
64
+ for (const record of records) {
65
+ const view = getDisplayValue(record, 'view');
66
+ if (view && !viewMap.has(view)) {
67
+ viewMap.set(view, true);
68
+ }
69
+ }
70
+
71
+ const views = Array.from(viewMap.keys()).sort();
72
+ const defaultViews = views.filter(v => v === 'Default view');
73
+ const workspaceViews = views.filter(v => v.toLowerCase().includes('workspace'));
74
+ const otherViews = views.filter(v => !defaultViews.includes(v) && !workspaceViews.includes(v));
75
+
76
+ const recordsOut = views.map(v => ({ view: v, table }));
77
+
78
+ app.ok({
79
+ table,
80
+ count: views.length,
81
+ default: defaultViews,
82
+ workspaces: workspaceViews,
83
+ other: otherViews,
84
+ records: recordsOut,
85
+ instance_url: app.getEffectiveInstance(),
86
+ }, {
87
+ summary: `${views.length} list views for ${table}`,
88
+ breadcrumbs: [
89
+ { action: 'show', cmd: `jsn dev lists show ${table} --view "Default view"`, description: 'Show Default view columns' },
90
+ ],
91
+ });
92
+ }),
93
+ })
94
+ .command({
95
+ command: 'show <table>',
96
+ aliases: ['get'],
97
+ describe: 'Show list layout',
98
+ builder: (y) => y
99
+ .option('view', { type: 'string', default: 'Default view', describe: 'View name' }),
100
+ handler: wrap(async (argv, app) => {
101
+ const table = argv.table;
102
+ const viewName = argv.view;
103
+
104
+ // Look up view sys_id
105
+ let viewSysID = '';
106
+ const viewParams = new URLSearchParams();
107
+ viewParams.set('sysparm_limit', '1');
108
+ viewParams.set('sysparm_fields', 'sys_id');
109
+ viewParams.set('sysparm_query', `name=${viewName}`);
110
+ try {
111
+ const viewRecords = await app.sdk.list('sys_ui_view', viewParams);
112
+ if (viewRecords.length > 0) {
113
+ viewSysID = getStringField(viewRecords[0], 'sys_id');
114
+ }
115
+ } catch {
116
+ // ignore
117
+ }
118
+
119
+ if (!viewSysID) {
120
+ viewParams.set('sysparm_query', `title=${viewName}`);
121
+ try {
122
+ const viewRecords = await app.sdk.list('sys_ui_view', viewParams);
123
+ if (viewRecords.length > 0) {
124
+ viewSysID = getStringField(viewRecords[0], 'sys_id');
125
+ }
126
+ } catch {
127
+ // ignore
128
+ }
129
+ }
130
+
131
+ if (!viewSysID) {
132
+ viewSysID = viewName;
133
+ }
134
+
135
+ // Fetch list layouts
136
+ const params = new URLSearchParams();
137
+ params.set('sysparm_limit', '10');
138
+ params.set('sysparm_fields', 'sys_id,name,view,parent,active,sys_created_on,sys_updated_on');
139
+ params.set('sysparm_display_value', 'all');
140
+
141
+ const parts = [];
142
+ if (table) parts.push(`name=${table}`);
143
+ if (viewSysID) parts.push(`view=${viewSysID}`);
144
+ const sysparmQuery = parts.length > 0 ? `${parts.join('^')}^ORDERBYsys_created_on` : 'ORDERBYsys_created_on';
145
+ params.set('sysparm_query', sysparmQuery);
146
+
147
+ const records = await app.sdk.list('sys_ui_list', params);
148
+ if (records.length === 0) {
149
+ throw new Error(`no list layout found for ${table} with view "${viewName}"`);
150
+ }
151
+
152
+ const mainLayout = records[0];
153
+ const layoutSysID = getStringField(mainLayout, 'sys_id');
154
+
155
+ // Fetch elements (columns) for the list
156
+ const elemParams = new URLSearchParams();
157
+ elemParams.set('sysparm_limit', '100');
158
+ elemParams.set('sysparm_fields', 'sys_id,list_id,element,position,type');
159
+ elemParams.set('sysparm_display_value', 'all');
160
+ elemParams.set('sysparm_query', `list_id=${layoutSysID}^ORDERBYposition`);
161
+
162
+ const elemRecords = await app.sdk.list('sys_ui_list_element', elemParams);
163
+
164
+ const elements = elemRecords.map(record => ({
165
+ sys_id: getStringField(record, 'sys_id'),
166
+ listID: getStringField(record, 'list_id'),
167
+ element: getDisplayValue(record, 'element'),
168
+ position: getIntValue(record, 'position'),
169
+ type: getDisplayValue(record, 'type'),
170
+ }));
171
+
172
+ elements.sort((a, b) => a.position - b.position);
173
+
174
+ // Build formatted output
175
+ const lines = [];
176
+ lines.push('');
177
+ lines.push(chalk.bold(chalk.hex('#e8a217')(`${table} (${viewName})`)));
178
+ lines.push('');
179
+
180
+ lines.push(chalk.bold(chalk.hex('#666666')('─ List Columns ─')));
181
+ if (elements.length === 0) {
182
+ lines.push(chalk.hex('#888888')(' (no columns defined)'));
183
+ } else {
184
+ for (let i = 0; i < elements.length; i++) {
185
+ const elem = elements[i];
186
+ lines.push(` ${chalk.hex('#666666')(`${String(i + 1).padStart(2)}.`)} ${chalk.hex('#cccccc')(elem.element)}`);
187
+ }
188
+ }
189
+ lines.push('');
190
+
191
+ lines.push('─────');
192
+ lines.push('');
193
+ lines.push(chalk.bold(chalk.hex('#e8a217')('Hints:')));
194
+ lines.push(` ${`jsn dev lists list ${table}`.padEnd(50)} ${chalk.hex('#888888')('List all views')}`);
195
+ lines.push(` ${`jsn dev forms show ${table} --view "${viewName}"`.padEnd(50)} ${chalk.hex('#888888')('Show form layout')}`);
196
+ lines.push('');
197
+
198
+ const formatted = lines.join('\n');
199
+
200
+ // Build structured data for JSON/quiet mode
201
+ const columnsData = elements.map(elem => {
202
+ const col = {
203
+ element: elem.element,
204
+ position: elem.position,
205
+ };
206
+ if (elem.type) {
207
+ col.type = elem.type;
208
+ }
209
+ return col;
210
+ });
211
+
212
+ app.ok({
213
+ table,
214
+ view: viewName,
215
+ columns: columnsData,
216
+ _formatted: formatted,
217
+ _context: {
218
+ instance_url: app.getEffectiveInstance(),
219
+ table: 'sys_ui_list',
220
+ },
221
+ }, {
222
+ summary: `List: ${table} (${viewName}) - ${elements.length} columns`,
223
+ breadcrumbs: [
224
+ { action: 'list', cmd: `jsn dev lists list ${table}`, description: 'List all views' },
225
+ { action: 'form', cmd: `jsn dev forms show ${table} --view "${viewName}"`, description: 'Show form layout' },
226
+ ],
227
+ });
228
+ }),
229
+ });
230
+ },
231
+ handler: () => {},
232
+ };
233
+ }
@@ -0,0 +1,51 @@
1
+ import { formatRecordForDisplay } from '../../helpers.js';
2
+
3
+ export function logsCmd(wrap) {
4
+ return {
5
+ command: 'logs [subcommand]',
6
+ aliases: ['log'],
7
+ describe: 'Query system logs',
8
+ builder: (yargs) => {
9
+ return yargs
10
+ .command({
11
+ command: 'list',
12
+ aliases: ['ls'],
13
+ describe: 'List system logs',
14
+ builder: (y) => y
15
+ .option('query', { type: 'string', describe: 'Encoded query string' })
16
+ .option('columns', { alias: 'c', type: 'string', describe: 'Comma-separated columns' })
17
+ .option('limit', { alias: 'l', type: 'number', default: 50, describe: 'Max records' }),
18
+ handler: wrap(async (argv, app) => {
19
+ const columns = argv.columns ? argv.columns.split(',') : ['level', 'message', 'source', 'created'];
20
+ const params = new URLSearchParams();
21
+ params.set('sysparm_limit', String(argv.limit));
22
+ params.set('sysparm_display_value', 'all');
23
+ params.set('sysparm_fields', ['sys_id', ...columns].join(','));
24
+ const q = argv.query ? argv.query + '^ORDERBYDESCsys_created_on' : 'ORDERBYDESCsys_created_on';
25
+ params.set('sysparm_query', q);
26
+ const records = await app.sdk.list('syslog', params);
27
+ app.ok({
28
+ table: 'syslog',
29
+ count: records.length,
30
+ columns,
31
+ records: records.map(r => formatRecordForDisplay(r, columns)),
32
+ context: { instance_url: app.getEffectiveInstance() },
33
+ }, { summary: `${records.length} log entry(s)` });
34
+ }),
35
+ })
36
+ .command({
37
+ command: 'show <sys_id>',
38
+ aliases: ['get'],
39
+ describe: 'Show a log entry by sys_id',
40
+ handler: wrap(async (argv, app) => {
41
+ const record = await app.sdk.get('syslog', argv.sys_id);
42
+ if (!record) {
43
+ throw new Error(`Log entry not found: ${argv.sys_id}`);
44
+ }
45
+ app.ok(record, { summary: `Log entry ${argv.sys_id}` });
46
+ }),
47
+ });
48
+ },
49
+ handler: () => {},
50
+ };
51
+ }
@@ -0,0 +1,64 @@
1
+ export function restCmd(wrap) {
2
+ return {
3
+ command: 'rest [endpoint]',
4
+ describe: 'Make raw REST API calls',
5
+ builder: (yargs) => {
6
+ return yargs
7
+ .option('method', {
8
+ alias: 'X',
9
+ type: 'string',
10
+ default: 'GET',
11
+ describe: 'HTTP method (GET, POST, PUT, DELETE, PATCH)',
12
+ })
13
+ .option('data', {
14
+ alias: 'd',
15
+ type: 'string',
16
+ describe: 'Request body data (JSON string)',
17
+ })
18
+ .option('table', {
19
+ alias: 't',
20
+ type: 'string',
21
+ describe: 'Table name shorthand for api/now/table/{table}',
22
+ })
23
+ .option('query', {
24
+ type: 'string',
25
+ describe: 'Query parameters string',
26
+ });
27
+ },
28
+ handler: wrap(async (argv, app) => {
29
+ const validMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'];
30
+ const method = (argv.method || 'GET').toUpperCase();
31
+
32
+ if (!validMethods.includes(method)) {
33
+ throw new Error(`Invalid HTTP method: ${method} (must be GET, POST, PUT, DELETE, or PATCH)`);
34
+ }
35
+
36
+ let endpoint;
37
+ if (argv.table) {
38
+ endpoint = `api/now/table/${argv.table}`;
39
+ if (argv.endpoint) {
40
+ endpoint = `${endpoint}/${argv.endpoint}`;
41
+ }
42
+ } else if (argv.endpoint) {
43
+ endpoint = argv.endpoint;
44
+ } else {
45
+ throw new Error('Either provide an endpoint argument or use --table flag');
46
+ }
47
+
48
+ // Trim leading slash from endpoint
49
+ endpoint = endpoint.replace(/^\/+/, '');
50
+
51
+ const instance = app.getEffectiveInstance();
52
+ const query = argv.query ? `?${argv.query}` : '';
53
+ const url = `${instance}/${endpoint}${query}`;
54
+
55
+ const requestOptions = { method };
56
+ if (argv.data) {
57
+ requestOptions.body = argv.data;
58
+ }
59
+
60
+ const result = await app.sdk.request(url, requestOptions);
61
+ app.ok(result, { summary: `${method} ${endpoint}` });
62
+ }),
63
+ };
64
+ }