@jacebenson/jsn 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -7
- package/package.json +1 -1
- package/src/commands/_ticket.js +1 -1
- package/src/commands/dev/_generic.js +3 -1
- package/src/commands/dev/flows.js +1 -1
- package/src/commands/dev/logs.js +1 -1
- package/src/commands/dev/scopes.js +1 -1
- package/src/commands/dev/updatesets.js +1 -1
- package/src/commands/groupmembers.js +1 -1
- package/src/commands/grouproles.js +1 -1
- package/src/commands/groups.js +1 -1
- package/src/commands/records.js +33 -17
- package/src/commands/tickets.js +1 -1
- package/src/commands/users.js +1 -1
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ jsn version --check
|
|
|
32
32
|
|
|
33
33
|
## What's New in v0.1.0
|
|
34
34
|
|
|
35
|
-
Full feature parity
|
|
35
|
+
Full feature parity — 128 tests, lint clean:
|
|
36
36
|
|
|
37
37
|
| Feature | Status |
|
|
38
38
|
|---------|--------|
|
|
@@ -44,7 +44,7 @@ Full feature parity with the original Go implementation — 128 tests, lint clea
|
|
|
44
44
|
| Group management (CRUD) | ✅ |
|
|
45
45
|
| Group memberships and roles | ✅ |
|
|
46
46
|
| Generic Table API (`jsn records`) | ✅ |
|
|
47
|
-
| OAuth PKCE with keychain (
|
|
47
|
+
| OAuth PKCE with keychain (shared auth store) | ✅ |
|
|
48
48
|
| Bot/CI/CD auth flags (`--code`, `--print-url`) | ✅ |
|
|
49
49
|
| Dev commands: flows, actions, includes, rules, ACLs, etc. | ✅ |
|
|
50
50
|
| Script execution (`jsn dev eval`) via OAuth session flow | ✅ |
|
|
@@ -211,7 +211,7 @@ jsn incidents -q | jq '.[].number'
|
|
|
211
211
|
|
|
212
212
|
## Authentication
|
|
213
213
|
|
|
214
|
-
OAuth 2.0 with PKCE. Credentials stored in `~/.config/servicenow/credentials
|
|
214
|
+
OAuth 2.0 with PKCE. Credentials stored in `~/.config/servicenow/credentials/`.
|
|
215
215
|
|
|
216
216
|
```bash
|
|
217
217
|
jsn auth login https://dev12345.service-now.com
|
|
@@ -335,10 +335,6 @@ jsn version --check
|
|
|
335
335
|
npm install -g @jacebenson/jsn
|
|
336
336
|
```
|
|
337
337
|
|
|
338
|
-
## Historical Note
|
|
339
|
-
|
|
340
|
-
JSN was originally implemented in Go (on the `main` branch) and migrated to Node.js for broader cross-platform support and simpler installation. As of v0.1.0, the Node.js version has full feature parity. The Go source remains available on the `main` branch for reference.
|
|
341
|
-
|
|
342
338
|
## License
|
|
343
339
|
|
|
344
340
|
MIT License — see LICENSE file for details
|
package/package.json
CHANGED
package/src/commands/_ticket.js
CHANGED
|
@@ -18,7 +18,7 @@ export function buildTicketCommands(table, displayName, alias, defaultColumns, s
|
|
|
18
18
|
describe: `List ${table}`,
|
|
19
19
|
builder: (y) => y
|
|
20
20
|
.option('query', { type: 'string', describe: 'Encoded query (e.g. "nameLIKEincident" or "active=true")' })
|
|
21
|
-
.option('columns', { alias: 'c', type: 'string', describe: 'Comma-separated columns (e.g. "number,short_description")' })
|
|
21
|
+
.option('columns', { alias: ['c', 'fields'], type: 'string', describe: 'Comma-separated columns (e.g. "number,short_description")' })
|
|
22
22
|
.option('limit', { alias: 'l', type: 'number', default: 20, describe: 'Max records' })
|
|
23
23
|
.option('offset', { alias: 'o', type: 'number', default: 0, describe: 'Offset for pagination' }),
|
|
24
24
|
handler: wrap(async (argv, app) => {
|
|
@@ -81,7 +81,7 @@ export function buildDevCmd(name, table, aliases, defaultColumns, wrap, opts = {
|
|
|
81
81
|
describe: `List ${name}`,
|
|
82
82
|
builder: (y) => y
|
|
83
83
|
.option('query', { type: 'string', describe: 'Encoded query (e.g. "nameLIKEincident" or "active=true^priority=1")' })
|
|
84
|
-
.option('columns', { alias: 'c', type: 'string', describe: 'Comma-separated columns (e.g. "name,label,super_class")' })
|
|
84
|
+
.option('columns', { alias: ['c', 'fields'], type: 'string', describe: 'Comma-separated columns (e.g. "name,label,super_class")' })
|
|
85
85
|
.option('limit', { alias: 'l', type: 'number', default: 20, describe: 'Max records' }),
|
|
86
86
|
handler: wrap(async (argv, app) => {
|
|
87
87
|
const query = argv.query || '';
|
|
@@ -251,6 +251,7 @@ export function buildDevCmd(name, table, aliases, defaultColumns, wrap, opts = {
|
|
|
251
251
|
const findParams = new URLSearchParams();
|
|
252
252
|
findParams.set('sysparm_query', `${queryField}=${id}`);
|
|
253
253
|
findParams.set('sysparm_limit', '1');
|
|
254
|
+
findParams.set('sysparm_display_value', 'all');
|
|
254
255
|
const records = await app.sdk.list(table, findParams);
|
|
255
256
|
if (records.length === 0) {
|
|
256
257
|
throw new Error(`${singular} not found: ${id}`);
|
|
@@ -280,6 +281,7 @@ export function buildDevCmd(name, table, aliases, defaultColumns, wrap, opts = {
|
|
|
280
281
|
const findParams = new URLSearchParams();
|
|
281
282
|
findParams.set('sysparm_query', `${queryField}=${id}`);
|
|
282
283
|
findParams.set('sysparm_limit', '1');
|
|
284
|
+
findParams.set('sysparm_display_value', 'all');
|
|
283
285
|
const records = await app.sdk.list(table, findParams);
|
|
284
286
|
if (records.length === 0) {
|
|
285
287
|
throw new Error(`${singular} not found: ${id}`);
|
|
@@ -13,7 +13,7 @@ export function flowsCmd(wrap) {
|
|
|
13
13
|
describe: 'List flows',
|
|
14
14
|
builder: (y) => y
|
|
15
15
|
.option('query', { type: 'string', describe: 'Encoded query (e.g. "nameLIKEincident" or "active=true")' })
|
|
16
|
-
.option('columns', { alias: 'c', type: 'string', describe: 'Comma-separated columns (e.g. "number,short_description")' })
|
|
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: 20, describe: 'Max records' }),
|
|
18
18
|
handler: wrap(async (argv, app) => {
|
|
19
19
|
const columns = argv.columns ? argv.columns.split(',') : ['name', 'active', 'description', 'sys_created_by', 'sys_updated_on'];
|
package/src/commands/dev/logs.js
CHANGED
|
@@ -13,7 +13,7 @@ export function logsCmd(wrap) {
|
|
|
13
13
|
describe: 'List system logs',
|
|
14
14
|
builder: (y) => y
|
|
15
15
|
.option('query', { type: 'string', describe: 'Encoded query (e.g. "nameLIKEincident" or "active=true")' })
|
|
16
|
-
.option('columns', { alias: 'c', type: 'string', describe: 'Comma-separated columns (e.g. "number,short_description")' })
|
|
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
19
|
const columns = argv.columns ? argv.columns.split(',') : ['level', 'message', 'source', 'created'];
|
|
@@ -13,7 +13,7 @@ export function scopesCmd(wrap) {
|
|
|
13
13
|
describe: 'List application scopes',
|
|
14
14
|
builder: (y) => y
|
|
15
15
|
.option('query', { type: 'string', describe: 'Encoded query (e.g. "nameLIKEincident" or "active=true")' })
|
|
16
|
-
.option('columns', { alias: 'c', type: 'string', describe: 'Comma-separated columns (e.g. "number,short_description")' })
|
|
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: 20, describe: 'Max records' }),
|
|
18
18
|
handler: wrap(async (argv, app) => {
|
|
19
19
|
const columns = argv.columns ? argv.columns.split(',') : ['name', 'scope', 'short_description', 'active'];
|
|
@@ -13,7 +13,7 @@ export function updateSetsCmd(wrap) {
|
|
|
13
13
|
describe: 'List update sets',
|
|
14
14
|
builder: (y) => y
|
|
15
15
|
.option('query', { type: 'string', describe: 'Encoded query (e.g. "nameLIKEincident" or "active=true")' })
|
|
16
|
-
.option('columns', { alias: 'c', type: 'string', describe: 'Comma-separated columns (e.g. "number,short_description")' })
|
|
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: 20, describe: 'Max records' }),
|
|
18
18
|
handler: wrap(async (argv, app) => {
|
|
19
19
|
const columns = argv.columns ? argv.columns.split(',') : ['name', 'state', 'application'];
|
|
@@ -13,7 +13,7 @@ export function groupMembersCmd(wrap) {
|
|
|
13
13
|
describe: 'List group members',
|
|
14
14
|
builder: (y) => y
|
|
15
15
|
.option('query', { type: 'string', describe: 'Encoded query (e.g. "nameLIKEincident" or "active=true")' })
|
|
16
|
-
.option('columns', { alias: 'c', type: 'string', describe: 'Comma-separated columns (e.g. "number,short_description")' })
|
|
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: 20, describe: 'Max records' }),
|
|
18
18
|
handler: wrap(async (argv, app) => {
|
|
19
19
|
const columns = argv.columns ? argv.columns.split(',') : ['user.name', 'group.name'];
|
|
@@ -13,7 +13,7 @@ export function groupRolesCmd(wrap) {
|
|
|
13
13
|
describe: 'List group roles',
|
|
14
14
|
builder: (y) => y
|
|
15
15
|
.option('query', { type: 'string', describe: 'Encoded query (e.g. "nameLIKEincident" or "active=true")' })
|
|
16
|
-
.option('columns', { alias: 'c', type: 'string', describe: 'Comma-separated columns (e.g. "number,short_description")' })
|
|
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: 20, describe: 'Max records' }),
|
|
18
18
|
handler: wrap(async (argv, app) => {
|
|
19
19
|
const columns = argv.columns ? argv.columns.split(',') : ['group.name', 'role.name'];
|
package/src/commands/groups.js
CHANGED
|
@@ -13,7 +13,7 @@ export function groupsCmd(wrap) {
|
|
|
13
13
|
describe: 'List groups',
|
|
14
14
|
builder: (y) => y
|
|
15
15
|
.option('query', { type: 'string', describe: 'Encoded query (e.g. "nameLIKEincident" or "active=true")' })
|
|
16
|
-
.option('columns', { alias: 'c', type: 'string', describe: 'Comma-separated columns (e.g. "number,short_description")' })
|
|
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: 20, describe: 'Max records' }),
|
|
18
18
|
handler: wrap(async (argv, app) => {
|
|
19
19
|
const columns = argv.columns ? argv.columns.split(',') : ['name', 'manager', 'email'];
|
package/src/commands/records.js
CHANGED
|
@@ -30,35 +30,44 @@ export function recordsCmd(wrap) {
|
|
|
30
30
|
describe: 'List records from a table',
|
|
31
31
|
builder: (y) => y
|
|
32
32
|
.option('table', { type: 'string', demandOption: true, describe: 'Table name' })
|
|
33
|
+
.option('sys-id', { type: 'string', describe: 'Record sys_id (filters to a single record)' })
|
|
33
34
|
.option('query', { type: 'string', describe: 'Encoded query (e.g. "nameLIKEincident" or "active=true")' })
|
|
34
|
-
.option('columns', { alias: 'c', type: 'string', describe: 'Comma-separated columns (e.g. "number,short_description")' })
|
|
35
|
+
.option('columns', { alias: ['c', 'fields'], type: 'string', describe: 'Comma-separated columns (e.g. "number,short_description")' })
|
|
35
36
|
.option('limit', { type: 'number', default: 20, describe: 'Max records' })
|
|
36
37
|
.option('offset', { type: 'number', default: 0, describe: 'Offset' }),
|
|
37
38
|
handler: wrap(async (argv, app) => {
|
|
38
39
|
const table = argv.table;
|
|
39
40
|
const columns = argv.columns ? argv.columns.split(',') : getDefaultColumns(table);
|
|
40
|
-
|
|
41
|
+
let query = argv.query || '';
|
|
41
42
|
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const cols = getDefaultColumns(table);
|
|
47
|
-
return cols.map(c => `${c}: ${getStringField(r, c) || '-'}`).join(' | ');
|
|
48
|
-
},
|
|
49
|
-
});
|
|
50
|
-
if (picked) {
|
|
51
|
-
picked._context = { instance_url: app.getEffectiveInstance(), table };
|
|
52
|
-
return app.ok(picked, { summary: `Record from ${table}` });
|
|
43
|
+
// If --sys-id is provided, append it to the query
|
|
44
|
+
if (argv['sys-id']) {
|
|
45
|
+
if (query) query += '^';
|
|
46
|
+
query += `sys_id=${argv['sys-id']}`;
|
|
53
47
|
}
|
|
54
48
|
|
|
55
|
-
//
|
|
49
|
+
// Interactive picker (only when no --sys-id filter, otherwise go straight to results)
|
|
50
|
+
if (!argv['sys-id']) {
|
|
51
|
+
const picked = await interactiveList({
|
|
52
|
+
app, table, singular: 'record', columns, limit: argv.limit, query, labelField: 'sys_id',
|
|
53
|
+
formatLabel: r => {
|
|
54
|
+
const cols = getDefaultColumns(table);
|
|
55
|
+
return cols.map(c => `${c}: ${getStringField(r, c) || '-'}`).join(' | ');
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
if (picked) {
|
|
59
|
+
picked._context = { instance_url: app.getEffectiveInstance(), table };
|
|
60
|
+
return app.ok(picked, { summary: `Record from ${table}` });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Text/table fallback (or sys-id direct lookup)
|
|
56
65
|
const params = new URLSearchParams();
|
|
57
|
-
params.set('sysparm_limit', String(argv.limit));
|
|
66
|
+
params.set('sysparm_limit', argv['sys-id'] ? '1' : String(argv.limit));
|
|
58
67
|
params.set('sysparm_offset', String(argv.offset));
|
|
59
68
|
params.set('sysparm_display_value', 'all');
|
|
60
69
|
params.set('sysparm_fields', ['sys_id', ...columns].join(','));
|
|
61
|
-
if (
|
|
70
|
+
if (query) params.set('sysparm_query', query);
|
|
62
71
|
const records = await app.sdk.list(table, params);
|
|
63
72
|
const displayRecords = records.map(r => formatRecordForDisplay(r, columns));
|
|
64
73
|
const breadcrumbs = [
|
|
@@ -66,6 +75,13 @@ export function recordsCmd(wrap) {
|
|
|
66
75
|
{ action: 'filter', cmd: `jsn records list --table ${table} --query "priority=1"`, description: 'Filter: priority 1 only' },
|
|
67
76
|
{ action: 'columns', cmd: `jsn dev columns --table ${table}`, description: 'View available columns' },
|
|
68
77
|
];
|
|
78
|
+
if (argv['sys-id']) {
|
|
79
|
+
breadcrumbs.unshift({
|
|
80
|
+
action: 'get',
|
|
81
|
+
cmd: `jsn records get --table ${table} --sys-id ${argv['sys-id']}`,
|
|
82
|
+
description: 'Get full record details',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
69
85
|
if (records.length === argv.limit) {
|
|
70
86
|
breadcrumbs.push({
|
|
71
87
|
action: 'next',
|
|
@@ -96,7 +112,7 @@ export function recordsCmd(wrap) {
|
|
|
96
112
|
builder: (y) => y
|
|
97
113
|
.option('table', { type: 'string', demandOption: true, describe: 'Table name' })
|
|
98
114
|
.option('sys-id', { type: 'string', demandOption: true, describe: 'Record sys_id' })
|
|
99
|
-
.option('columns', { type: 'string', describe: 'Comma-separated columns (e.g. "number,short_description")' }),
|
|
115
|
+
.option('columns', { alias: ['c', 'fields'], type: 'string', describe: 'Comma-separated columns (e.g. "number,short_description")' }),
|
|
100
116
|
handler: wrap(async (argv, app) => {
|
|
101
117
|
const params = new URLSearchParams();
|
|
102
118
|
params.set('sysparm_query', `sys_id=${argv['sys-id']}`);
|
package/src/commands/tickets.js
CHANGED
|
@@ -13,7 +13,7 @@ export function ticketsCmd(wrap) {
|
|
|
13
13
|
describe: 'List tickets',
|
|
14
14
|
builder: (y) => y
|
|
15
15
|
.option('query', { type: 'string', describe: 'Encoded query (e.g. "active=true^priority=1")' })
|
|
16
|
-
.option('columns', { alias: 'c', type: 'string', describe: 'Comma-separated columns (e.g. "number,short_description,priority")' })
|
|
16
|
+
.option('columns', { alias: ['c', 'fields'], type: 'string', describe: 'Comma-separated columns (e.g. "number,short_description,priority")' })
|
|
17
17
|
.option('limit', { alias: 'l', type: 'number', default: 20, describe: 'Max records' })
|
|
18
18
|
.option('offset', { alias: 'o', type: 'number', default: 0, describe: 'Offset' }),
|
|
19
19
|
handler: wrap(async (argv, app) => {
|
package/src/commands/users.js
CHANGED
|
@@ -13,7 +13,7 @@ export function usersCmd(wrap) {
|
|
|
13
13
|
describe: 'List users',
|
|
14
14
|
builder: (y) => y
|
|
15
15
|
.option('query', { type: 'string', describe: 'Encoded query (e.g. "nameLIKEincident" or "active=true")' })
|
|
16
|
-
.option('columns', { alias: 'c', type: 'string', describe: 'Comma-separated columns (e.g. "number,short_description")' })
|
|
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: 20, describe: 'Max records' }),
|
|
18
18
|
handler: wrap(async (argv, app) => {
|
|
19
19
|
const columns = argv.columns ? argv.columns.split(',') : ['user_name', 'name', 'email', 'active'];
|