@jacebenson/jsn 3.0.0 → 3.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jacebenson/jsn",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
4
4
  "description": "A command-line interface for ServiceNow that follows the Unix philosophy: simple, composable, and scriptable.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/app.js CHANGED
@@ -7,6 +7,7 @@ import { getEffectiveInstance } from './config.js';
7
7
  import { extractProfileName } from './helpers.js';
8
8
  import { getCurrentUser, getCurrentApplication, getCurrentUpdateSet } from './context.js';
9
9
  import { errUsage, errAuth } from './errors.js';
10
+ import { isMutationCommand } from './mutations.js';
10
11
  import process from 'node:process';
11
12
 
12
13
 
@@ -57,7 +58,7 @@ export class App {
57
58
  return getEffectiveInstance(this.config);
58
59
  }
59
60
 
60
- async printContextHeader() {
61
+ async printContextHeader(argv = {}) {
61
62
  if (!this.getEffectiveInstance() || !this.sdk) return;
62
63
  if (process.env.JSN_NO_HEADER) return;
63
64
  if (this.output.getFormat() === FormatJSON || this.output.getFormat() === FormatQuiet) return;
@@ -127,8 +128,10 @@ export class App {
127
128
 
128
129
  process.stderr.write(`${profileStr} ${userStr} ${scopeStr} ${updateSetStr}\n\n`);
129
130
 
130
- // ⚠️ Warning if in the Default update set
131
- if (updateSet && updateSet.toLowerCase().includes('default')) {
131
+ // ⚠️ Warning if in the Default update set (mutation commands only)
132
+ const profile = this.context.profileName ? this.config.profiles[this.context.profileName] : null;
133
+ const isYolo = profile?.yolo === true;
134
+ if (!isYolo && isMutationCommand(argv) && updateSet && updateSet.toLowerCase().includes('default')) {
132
135
  const isGlobal = scope === 'global';
133
136
  if (isGlobal) {
134
137
  // 🔴 HARD WARNING — Global + Default
@@ -146,7 +149,7 @@ export class App {
146
149
  '┃ Or switch to a scoped scope first: ┃\n' +
147
150
  '┃ jsn dev scopes list ┃\n' +
148
151
  '┃ ┃\n' +
149
- '┃ (Run jsn updatesets yolo to silence this check) ┃\n' +
152
+ '┃ (Run jsn dev updatesets yolo to silence this check) ┃\n' +
150
153
  '┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\x1b[0m\n'
151
154
  );
152
155
  } else {
@@ -157,7 +160,7 @@ export class App {
157
160
  ' ⚠ Default update set in scope [' + scope + ']\n' +
158
161
  ' Changes are contained to this scope, but a named\n' +
159
162
  ' update set is still recommended for tracking.\n' +
160
- ' (Run \x1b[1mjsn updatesets yolo\x1b[22m to silence this warning)\n' +
163
+ ' (Run \x1b[1mjsn dev updatesets yolo\x1b[22m to silence this warning)\n' +
161
164
  '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' +
162
165
  '\x1b[0m' // reset
163
166
  );
package/src/cli.js CHANGED
@@ -156,7 +156,7 @@ export const cli = yargs(hideBin(process.argv))
156
156
 
157
157
  // Print context header for interactive terminals (at the TOP, before command output)
158
158
  if (!['help', 'version', 'completion', 'skill'].includes(cmd)) {
159
- await argv.app.printContextHeader();
159
+ await argv.app.printContextHeader(argv);
160
160
  }
161
161
  })
162
162
  .command(setupCmd(wrap))
@@ -185,6 +185,25 @@ export const cli = yargs(hideBin(process.argv))
185
185
  if (err) throw err;
186
186
  // No command given → show custom grouped help instead of yargs error
187
187
  if (msg === 'You must specify a command') {
188
+ // Check if --profile/-p was used without a value — show profile info instead
189
+ const raw = process.argv.slice(2);
190
+ const hasLoneProfile = raw.some((a, i, arr) => {
191
+ if (a !== '--profile' && a !== '-p') return false;
192
+ const next = arr[i + 1];
193
+ return !next || next.startsWith('-');
194
+ });
195
+ if (hasLoneProfile) {
196
+ const cfg = loadConfig();
197
+ const activeName = cfg.activeProfile || cfg.defaultProfile;
198
+ process.stdout.write('Profiles:\n');
199
+ for (const [name, p] of Object.entries(cfg.profiles || {})) {
200
+ const marker = name === activeName ? ' *' : ' ';
201
+ process.stdout.write(` ${marker} ${name.padEnd(20)} ${p.instance_url || ''}\n`);
202
+ }
203
+ process.stdout.write('\nUsage: jsn --profile <name> <command>\n');
204
+ process.stdout.write(' jsn profiles use <name>\n');
205
+ process.exit(0);
206
+ }
188
207
  process.stdout.write(renderHelp());
189
208
  process.exit(0);
190
209
  }
@@ -1,7 +1,7 @@
1
1
  // Generic command builder for CRUD operations on a ServiceNow table
2
2
  // Used by incidents, changes, requests, tasks, and most dev subcommands
3
3
 
4
- import { getStringField, formatRecordForDisplay, buildQuerySuffix } from '../helpers.js';
4
+ import { getStringField, formatRecordForDisplay, buildQuerySuffix, resolveFieldsParam } from '../helpers.js';
5
5
  import search from '@inquirer/search';
6
6
  import { isTTY, FormatAuto } from '../output.js';
7
7
 
@@ -114,13 +114,13 @@ export function buildTicketCommands(table, displayName, alias, defaultColumns, s
114
114
  params.set('sysparm_limit', String(limit));
115
115
  params.set('sysparm_offset', String(offset));
116
116
  params.set('sysparm_display_value', 'all');
117
- const fetchColumns = ['sys_id', ...columns];
118
- params.set('sysparm_fields', fetchColumns.join(','));
117
+ const fields = resolveFieldsParam(columns);
118
+ if (fields) params.set('sysparm_fields', fields);
119
119
  const q = query ? query + '^ORDERBYDESCsys_updated_on' : 'ORDERBYDESCsys_updated_on';
120
120
  params.set('sysparm_query', q);
121
121
 
122
122
  const records = await app.sdk.list(table, params);
123
- const displayRecords = records.map(r => formatRecordForDisplay(r, columns));
123
+ const displayRecords = fields ? records.map(r => formatRecordForDisplay(r, columns)) : records;
124
124
 
125
125
  const breadcrumbs = [
126
126
  { action: 'create', cmd: `jsn ${alias} create --description "..."`, description: `Create a new ${displayName}` },
@@ -6,8 +6,8 @@ export function evalCmd(wrap) {
6
6
  describe: 'Execute background scripts on the instance',
7
7
  builder: (yargs) => {
8
8
  return yargs
9
- .option('script', { alias: 's', type: 'string', describe: 'JavaScript code to execute' })
10
- .option('file', { alias: 'f', type: 'string', describe: 'Read script from file' });
9
+ .option('script', { alias: 's', type: 'string', describe: "JavaScript code to execute (single-quote the script, double quotes inside: --script 'gs.log(\"Hello\")')" })
10
+ .option('file', { alias: 'f', type: 'string', describe: 'Read script from file (avoids shell quoting issues)' });
11
11
  },
12
12
  handler: wrap(async (argv, app) => {
13
13
  let script;
@@ -54,6 +54,10 @@ export function formsCmd(wrap) {
54
54
  aliases: ['ls'],
55
55
  describe: 'List form views for a table',
56
56
  builder: (y) => y
57
+ .positional('table', {
58
+ describe: 'Table name (e.g. incident, change_request)',
59
+ type: 'string',
60
+ })
57
61
  .option('limit', { alias: 'l', type: 'number', default: 50, describe: 'Max records' }),
58
62
  handler: wrap(async (argv, app) => {
59
63
  const table = argv.table;
@@ -45,6 +45,10 @@ export function listsCmd(wrap) {
45
45
  aliases: ['ls'],
46
46
  describe: 'List list views for a table',
47
47
  builder: (y) => y
48
+ .positional('table', {
49
+ describe: 'Table name (e.g. incident, change_request)',
50
+ type: 'string',
51
+ })
48
52
  .option('limit', { alias: 'l', type: 'number', default: 50, describe: 'Max records' }),
49
53
  handler: wrap(async (argv, app) => {
50
54
  const table = argv.table;
@@ -47,14 +47,24 @@ export function scopesCmd(wrap) {
47
47
  .command({
48
48
  command: 'show <scope>',
49
49
  aliases: ['get'],
50
- describe: 'Show a scope',
50
+ describe: 'Show a scope by scope value or name',
51
+ builder: (y) => y
52
+ .positional('scope', {
53
+ describe: 'Scope value (e.g. x_417611_daves_o_0) or display name',
54
+ type: 'string',
55
+ }),
51
56
  handler: wrap(async (argv, app) => {
52
- const params = new URLSearchParams();
53
- params.set('sysparm_query', `scope=${argv.scope}`);
54
- params.set('sysparm_limit', '1');
55
- params.set('sysparm_display_value', 'all');
56
- const records = await app.sdk.list('sys_scope', params);
57
- if (records.length === 0) {
57
+ let records;
58
+ // Try by scope value first, then by name
59
+ for (const queryField of ['scope', 'name']) {
60
+ const params = new URLSearchParams();
61
+ params.set('sysparm_query', `${queryField}=${argv.scope}`);
62
+ params.set('sysparm_limit', '1');
63
+ params.set('sysparm_display_value', 'all');
64
+ records = await app.sdk.list('sys_scope', params);
65
+ if (records.length > 0) break;
66
+ }
67
+ if (!records || records.length === 0) {
58
68
  throw new Error(`Scope not found: ${argv.scope}`);
59
69
  }
60
70
  app.ok(records[0], { summary: `Scope ${argv.scope}` });
@@ -162,6 +162,21 @@ export function updateSetsCmd(wrap) {
162
162
  });
163
163
  }),
164
164
  })
165
+ .command({
166
+ command: 'yolo',
167
+ aliases: ['silence'],
168
+ describe: 'Silence the "Default update set" warning for this profile',
169
+ handler: wrap(async (argv, app) => {
170
+ const name = app.context.profileName;
171
+ if (!name) {
172
+ throw new Error('No active profile to mark as yolo. Run jsn setup first.');
173
+ }
174
+ app.config.profiles[name].yolo = true;
175
+ const { saveConfig } = await import('../../config.js');
176
+ saveConfig(app.config);
177
+ app.ok({ profile: name, yolo: true }, { summary: 'Default update set warning silenced for this profile' });
178
+ }),
179
+ })
165
180
 
166
181
  },
167
182
  handler: (argv) => {
@@ -1,4 +1,4 @@
1
- import { formatRecordForDisplay, getStringField } from '../helpers.js';
1
+ import { formatRecordForDisplay, getStringField, resolveFieldsParam } from '../helpers.js';
2
2
 
3
3
  export function groupMembersCmd(wrap) {
4
4
  return {
@@ -20,14 +20,15 @@ export function groupMembersCmd(wrap) {
20
20
  const params = new URLSearchParams();
21
21
  params.set('sysparm_limit', String(argv.limit));
22
22
  params.set('sysparm_display_value', 'all');
23
- params.set('sysparm_fields', ['sys_id', ...columns].join(','));
23
+ const fields = resolveFieldsParam(columns);
24
+ if (fields) params.set('sysparm_fields', fields);
24
25
  if (argv.query) params.set('sysparm_query', argv.query);
25
26
  const records = await app.sdk.list('sys_user_grmember', params);
26
27
  app.ok({
27
28
  table: 'sys_user_grmember',
28
29
  count: records.length,
29
30
  columns,
30
- records: records.map(r => formatRecordForDisplay(r, columns)),
31
+ records: fields ? records.map(r => formatRecordForDisplay(r, columns)) : records,
31
32
  context: { instance_url: app.getEffectiveInstance() },
32
33
  }, { summary: `${records.length} group member(s)` });
33
34
  }),
@@ -1,4 +1,4 @@
1
- import { formatRecordForDisplay, getStringField } from '../helpers.js';
1
+ import { formatRecordForDisplay, getStringField, resolveFieldsParam } from '../helpers.js';
2
2
 
3
3
  export function groupRolesCmd(wrap) {
4
4
  return {
@@ -20,14 +20,15 @@ export function groupRolesCmd(wrap) {
20
20
  const params = new URLSearchParams();
21
21
  params.set('sysparm_limit', String(argv.limit));
22
22
  params.set('sysparm_display_value', 'all');
23
- params.set('sysparm_fields', ['sys_id', ...columns].join(','));
23
+ const fields = resolveFieldsParam(columns);
24
+ if (fields) params.set('sysparm_fields', fields);
24
25
  if (argv.query) params.set('sysparm_query', argv.query);
25
26
  const records = await app.sdk.list('sys_group_has_role', params);
26
27
  app.ok({
27
28
  table: 'sys_group_has_role',
28
29
  count: records.length,
29
30
  columns,
30
- records: records.map(r => formatRecordForDisplay(r, columns)),
31
+ records: fields ? records.map(r => formatRecordForDisplay(r, columns)) : records,
31
32
  context: { instance_url: app.getEffectiveInstance() },
32
33
  }, { summary: `${records.length} group role(s)` });
33
34
  }),
@@ -1,4 +1,4 @@
1
- import { formatRecordForDisplay, getStringField, interactiveList } from '../helpers.js';
1
+ import { formatRecordForDisplay, getStringField, interactiveList, resolveFieldsParam } from '../helpers.js';
2
2
 
3
3
  export function groupsCmd(wrap) {
4
4
  return {
@@ -31,14 +31,15 @@ export function groupsCmd(wrap) {
31
31
  const params = new URLSearchParams();
32
32
  params.set('sysparm_limit', String(argv.limit));
33
33
  params.set('sysparm_display_value', 'all');
34
- params.set('sysparm_fields', ['sys_id', ...columns].join(','));
34
+ const fields = resolveFieldsParam(columns);
35
+ if (fields) params.set('sysparm_fields', fields);
35
36
  if (argv.query) params.set('sysparm_query', argv.query);
36
37
  const records = await app.sdk.list('sys_user_group', params);
37
38
  app.ok({
38
39
  table: 'sys_user_group',
39
40
  count: records.length,
40
41
  columns,
41
- records: records.map(r => formatRecordForDisplay(r, columns)),
42
+ records: fields ? records.map(r => formatRecordForDisplay(r, columns)) : records,
42
43
  context: { instance_url: app.getEffectiveInstance() },
43
44
  }, { summary: `${records.length} group(s)` });
44
45
  }),
@@ -181,6 +181,9 @@ export function profilesCmd(wrap) {
181
181
  console.log(' use <name> Switch to a different profile');
182
182
  console.log(' remove <name> Remove a profile');
183
183
  console.log('\nRun "jsn profiles <command> --help" for more details.');
184
+ console.log('\nNote: You can also create a profile by authenticating:');
185
+ console.log(' jsn auth login --profile <name> <instance-url>');
186
+ console.log(' jsn auth login --profile staging --password https://dev12345.service-now.com');
184
187
  }
185
188
  },
186
189
  };
@@ -1,4 +1,4 @@
1
- import { formatRecordForDisplay, buildQuerySuffix, parseDataArg, getStringField, interactiveList } from '../helpers.js';
1
+ import { formatRecordForDisplay, buildQuerySuffix, parseDataArg, getStringField, interactiveList, resolveFieldsParam } from '../helpers.js';
2
2
 
3
3
  const tableDefaultColumns = {
4
4
  incident: ['number', 'short_description', 'priority', 'state', 'assigned_to'],
@@ -66,10 +66,11 @@ export function recordsCmd(wrap) {
66
66
  params.set('sysparm_limit', argv['sys-id'] ? '1' : String(argv.limit));
67
67
  params.set('sysparm_offset', String(argv.offset));
68
68
  params.set('sysparm_display_value', 'all');
69
- params.set('sysparm_fields', ['sys_id', ...columns].join(','));
69
+ const fields = resolveFieldsParam(columns);
70
+ if (fields) params.set('sysparm_fields', fields);
70
71
  if (query) params.set('sysparm_query', query);
71
72
  const records = await app.sdk.list(table, params);
72
- const displayRecords = records.map(r => formatRecordForDisplay(r, columns));
73
+ const displayRecords = fields ? records.map(r => formatRecordForDisplay(r, columns)) : records;
73
74
  const breadcrumbs = [
74
75
  { action: 'create', cmd: `jsn records create --table ${table} --data '{...}'`, description: 'Create a new record' },
75
76
  { action: 'filter', cmd: `jsn records list --table ${table} --query "priority=1"`, description: 'Filter: priority 1 only' },
@@ -118,7 +119,7 @@ export function recordsCmd(wrap) {
118
119
  params.set('sysparm_query', `sys_id=${argv['sys-id']}`);
119
120
  params.set('sysparm_limit', '1');
120
121
  params.set('sysparm_display_value', 'true');
121
- if (argv.columns) params.set('sysparm_fields', argv.columns);
122
+ if (argv.columns && argv.columns !== '*') params.set('sysparm_fields', argv.columns);
122
123
  const records = await app.sdk.list(argv.table, params);
123
124
  if (records.length === 0) {
124
125
  throw new Error(`Record not found: ${argv['sys-id']}`);
@@ -1,4 +1,4 @@
1
- import { formatRecordForDisplay, getStringField, buildQuerySuffix } from '../helpers.js';
1
+ import { formatRecordForDisplay, getStringField, buildQuerySuffix, resolveFieldsParam } from '../helpers.js';
2
2
 
3
3
  export function ticketsCmd(wrap) {
4
4
  return {
@@ -22,11 +22,12 @@ export function ticketsCmd(wrap) {
22
22
  params.set('sysparm_limit', String(argv.limit));
23
23
  params.set('sysparm_offset', String(argv.offset));
24
24
  params.set('sysparm_display_value', 'all');
25
- params.set('sysparm_fields', ['sys_id', ...columns].join(','));
25
+ const fields = resolveFieldsParam(columns);
26
+ if (fields) params.set('sysparm_fields', fields);
26
27
  const q = argv.query ? argv.query + '^ORDERBYDESCsys_updated_on' : 'ORDERBYDESCsys_updated_on';
27
28
  params.set('sysparm_query', q);
28
- const records = await app.sdk.list('ticket', params);
29
- const displayRecords = records.map(r => formatRecordForDisplay(r, columns));
29
+ const records = await app.sdk.list('task', params);
30
+ const displayRecords = fields ? records.map(r => formatRecordForDisplay(r, columns)) : records;
30
31
  const breadcrumbs = [
31
32
  { action: 'create', cmd: 'jsn tickets create --data \'{...}\'', description: 'Create a new ticket' },
32
33
  ];
@@ -45,7 +46,7 @@ export function ticketsCmd(wrap) {
45
46
  });
46
47
  }
47
48
  app.ok({
48
- table: 'ticket',
49
+ table: 'task',
49
50
  count: records.length,
50
51
  columns,
51
52
  records: displayRecords,
@@ -63,7 +64,7 @@ export function ticketsCmd(wrap) {
63
64
  params.set('sysparm_query', `number=${argv.number}`);
64
65
  params.set('sysparm_limit', '1');
65
66
  params.set('sysparm_display_value', 'all');
66
- const records = await app.sdk.list('ticket', params);
67
+ const records = await app.sdk.list('task', params);
67
68
  if (records.length === 0) {
68
69
  throw new Error(`Ticket not found: ${argv.number}`);
69
70
  }
@@ -76,7 +77,7 @@ export function ticketsCmd(wrap) {
76
77
  builder: (y) => y.option('data', { type: 'string', demandOption: true, describe: 'JSON fields (e.g. \'{"state":"2","priority":"1"}\')' }),
77
78
  handler: wrap(async (argv, app) => {
78
79
  const recordData = JSON.parse(argv.data);
79
- const record = await app.sdk.create('ticket', recordData);
80
+ const record = await app.sdk.create('task', recordData);
80
81
  app.ok(record, { summary: `Created ticket ${getStringField(record, 'number')}` });
81
82
  }),
82
83
  })
@@ -89,12 +90,12 @@ export function ticketsCmd(wrap) {
89
90
  const findParams = new URLSearchParams();
90
91
  findParams.set('sysparm_query', `number=${argv.number}`);
91
92
  findParams.set('sysparm_limit', '1');
92
- const records = await app.sdk.list('ticket', findParams);
93
+ const records = await app.sdk.list('task', findParams);
93
94
  if (records.length === 0) {
94
95
  throw new Error(`Ticket not found: ${argv.number}`);
95
96
  }
96
97
  const sysID = getStringField(records[0], 'sys_id');
97
- const updated = await app.sdk.update('ticket', sysID, recordData);
98
+ const updated = await app.sdk.update('task', sysID, recordData);
98
99
  app.ok(updated, { summary: `Updated ticket ${argv.number}` });
99
100
  }),
100
101
  })
@@ -105,12 +106,12 @@ export function ticketsCmd(wrap) {
105
106
  const findParams = new URLSearchParams();
106
107
  findParams.set('sysparm_query', `number=${argv.number}`);
107
108
  findParams.set('sysparm_limit', '1');
108
- const records = await app.sdk.list('ticket', findParams);
109
+ const records = await app.sdk.list('task', findParams);
109
110
  if (records.length === 0) {
110
111
  throw new Error(`Ticket not found: ${argv.number}`);
111
112
  }
112
113
  const sysID = getStringField(records[0], 'sys_id');
113
- await app.sdk.delete('ticket', sysID);
114
+ await app.sdk.delete('task', sysID);
114
115
  app.ok({ number: argv.number, message: 'Ticket deleted' }, { summary: `Deleted ticket ${argv.number}` });
115
116
  }),
116
117
  })
@@ -1,4 +1,4 @@
1
- import { formatRecordForDisplay, getStringField, interactiveList } from '../helpers.js';
1
+ import { formatRecordForDisplay, getStringField, interactiveList, resolveFieldsParam } from '../helpers.js';
2
2
 
3
3
  export function usersCmd(wrap) {
4
4
  return {
@@ -31,14 +31,15 @@ export function usersCmd(wrap) {
31
31
  const params = new URLSearchParams();
32
32
  params.set('sysparm_limit', String(argv.limit));
33
33
  params.set('sysparm_display_value', 'all');
34
- params.set('sysparm_fields', ['sys_id', ...columns].join(','));
34
+ const fields = resolveFieldsParam(columns);
35
+ if (fields) params.set('sysparm_fields', fields);
35
36
  if (argv.query) params.set('sysparm_query', argv.query);
36
37
  const records = await app.sdk.list('sys_user', params);
37
38
  app.ok({
38
39
  table: 'sys_user',
39
40
  count: records.length,
40
41
  columns,
41
- records: records.map(r => formatRecordForDisplay(r, columns)),
42
+ records: fields ? records.map(r => formatRecordForDisplay(r, columns)) : records,
42
43
  context: { instance_url: app.getEffectiveInstance() },
43
44
  }, { summary: `${records.length} user(s)` });
44
45
  }),
package/src/helpers.js CHANGED
@@ -63,6 +63,17 @@ export function buildQuerySuffix(query) {
63
63
  return query ? ` --query "${query}"` : '';
64
64
  }
65
65
 
66
+ /**
67
+ * Resolve sysparm_fields value from user-supplied columns.
68
+ * Returns null when columns includes '*' (signals "fetch all fields"),
69
+ * so callers should omit sysparm_fields and skip formatRecordForDisplay.
70
+ * Otherwise returns the fields string with sys_id prepended.
71
+ */
72
+ export function resolveFieldsParam(columns) {
73
+ if (columns.includes('*')) return null;
74
+ return ['sys_id', ...columns].join(',');
75
+ }
76
+
66
77
  /**
67
78
  * Shared interactive list helper with search-as-you-type.
68
79
  * All list commands that want an interactive TTY picker should use this.
@@ -84,11 +95,12 @@ export async function interactiveList({ app, table, singular, columns, limit = 2
84
95
  return null; // not interactive — caller should fall back to text/table
85
96
  }
86
97
 
87
- const pickerColumns = ['sys_id', labelField, ...columns.filter(c => c !== labelField && c !== 'sys_id')];
98
+ const pickerColumns = ['sys_id', labelField, ...columns.filter(c => c !== labelField && c !== 'sys_id' && c !== '*')];
88
99
  const params = new URLSearchParams();
89
100
  params.set('sysparm_limit', String(limit));
90
101
  params.set('sysparm_display_value', 'all');
91
- params.set('sysparm_fields', pickerColumns.join(','));
102
+ const pickerFields = pickerColumns.join(',');
103
+ if (pickerFields) params.set('sysparm_fields', pickerFields);
92
104
  params.set('sysparm_query', 'ORDERBYDESCsys_updated_on');
93
105
 
94
106
  const records = await app.sdk.list(table, params);
package/src/mutations.js CHANGED
@@ -23,11 +23,32 @@ export const MUTATION_COMMANDS = [
23
23
  ['tasks', 'create'],
24
24
  ['tasks', 'update'],
25
25
  ['tasks', 'delete'],
26
- // Dev
26
+ // Tickets
27
+ ['tickets', 'create'],
28
+ ['tickets', 'update'],
29
+ ['tickets', 'delete'],
30
+ // Users
31
+ ['users', 'create'],
32
+ ['users', 'update'],
33
+ ['users', 'delete'],
34
+ // Groups
35
+ ['groups', 'create'],
36
+ ['groups', 'update'],
37
+ ['groups', 'delete'],
38
+ // Catalog
39
+ ['catalog', 'create-item'],
40
+ // Dev flows
41
+ ['dev', 'flows', 'create'],
42
+ ['dev', 'flows', 'update'],
43
+ ['dev', 'flows', 'delete'],
44
+ // Dev scopes
45
+ ['dev', 'scopes', 'create'],
46
+ ['dev', 'scopes', 'set'],
47
+ // Dev eval
27
48
  ['dev', 'eval'],
49
+ // Dev updatesets
28
50
  ['dev', 'updatesets', 'set'],
29
51
  ['dev', 'updatesets', 'create'],
30
- ['dev', 'scopes', 'set'],
31
52
  ];
32
53
 
33
54
  /**