@jacebenson/jsn 3.0.6 → 3.1.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 +60 -285
- package/package.json +3 -1
- package/scripts/postinstall.mjs +80 -0
- package/scripts/pre-commit-check.sh +61 -0
- package/scripts/release.js +62 -0
- package/scripts/tag-reminder.js +30 -0
- package/skills/servicenow/SKILL.md +41 -4
- package/src/app.js +5 -5
- package/src/cli.js +60 -0
- package/src/commands/_ticket.js +1 -1
- package/src/commands/dev/_generic.js +13 -13
- package/src/commands/dev/eval.js +1 -1
- package/src/commands/dev/flows.js +31 -8
- package/src/commands/dev/forms.js +16 -6
- package/src/commands/dev/lists.js +16 -6
- package/src/commands/dev/logs.js +11 -1
- package/src/commands/dev/scopes.js +4 -3
- package/src/commands/dev/scrapi.js +13 -13
- package/src/commands/dev/updatesets.js +6 -5
- package/src/commands/records.js +3 -3
- package/src/help.js +52 -9
- package/src/helpers.js +36 -33
- package/src/paginated-search.js +178 -0
|
@@ -18,7 +18,7 @@ compatibility: |
|
|
|
18
18
|
the correct command path.
|
|
19
19
|
metadata:
|
|
20
20
|
author: jacebenson
|
|
21
|
-
version: "1.
|
|
21
|
+
version: "1.1.0"
|
|
22
22
|
repository: https://github.com/jacebenson/jsn
|
|
23
23
|
---
|
|
24
24
|
|
|
@@ -31,9 +31,8 @@ Explore and manage ServiceNow instances. Works standalone or with AI agents.
|
|
|
31
31
|
Commands are self-documenting. Use these to learn what's available:
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
jsn --help # Root help with all top-level commands
|
|
34
|
+
jsn --help # Root help with all top-level commands (grouped by category)
|
|
35
35
|
jsn <command> --help # Detailed usage, flags, and examples for any command
|
|
36
|
-
jsn dev # Shows categorized dev subcommands
|
|
37
36
|
```
|
|
38
37
|
|
|
39
38
|
## Agent Rules
|
|
@@ -60,7 +59,7 @@ jsn dev # Shows categorized dev subcommands
|
|
|
60
59
|
- `jsn records --table <name> create`
|
|
61
60
|
- `jsn records --table <name> update <sys_id>`
|
|
62
61
|
- `jsn records --table <name> delete <sys_id>`
|
|
63
|
-
- `jsn
|
|
62
|
+
- `jsn updatesets set <name>`
|
|
64
63
|
- `jsn profiles remove <name>`
|
|
65
64
|
- Any `delete`, `update`, `create`, `set`, or `remove` subcommand
|
|
66
65
|
|
|
@@ -121,6 +120,44 @@ All commands support `--json`. The envelope structure:
|
|
|
121
120
|
|
|
122
121
|
Breadcrumbs suggest next commands — follow them for navigation.
|
|
123
122
|
|
|
123
|
+
## Troubleshooting Field Behavior
|
|
124
|
+
|
|
125
|
+
When a user asks about field behavior (why a picker won't show inactive users, why a field is read-only, why a reference qualifier behaves unexpectedly), do NOT just check dictionary records or ACLs. Field behavior is often controlled by **before-query business rules**.
|
|
126
|
+
|
|
127
|
+
**Check order (priority):**
|
|
128
|
+
|
|
129
|
+
1. **Before-query business rules** — `sys_script` with `when=before`, `action=query`, matching `collection` (table)
|
|
130
|
+
```bash
|
|
131
|
+
jsn records list --table sys_script --query "collection=sys_user^when=before^action=query^active=true" --json
|
|
132
|
+
```
|
|
133
|
+
A common case: inactive user filters on `sys_user` are implemented as a before-query BR that adds `active=true` to the query condition.
|
|
134
|
+
|
|
135
|
+
2. **ACLs** — `sys_security_acl` — read/write/delete permissions on tables and fields
|
|
136
|
+
```bash
|
|
137
|
+
jsn acls list --query "name=incident" --json
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
3. **Dictionary overrides** — `sys_dictionary` — reference qualifiers, field attributes, default values
|
|
141
|
+
```bash
|
|
142
|
+
jsn records list --table sys_dictionary --query "name=incident^element=caller_id" --json
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
4. **Client scripts** — `sys_script_client` — UI-side visibility, read-only, or value-change logic
|
|
146
|
+
```bash
|
|
147
|
+
jsn records list --table sys_script_client --query "table=incident^active=true" --json
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
5. **UI policies** — `sys_ui_policy` — form-level field behavior (mandatory, read-only, visible)
|
|
151
|
+
```bash
|
|
152
|
+
jsn records list --table sys_ui_policy --query "table=incident^active=true" --json
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Example:** "Why can't I pick an inactive user on the incident form?"
|
|
156
|
+
- First guess: Dictionary reference qualifier on `caller_id`? → Check `sys_dictionary`
|
|
157
|
+
- Most likely: Before-query BR on `sys_user` that filters `active=true` → Check `sys_script` first
|
|
158
|
+
|
|
159
|
+
**Rule of thumb:** If a picker is silently filtering results (no error, just fewer options), it's a before-query BR. If it shows an error, it's an ACL or dictionary constraint.
|
|
160
|
+
|
|
124
161
|
## Configuration
|
|
125
162
|
|
|
126
163
|
```
|
package/src/app.js
CHANGED
|
@@ -118,7 +118,7 @@ export class App {
|
|
|
118
118
|
|
|
119
119
|
const scopeFormatted = `[${scope}]`;
|
|
120
120
|
|
|
121
|
-
process.stderr.write('# Use `jsn
|
|
121
|
+
process.stderr.write('# Use `jsn scopes` to change scope, `jsn updatesets set` to change updateset\n');
|
|
122
122
|
process.stderr.write('PROFILE USER [SCOPE] UPDATE SET\n');
|
|
123
123
|
|
|
124
124
|
const profileStr = `]8;;${instanceLink}\x07${String(this.context.profileName).padEnd(9)}]8;;\x07`;
|
|
@@ -144,12 +144,12 @@ export class App {
|
|
|
144
144
|
'┃ manual surgery, one change at a time. Avoid the mess. ┃\n' +
|
|
145
145
|
'┃ ┃\n' +
|
|
146
146
|
'┃ Create a named update set now: ┃\n' +
|
|
147
|
-
'┃ jsn
|
|
147
|
+
'┃ jsn updatesets create --name "My Feature" ┃\n' +
|
|
148
148
|
'┃ ┃\n' +
|
|
149
149
|
'┃ Or switch to a scoped scope first: ┃\n' +
|
|
150
|
-
'┃ jsn
|
|
150
|
+
'┃ jsn scopes list ┃\n' +
|
|
151
151
|
'┃ ┃\n' +
|
|
152
|
-
'┃ (Run jsn
|
|
152
|
+
'┃ (Run jsn updatesets yolo to silence this check) ┃\n' +
|
|
153
153
|
'┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\x1b[0m\n'
|
|
154
154
|
);
|
|
155
155
|
} else {
|
|
@@ -160,7 +160,7 @@ export class App {
|
|
|
160
160
|
' ⚠ Default update set in scope [' + scope + ']\n' +
|
|
161
161
|
' Changes are contained to this scope, but a named\n' +
|
|
162
162
|
' update set is still recommended for tracking.\n' +
|
|
163
|
-
' (Run \x1b[1mjsn
|
|
163
|
+
' (Run \x1b[1mjsn updatesets yolo\x1b[22m to silence this warning)\n' +
|
|
164
164
|
'━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' +
|
|
165
165
|
'\x1b[0m' // reset
|
|
166
166
|
);
|
package/src/cli.js
CHANGED
|
@@ -27,6 +27,27 @@ import { versionCmd } from './commands/version.js';
|
|
|
27
27
|
import { devCmd } from './commands/dev.js';
|
|
28
28
|
import { skillCmd, checkSkill } from './commands/skill.js';
|
|
29
29
|
|
|
30
|
+
// Dev subcommands promoted to root level for progressive disclosure
|
|
31
|
+
import {
|
|
32
|
+
actionsCmd, includesCmd, rulesCmd,
|
|
33
|
+
clientScriptsCmd, uiActionsCmd, uiPoliciesCmd,
|
|
34
|
+
tablesCmd, columnsCmd, importCmd,
|
|
35
|
+
spPagesCmd, spWidgetsCmd, uiPagesCmd, appMenuCmd,
|
|
36
|
+
aclsCmd, rolesCmd, propertiesCmd,
|
|
37
|
+
relationshipsCmd, appmodulesCmd, listcontrolsCmd, viewsCmd,
|
|
38
|
+
privilegesCmd, securitytypesCmd, uxscriptsCmd, aliasesCmd,
|
|
39
|
+
catalogscriptsCmd, cataloguipoliciesCmd,
|
|
40
|
+
} from './commands/dev/_simple.js';
|
|
41
|
+
import { flowsCmd } from './commands/dev/flows.js';
|
|
42
|
+
import { formsCmd } from './commands/dev/forms.js';
|
|
43
|
+
import { listsCmd } from './commands/dev/lists.js';
|
|
44
|
+
import { updateSetsCmd } from './commands/dev/updatesets.js';
|
|
45
|
+
import { scopesCmd } from './commands/dev/scopes.js';
|
|
46
|
+
import { evalCmd } from './commands/dev/eval.js';
|
|
47
|
+
import { restCmd } from './commands/dev/rest.js';
|
|
48
|
+
import { logsCmd } from './commands/dev/logs.js';
|
|
49
|
+
import { scrapiCmd } from './commands/dev/scrapi.js';
|
|
50
|
+
|
|
30
51
|
function wrap(handler) {
|
|
31
52
|
return async (argv) => {
|
|
32
53
|
try {
|
|
@@ -177,6 +198,45 @@ export const cli = yargs(hideBin(process.argv))
|
|
|
177
198
|
.command(groupRolesCmd(wrap))
|
|
178
199
|
.command(ticketsCmd(wrap))
|
|
179
200
|
.command(devCmd(wrap))
|
|
201
|
+
// AUTOMATION — things that react, run, or transform
|
|
202
|
+
.command(flowsCmd(wrap))
|
|
203
|
+
.command(actionsCmd(wrap))
|
|
204
|
+
.command(rulesCmd(wrap))
|
|
205
|
+
.command(scrapiCmd(wrap))
|
|
206
|
+
.command(updateSetsCmd(wrap))
|
|
207
|
+
.command(evalCmd(wrap))
|
|
208
|
+
.command(restCmd(wrap))
|
|
209
|
+
// ACCESS — who can see/do what
|
|
210
|
+
.command(aclsCmd(wrap))
|
|
211
|
+
.command(rolesCmd(wrap))
|
|
212
|
+
.command(propertiesCmd(wrap))
|
|
213
|
+
.command(scopesCmd(wrap))
|
|
214
|
+
.command(privilegesCmd(wrap))
|
|
215
|
+
.command(securitytypesCmd(wrap))
|
|
216
|
+
.command(aliasesCmd(wrap))
|
|
217
|
+
// UX — what users see/interact with
|
|
218
|
+
.command(formsCmd(wrap))
|
|
219
|
+
.command(listsCmd(wrap))
|
|
220
|
+
.command(clientScriptsCmd(wrap))
|
|
221
|
+
.command(uiActionsCmd(wrap))
|
|
222
|
+
.command(uiPoliciesCmd(wrap))
|
|
223
|
+
.command(spPagesCmd(wrap))
|
|
224
|
+
.command(spWidgetsCmd(wrap))
|
|
225
|
+
.command(uiPagesCmd(wrap))
|
|
226
|
+
.command(appMenuCmd(wrap))
|
|
227
|
+
.command(listcontrolsCmd(wrap))
|
|
228
|
+
.command(viewsCmd(wrap))
|
|
229
|
+
.command(uxscriptsCmd(wrap))
|
|
230
|
+
.command(catalogscriptsCmd(wrap))
|
|
231
|
+
.command(cataloguipoliciesCmd(wrap))
|
|
232
|
+
// DATA — where data lives
|
|
233
|
+
.command(tablesCmd(wrap))
|
|
234
|
+
.command(columnsCmd(wrap))
|
|
235
|
+
.command(includesCmd(wrap))
|
|
236
|
+
.command(importCmd(wrap))
|
|
237
|
+
.command(logsCmd(wrap))
|
|
238
|
+
.command(relationshipsCmd(wrap))
|
|
239
|
+
.command(appmodulesCmd(wrap))
|
|
180
240
|
.command(skillCmd(wrap))
|
|
181
241
|
.command(versionCmd(wrap))
|
|
182
242
|
.demandCommand(1, 'You must specify a command')
|
package/src/commands/_ticket.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Used by incidents, changes, requests, tasks, and most dev subcommands
|
|
3
3
|
|
|
4
4
|
import { getStringField, formatRecordForDisplay, buildQuerySuffix, resolveFieldsParam } from '../helpers.js';
|
|
5
|
-
import search from '@inquirer/
|
|
5
|
+
import { search } from '@inquirer/prompts';
|
|
6
6
|
import { isTTY, FormatAuto } from '../output.js';
|
|
7
7
|
|
|
8
8
|
export function buildTicketCommands(table, displayName, alias, defaultColumns, stateMap, iconFn, wrap) {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { formatRecordForDisplay, getStringField, isHexString, parseDataArg } from '../../helpers.js';
|
|
4
4
|
import { getCurrentUser, getCurrentApplication } from '../../context.js';
|
|
5
5
|
import readline from 'node:readline';
|
|
6
|
-
import search from '@inquirer/
|
|
6
|
+
import { search } from '@inquirer/prompts';
|
|
7
7
|
import { isTTY, FormatAuto } from '../../output.js';
|
|
8
8
|
|
|
9
9
|
function vowelArticle(word) {
|
|
@@ -16,13 +16,13 @@ function buildHints(name, singular, readOnly) {
|
|
|
16
16
|
if (!readOnly) {
|
|
17
17
|
crumbs.push({
|
|
18
18
|
action: 'create',
|
|
19
|
-
cmd: `jsn
|
|
19
|
+
cmd: `jsn ${name} create --name ... --label "..."`,
|
|
20
20
|
description: `Create ${vowelArticle(singular)} ${singular}`,
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
23
|
crumbs.push({
|
|
24
24
|
action: 'show',
|
|
25
|
-
cmd: `jsn
|
|
25
|
+
cmd: `jsn ${name} show <name_or_sys_id>`,
|
|
26
26
|
description: `Show ${singular} details`,
|
|
27
27
|
});
|
|
28
28
|
return crumbs;
|
|
@@ -84,12 +84,12 @@ export function buildDevCmd(name, table, aliases, defaultColumns, wrap, opts = {
|
|
|
84
84
|
const showSummary = opts.showSummary || ((record, id) => `${singular.charAt(0).toUpperCase() + singular.slice(1)}: ${getStringField(record, 'name') || id}`);
|
|
85
85
|
const showBreadcrumbs = opts.showBreadcrumbs || ((record, id) => {
|
|
86
86
|
const crumbs = [
|
|
87
|
-
{ action: 'list', cmd: `jsn
|
|
87
|
+
{ action: 'list', cmd: `jsn ${name} list`, description: `Back to all ${name}` },
|
|
88
88
|
];
|
|
89
89
|
if (!readOnly) {
|
|
90
90
|
crumbs.unshift(
|
|
91
|
-
{ action: 'delete', cmd: `jsn
|
|
92
|
-
{ action: 'update', cmd: `jsn
|
|
91
|
+
{ action: 'delete', cmd: `jsn ${name} delete ${id}`, description: `Delete this ${singular}` },
|
|
92
|
+
{ action: 'update', cmd: `jsn ${name} update ${id} --data '{...}'`, description: `Update this ${singular}` }
|
|
93
93
|
);
|
|
94
94
|
}
|
|
95
95
|
return crumbs;
|
|
@@ -104,7 +104,7 @@ export function buildDevCmd(name, table, aliases, defaultColumns, wrap, opts = {
|
|
|
104
104
|
builder: (y) => y
|
|
105
105
|
.option('query', { type: 'string', describe: 'Encoded query (e.g. "nameLIKEincident" or "active=true^priority=1")' })
|
|
106
106
|
.option('columns', { alias: ['c', 'fields'], type: 'string', describe: 'Comma-separated columns (e.g. "name,label,super_class")' })
|
|
107
|
-
.option('limit', { alias: 'l', type: 'number', default:
|
|
107
|
+
.option('limit', { alias: 'l', type: 'number', default: 50, describe: 'Max records' }),
|
|
108
108
|
handler: wrap(async (argv, app) => {
|
|
109
109
|
const query = argv.query || '';
|
|
110
110
|
const columns = argv.columns ? argv.columns.split(',') : defaultColumns;
|
|
@@ -178,8 +178,8 @@ export function buildDevCmd(name, table, aliases, defaultColumns, wrap, opts = {
|
|
|
178
178
|
app.ok(record, {
|
|
179
179
|
summary: `${singular.charAt(0).toUpperCase() + singular.slice(1)}: ${selectedName}`,
|
|
180
180
|
breadcrumbs: [
|
|
181
|
-
...(readOnly ? [] : [{ action: 'update', cmd: `jsn
|
|
182
|
-
{ action: 'list', cmd: `jsn
|
|
181
|
+
...(readOnly ? [] : [{ action: 'update', cmd: `jsn ${name} update ${selectedName} --data '{...}'`, description: `Update this ${singular}` }]),
|
|
182
|
+
{ action: 'list', cmd: `jsn ${name} list`, description: `Back to all ${name}` },
|
|
183
183
|
],
|
|
184
184
|
});
|
|
185
185
|
}
|
|
@@ -256,7 +256,7 @@ export function buildDevCmd(name, table, aliases, defaultColumns, wrap, opts = {
|
|
|
256
256
|
app.ok(record, {
|
|
257
257
|
summary: `Created ${singular}`,
|
|
258
258
|
breadcrumbs: [
|
|
259
|
-
{ action: 'show', cmd: `jsn
|
|
259
|
+
{ action: 'show', cmd: `jsn ${name} show ${getStringField(record, 'name') || getStringField(record, 'sys_id')}`, description: `View the new ${singular}` },
|
|
260
260
|
],
|
|
261
261
|
});
|
|
262
262
|
}),
|
|
@@ -284,7 +284,7 @@ export function buildDevCmd(name, table, aliases, defaultColumns, wrap, opts = {
|
|
|
284
284
|
if (scopeValidation) {
|
|
285
285
|
const scopeErr = await checkScope(app.sdk, recordScope);
|
|
286
286
|
if (scopeErr) {
|
|
287
|
-
throw new Error(`record is in scope '${scopeErr.recordScope}', but your current scope is '${scopeErr.currentScope}'. Switch scope first: jsn
|
|
287
|
+
throw new Error(`record is in scope '${scopeErr.recordScope}', but your current scope is '${scopeErr.currentScope}'. Switch scope first: jsn scopes set ${scopeErr.recordScope}`);
|
|
288
288
|
}
|
|
289
289
|
}
|
|
290
290
|
|
|
@@ -315,7 +315,7 @@ export function buildDevCmd(name, table, aliases, defaultColumns, wrap, opts = {
|
|
|
315
315
|
if (scopeValidation) {
|
|
316
316
|
const scopeErr = await checkScope(app.sdk, recordScope);
|
|
317
317
|
if (scopeErr) {
|
|
318
|
-
throw new Error(`record is in scope '${scopeErr.recordScope}', but your current scope is '${scopeErr.currentScope}'. Switch scope first: jsn
|
|
318
|
+
throw new Error(`record is in scope '${scopeErr.recordScope}', but your current scope is '${scopeErr.currentScope}'. Switch scope first: jsn scopes set ${scopeErr.recordScope}`);
|
|
319
319
|
}
|
|
320
320
|
}
|
|
321
321
|
|
|
@@ -358,7 +358,7 @@ export function buildDevCmd(name, table, aliases, defaultColumns, wrap, opts = {
|
|
|
358
358
|
console.log(' update Update a record');
|
|
359
359
|
console.log(' delete Delete a record');
|
|
360
360
|
}
|
|
361
|
-
console.log(`\nRun "jsn
|
|
361
|
+
console.log(`\nRun "jsn ${name} <command> --help" for details.`);
|
|
362
362
|
},
|
|
363
363
|
};
|
|
364
364
|
}
|
package/src/commands/dev/eval.js
CHANGED
|
@@ -45,7 +45,7 @@ export function evalCmd(wrap) {
|
|
|
45
45
|
summary: 'Script executed' + warning,
|
|
46
46
|
breadcrumbs: [{
|
|
47
47
|
action: 'eval',
|
|
48
|
-
cmd: 'jsn
|
|
48
|
+
cmd: 'jsn eval --script \'...\'',
|
|
49
49
|
description: 'Execute a background script on the ServiceNow instance',
|
|
50
50
|
}],
|
|
51
51
|
});
|
|
@@ -14,22 +14,24 @@ export function flowsCmd(wrap) {
|
|
|
14
14
|
builder: (y) => y
|
|
15
15
|
.option('query', { type: 'string', describe: 'Encoded query (e.g. "nameLIKEincident" or "active=true")' })
|
|
16
16
|
.option('columns', { alias: ['c', 'fields'], type: 'string', describe: 'Comma-separated columns (e.g. "number,short_description")' })
|
|
17
|
-
.option('limit', { alias: 'l', type: 'number', default:
|
|
17
|
+
.option('limit', { alias: 'l', type: 'number', default: 50, describe: 'Max records' }),
|
|
18
18
|
handler: wrap(async (argv, app) => {
|
|
19
|
+
app.requireInstance();
|
|
19
20
|
const columns = argv.columns ? argv.columns.split(',') : ['name', 'active', 'description', 'sys_created_by', 'sys_updated_on'];
|
|
20
21
|
const query = argv.query || '';
|
|
21
22
|
|
|
22
23
|
// Interactive picker
|
|
23
24
|
const picked = await interactiveList({
|
|
24
25
|
app, table: 'sys_hub_flow', singular: 'flow', columns, limit: argv.limit, query, labelField: 'name',
|
|
25
|
-
formatLabel: r => `${getStringField(r, '
|
|
26
|
+
formatLabel: r => `${getStringField(r, 'active') === 'true' ? '🟢' : '🔴'} ${getStringField(r, 'name')}`,
|
|
26
27
|
});
|
|
28
|
+
if (picked === undefined) return; // user cancelled
|
|
27
29
|
if (picked) {
|
|
28
30
|
const inspection = await app.sdk.inspectFlow(getStringField(picked, 'sys_id'));
|
|
29
31
|
const formatted = formatFlowInspection(inspection, app.getEffectiveInstance());
|
|
30
32
|
return app.ok({ ...inspection, _formatted: formatted }, {
|
|
31
33
|
summary: `Flow: ${inspection.flow.name}`,
|
|
32
|
-
breadcrumbs: [{ action: 'list', cmd: 'jsn
|
|
34
|
+
breadcrumbs: [{ action: 'list', cmd: 'jsn flows list', description: 'Back to all flows' }],
|
|
33
35
|
});
|
|
34
36
|
}
|
|
35
37
|
|
|
@@ -61,7 +63,7 @@ export function flowsCmd(wrap) {
|
|
|
61
63
|
app.ok(data, {
|
|
62
64
|
summary: `Flow: ${inspection.flow.name}`,
|
|
63
65
|
breadcrumbs: [
|
|
64
|
-
{ action: 'list', cmd: 'jsn
|
|
66
|
+
{ action: 'list', cmd: 'jsn flows list', description: 'Back to all flows' },
|
|
65
67
|
],
|
|
66
68
|
});
|
|
67
69
|
}),
|
|
@@ -73,7 +75,7 @@ export function flowsCmd(wrap) {
|
|
|
73
75
|
.option('data', { type: 'string', describe: 'JSON data for the flow' }),
|
|
74
76
|
handler: wrap(async (_argv, _app) => {
|
|
75
77
|
throw new Error('Flow creation requires the Flow Designer GraphQL API - not yet implemented.\n'
|
|
76
|
-
+ 'Use the ServiceNow web UI to create flows, then use "jsn
|
|
78
|
+
+ 'Use the ServiceNow web UI to create flows, then use "jsn flows list" to view them.');
|
|
77
79
|
}),
|
|
78
80
|
})
|
|
79
81
|
.command({
|
|
@@ -83,7 +85,7 @@ export function flowsCmd(wrap) {
|
|
|
83
85
|
.option('data', { type: 'string', describe: 'JSON data to update' }),
|
|
84
86
|
handler: wrap(async (_argv, _app) => {
|
|
85
87
|
throw new Error('Flow updates require the Flow Designer GraphQL API - not yet implemented.\n'
|
|
86
|
-
+ 'Use the ServiceNow web UI to update flows, then use "jsn
|
|
88
|
+
+ 'Use the ServiceNow web UI to update flows, then use "jsn flows list" to view them.');
|
|
87
89
|
}),
|
|
88
90
|
})
|
|
89
91
|
.command({
|
|
@@ -95,7 +97,22 @@ export function flowsCmd(wrap) {
|
|
|
95
97
|
}),
|
|
96
98
|
});
|
|
97
99
|
},
|
|
98
|
-
handler: () => {
|
|
100
|
+
handler: () => {
|
|
101
|
+
console.log('Manage Flow Designer flows from the sys_hub_flow table.');
|
|
102
|
+
console.log('');
|
|
103
|
+
console.log('Available subcommands:');
|
|
104
|
+
console.log(' list List flows');
|
|
105
|
+
console.log(' show <identifier> Show flow details by name or sys_id');
|
|
106
|
+
console.log(' create Create a new flow (not yet implemented)');
|
|
107
|
+
console.log(' update <identifier> Update a flow (not yet implemented)');
|
|
108
|
+
console.log(' delete <identifier> Delete a flow (not yet implemented)');
|
|
109
|
+
console.log('');
|
|
110
|
+
console.log('Run "jsn flows <command> --help" for details.');
|
|
111
|
+
console.log('');
|
|
112
|
+
console.log('Note: Flow inspection detail varies. V2 flows (with payload)');
|
|
113
|
+
console.log('show full action inputs and conditions. Subflows and V1 flows');
|
|
114
|
+
console.log('show step names only.');
|
|
115
|
+
},
|
|
99
116
|
};
|
|
100
117
|
}
|
|
101
118
|
|
|
@@ -168,7 +185,13 @@ function formatFlowInspection(inspection, instanceURL) {
|
|
|
168
185
|
lines.push('');
|
|
169
186
|
lines.push('⚡ FLOW STRUCTURE');
|
|
170
187
|
lines.push('─'.repeat(50));
|
|
171
|
-
|
|
188
|
+
const structureLines = formatFlowStructure(inspection);
|
|
189
|
+
lines.push(...structureLines);
|
|
190
|
+
// Note limitation for V1/subflows without payload detail
|
|
191
|
+
if (!inspection.payload || Object.keys(inspection.payload).length === 0) {
|
|
192
|
+
lines.push('');
|
|
193
|
+
lines.push(' (step detail limited — this flow lacks a V2 payload)');
|
|
194
|
+
}
|
|
172
195
|
|
|
173
196
|
lines.push('');
|
|
174
197
|
return lines.join('\n') + '\n';
|
|
@@ -60,6 +60,7 @@ export function formsCmd(wrap) {
|
|
|
60
60
|
})
|
|
61
61
|
.option('limit', { alias: 'l', type: 'number', default: 50, describe: 'Max records' }),
|
|
62
62
|
handler: wrap(async (argv, app) => {
|
|
63
|
+
app.requireInstance();
|
|
63
64
|
const table = argv.table;
|
|
64
65
|
const limit = argv.limit || 50;
|
|
65
66
|
|
|
@@ -99,7 +100,7 @@ export function formsCmd(wrap) {
|
|
|
99
100
|
}, {
|
|
100
101
|
summary: `${views.length} views for ${table}`,
|
|
101
102
|
breadcrumbs: [
|
|
102
|
-
{ action: 'show', cmd: `jsn
|
|
103
|
+
{ action: 'show', cmd: `jsn forms show ${table} --view "Default view"`, description: 'Show Default view layout' },
|
|
103
104
|
],
|
|
104
105
|
});
|
|
105
106
|
}),
|
|
@@ -111,6 +112,7 @@ export function formsCmd(wrap) {
|
|
|
111
112
|
builder: (y) => y
|
|
112
113
|
.option('view', { type: 'string', default: 'Default view', describe: 'View name' }),
|
|
113
114
|
handler: wrap(async (argv, app) => {
|
|
115
|
+
app.requireInstance();
|
|
114
116
|
const table = argv.table;
|
|
115
117
|
const viewName = argv.view;
|
|
116
118
|
|
|
@@ -256,8 +258,8 @@ export function formsCmd(wrap) {
|
|
|
256
258
|
lines.push('─────');
|
|
257
259
|
lines.push('');
|
|
258
260
|
lines.push(chalk.bold(chalk.hex('#e8a217')('Hints:')));
|
|
259
|
-
lines.push(` ${`jsn
|
|
260
|
-
lines.push(` ${`jsn
|
|
261
|
+
lines.push(` ${`jsn forms list ${table}`.padEnd(50)} ${chalk.hex('#888888')('List all views')}`);
|
|
262
|
+
lines.push(` ${`jsn columns --table ${table}`.padEnd(50)} ${chalk.hex('#888888')('View table columns')}`);
|
|
261
263
|
lines.push('');
|
|
262
264
|
|
|
263
265
|
const formatted = lines.join('\n');
|
|
@@ -305,13 +307,21 @@ export function formsCmd(wrap) {
|
|
|
305
307
|
}, {
|
|
306
308
|
summary: `Form: ${table} (${viewName}) - ${sections.length} sections`,
|
|
307
309
|
breadcrumbs: [
|
|
308
|
-
{ action: 'list', cmd: `jsn
|
|
309
|
-
{ action: 'columns', cmd: `jsn
|
|
310
|
+
{ action: 'list', cmd: `jsn forms list ${table}`, description: 'List all views' },
|
|
311
|
+
{ action: 'columns', cmd: `jsn columns --table ${table}`, description: 'View table columns' },
|
|
310
312
|
],
|
|
311
313
|
});
|
|
312
314
|
}),
|
|
313
315
|
});
|
|
314
316
|
},
|
|
315
|
-
handler: () => {
|
|
317
|
+
handler: () => {
|
|
318
|
+
console.log('Manage form layouts from the sys_ui_form_section table.');
|
|
319
|
+
console.log('');
|
|
320
|
+
console.log('Available subcommands:');
|
|
321
|
+
console.log(' list <table> List form views for a table');
|
|
322
|
+
console.log(' show <table> Show form layout for a table');
|
|
323
|
+
console.log('');
|
|
324
|
+
console.log('Run "jsn forms <command> --help" for details.');
|
|
325
|
+
},
|
|
316
326
|
};
|
|
317
327
|
}
|
|
@@ -51,6 +51,7 @@ export function listsCmd(wrap) {
|
|
|
51
51
|
})
|
|
52
52
|
.option('limit', { alias: 'l', type: 'number', default: 50, describe: 'Max records' }),
|
|
53
53
|
handler: wrap(async (argv, app) => {
|
|
54
|
+
app.requireInstance();
|
|
54
55
|
const table = argv.table;
|
|
55
56
|
const limit = argv.limit || 50;
|
|
56
57
|
|
|
@@ -90,7 +91,7 @@ export function listsCmd(wrap) {
|
|
|
90
91
|
}, {
|
|
91
92
|
summary: `${views.length} list views for ${table}`,
|
|
92
93
|
breadcrumbs: [
|
|
93
|
-
{ action: 'show', cmd: `jsn
|
|
94
|
+
{ action: 'show', cmd: `jsn lists show ${table} --view "Default view"`, description: 'Show Default view columns' },
|
|
94
95
|
],
|
|
95
96
|
});
|
|
96
97
|
}),
|
|
@@ -102,6 +103,7 @@ export function listsCmd(wrap) {
|
|
|
102
103
|
builder: (y) => y
|
|
103
104
|
.option('view', { type: 'string', default: 'Default view', describe: 'View name' }),
|
|
104
105
|
handler: wrap(async (argv, app) => {
|
|
106
|
+
app.requireInstance();
|
|
105
107
|
const table = argv.table;
|
|
106
108
|
const viewName = argv.view;
|
|
107
109
|
|
|
@@ -195,8 +197,8 @@ export function listsCmd(wrap) {
|
|
|
195
197
|
lines.push('─────');
|
|
196
198
|
lines.push('');
|
|
197
199
|
lines.push(chalk.bold(chalk.hex('#e8a217')('Hints:')));
|
|
198
|
-
lines.push(` ${`jsn
|
|
199
|
-
lines.push(` ${`jsn
|
|
200
|
+
lines.push(` ${`jsn lists list ${table}`.padEnd(50)} ${chalk.hex('#888888')('List all views')}`);
|
|
201
|
+
lines.push(` ${`jsn forms show ${table} --view "${viewName}"`.padEnd(50)} ${chalk.hex('#888888')('Show form layout')}`);
|
|
200
202
|
lines.push('');
|
|
201
203
|
|
|
202
204
|
const formatted = lines.join('\n');
|
|
@@ -225,13 +227,21 @@ export function listsCmd(wrap) {
|
|
|
225
227
|
}, {
|
|
226
228
|
summary: `List: ${table} (${viewName}) - ${elements.length} columns`,
|
|
227
229
|
breadcrumbs: [
|
|
228
|
-
{ action: 'list', cmd: `jsn
|
|
229
|
-
{ action: 'form', cmd: `jsn
|
|
230
|
+
{ action: 'list', cmd: `jsn lists list ${table}`, description: 'List all views' },
|
|
231
|
+
{ action: 'form', cmd: `jsn forms show ${table} --view "${viewName}"`, description: 'Show form layout' },
|
|
230
232
|
],
|
|
231
233
|
});
|
|
232
234
|
}),
|
|
233
235
|
});
|
|
234
236
|
},
|
|
235
|
-
handler: () => {
|
|
237
|
+
handler: () => {
|
|
238
|
+
console.log('Manage list layouts from the sys_ui_list table.');
|
|
239
|
+
console.log('');
|
|
240
|
+
console.log('Available subcommands:');
|
|
241
|
+
console.log(' list <table> List list views for a table');
|
|
242
|
+
console.log(' show <table> Show list layout for a table');
|
|
243
|
+
console.log('');
|
|
244
|
+
console.log('Run "jsn lists <command> --help" for details.');
|
|
245
|
+
},
|
|
236
246
|
};
|
|
237
247
|
}
|
package/src/commands/dev/logs.js
CHANGED
|
@@ -16,6 +16,7 @@ export function logsCmd(wrap) {
|
|
|
16
16
|
.option('columns', { alias: ['c', 'fields'], type: 'string', describe: 'Comma-separated columns (e.g. "number,short_description")' })
|
|
17
17
|
.option('limit', { alias: 'l', type: 'number', default: 50, describe: 'Max records' }),
|
|
18
18
|
handler: wrap(async (argv, app) => {
|
|
19
|
+
app.requireInstance();
|
|
19
20
|
const columns = argv.columns ? argv.columns.split(',') : ['level', 'message', 'source', 'created'];
|
|
20
21
|
const query = argv.query || '';
|
|
21
22
|
|
|
@@ -24,6 +25,7 @@ export function logsCmd(wrap) {
|
|
|
24
25
|
app, table: 'syslog', singular: 'log entry', columns, limit: argv.limit, query, labelField: 'message',
|
|
25
26
|
formatLabel: r => `[${getStringField(r, 'level') || '?'}] ${(getStringField(r, 'message') || '').substring(0, 80)}`,
|
|
26
27
|
});
|
|
28
|
+
if (picked === undefined) return; // user cancelled
|
|
27
29
|
if (picked) {
|
|
28
30
|
picked._context = { instance_url: app.getEffectiveInstance(), table: 'syslog' };
|
|
29
31
|
return app.ok(picked, { summary: `Log entry` });
|
|
@@ -59,6 +61,14 @@ export function logsCmd(wrap) {
|
|
|
59
61
|
}),
|
|
60
62
|
});
|
|
61
63
|
},
|
|
62
|
-
handler: () => {
|
|
64
|
+
handler: () => {
|
|
65
|
+
console.log('View system logs from the syslog table.');
|
|
66
|
+
console.log('');
|
|
67
|
+
console.log('Available subcommands:');
|
|
68
|
+
console.log(' list List log entries');
|
|
69
|
+
console.log(' show <sys_id> Show a log entry by sys_id');
|
|
70
|
+
console.log('');
|
|
71
|
+
console.log('Run "jsn logs <command> --help" for details.');
|
|
72
|
+
},
|
|
63
73
|
};
|
|
64
74
|
}
|
|
@@ -14,7 +14,7 @@ export function scopesCmd(wrap) {
|
|
|
14
14
|
builder: (y) => y
|
|
15
15
|
.option('query', { type: 'string', describe: 'Encoded query (e.g. "nameLIKEincident" or "active=true")' })
|
|
16
16
|
.option('columns', { alias: ['c', 'fields'], type: 'string', describe: 'Comma-separated columns (e.g. "number,short_description")' })
|
|
17
|
-
.option('limit', { alias: 'l', type: 'number', default:
|
|
17
|
+
.option('limit', { alias: 'l', type: 'number', default: 50, describe: 'Max records' }),
|
|
18
18
|
handler: wrap(async (argv, app) => {
|
|
19
19
|
const columns = argv.columns ? argv.columns.split(',') : ['name', 'scope', 'short_description', 'active'];
|
|
20
20
|
const query = argv.query || '';
|
|
@@ -23,6 +23,7 @@ export function scopesCmd(wrap) {
|
|
|
23
23
|
app, table: 'sys_scope', singular: 'scope', columns, limit: argv.limit, query, labelField: 'name',
|
|
24
24
|
formatLabel: r => `${getStringField(r, 'name')} [${getStringField(r, 'scope') || '?'}]`,
|
|
25
25
|
});
|
|
26
|
+
if (picked === undefined) return; // user cancelled
|
|
26
27
|
if (picked) {
|
|
27
28
|
picked._context = { instance_url: app.getEffectiveInstance(), table: 'sys_scope' };
|
|
28
29
|
return app.ok(picked, { summary: `Scope: ${getStringField(picked, 'name')}` });
|
|
@@ -143,7 +144,7 @@ export function scopesCmd(wrap) {
|
|
|
143
144
|
summary: `Created scope: ${scope}`,
|
|
144
145
|
breadcrumbs: [{
|
|
145
146
|
action: 'show',
|
|
146
|
-
cmd: `jsn
|
|
147
|
+
cmd: `jsn scopes show ${scope}`,
|
|
147
148
|
description: 'View the new scope',
|
|
148
149
|
}],
|
|
149
150
|
});
|
|
@@ -159,7 +160,7 @@ export function scopesCmd(wrap) {
|
|
|
159
160
|
console.log(' show <scope> Show a scope');
|
|
160
161
|
console.log(' set <scope> Set the current application scope');
|
|
161
162
|
console.log(' create Create a new application scope');
|
|
162
|
-
console.log('\nRun "jsn
|
|
163
|
+
console.log('\nRun "jsn scopes <command> --help" for details.');
|
|
163
164
|
}
|
|
164
165
|
},
|
|
165
166
|
};
|