@jacebenson/jsn 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,398 @@
1
+ # JSN - ServiceNow CLI
2
+
3
+ A command-line interface for ServiceNow that follows the Unix philosophy: simple, composable, and scriptable.
4
+
5
+ ## Installation
6
+
7
+ ### npm (Cross-platform — recommended for Windows)
8
+
9
+ ```bash
10
+ npm install -g @jacebenson/jsn
11
+ ```
12
+
13
+ Works on macOS, Linux, and Windows. The correct binary for your platform is downloaded automatically during install.
14
+
15
+ ### Download Binary
16
+
17
+ ```bash
18
+ # Download the latest release
19
+ curl -L https://github.com/jacebenson/jsn/releases/latest/download/jsn-linux-amd64 -o jsn
20
+ chmod +x jsn
21
+ sudo mv jsn /usr/local/bin/
22
+ ```
23
+
24
+ ### Go Install
25
+
26
+ ```bash
27
+ go install github.com/jacebenson/jsn/cmd/jsn@latest
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ### 1. Setup
33
+
34
+ Run the interactive setup to configure your first ServiceNow instance:
35
+
36
+ ```bash
37
+ jsn setup
38
+ ```
39
+
40
+ This will:
41
+ 1. Ask for your ServiceNow instance URL
42
+ 2. Open a browser for OAuth authentication
43
+ 3. Set the instance as your default
44
+
45
+ ### 2. Verify Authentication
46
+
47
+ ```bash
48
+ jsn auth status
49
+ ```
50
+
51
+ ### 3. Start Using
52
+
53
+ ```bash
54
+ # List all incidents
55
+ jsn incidents
56
+
57
+ # Show a specific incident
58
+ jsn incidents INC0010001
59
+
60
+ # Create a new incident
61
+ jsn incidents create --description "Server down" --priority 1
62
+
63
+ # List change requests
64
+ jsn changes
65
+
66
+ # Query any table
67
+ jsn records list --table incident --query "priority=1^active=true"
68
+ ```
69
+
70
+ ## Configuration
71
+
72
+ JSN uses a layered configuration system:
73
+
74
+ | Source | Priority | Description |
75
+ |--------|----------|-------------|
76
+ | Flags | Highest | `--instance`, `--profile`, `--format` |
77
+ | Environment | High | `SERVICENOW_INSTANCE_URL`, `SERVICENOW_FORMAT` |
78
+ | Local config | Medium | `./.servicenow/config.json` |
79
+ | Global config | Low | `~/.config/servicenow/config.json` |
80
+ | Defaults | Lowest | Built-in defaults |
81
+
82
+ ### Profiles
83
+
84
+ Work with multiple ServiceNow instances using profiles:
85
+
86
+ ```bash
87
+ # Login to a new instance
88
+ jsn auth login https://dev12345.service-now.com
89
+
90
+ # List all profiles
91
+ jsn profiles list
92
+
93
+ # Switch to a different profile
94
+ jsn profiles use dev12345
95
+
96
+ # Show current profile
97
+ jsn profiles show
98
+ ```
99
+
100
+ ## Commands
101
+
102
+ ### Work Commands (Day-to-day operations)
103
+
104
+ | Command | Aliases | Description |
105
+ |---------|---------|-------------|
106
+ | `incidents` | `incident`, `inc` | Manage IT incidents |
107
+ | `changes` | `change`, `chg` | Manage change requests |
108
+ | `requests` | `request`, `req`, `ritm` | Manage service catalog requests |
109
+ | `tasks` | `task`, `sctask` | Manage service catalog tasks |
110
+ | `users` | `user` | Manage users |
111
+ | `groups` | `group` | Manage user groups |
112
+ | `records` | - | Generic Table API access |
113
+
114
+ ### Dev Commands (Development artifacts)
115
+
116
+ | Category | Command | Description |
117
+ |----------|---------|-------------|
118
+ | **Automations** | `dev flows` | Manage Flow Designer flows |
119
+ | | `dev actions` | Manage action definitions |
120
+ | **Scripts** | `dev includes` | Manage script includes |
121
+ | | `dev rules` | Manage business rules |
122
+ | | `dev clientscripts` | Manage client scripts |
123
+ | | `dev uiactions` | Manage UI actions |
124
+ | | `dev uipolicies` | Manage UI policies |
125
+ | **Data** | `dev tables` | View table definitions |
126
+ | | `dev columns` | Manage column definitions |
127
+ | | `dev import` | Manage import sets |
128
+ | **Security** | `dev acls` | Manage access controls |
129
+ | | `dev roles` | Manage roles |
130
+ | **Platform** | `dev updatesets` | Manage update sets |
131
+ | | `dev scopes` | Manage application scopes |
132
+ | | `dev properties` | Manage system properties |
133
+ | | `dev logs` | Query system logs |
134
+ | | `dev rest` | Raw REST API calls |
135
+ | | `dev eval` | Execute background scripts |
136
+
137
+ ## Usage Examples
138
+
139
+ ### Incidents
140
+
141
+ ```bash
142
+ # List all incidents
143
+ jsn incidents
144
+
145
+ # List critical incidents
146
+ jsn incidents list --query "priority=1"
147
+
148
+ # Show specific incident
149
+ jsn incidents INC0010001
150
+
151
+ # Create incident
152
+ jsn incidents create --description "Server down" --priority 1
153
+
154
+ # Update incident
155
+ jsn incidents update INC0010001 --data '{"state": "6"}'
156
+
157
+ # Delete incident
158
+ jsn incidents delete INC0010001
159
+ ```
160
+
161
+ ### Changes
162
+
163
+ ```bash
164
+ # List all change requests
165
+ jsn changes
166
+
167
+ # List high-risk changes
168
+ jsn changes list --query "risk=high"
169
+
170
+ # Create change request
171
+ jsn changes create --description "Deploy feature" --risk medium
172
+
173
+ # Update change
174
+ jsn changes update CHG0010001 --data '{"state": "3"}'
175
+
176
+ # Delete change
177
+ jsn changes delete CHG0010001
178
+ ```
179
+
180
+ ### Development Artifacts
181
+
182
+ ```bash
183
+ # AUTOMATIONS
184
+ # List Flow Designer flows
185
+ jsn dev flows
186
+
187
+ # List action definitions
188
+ jsn dev actions
189
+
190
+ # SCRIPTS
191
+ # List script includes
192
+ jsn dev includes
193
+
194
+ # Get a specific script include
195
+ jsn dev includes MyScriptInclude
196
+
197
+ # List business rules
198
+ jsn dev rules
199
+
200
+ # List client scripts
201
+ jsn dev clientscripts
202
+
203
+ # List UI actions
204
+ jsn dev uiactions
205
+
206
+ # List UI policies
207
+ jsn dev uipolicies
208
+
209
+ # DATA
210
+ # View table definition
211
+ jsn dev tables incident
212
+
213
+ # List table columns
214
+ jsn dev columns --table incident
215
+
216
+ # SECURITY
217
+ # List access controls (ACLs)
218
+ jsn dev acls
219
+
220
+ # List roles
221
+ jsn dev roles
222
+
223
+ # PLATFORM
224
+ # List update sets
225
+ jsn dev updatesets
226
+
227
+ # Set current update set
228
+ jsn dev updatesets set "My Update Set"
229
+
230
+ # List application scopes
231
+ jsn dev scopes
232
+
233
+ # Query system properties
234
+ jsn dev properties
235
+
236
+ # Query system logs
237
+ jsn dev logs --level error
238
+ jsn dev logs --source "Business Rule" --level warn
239
+
240
+ # Execute background script
241
+ jsn dev eval "gs.info('Hello World')"
242
+ ```
243
+
244
+ ### Generic Table API
245
+
246
+ ```bash
247
+ # List any table
248
+ jsn records list --table incident --limit 10
249
+
250
+ # Query with encoded query
251
+ jsn records list --table incident --query "priority=1^active=true"
252
+
253
+ # Show specific columns
254
+ jsn records list --table incident --columns "number,short_description,priority"
255
+
256
+ # Get a record by sys_id
257
+ jsn records get --table incident --sys-id abc123
258
+
259
+ # Create a record
260
+ jsn records create --table incident --data '{"short_description": "Test"}'
261
+
262
+ # Update a record
263
+ jsn records update --table incident --sys-id abc123 --data '{"priority": "1"}'
264
+
265
+ # Delete a record
266
+ jsn records delete --table incident --sys-id abc123
267
+ ```
268
+
269
+ ## Output Formats
270
+
271
+ JSN supports multiple output formats:
272
+
273
+ | Format | Flag | Description |
274
+ |--------|------|-------------|
275
+ | Auto (default) | `--format=auto` | JSON for pipes, styled for TTY |
276
+ | JSON | `--json` or `--format=json` | Machine-readable JSON |
277
+ | Styled | `--styled` | ANSI-styled tables (for humans) |
278
+ | Markdown | `--markdown` | Markdown tables |
279
+ | Quiet | `--quiet` or `-q` | Data only, no envelope |
280
+
281
+ ```bash
282
+ # JSON output
283
+ jsn incidents --json
284
+
285
+ # Styled table output
286
+ jsn incidents --styled
287
+
288
+ # Markdown output for documentation
289
+ jsn incidents --markdown
290
+
291
+ # Quiet mode for piping
292
+ jsn incidents -q | jq '.[].number'
293
+ ```
294
+
295
+ ## Authentication
296
+
297
+ JSN uses OAuth 2.0 with PKCE for secure authentication:
298
+
299
+ ```bash
300
+ # Login to an instance
301
+ jsn auth login https://dev12345.service-now.com
302
+
303
+ # Check authentication status
304
+ jsn auth status
305
+
306
+ # Logout
307
+ jsn auth logout
308
+ ```
309
+
310
+ Credentials are securely stored in your OS keychain (or file fallback at `~/.config/servicenow/credentials/`).
311
+
312
+ ## Environment Variables
313
+
314
+ | Variable | Description |
315
+ |----------|-------------|
316
+ | `SERVICENOW_INSTANCE_URL` | Default instance URL |
317
+ | `SERVICENOW_FORMAT` | Default output format |
318
+ | `SERVICENOW_OAUTH_TOKEN` | OAuth token (for CI/CD) |
319
+
320
+ ## CI/CD Integration
321
+
322
+ For automated environments, use the OAuth token:
323
+
324
+ ```bash
325
+ export SERVICENOW_INSTANCE_URL="https://dev12345.service-now.com"
326
+ export SERVICENOW_OAUTH_TOKEN="your-oauth-token"
327
+
328
+ # Now run commands without interactive auth
329
+ jsn incidents list
330
+ ```
331
+
332
+ ## Shell Completion
333
+
334
+ ```bash
335
+ # Bash
336
+ source <(jsn completion bash)
337
+
338
+ # Zsh
339
+ source <(jsn completion zsh)
340
+
341
+ # Fish
342
+ jsn completion fish | source
343
+ ```
344
+
345
+ ## Getting Help
346
+
347
+ ```bash
348
+ # General help
349
+ jsn --help
350
+
351
+ # Command help
352
+ jsn incidents --help
353
+
354
+ # Subcommand help
355
+ jsn incidents create --help
356
+ ```
357
+
358
+ ## Troubleshooting
359
+
360
+ ### Not authenticated
361
+
362
+ ```bash
363
+ ⚠️ Not authenticated to https://dev12345.service-now.com
364
+
365
+ To get started, run:
366
+ jsn setup # Interactive setup
367
+ jsn auth login # Login to instance
368
+ ```
369
+
370
+ **Solution**: Run `jsn setup` or `jsn auth login <instance>`
371
+
372
+ ### Instance URL required
373
+
374
+ ```bash
375
+ Error (usage): Instance URL required. Set via --instance flag, SERVICENOW_INSTANCE_URL env, or config file.
376
+ ```
377
+
378
+ **Solution**: Set the instance with one of:
379
+ - `jsn setup`
380
+ - `jsn auth login <instance>`
381
+ - `--instance` flag
382
+ - `SERVICENOW_INSTANCE_URL` environment variable
383
+
384
+ ## Contributing
385
+
386
+ 1. Fork the repository
387
+ 2. Create a feature branch
388
+ 3. Make your changes
389
+ 4. Add tests
390
+ 5. Submit a pull request
391
+
392
+ ## License
393
+
394
+ MIT License - see LICENSE file for details
395
+
396
+ ## Acknowledgments
397
+
398
+ This project follows the architectural patterns from [basecamp-cli](https://github.com/basecamp/basecamp-cli).
package/bin/jsn.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { cli } from '../src/cli.js';
4
+
5
+ cli.parse();
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@jacebenson/jsn",
3
+ "version": "0.0.3",
4
+ "description": "A command-line interface for ServiceNow that follows the Unix philosophy: simple, composable, and scriptable.",
5
+ "type": "module",
6
+ "bin": {
7
+ "jsn": "bin/jsn.js"
8
+ },
9
+ "main": "src/cli.js",
10
+ "scripts": {
11
+ "test": "node --test $(find test -name '*.test.js')",
12
+ "lint": "npx eslint src/ bin/ test/",
13
+ "start": "node bin/jsn.js",
14
+ "tag-reminder": "node scripts/tag-reminder.js",
15
+ "release": "node scripts/release.js"
16
+ },
17
+ "keywords": [
18
+ "servicenow",
19
+ "cli",
20
+ "rest-api",
21
+ "automation"
22
+ ],
23
+ "author": "Jace Benson",
24
+ "license": "MIT",
25
+ "engines": {
26
+ "node": ">=18.0.0"
27
+ },
28
+ "dependencies": {
29
+ "chalk": "^5.4.1",
30
+ "cli-table3": "^0.6.5",
31
+ "yargs": "^17.7.2"
32
+ },
33
+ "devDependencies": {
34
+ "@eslint/js": "^10.0.1",
35
+ "@inquirer/prompts": "^7.5.1",
36
+ "globals": "^17.6.0"
37
+ },
38
+ "files": [
39
+ "bin/jsn.js",
40
+ "src/",
41
+ "README.md",
42
+ "LICENSE"
43
+ ]
44
+ }
package/src/app.js ADDED
@@ -0,0 +1,157 @@
1
+ // App context: bundles config, auth, SDK, output, and runtime context
2
+
3
+ import { AuthManager } from './auth.js';
4
+ import { SDKClient } from './sdk.js';
5
+ import { OutputWriter, FormatAuto, FormatJSON, FormatMarkdown, FormatQuiet, FormatStyled } from './output.js';
6
+ import { getEffectiveInstance } from './config.js';
7
+ import { extractProfileName } from './helpers.js';
8
+ import { getCurrentUser, getCurrentApplication, getCurrentUpdateSet } from './context.js';
9
+ import { errUsage, errAuth } from './errors.js';
10
+ import process from 'node:process';
11
+
12
+
13
+ export class App {
14
+ constructor(cfg) {
15
+ this.config = cfg;
16
+ this.auth = new AuthManager(this);
17
+ this.output = new OutputWriter({ format: resolveFormat(cfg.format) });
18
+ this.sdk = null;
19
+
20
+ const instance = getEffectiveInstance(cfg);
21
+ if (instance) {
22
+ this.sdk = new SDKClient(instance, this.auth);
23
+ }
24
+
25
+ this.context = {
26
+ profileName: '',
27
+ username: '',
28
+ scope: '',
29
+ updateSet: '',
30
+ };
31
+
32
+ this.loadContext();
33
+ }
34
+
35
+ loadContext() {
36
+ const instance = getEffectiveInstance(this.config);
37
+ if (!instance) return;
38
+ this.context.profileName = extractProfileName(instance);
39
+ for (const [name, profile] of Object.entries(this.config.profiles || {})) {
40
+ if (profile.instance_url === instance) {
41
+ this.context.profileName = name;
42
+ this.context.username = profile.username || '';
43
+ break;
44
+ }
45
+ }
46
+ }
47
+
48
+ getEffectiveInstance() {
49
+ return getEffectiveInstance(this.config);
50
+ }
51
+
52
+ async printContextHeader() {
53
+ if (!this.getEffectiveInstance() || !this.sdk) return;
54
+ if (process.env.JSN_NO_HEADER) return;
55
+ if (this.output.getFormat() === FormatJSON || this.output.getFormat() === FormatQuiet) return;
56
+
57
+ let userDisplayName = 'Unknown';
58
+ let userSysID = '';
59
+
60
+ try {
61
+ const user = await getCurrentUser(this.sdk);
62
+ if (user) {
63
+ userDisplayName = user.name || user.user_name;
64
+ userSysID = user.sys_id;
65
+ this.context.username = userDisplayName;
66
+ }
67
+ } catch {
68
+ // ignore
69
+ }
70
+
71
+ let displayUserName = userDisplayName;
72
+ if (displayUserName.length > 10) {
73
+ displayUserName = displayUserName.slice(0, 6) + '...';
74
+ }
75
+
76
+ let scope = 'global';
77
+ if (userSysID) {
78
+ try {
79
+ const app = await getCurrentApplication(this.sdk, userSysID);
80
+ if (app && app.scope) scope = app.scope;
81
+ } catch {
82
+ // ignore
83
+ }
84
+ }
85
+ this.context.scope = scope;
86
+
87
+ let updateSet = 'Default';
88
+ let updateSetSysID = '';
89
+ if (userSysID) {
90
+ try {
91
+ const us = await getCurrentUpdateSet(this.sdk, userSysID);
92
+ if (us && us.name && us.name !== '-') {
93
+ updateSet = us.name;
94
+ updateSetSysID = us.sys_id;
95
+ }
96
+ } catch {
97
+ // ignore
98
+ }
99
+ }
100
+ this.context.updateSet = updateSet;
101
+
102
+ const instance = this.getEffectiveInstance();
103
+ const instanceLink = instance;
104
+ const userLink = `${instance}/sys_user_list.do?sysparm_query=sys_id=${userSysID}`;
105
+ const scopeLink = `${instance}/sys_scope.do?sysparm_query=scope=${scope}`;
106
+ const updateSetLink = updateSetSysID
107
+ ? `${instance}/sys_update_set.do?sys_id=${updateSetSysID}`
108
+ : `${instance}/sys_update_set_list.do`;
109
+
110
+ const scopeFormatted = `[${scope}]`;
111
+
112
+ process.stderr.write('# Use `jsn updateset use` or `jsn scope use` to change scope/updateset\n');
113
+ process.stderr.write('PROFILE USER [SCOPE] UPDATE SET\n');
114
+
115
+ const profileStr = `]8;;${instanceLink}\x07${String(this.context.profileName).padEnd(9)}]8;;\x07`;
116
+ const userStr = `]8;;${userLink}\x07${String(displayUserName).padEnd(9)}]8;;\x07`;
117
+ const scopeStr = `]8;;${scopeLink}\x07${String(scopeFormatted).padEnd(17)}]8;;\x07`;
118
+ const updateSetStr = `]8;;${updateSetLink}\x07${updateSet}]8;;\x07`;
119
+
120
+ process.stderr.write(`${profileStr} ${userStr} ${scopeStr} ${updateSetStr}\n\n`);
121
+ }
122
+
123
+ ok(data, opts = {}) {
124
+ this.output.ok(data, opts);
125
+ }
126
+
127
+ err(error) {
128
+ this.output.err(error);
129
+ }
130
+
131
+ isInteractive() {
132
+ return process.stdout.isTTY === true;
133
+ }
134
+
135
+ requireInstance() {
136
+ if (!this.getEffectiveInstance()) {
137
+ throw errUsage('Instance URL required. Set via --instance flag, SERVICENOW_INSTANCE_URL env, or config file.');
138
+ }
139
+ }
140
+
141
+ requireAuth() {
142
+ if (!this.auth.isAuthenticated()) {
143
+ throw errAuth('Not authenticated');
144
+ }
145
+ }
146
+ }
147
+
148
+ function resolveFormat(fmt) {
149
+ switch (fmt) {
150
+ case 'json': return FormatJSON;
151
+ case 'markdown':
152
+ case 'md': return FormatMarkdown;
153
+ case 'quiet': return FormatQuiet;
154
+ case 'styled': return FormatStyled;
155
+ default: return FormatAuto;
156
+ }
157
+ }