@jacebenson/jsn 4.3.13-dev.87.1 → 4.3.13-dev.89.1
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 +1 -1
- package/skills/servicenow/SKILL.md +20 -141
- package/src/commands/_generic.js +15 -9
- package/src/commands/b4rules.js +8 -3
- package/src/commands/columns.js +2 -1
- package/src/constrained-query.js +20 -0
- package/src/field-behavior.js +15 -0
- package/src/record-command-profiles.js +1 -1
package/package.json
CHANGED
|
@@ -1,154 +1,33 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: servicenow
|
|
3
|
-
description:
|
|
4
|
-
Interact with ServiceNow instances via the jsn CLI. Use when working with ServiceNow
|
|
5
|
-
development, administration, or data exploration. Handles tables, records, business rules,
|
|
6
|
-
flows, script includes, ACLs, update sets, and more. Triggered by ServiceNow URLs
|
|
7
|
-
(service-now.com, servicenow.com) or when the user mentions ServiceNow, jsn, servicenow,
|
|
8
|
-
or related terms like tables, records, business rules, flows, script includes, ACLs,
|
|
9
|
-
update sets, or encoded queries.
|
|
3
|
+
description: Use the jsn CLI for ServiceNow development, administration, and data exploration.
|
|
10
4
|
license: MIT
|
|
11
|
-
compatibility: |
|
|
12
|
-
Requires jsn CLI (github.com/jacebenson/jsn). Install via:
|
|
13
|
-
npm install -g @jacebenson/jsn
|
|
14
|
-
Works with Claude Code, OpenCode, Cursor, and agentskills-compatible tools.
|
|
15
5
|
metadata:
|
|
16
6
|
author: jacebenson
|
|
17
|
-
version: "4.3.
|
|
7
|
+
version: "4.3.13"
|
|
18
8
|
repository: https://github.com/jacebenson/jsn
|
|
19
9
|
---
|
|
20
10
|
|
|
21
|
-
#
|
|
11
|
+
# ServiceNow with JSN
|
|
22
12
|
|
|
23
|
-
|
|
13
|
+
Discover commands with `jsn --help`, `jsn discover "search words"`, and
|
|
14
|
+
`jsn <command> --help`.
|
|
24
15
|
|
|
25
|
-
|
|
16
|
+
- Verify the intended instance and select it explicitly with `--profile <name>`.
|
|
17
|
+
- Prefer purpose-built or records commands over `eval`. Explain the need and get
|
|
18
|
+
approval before using eval; avoid bypassing ServiceNow protections.
|
|
19
|
+
- Honor delete confirmations. Get approval for destructive work and Global +
|
|
20
|
+
Default mutations; use `--force` only for the approved operation.
|
|
26
21
|
|
|
27
|
-
|
|
28
|
-
jsn --help # All commands grouped by domain
|
|
29
|
-
jsn <command> --help # Flags, subcommands, and examples
|
|
30
|
-
```
|
|
22
|
+
For development:
|
|
31
23
|
|
|
32
|
-
|
|
24
|
+
1. Choose or create the application scope and update set.
|
|
25
|
+
2. Do the work, keeping related changes together.
|
|
26
|
+
3. Verify the results on the instance and report where changes were captured.
|
|
27
|
+
4. Offer to create ATFs and instance scans to verify the outcomes.
|
|
28
|
+
5. Ask whether anything else belongs in the update set. If the user confirms
|
|
29
|
+
it is finished, complete it with notes.
|
|
33
30
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- Scroll past last loaded item to load next page
|
|
38
|
-
- Enter to select, Esc to cancel
|
|
39
|
-
- Falls back to table output with `--json`
|
|
40
|
-
|
|
41
|
-
## Agent Rules
|
|
42
|
-
|
|
43
|
-
1. **Output modes** — `--json` when parsing data; text when presenting to humans
|
|
44
|
-
2. **Use sys_id for updates** — All update/delete operations require sys_id
|
|
45
|
-
3. **Verify the intended instance** — Use `--profile <name>` for explicit targeting; never assume the default is right. `jsn auth switch <name>` changes the default.
|
|
46
|
-
4. **Eval safety** — Prefer purpose-built or `records` commands. Never use `eval` to bypass a blocked business rule, ACL, or other ServiceNow protection. If eval is genuinely needed, explain why and obtain explicit user approval.
|
|
47
|
-
5. **CONFIRM before destructive operations** — Show what will be created/updated/deleted and ask for approval.
|
|
48
|
-
|
|
49
|
-
## Command Hierarchy
|
|
50
|
-
|
|
51
|
-
Pick the most specific tool for the job. **Never default to eval** — it's the last resort:
|
|
52
|
-
|
|
53
|
-
1. **Specific commands** — `jsn flows`, `jsn rules`, `jsn catalogitems`, etc.
|
|
54
|
-
2. **`jsn records --table <name>`** — generic CRUD on any table
|
|
55
|
-
3. **`jsn docs search <query>`** — offline ServiceNow documentation search (no instance needed)
|
|
56
|
-
4. **`jsn rest`** — raw Table API escape hatch
|
|
57
|
-
5. **`jsn eval`** — ⚠️ **LAST RESORT ONLY**
|
|
58
|
-
6. **Ask the human** — if none of the above work
|
|
59
|
-
|
|
60
|
-
## Key Commands
|
|
61
|
-
|
|
62
|
-
| Domain | Commands |
|
|
63
|
-
|--------|---------|
|
|
64
|
-
| **Core** | `incidents`, `changes`, `requests`, `tasks` |
|
|
65
|
-
| **Catalog** | `catalogitems list/show/create` — items with variables and flow info |
|
|
66
|
-
| **Automation** | `flows`, `actions`, `rules`, `triggers`, `scheduledjobs` |
|
|
67
|
-
| **Access** | `acls`, `b4rules`, `roles`, `groups`, `users` |
|
|
68
|
-
| **UX** | `forms` (section/element layout), `lists`, `clientscripts`, `uipolicies` |
|
|
69
|
-
| **Data** | `tables`, `columns`, `includes`, `logs`, `properties`, `records` |
|
|
70
|
-
| **Config** | `setup`, `auth`, `updatesets`, `scopes` |
|
|
71
|
-
| **Docs** | `docs sync`, `docs status`, `docs search`, `docs serve` |
|
|
72
|
-
| **Developer** | `eval`, `rest`, `skill`, `version` |
|
|
73
|
-
|
|
74
|
-
### Customization evidence
|
|
75
|
-
|
|
76
|
-
Use `jsn customizations --help` to discover bounded read-only capture and offline
|
|
77
|
-
review/comparison. For example:
|
|
78
|
-
|
|
79
|
-
```sh
|
|
80
|
-
jsn customizations capture --label review --customer-scope x_acme_app --json
|
|
81
|
-
jsn customizations show <run_id> --json
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Read the CLI's interpretation, coverage and per-artifact triage before drawing
|
|
85
|
-
conclusions. Verify ownership and current behavior before proposing changes;
|
|
86
|
-
never recommend automatic reversion.
|
|
87
|
-
|
|
88
|
-
### Complete an update set
|
|
89
|
-
|
|
90
|
-
Before development changes, identify the intended application scope and update
|
|
91
|
-
set. Keep related changes together, use the appropriate update-set or application
|
|
92
|
-
deployment mechanism, and report where changes were captured. Not all changes
|
|
93
|
-
are update-set tracked. Only complete a set when the human explicitly asks,
|
|
94
|
-
including sets you created; completing the current set returns selection to Default.
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
jsn updatesets complete "My Feature" --notes "Fixed validation" --force --json
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
`complete [name]` defaults to the current set when the name is omitted. Explicit
|
|
101
|
-
names must be unique. Supply nonempty `--notes` in automation; real newlines
|
|
102
|
-
separate bullets. A human terminal opens a notes editor when `--notes` is absent.
|
|
103
|
-
Existing Description text is preserved and appended after confirmation. Use
|
|
104
|
-
`--force` only after approval, or the profile's `skip_confirmations` policy.
|
|
105
|
-
Notes apply only to the selected set. ServiceNow's normal batch rules can complete
|
|
106
|
-
child sets when the parent completes; their descriptions are not replaced.
|
|
107
|
-
Both selected-set Description and state are read back before success. On
|
|
108
|
-
verification failure, inspect the set before retrying.
|
|
109
|
-
|
|
110
|
-
### Table and raw REST discovery
|
|
111
|
-
|
|
112
|
-
Use `jsn records list --help`, `jsn records get --help`, and `jsn rest --help`
|
|
113
|
-
for syntax and examples. REST is blocked on read-only connections, including GET.
|
|
114
|
-
|
|
115
|
-
### `jsn docs` — offline documentation search
|
|
116
|
-
|
|
117
|
-
No ServiceNow instance required. Searches the full ServiceNow docs locally with FTS5 + semantic hybrid search.
|
|
118
|
-
|
|
119
|
-
```bash
|
|
120
|
-
# First run: download docs and build the index (~45k files, ~3-5 min)
|
|
121
|
-
jsn docs sync
|
|
122
|
-
|
|
123
|
-
# Check state
|
|
124
|
-
jsn docs status
|
|
125
|
-
|
|
126
|
-
# Search (keyword + semantic hybrid)
|
|
127
|
-
jsn docs search "GlideRecord query" --limit 5 --json
|
|
128
|
-
|
|
129
|
-
# Filter by bundle
|
|
130
|
-
jsn docs search "REST API" --bundle it-service-management --json
|
|
131
|
-
|
|
132
|
-
# Start the web UI
|
|
133
|
-
jsn docs serve
|
|
134
|
-
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
**Re-running `jsn docs sync`** does a smart incremental refresh — pulls latest docs and only rebuilds changed files (seconds, not minutes).
|
|
138
|
-
|
|
139
|
-
## Troubleshooting Field Behavior
|
|
140
|
-
|
|
141
|
-
Check order (priority):
|
|
142
|
-
|
|
143
|
-
1. **Before-query business rules** — `sys_script` with `when=before`, `action=query`
|
|
144
|
-
```bash
|
|
145
|
-
jsn b4rules list --query "collection=sys_user"
|
|
146
|
-
```
|
|
147
|
-
2. **ACLs** — `jsn acls list --query "name=incident"`
|
|
148
|
-
3. **Dictionary overrides** — `jsn records list --table sys_dictionary --query "name=incident^element=caller_id"`
|
|
149
|
-
4. **Client scripts** / **UI policies** — form-level field behavior
|
|
150
|
-
|
|
151
|
-
## Configuration
|
|
152
|
-
|
|
153
|
-
Use `jsn setup` for interactive configuration. `jsn setup --help` lists the
|
|
154
|
-
scriptable `jsn auth` equivalents and secure authentication input options.
|
|
31
|
+
For incoming update sets: import the XML, preview the set, and resolve preview
|
|
32
|
+
errors with the human where needed. Once the preview is clear, commit the set
|
|
33
|
+
and verify the results.
|
package/src/commands/_generic.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { constrainQuery, constrainedQueryHelp } from '../constrained-query.js';
|
|
1
2
|
import { enrichRelatedLists, relatedListOptions } from '../related-lists.js';
|
|
2
3
|
// Generic table-based CRUD command builder
|
|
3
4
|
|
|
@@ -122,19 +123,24 @@ export function buildDevCmd(name, table, aliases, defaultColumns, wrap, opts = {
|
|
|
122
123
|
command: 'list',
|
|
123
124
|
aliases: ['ls'],
|
|
124
125
|
describe: `List ${name}`,
|
|
125
|
-
builder: (y) =>
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
builder: (y) => {
|
|
127
|
+
const command = recordOptions(y)
|
|
128
|
+
.option('query', { type: 'string', describe: 'Encoded query (e.g. "nameLIKEincident" or "active=true^priority=1")' })
|
|
129
|
+
.option('columns', { alias: ['c', 'fields'], type: 'string', describe: 'Comma-separated columns (e.g. "name,label,super_class")' })
|
|
130
|
+
.option('limit', { alias: 'l', type: 'number', default: 50, describe: 'Max records' });
|
|
131
|
+
const help = [opts.help, extraQuery ? constrainedQueryHelp(extraQuery) : ''].filter(Boolean).join('\n');
|
|
132
|
+
return help ? command.epilogue(help) : command;
|
|
133
|
+
},
|
|
129
134
|
handler: wrap(async (argv, app) => {
|
|
130
135
|
const query = argv.query || '';
|
|
136
|
+
const constrainedQuery = constrainQuery(query, extraQuery);
|
|
131
137
|
const columns = argv.columns ? argv.columns.split(',') : defaultColumns;
|
|
132
138
|
const limit = argv.limit;
|
|
133
139
|
|
|
134
140
|
// Interactive picker with pagination
|
|
135
141
|
if (canPrompt() && app.output.getFormat() === FormatAuto && !query) {
|
|
136
142
|
const picked = await interactiveList({
|
|
137
|
-
app, table, singular, columns: ['name', 'sys_scope'], limit, query:
|
|
143
|
+
app, table, singular, columns: ['name', 'sys_scope'], limit, query: constrainedQuery,
|
|
138
144
|
labelField: 'name',
|
|
139
145
|
message: `Select ${vowelArticle(singular)} ${singular}`,
|
|
140
146
|
formatLabel: (r) => {
|
|
@@ -162,8 +168,7 @@ export function buildDevCmd(name, table, aliases, defaultColumns, wrap, opts = {
|
|
|
162
168
|
}
|
|
163
169
|
|
|
164
170
|
// Non-interactive: text/table output
|
|
165
|
-
const
|
|
166
|
-
const q = query ? query + '^ORDERBYDESCsys_updated_on' : (baseQ + 'ORDERBYDESCsys_updated_on');
|
|
171
|
+
const q = `${constrainedQuery}${constrainedQuery ? '^' : ''}ORDERBYDESCsys_updated_on`;
|
|
167
172
|
const params = recordListParams({ limit, fields: ['sys_id', ...columns], query: q });
|
|
168
173
|
const records = await listRecords(app.sdk, { table, params, displayValues: argv.displayValues ?? opts.referenceMode });
|
|
169
174
|
app.ok({
|
|
@@ -277,13 +282,14 @@ export function buildDevCmd(name, table, aliases, defaultColumns, wrap, opts = {
|
|
|
277
282
|
});
|
|
278
283
|
}
|
|
279
284
|
|
|
280
|
-
|
|
285
|
+
const help = [opts.help, extraQuery ? constrainedQueryHelp(extraQuery) : ''].filter(Boolean).join('\n');
|
|
286
|
+
return help ? y.epilogue(help) : y;
|
|
281
287
|
};
|
|
282
288
|
|
|
283
289
|
return {
|
|
284
290
|
command: `${name} [subcommand]`,
|
|
285
291
|
aliases: aliases || [],
|
|
286
|
-
describe: `Manage ${name} (e.g. "${name} list --query nameLIKEincident")`,
|
|
292
|
+
describe: opts.describe || `Manage ${name} (e.g. "${name} list --query nameLIKEincident")`,
|
|
287
293
|
discovery: {
|
|
288
294
|
table,
|
|
289
295
|
defaultColumns,
|
package/src/commands/b4rules.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { buildDevCmd } from './_generic.js';
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
// Query rules are explicitly marked; their order is not a classification rule.
|
|
4
|
+
export const b4rulesCmd = wrap => buildDevCmd('b4rules', 'sys_script', ['b4rule'],
|
|
5
|
+
['name', 'collection', 'active', 'order', 'sys_scope'], wrap, {
|
|
6
|
+
singular: 'before-query business rule', scopeValidation: true,
|
|
7
|
+
extraQuery: 'when=before^action_query=true',
|
|
8
|
+
describe: 'Inspect before-query business rules that can filter record reads',
|
|
9
|
+
help: 'Before-query business rules use when=before and the Boolean action_query=true. Filter collection and active status for the affected table. For field behavior, see jsn columns --help; do not bypass server rules or ACLs.',
|
|
10
|
+
});
|
package/src/commands/columns.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { fieldBehaviorHelp } from '../field-behavior.js';
|
|
1
2
|
import { buildDevCmd } from './_generic.js';
|
|
2
3
|
|
|
3
4
|
export const columnsCmd = (wrap) => {
|
|
4
5
|
const base = buildDevCmd('columns', 'sys_dictionary', ['column', 'col'],
|
|
5
6
|
['element', 'column_label', 'internal_type', 'mandatory', 'max_length', 'active'],
|
|
6
|
-
wrap, { singular: 'column', scopeValidation: true });
|
|
7
|
+
wrap, { singular: 'column', scopeValidation: true, help: fieldBehaviorHelp });
|
|
7
8
|
|
|
8
9
|
// Add --table filter
|
|
9
10
|
const origBuilder = base.builder;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { errUsageHint } from './errors.js';
|
|
2
|
+
|
|
3
|
+
/** Constrained table views accept literal AND filters. Encoded queries have no
|
|
4
|
+
* portable parenthesization; OR/NQ/dynamic clauses must not escape fixed filters. */
|
|
5
|
+
export function constrainQuery(query = '', constraint = '') {
|
|
6
|
+
if (!constraint) return query;
|
|
7
|
+
const clauses = String(query).split('^').filter((clause, index, values) => clause !== 'EQ' || index !== values.length - 1);
|
|
8
|
+
if (!query) return constraint;
|
|
9
|
+
const literal = /^[a-z_][a-z0-9_]*(?:\.[a-z_][a-z0-9_]*)*(?:(?:!=|>=|<=|=|>|<|NOT LIKE|STARTSWITH|ENDSWITH|LIKE|NOT IN|IN)[^^]*|ISEMPTY|ISNOTEMPTY)$/;
|
|
10
|
+
const ordering = /^ORDERBY(?:DESC)?[a-z_][a-z0-9_]*(?:\.[a-z_][a-z0-9_]*)*$/;
|
|
11
|
+
const allowed = clause => ordering.test(clause) || (literal.test(clause)
|
|
12
|
+
&& (!/^(?:NQ|OR)/i.test(clause) || /^order(?:!=|>=|<=|=|>|<|NOT LIKE|STARTSWITH|ENDSWITH|LIKE|NOT IN|IN|ISEMPTY|ISNOTEMPTY)/.test(clause)));
|
|
13
|
+
if (/javascript\s*:/i.test(query) || clauses.some(clause => !allowed(clause))) {
|
|
14
|
+
throw errUsageHint('This constrained command accepts literal AND filters and ORDERBY only; OR, NQ and dynamic query clauses are unsupported.',
|
|
15
|
+
`The fixed filter is ${constraint}. Use literal AND clauses, or explicitly use records list for advanced queries and retain the intended constraints in every branch.`);
|
|
16
|
+
}
|
|
17
|
+
return clauses.length ? `${constraint}^${clauses.join('^')}` : constraint;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const constrainedQueryHelp = constraint => `Always includes: ${constraint}. Additional --query filters use literal AND clauses (plus ORDERBY). OR, NQ and dynamic query clauses are rejected before requests so they cannot bypass this view's fixed filter.`;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Schema-backed investigation paths; no universal priority order is assumed.
|
|
2
|
+
export const fieldBehaviorHelp = [
|
|
3
|
+
'Investigate the observed symptom; there is no fixed priority order for every field problem.',
|
|
4
|
+
'Query filtering: jsn b4rules list --query "collection=incident^active=true" --json',
|
|
5
|
+
'Before-query rules use sys_script.when=before and the Boolean action_query=true.',
|
|
6
|
+
'Dictionary entry: jsn records list --table sys_dictionary --query "name=incident^element=caller_id" --json',
|
|
7
|
+
'Inherited dictionary overrides: jsn records list --table sys_dictionary_override --query "name=incident^element=caller_id" --json',
|
|
8
|
+
'Inspect base_table and inherited tables as needed; the dictionary entry is separate from its overrides.',
|
|
9
|
+
'Access controls: jsn acls list --query "nameINincident,incident.*,incident.caller_id,*.caller_id,*,*.*" --json',
|
|
10
|
+
'Also inspect inherited-table and wildcard ACLs. Exact name=incident alone omits field/wildcard rules.',
|
|
11
|
+
'ACL rows are evidence, not proof of effective access: operation, roles, conditions and scripts matter.',
|
|
12
|
+
'Form behavior: jsn clientscripts list --query "table=incident^active=true" --json',
|
|
13
|
+
'Form policies: jsn uipolicies list --query "table=incident^active=true" --json',
|
|
14
|
+
'Use the command help and show output to inspect the relevant records. Never bypass a business rule, ACL or other protection to make a symptom disappear.',
|
|
15
|
+
].join('\n');
|
|
@@ -18,7 +18,7 @@ export default {
|
|
|
18
18
|
appmenu: { table: 'sys_app_application', aliases: ['app-menu', 'menu'], columns: ['name', 'active', 'sys_scope'],
|
|
19
19
|
options: { singular: 'application menu', scopeValidation: true } },
|
|
20
20
|
acls: { table: 'sys_security_acl', aliases: ['acl'], columns: ['name', 'operation', 'type', 'active', 'sys_scope'],
|
|
21
|
-
options: { singular: 'ACL', scopeValidation: true } },
|
|
21
|
+
options: { singular: 'ACL', scopeValidation: true, help: 'Investigate table, field, wildcard and inherited ACL names for the relevant operation. Exact name=incident alone does not cover incident.caller_id, incident.*, *.caller_id or inherited-table ACLs. Roles, conditions and scripts affect effective access; rows alone do not prove access. See jsn columns --help for field investigation paths.' } },
|
|
22
22
|
roles: { table: 'sys_user_role', aliases: ['role', 'r'], columns: ['name', 'description', 'elevated_privilege', 'sys_scope'],
|
|
23
23
|
options: { singular: 'role', scopeValidation: true } },
|
|
24
24
|
properties: { table: 'sys_properties', aliases: ['property', 'prop'], columns: ['name', 'value', 'description', 'sys_scope'],
|