@jacebenson/jsn 3.0.6 → 3.1.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/README.md +60 -285
- package/package.json +5 -2
- 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 +47 -92
- package/src/app.js +5 -5
- package/src/cli.js +268 -161
- package/src/commands/_ticket.js +15 -2
- package/src/commands/auth.js +10 -1
- package/src/commands/catalog.js +179 -292
- package/src/commands/dev/_generic.js +50 -62
- package/src/commands/dev/_simple.js +27 -0
- package/src/commands/dev/b4rules.js +5 -0
- package/src/commands/dev/eval.js +1 -1
- package/src/commands/dev/flows.js +31 -8
- package/src/commands/dev/forms.js +131 -253
- package/src/commands/dev/lists.js +16 -6
- package/src/commands/dev/logs.js +52 -9
- 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/groupmembers.js +10 -1
- package/src/commands/grouproles.js +10 -1
- package/src/commands/groups.js +12 -1
- package/src/commands/records.js +16 -4
- package/src/commands/skill.js +11 -1
- package/src/commands/tickets.js +12 -1
- package/src/commands/users.js +12 -1
- package/src/help.js +125 -35
- package/src/helpers.js +50 -34
- package/src/paginated-search.js +210 -0
- package/src/sdk.js +4 -0
|
@@ -12,114 +12,76 @@ compatibility: |
|
|
|
12
12
|
Requires jsn CLI (github.com/jacebenson/jsn). Install via:
|
|
13
13
|
npm install -g @jacebenson/jsn
|
|
14
14
|
Works with Claude Code, OpenCode, Cursor, and agentskills-compatible tools.
|
|
15
|
-
|
|
16
|
-
Windows users: Install Node.js first, then the npm command above. If jsn is not
|
|
17
|
-
in your PATH, the setup command will automatically detect its location and show
|
|
18
|
-
the correct command path.
|
|
19
15
|
metadata:
|
|
20
16
|
author: jacebenson
|
|
21
|
-
version: "1.
|
|
17
|
+
version: "3.1.1"
|
|
22
18
|
repository: https://github.com/jacebenson/jsn
|
|
23
19
|
---
|
|
24
20
|
|
|
25
|
-
# Jace's ServiceNow CLI
|
|
21
|
+
# Jace's ServiceNow CLI (jsn)
|
|
26
22
|
|
|
27
|
-
Explore and manage ServiceNow instances.
|
|
23
|
+
Explore and manage ServiceNow instances. Interactive paginated pickers with server-side search on all list commands.
|
|
28
24
|
|
|
29
25
|
## Discovery
|
|
30
26
|
|
|
31
|
-
Commands are self-documenting. Use these to learn what's available:
|
|
32
|
-
|
|
33
27
|
```bash
|
|
34
|
-
jsn --help #
|
|
35
|
-
jsn <command> --help
|
|
36
|
-
jsn dev # Shows categorized dev subcommands
|
|
28
|
+
jsn --help # All commands grouped by domain
|
|
29
|
+
jsn <command> --help # Flags, subcommands, and examples
|
|
37
30
|
```
|
|
38
31
|
|
|
32
|
+
## Interactive Picker
|
|
33
|
+
|
|
34
|
+
All `list` subcommands use a paginated interactive picker in TTY mode:
|
|
35
|
+
- Arrow through items (10 per page)
|
|
36
|
+
- Type to search (server-side LIKE query)
|
|
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
|
+
|
|
39
41
|
## Agent Rules
|
|
40
42
|
|
|
41
|
-
1. **Output modes** — `--json` when parsing data;
|
|
43
|
+
1. **Output modes** — `--json` when parsing data; text when presenting to humans
|
|
42
44
|
2. **Use sys_id for updates** — All update/delete operations require sys_id
|
|
43
45
|
3. **Check auth first** — Run `jsn auth status` before operations
|
|
44
46
|
4. **NEVER logout** — Only run `jsn auth logout` if the user explicitly asks
|
|
45
47
|
5. **Use `--profile <name>`** to target a specific instance, or `jsn profiles use <name>` to change default
|
|
46
|
-
6. **Before using `eval` or `rest`** — Ask
|
|
47
|
-
7. **CONFIRM before destructive operations** —
|
|
48
|
-
|
|
49
|
-
## ⚠️ Destructive Operations Require Confirmation
|
|
50
|
-
|
|
51
|
-
**jsn does not have a `--confirm` or `--dry-run` flag.** It assumes you mean what you type. This means **YOU** (the agent) are the safety layer.
|
|
52
|
-
|
|
53
|
-
**Before any write operation, you MUST:**
|
|
54
|
-
|
|
55
|
-
1. **Show the user what you found** — Run a `list` or `show` command first
|
|
56
|
-
2. **Present the exact command you plan to run** — Include the sys_id, table name, and data
|
|
57
|
-
3. **Wait for explicit approval** — "Proceed? [y/N]" or similar
|
|
58
|
-
|
|
59
|
-
**Examples of destructive commands that require confirmation:**
|
|
60
|
-
- `jsn records --table <name> create`
|
|
61
|
-
- `jsn records --table <name> update <sys_id>`
|
|
62
|
-
- `jsn records --table <name> delete <sys_id>`
|
|
63
|
-
- `jsn dev updatesets set <name>`
|
|
64
|
-
- `jsn profiles remove <name>`
|
|
65
|
-
- Any `delete`, `update`, `create`, `set`, or `remove` subcommand
|
|
66
|
-
|
|
67
|
-
**Good pattern:**
|
|
68
|
-
```
|
|
69
|
-
User: "Delete that incident"
|
|
70
|
-
Agent: "Found: INC0010001 'Server down' (sys_id: abc123...)
|
|
71
|
-
Command: jsn records --table incident delete abc123...
|
|
72
|
-
Proceed? [y/N]"
|
|
73
|
-
User: "y"
|
|
74
|
-
Agent: [runs command]
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
**Bad pattern:**
|
|
78
|
-
```
|
|
79
|
-
User: "Delete that incident"
|
|
80
|
-
Agent: [immediately runs jsn records --table incident delete ...]
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
## ⚠️ AVOID `jsn eval` for Record Creation
|
|
84
|
-
|
|
85
|
-
**Why:** `jsn eval` runs server-side JavaScript in the global scope. It returns HTTP 200 even when:
|
|
86
|
-
- The insert fails due to ACL violations
|
|
87
|
-
- Mandatory fields are missing
|
|
88
|
-
- The record lands in the wrong scope
|
|
89
|
-
- Logic errors prevent the insert entirely
|
|
90
|
-
|
|
91
|
-
**The AI trap:** Agents see "success" and assume the record was created. It wasn't.
|
|
92
|
-
|
|
93
|
-
**Always use instead:** `jsn records --table <name> create` — it returns the created record with sys_id or an explicit validation error.
|
|
94
|
-
|
|
95
|
-
**Only use `eval` when:** No specific command exists, `records` lacks required fields, and `rest` doesn't work. This is rare.
|
|
48
|
+
6. **Before using `eval` or `rest`** — Ask: *"Is there a more specific jsn command?"* Prefer specific over generic over escape hatch over eval.
|
|
49
|
+
7. **CONFIRM before destructive operations** — Show what will be created/updated/deleted and ask for approval.
|
|
96
50
|
|
|
97
51
|
## Command Hierarchy
|
|
98
52
|
|
|
99
53
|
Pick the most specific tool for the job. **Never default to eval** — it's the last resort:
|
|
100
54
|
|
|
101
|
-
1. **Specific commands** — `jsn flows`, `jsn rules`, `jsn
|
|
102
|
-
2. **`jsn records --table <name>`** — generic CRUD on any table
|
|
103
|
-
3. **`jsn rest`** — raw Table API escape hatch
|
|
104
|
-
4. **`jsn eval`** — ⚠️ **LAST RESORT ONLY**
|
|
105
|
-
5. **Ask the human** — if none of the above work
|
|
106
|
-
|
|
107
|
-
##
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
55
|
+
1. **Specific commands** — `jsn flows`, `jsn rules`, `jsn catalogitems`, etc.
|
|
56
|
+
2. **`jsn records --table <name>`** — generic CRUD on any table
|
|
57
|
+
3. **`jsn rest`** — raw Table API escape hatch
|
|
58
|
+
4. **`jsn eval`** — ⚠️ **LAST RESORT ONLY**
|
|
59
|
+
5. **Ask the human** — if none of the above work
|
|
60
|
+
|
|
61
|
+
## Key Commands
|
|
62
|
+
|
|
63
|
+
| Domain | Commands |
|
|
64
|
+
|--------|---------|
|
|
65
|
+
| **Core** | `incidents`, `changes`, `requests`, `tasks` |
|
|
66
|
+
| **Catalog** | `catalogitems list/show/create` — items with variables and flow info |
|
|
67
|
+
| **Automation** | `flows`, `actions`, `rules`, `workflows`, `triggers`, `scheduledjobs` |
|
|
68
|
+
| **Access** | `acls`, `b4rules`, `roles`, `groups`, `users` |
|
|
69
|
+
| **UX** | `forms` (section/element layout), `lists`, `clientscripts`, `uipolicies` |
|
|
70
|
+
| **Data** | `tables`, `columns`, `includes`, `logs`, `properties`, `records` |
|
|
71
|
+
| **Config** | `setup`, `auth`, `profiles`, `updatesets`, `scopes` |
|
|
72
|
+
| **Developer** | `eval`, `rest`, `skill`, `version` |
|
|
73
|
+
|
|
74
|
+
## Troubleshooting Field Behavior
|
|
75
|
+
|
|
76
|
+
Check order (priority):
|
|
77
|
+
|
|
78
|
+
1. **Before-query business rules** — `sys_script` with `when=before`, `action=query`
|
|
79
|
+
```bash
|
|
80
|
+
jsn b4rules list --query "collection=sys_user"
|
|
81
|
+
```
|
|
82
|
+
2. **ACLs** — `jsn acls list --query "name=incident"`
|
|
83
|
+
3. **Dictionary overrides** — `jsn records list --table sys_dictionary --query "name=incident^element=caller_id"`
|
|
84
|
+
4. **Client scripts** / **UI policies** — form-level field behavior
|
|
123
85
|
|
|
124
86
|
## Configuration
|
|
125
87
|
|
|
@@ -127,11 +89,4 @@ Breadcrumbs suggest next commands — follow them for navigation.
|
|
|
127
89
|
~/.config/servicenow/
|
|
128
90
|
├── config.json # Profiles and settings
|
|
129
91
|
└── credentials.json # Auth tokens (fallback)
|
|
130
|
-
|
|
131
|
-
.servicenow/ # Per-repo config (optional)
|
|
132
|
-
└── config.json # Project-specific settings
|
|
133
92
|
```
|
|
134
|
-
|
|
135
|
-
**Environment variables:**
|
|
136
|
-
- `SERVICENOW_TOKEN` — Override stored token
|
|
137
|
-
- `SERVICENOW_INSTANCE` — Override instance URL
|
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,33 @@ 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
|
+
scriptactionsCmd, scheduledjobsCmd, asyncrulesCmd, triggersCmd,
|
|
41
|
+
workflowsCmd, decisiontablesCmd, assignmentsCmd,
|
|
42
|
+
emailCmd,
|
|
43
|
+
restmessageCmd, restmethodsCmd, soapmessagesCmd, soapfunctionsCmd,
|
|
44
|
+
uimacrosCmd, uxlistsCmd, uxapplicabilityCmd,
|
|
45
|
+
} from './commands/dev/_simple.js';
|
|
46
|
+
import { flowsCmd } from './commands/dev/flows.js';
|
|
47
|
+
import { formsCmd } from './commands/dev/forms.js';
|
|
48
|
+
import { listsCmd } from './commands/dev/lists.js';
|
|
49
|
+
import { updateSetsCmd } from './commands/dev/updatesets.js';
|
|
50
|
+
import { scopesCmd } from './commands/dev/scopes.js';
|
|
51
|
+
import { evalCmd } from './commands/dev/eval.js';
|
|
52
|
+
import { restCmd } from './commands/dev/rest.js';
|
|
53
|
+
import { logsCmd } from './commands/dev/logs.js';
|
|
54
|
+
import { scrapiCmd } from './commands/dev/scrapi.js';
|
|
55
|
+
import { b4rulesCmd } from './commands/dev/b4rules.js';
|
|
56
|
+
|
|
30
57
|
function wrap(handler) {
|
|
31
58
|
return async (argv) => {
|
|
32
59
|
try {
|
|
@@ -37,179 +64,259 @@ function wrap(handler) {
|
|
|
37
64
|
}
|
|
38
65
|
await handler(argv, app);
|
|
39
66
|
} catch (err) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
67
|
+
if (err.code === 'not_found') {
|
|
68
|
+
const id = argv._.slice(1).join(' ') || argv.id || argv.name || argv.sysID || '';
|
|
69
|
+
process.stderr.write(`Error (${err.code}): ${err.message}\n`);
|
|
70
|
+
if (id) {
|
|
71
|
+
process.stderr.write(`\nHint: The identifier "${id}" was not found. Check the name or sys_id.\n`);
|
|
72
|
+
}
|
|
73
|
+
process.exit(1);
|
|
45
74
|
}
|
|
75
|
+
if (err.code === 'usage') {
|
|
76
|
+
process.stderr.write(`Error (usage): ${err.message}\n`);
|
|
77
|
+
process.exit(2);
|
|
78
|
+
}
|
|
79
|
+
if (err.code === 'system_error') {
|
|
80
|
+
process.stderr.write(`Error (system): ${err.message}\n`);
|
|
81
|
+
process.exit(3);
|
|
82
|
+
}
|
|
83
|
+
process.stderr.write(`Error: ${err.message}\n`);
|
|
46
84
|
process.exit(1);
|
|
47
85
|
}
|
|
48
86
|
};
|
|
49
87
|
}
|
|
50
88
|
|
|
51
|
-
export
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
.option('instance', {
|
|
55
|
-
describe: 'ServiceNow instance URL (e.g., https://dev12345.service-now.com)',
|
|
56
|
-
type: 'string',
|
|
57
|
-
global: true,
|
|
58
|
-
})
|
|
59
|
-
.option('profile', {
|
|
60
|
-
alias: 'p',
|
|
61
|
-
describe: 'Configuration profile to use',
|
|
62
|
-
type: 'string',
|
|
63
|
-
global: true,
|
|
64
|
-
})
|
|
65
|
-
.option('format', {
|
|
66
|
-
describe: 'Output format: auto, json, markdown, styled, quiet',
|
|
67
|
-
type: 'string',
|
|
68
|
-
global: true,
|
|
69
|
-
})
|
|
70
|
-
.option('json', {
|
|
71
|
-
describe: 'Output in JSON format',
|
|
72
|
-
type: 'boolean',
|
|
73
|
-
global: true,
|
|
74
|
-
})
|
|
75
|
-
.option('quiet', {
|
|
76
|
-
alias: 'q',
|
|
77
|
-
describe: 'Output only data, no envelope',
|
|
78
|
-
type: 'boolean',
|
|
79
|
-
global: true,
|
|
80
|
-
})
|
|
81
|
-
.option('styled', {
|
|
82
|
-
describe: 'Force styled output',
|
|
83
|
-
type: 'boolean',
|
|
84
|
-
global: true,
|
|
85
|
-
})
|
|
86
|
-
.option('markdown', {
|
|
87
|
-
describe: 'Output in Markdown format',
|
|
88
|
-
type: 'boolean',
|
|
89
|
-
global: true,
|
|
90
|
-
})
|
|
91
|
-
.option('no-skill-check', {
|
|
92
|
-
describe: 'Skip the automatic skill version check',
|
|
93
|
-
type: 'boolean',
|
|
94
|
-
global: true,
|
|
95
|
-
})
|
|
96
|
-
.middleware(async (argv) => {
|
|
97
|
-
// Determine format from flags
|
|
98
|
-
let format = 'auto';
|
|
99
|
-
if (argv.json) format = 'json';
|
|
100
|
-
else if (argv.quiet) format = 'quiet';
|
|
101
|
-
else if (argv.styled) format = 'styled';
|
|
102
|
-
else if (argv.markdown) format = 'markdown';
|
|
103
|
-
else if (argv.format) format = argv.format;
|
|
104
|
-
|
|
105
|
-
const cfg = loadConfig({
|
|
106
|
-
instance: argv.instance,
|
|
107
|
-
profile: argv.profile,
|
|
108
|
-
format,
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
argv.app = new App(cfg);
|
|
89
|
+
export function buildCLI() {
|
|
90
|
+
const cfg = loadConfig();
|
|
91
|
+
const app = new App(cfg);
|
|
112
92
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
process.stderr.write('To get started, run:\n');
|
|
121
|
-
process.stderr.write(' jsn setup # Interactive setup\n');
|
|
122
|
-
process.stderr.write(` jsn auth login ${instance} # Login to instance\n\n`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
93
|
+
// Resolve instance once
|
|
94
|
+
let effectiveInstance;
|
|
95
|
+
try {
|
|
96
|
+
effectiveInstance = getEffectiveInstance(cfg);
|
|
97
|
+
} catch {
|
|
98
|
+
effectiveInstance = null;
|
|
99
|
+
}
|
|
125
100
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
101
|
+
return yargs(hideBin(process.argv))
|
|
102
|
+
.scriptName('jsn')
|
|
103
|
+
.usage('Usage: jsn <command> [options]')
|
|
104
|
+
.middleware([
|
|
105
|
+
// Attach App instance to every command
|
|
106
|
+
(argv) => {
|
|
107
|
+
argv.app = app;
|
|
108
|
+
argv._profile = cfg.profiles || {};
|
|
109
|
+
},
|
|
110
|
+
])
|
|
111
|
+
.options({
|
|
112
|
+
instance: {
|
|
113
|
+
type: 'string',
|
|
114
|
+
description: 'ServiceNow instance URL',
|
|
115
|
+
alias: 'i',
|
|
116
|
+
},
|
|
117
|
+
profile: {
|
|
118
|
+
type: 'string',
|
|
119
|
+
description: 'Configuration profile to use',
|
|
120
|
+
alias: 'p',
|
|
121
|
+
},
|
|
122
|
+
format: {
|
|
123
|
+
type: 'string',
|
|
124
|
+
description: 'Output format: auto, json, markdown, styled, quiet',
|
|
125
|
+
choices: ['auto', 'json', 'markdown', 'styled', 'quiet'],
|
|
126
|
+
},
|
|
127
|
+
json: {
|
|
128
|
+
type: 'boolean',
|
|
129
|
+
description: 'Output in JSON format (shortcut for --format=json)',
|
|
130
|
+
},
|
|
131
|
+
quiet: {
|
|
132
|
+
type: 'boolean',
|
|
133
|
+
description: 'Output only data, no envelope (shortcut for --format=quiet)',
|
|
134
|
+
alias: 'q',
|
|
135
|
+
},
|
|
136
|
+
styled: {
|
|
137
|
+
type: 'boolean',
|
|
138
|
+
description: 'Force styled output even when piped',
|
|
139
|
+
},
|
|
140
|
+
markdown: {
|
|
141
|
+
type: 'boolean',
|
|
142
|
+
description: 'Output in Markdown table format',
|
|
143
|
+
},
|
|
144
|
+
})
|
|
145
|
+
.middleware([
|
|
146
|
+
// Apply format option overrides
|
|
147
|
+
(argv) => {
|
|
148
|
+
if (argv.json) argv.format = 'json';
|
|
149
|
+
if (argv.quiet) argv.format = 'quiet';
|
|
150
|
+
if (argv.styled) argv.format = 'styled';
|
|
151
|
+
if (argv.markdown) argv.format = 'markdown';
|
|
152
|
+
if (argv.format) {
|
|
153
|
+
app.output.setFormatByName(argv.format);
|
|
138
154
|
}
|
|
139
|
-
|
|
140
|
-
|
|
155
|
+
if (argv.instance) {
|
|
156
|
+
app._overrideInstance = argv.instance;
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
// Guard mutation commands when no instance configured
|
|
160
|
+
(argv) => {
|
|
161
|
+
const cmd = (argv._[0] || '').toString();
|
|
162
|
+
if (!cmd || ['help', 'version', 'completion', 'setup', 'auth', 'profiles', 'skill'].includes(cmd)) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
// Check for --instance override
|
|
166
|
+
if (argv.instance) {
|
|
167
|
+
app._overrideInstance = argv.instance;
|
|
168
|
+
}
|
|
169
|
+
// Determine if this is a mutation
|
|
170
|
+
const subcommand = (argv._[1] || '').toString();
|
|
171
|
+
const isMutation = isMutationCommand(cmd, subcommand);
|
|
172
|
+
if (isMutation) {
|
|
173
|
+
app.requireInstance();
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
])
|
|
177
|
+
.middleware(async (argv) => {
|
|
178
|
+
// Override instance for this command run
|
|
179
|
+
if (argv._overrideInstance) {
|
|
180
|
+
app.setEffectiveInstance(argv._overrideInstance);
|
|
141
181
|
}
|
|
142
|
-
}
|
|
143
182
|
|
|
144
|
-
|
|
145
|
-
// Skipped under --json, --quiet, --no-skill-check, or env var
|
|
146
|
-
const skipSkillCheck = ['help', 'version', 'completion', 'skill'].includes(cmd)
|
|
147
|
-
|| process.env.JSN_NO_SKILL_CHECK === '1'
|
|
148
|
-
|| argv['no-skill-check']
|
|
149
|
-
|| argv.json
|
|
150
|
-
|| argv.quiet;
|
|
151
|
-
if (!skipSkillCheck) {
|
|
152
|
-
// Fire check but don't await — it runs in background
|
|
153
|
-
checkSkill().then(result => {
|
|
154
|
-
if (result && !result.current && result.error) {
|
|
155
|
-
process.stderr.write(`\n⚠ ${result.error}\n\n`);
|
|
156
|
-
}
|
|
157
|
-
}).catch(() => {});
|
|
158
|
-
}
|
|
183
|
+
const cmd = (argv._[0] || '').toString();
|
|
159
184
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
185
|
+
// Auto-check skill on every command (fire-and-forget, non-blocking)
|
|
186
|
+
const skipSkillCheck = ['help', 'version', 'completion', 'skill'].includes(cmd)
|
|
187
|
+
|| process.env.JSN_NO_SKILL_CHECK === '1'
|
|
188
|
+
|| argv['no-skill-check']
|
|
189
|
+
|| argv.json
|
|
190
|
+
|| argv.quiet;
|
|
191
|
+
if (!skipSkillCheck) {
|
|
192
|
+
checkSkill().then(result => {
|
|
193
|
+
if (result && !result.current && result.error) {
|
|
194
|
+
process.stderr.write(`\n⚠ ${result.error}\n\n`);
|
|
195
|
+
}
|
|
196
|
+
}).catch(() => {});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Print context header for interactive terminals
|
|
200
|
+
if (!['help', 'version', 'completion', 'skill'].includes(cmd)) {
|
|
201
|
+
await argv.app.printContextHeader(argv);
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
// CONFIGURATION
|
|
205
|
+
.command(setupCmd(wrap))
|
|
206
|
+
.command(authCmd(wrap))
|
|
207
|
+
.command(profilesCmd(wrap))
|
|
208
|
+
.command(updateSetsCmd(wrap))
|
|
209
|
+
.command(scopesCmd(wrap))
|
|
210
|
+
// CORE
|
|
211
|
+
.command(incidentsCmd(wrap))
|
|
212
|
+
.command(changesCmd(wrap))
|
|
213
|
+
.command(requestsCmd(wrap))
|
|
214
|
+
.command(tasksCmd(wrap))
|
|
215
|
+
.command(catalogCmd(wrap))
|
|
216
|
+
// AUTOMATION — Async
|
|
217
|
+
.command(flowsCmd(wrap))
|
|
218
|
+
.command(actionsCmd(wrap))
|
|
219
|
+
.command(scriptactionsCmd(wrap))
|
|
220
|
+
.command(scheduledjobsCmd(wrap))
|
|
221
|
+
.command(asyncrulesCmd(wrap))
|
|
222
|
+
.command(triggersCmd(wrap))
|
|
223
|
+
// AUTOMATION — In Memory
|
|
224
|
+
.command(rulesCmd(wrap))
|
|
225
|
+
.command(workflowsCmd(wrap))
|
|
226
|
+
.command(decisiontablesCmd(wrap))
|
|
227
|
+
.command(assignmentsCmd(wrap))
|
|
228
|
+
// AUTOMATION — Inbound
|
|
229
|
+
.command(scrapiCmd(wrap))
|
|
230
|
+
.command(emailCmd(wrap))
|
|
231
|
+
// AUTOMATION — Outbound
|
|
232
|
+
.command(restmessageCmd(wrap))
|
|
233
|
+
.command(restmethodsCmd(wrap))
|
|
234
|
+
.command(soapmessagesCmd(wrap))
|
|
235
|
+
.command(soapfunctionsCmd(wrap))
|
|
236
|
+
// ACCESS
|
|
237
|
+
.command(aclsCmd(wrap))
|
|
238
|
+
.command(b4rulesCmd(wrap))
|
|
239
|
+
.command(rolesCmd(wrap))
|
|
240
|
+
.command(privilegesCmd(wrap))
|
|
241
|
+
.command(securitytypesCmd(wrap))
|
|
242
|
+
.command(aliasesCmd(wrap))
|
|
243
|
+
.command(groupsCmd(wrap))
|
|
244
|
+
.command(groupMembersCmd(wrap))
|
|
245
|
+
.command(groupRolesCmd(wrap))
|
|
246
|
+
.command(usersCmd(wrap))
|
|
247
|
+
// USER EXPERIENCE — Shared
|
|
248
|
+
.command(formsCmd(wrap))
|
|
249
|
+
.command(listsCmd(wrap))
|
|
250
|
+
.command(clientScriptsCmd(wrap))
|
|
251
|
+
.command(uiPoliciesCmd(wrap))
|
|
252
|
+
.command(uiActionsCmd(wrap))
|
|
253
|
+
.command(viewsCmd(wrap))
|
|
254
|
+
.command(catalogscriptsCmd(wrap))
|
|
255
|
+
.command(cataloguipoliciesCmd(wrap))
|
|
256
|
+
// USER EXPERIENCE — Core UI
|
|
257
|
+
.command(uiPagesCmd(wrap))
|
|
258
|
+
.command(uimacrosCmd(wrap))
|
|
259
|
+
.command(listcontrolsCmd(wrap))
|
|
260
|
+
// USER EXPERIENCE — Service Portal
|
|
261
|
+
.command(spPagesCmd(wrap))
|
|
262
|
+
.command(spWidgetsCmd(wrap))
|
|
263
|
+
// USER EXPERIENCE — Next Experience
|
|
264
|
+
.command(uxscriptsCmd(wrap))
|
|
265
|
+
.command(uxlistsCmd(wrap))
|
|
266
|
+
.command(uxapplicabilityCmd(wrap))
|
|
267
|
+
// USER EXPERIENCE — Navigation
|
|
268
|
+
.command(appMenuCmd(wrap))
|
|
269
|
+
.command(appmodulesCmd(wrap))
|
|
270
|
+
// DATA — DB Schema
|
|
271
|
+
.command(tablesCmd(wrap))
|
|
272
|
+
.command(columnsCmd(wrap))
|
|
273
|
+
.command(relationshipsCmd(wrap))
|
|
274
|
+
// DATA — Shared Code, Transforms, Logs
|
|
275
|
+
.command(includesCmd(wrap))
|
|
276
|
+
.command(importCmd(wrap))
|
|
277
|
+
.command(logsCmd(wrap))
|
|
278
|
+
.command(propertiesCmd(wrap))
|
|
279
|
+
// DATA
|
|
280
|
+
.command(recordsCmd(wrap))
|
|
281
|
+
.command(ticketsCmd(wrap))
|
|
282
|
+
// DEVELOPER
|
|
283
|
+
.command(evalCmd(wrap))
|
|
284
|
+
.command(restCmd(wrap))
|
|
285
|
+
.command(skillCmd(wrap))
|
|
286
|
+
.command(versionCmd(wrap))
|
|
287
|
+
// Legacy
|
|
288
|
+
.command(devCmd(wrap))
|
|
289
|
+
.demandCommand(1, 'You must specify a command')
|
|
290
|
+
.help('help', 'Show help')
|
|
291
|
+
.version(false)
|
|
292
|
+
.strictCommands()
|
|
293
|
+
.strictOptions(false)
|
|
294
|
+
.fail((msg, err) => {
|
|
295
|
+
if (err) throw err;
|
|
296
|
+
if (msg === 'You must specify a command') {
|
|
297
|
+
const raw = process.argv.slice(2);
|
|
298
|
+
const hasLoneProfile = raw.some((a, i, arr) => {
|
|
299
|
+
if (a !== '--profile' && a !== '-p') return false;
|
|
300
|
+
const next = arr[i + 1];
|
|
301
|
+
return !next || next.startsWith('-');
|
|
302
|
+
});
|
|
303
|
+
if (hasLoneProfile) {
|
|
304
|
+
const cfg = loadConfig();
|
|
305
|
+
const activeName = cfg.activeProfile || cfg.defaultProfile;
|
|
306
|
+
process.stdout.write('Profiles:\n');
|
|
307
|
+
for (const [name, p] of Object.entries(cfg.profiles || {})) {
|
|
308
|
+
const isActive = name === activeName;
|
|
309
|
+
process.stdout.write(` ${isActive ? '*' : ' '} ${name} → ${p.instance_url || '(no url)'}\n`);
|
|
310
|
+
}
|
|
311
|
+
process.exit(0);
|
|
205
312
|
}
|
|
206
|
-
process.stdout.write('\
|
|
207
|
-
process.stdout.write(' jsn profiles use <name>\n');
|
|
313
|
+
process.stdout.write(renderHelp() + '\n');
|
|
208
314
|
process.exit(0);
|
|
209
315
|
}
|
|
210
|
-
process.
|
|
211
|
-
process.exit(
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
316
|
+
process.stderr.write(msg + '\n');
|
|
317
|
+
process.exit(1);
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Run if invoked directly (bin/jsn.js calls cli.parse())
|
|
322
|
+
export const cli = buildCLI();
|