@jacebenson/jsn 4.3.13-dev.72.1 → 4.3.13-dev.73.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 +13 -16
- package/src/auth.js +2 -0
- package/src/commands/auth.js +29 -5
- package/src/commands/records.js +11 -2
- package/src/commands/rest.js +14 -1
- package/src/commands/skill.js +10 -10
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@ compatibility: |
|
|
|
14
14
|
Works with Claude Code, OpenCode, Cursor, and agentskills-compatible tools.
|
|
15
15
|
metadata:
|
|
16
16
|
author: jacebenson
|
|
17
|
-
version: "4.3.
|
|
17
|
+
version: "4.3.9"
|
|
18
18
|
repository: https://github.com/jacebenson/jsn
|
|
19
19
|
---
|
|
20
20
|
|
|
@@ -43,10 +43,9 @@ All `list` subcommands use a paginated interactive picker in TTY mode:
|
|
|
43
43
|
1. **Output modes** — `--json` when parsing data; text when presenting to humans
|
|
44
44
|
2. **Use sys_id for updates** — All update/delete operations require sys_id
|
|
45
45
|
3. **Check auth first** — Run `jsn auth status` before operations
|
|
46
|
-
4. **
|
|
47
|
-
5. **
|
|
48
|
-
6. **
|
|
49
|
-
7. **CONFIRM before destructive operations** — Show what will be created/updated/deleted and ask for approval.
|
|
46
|
+
4. **Verify the intended instance** — Use `--profile <name>` for explicit targeting; never assume the default is right. `jsn auth switch <name>` changes the default.
|
|
47
|
+
5. **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.
|
|
48
|
+
6. **CONFIRM before destructive operations** — Show what will be created/updated/deleted and ask for approval.
|
|
50
49
|
|
|
51
50
|
## Command Hierarchy
|
|
52
51
|
|
|
@@ -113,6 +112,12 @@ excludes that history row. Use raw values, not display labels or other skip reas
|
|
|
113
112
|
|
|
114
113
|
### Complete an update set
|
|
115
114
|
|
|
115
|
+
Before development changes, identify the intended application scope and update
|
|
116
|
+
set. Keep related changes together, use the appropriate update-set or application
|
|
117
|
+
deployment mechanism, and report where changes were captured. Not all changes
|
|
118
|
+
are update-set tracked. Only complete a set when the human explicitly asks,
|
|
119
|
+
including sets you created; completing the current set returns selection to Default.
|
|
120
|
+
|
|
116
121
|
```bash
|
|
117
122
|
jsn updatesets complete "My Feature" --notes "Fixed validation" --force --json
|
|
118
123
|
```
|
|
@@ -127,18 +132,10 @@ child sets when the parent completes; their descriptions are not replaced.
|
|
|
127
132
|
Both selected-set Description and state are read back before success. On
|
|
128
133
|
verification failure, inspect the set before retrying.
|
|
129
134
|
|
|
130
|
-
### Table and raw REST
|
|
131
|
-
|
|
132
|
-
```bash
|
|
133
|
-
# Query a table with the normal records command
|
|
134
|
-
jsn records list --table incident --query "active=true" --limit 5 --json
|
|
135
|
+
### Table and raw REST discovery
|
|
135
136
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
# Raw endpoint access when no specific command fits
|
|
140
|
-
jsn rest "/api/now/table/incident?sysparm_limit=3" --json
|
|
141
|
-
```
|
|
137
|
+
Use `jsn records list --help`, `jsn records get --help`, and `jsn rest --help`
|
|
138
|
+
for syntax and examples. REST is blocked on read-only connections, including GET.
|
|
142
139
|
|
|
143
140
|
### `jsn docs` — offline documentation search
|
|
144
141
|
|
package/src/auth.js
CHANGED
|
@@ -612,7 +612,9 @@ export class AuthManager {
|
|
|
612
612
|
if (!instance) {
|
|
613
613
|
throw errAuth('No instance specified');
|
|
614
614
|
}
|
|
615
|
+
const existed = !!this.credentialStore.load(instance, username);
|
|
615
616
|
this.credentialStore.delete(instance, username);
|
|
617
|
+
return existed && !this.credentialStore.load(instance, username);
|
|
616
618
|
}
|
|
617
619
|
|
|
618
620
|
/**
|
package/src/commands/auth.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getEffectiveInstance, normalizeInstanceURL, saveConfig, setActiveProfile } from '../config.js';
|
|
2
|
-
import { errUsage, errUsageHint } from '../errors.js';
|
|
2
|
+
import { errUsage, errUsageHint, errConfirmationRequired } from '../errors.js';
|
|
3
3
|
import { canUsePicker, formatCommandList, quoteShellArg } from '../helpers.js';
|
|
4
4
|
import { declareCapabilities } from '../capabilities.js';
|
|
5
5
|
import process from 'node:process';
|
|
@@ -808,10 +808,33 @@ Examples:
|
|
|
808
808
|
}
|
|
809
809
|
const target = resolveTargetProfile(argv, app.config, instanceURL);
|
|
810
810
|
const username = target.profile?.username ?? null;
|
|
811
|
-
if (username
|
|
812
|
-
|
|
811
|
+
if (!username && !canLogoutBareProfile(app, instanceURL, target.profile, !!target.name)) {
|
|
812
|
+
throw errUsageHint('Cannot identify which stored credentials to clear.', 'Choose an unambiguous identity with --profile <name>.');
|
|
813
813
|
}
|
|
814
|
-
|
|
814
|
+
const selector = target.name ? `--profile ${quoteShellArg(target.name)}` : quoteShellArg(instanceURL);
|
|
815
|
+
const rerun = `jsn auth logout ${selector}`;
|
|
816
|
+
const warning = `Clear locally stored credentials for ${target.name ? `profile "${target.name}" at ` : ''}${instanceURL}${username ? ` (user ${username})` : ''}? You will need to authenticate again. The profile is retained; environment credentials and ServiceNow/Chrome sessions are unaffected.`;
|
|
817
|
+
if (!argv.force) {
|
|
818
|
+
if (!canUsePicker(app, argv)) throw errConfirmationRequired(warning, rerun);
|
|
819
|
+
const { confirm } = await import('../prompts.js');
|
|
820
|
+
if (!await confirm({ message: warning, default: false })) {
|
|
821
|
+
app.ok({ logged_out: false, credentials_cleared: false, profile: target.name, instance: instanceURL },
|
|
822
|
+
{ summary: 'Logout cancelled; stored credentials were retained.' });
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
const cleared = app.auth.logout(instanceURL, username) === true;
|
|
827
|
+
const nextActions = cleared ? [
|
|
828
|
+
{ action: 'Authenticate again', cmd: `jsn auth login ${selector}`, description: 'Restore locally stored authentication' },
|
|
829
|
+
...(target.name ? [{ action: 'Remove retained profile', cmd: `jsn auth remove ${quoteShellArg(target.name)}`, description: 'Remove this connection if no longer needed' }] : []),
|
|
830
|
+
] : [];
|
|
831
|
+
app.ok({ logged_out: cleared, credentials_cleared: cleared, profile: target.name, instance: instanceURL,
|
|
832
|
+
profile_retained: !!target.name, environment_credentials_affected: false, remote_session_terminated: false,
|
|
833
|
+
next_actions: nextActions }, {
|
|
834
|
+
summary: cleared ? `✓ Cleared local credentials for ${target.name || instanceURL}. Profile retained; environment credentials and ServiceNow/Chrome sessions are unaffected.`
|
|
835
|
+
: `No stored credentials were cleared for ${target.name || instanceURL}. Environment credentials and ServiceNow/Chrome sessions are unaffected.`,
|
|
836
|
+
breadcrumbs: nextActions,
|
|
837
|
+
});
|
|
815
838
|
}
|
|
816
839
|
|
|
817
840
|
export async function showAuthStatus(argv, app) {
|
|
@@ -1099,7 +1122,8 @@ export function authCmd(wrap) {
|
|
|
1099
1122
|
})
|
|
1100
1123
|
.command({
|
|
1101
1124
|
command: 'logout [instance]',
|
|
1102
|
-
describe: 'Clear
|
|
1125
|
+
describe: 'Clear local credentials after approval; retains the profile. Unnecessary after ordinary operations; does not sign out of ServiceNow/Chrome or clear environment credentials.',
|
|
1126
|
+
builder: sub => sub.option('force', { type: 'boolean', describe: 'Clear credentials only after explicit approval for this exact logout' }),
|
|
1103
1127
|
handler: wrap(logoutProfile),
|
|
1104
1128
|
})
|
|
1105
1129
|
.command({
|
package/src/commands/records.js
CHANGED
|
@@ -83,7 +83,15 @@ export function recordsCmd(wrap) {
|
|
|
83
83
|
.option('columns', { alias: ['c', 'fields'], type: 'string', describe: 'Comma-separated columns (e.g. "number,short_description")' })
|
|
84
84
|
.option('limit', { type: 'number', default: 20, describe: 'Max records' })
|
|
85
85
|
.option('offset', { type: 'number', default: 0, describe: 'Offset' })
|
|
86
|
-
.option('count', { type: 'boolean', default: true, describe: 'Include the total-matching count (use --no-count to opt out)' })
|
|
86
|
+
.option('count', { type: 'boolean', default: true, describe: 'Include the total-matching count (use --no-count to opt out)' })
|
|
87
|
+
.epilogue([
|
|
88
|
+
'In an interactive terminal with automatic output, browse a picker (10 visible items).',
|
|
89
|
+
'Use arrow keys to browse, type to search sys_id on the server, scroll to load more,',
|
|
90
|
+
'and press Enter to select or Esc to cancel. --sys-id skips the picker.',
|
|
91
|
+
'Use --json for noninteractive structured output; pipes and explicit formats also skip the picker.',
|
|
92
|
+
'Example: jsn records list --table incident --query "active=true" --limit 5 --json',
|
|
93
|
+
'Discover fields with: jsn columns --table incident',
|
|
94
|
+
].join('\n')),
|
|
87
95
|
handler: wrap(async (argv, app) => {
|
|
88
96
|
const table = argv.table;
|
|
89
97
|
const columns = argv.columns ? argv.columns.split(',') : getDefaultColumns(table);
|
|
@@ -187,7 +195,8 @@ export function recordsCmd(wrap) {
|
|
|
187
195
|
.option('table', { type: 'string', demandOption: true, describe: 'Table name' })
|
|
188
196
|
.option('sys-id', { type: 'string', demandOption: true, describe: 'Record sys_id' })
|
|
189
197
|
.option('columns', { alias: ['c', 'fields'], type: 'string', describe: 'Comma-separated columns (e.g. "number,short_description")' })
|
|
190
|
-
.option('attachments', { type: 'boolean', default: false, describe: 'Also list the record\'s attachments' })
|
|
198
|
+
.option('attachments', { type: 'boolean', default: false, describe: 'Also list the record\'s attachments' })
|
|
199
|
+
.epilogue('Example: jsn records get --table incident --sys-id <sys_id> --json'),
|
|
191
200
|
handler: wrap(async (argv, app) => {
|
|
192
201
|
assertSafeExactMatch(argv['sys-id']);
|
|
193
202
|
const params = new URLSearchParams();
|
package/src/commands/rest.js
CHANGED
|
@@ -10,6 +10,10 @@ export function restCmd(wrap) {
|
|
|
10
10
|
describe: 'Make raw REST API calls',
|
|
11
11
|
builder: (yargs) => {
|
|
12
12
|
return yargs
|
|
13
|
+
.positional('endpoint', {
|
|
14
|
+
type: 'string',
|
|
15
|
+
describe: 'API path, optionally including query parameters; with --table, a record sys_id',
|
|
16
|
+
})
|
|
13
17
|
.option('method', {
|
|
14
18
|
alias: 'X',
|
|
15
19
|
type: 'string',
|
|
@@ -34,7 +38,16 @@ export function restCmd(wrap) {
|
|
|
34
38
|
.option('query', {
|
|
35
39
|
type: 'string',
|
|
36
40
|
describe: 'Query parameters string',
|
|
37
|
-
})
|
|
41
|
+
})
|
|
42
|
+
.epilogue([
|
|
43
|
+
'Prefer a specific jsn command or records for normal table operations.',
|
|
44
|
+
'Raw REST is blocked on read-only profiles for every method, including GET.',
|
|
45
|
+
'Examples:',
|
|
46
|
+
' jsn rest "/api/now/table/incident?sysparm_limit=3" --json',
|
|
47
|
+
' jsn rest --table incident --query "sysparm_limit=3" --json',
|
|
48
|
+
' jsn rest /stats.do --raw',
|
|
49
|
+
'Use either query parameters in the endpoint or --query, rather than both.',
|
|
50
|
+
].join('\n'));
|
|
38
51
|
},
|
|
39
52
|
handler: wrap(async (argv, app) => {
|
|
40
53
|
const validMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'];
|
package/src/commands/skill.js
CHANGED
|
@@ -119,9 +119,8 @@ function saveSkillConfig(updates) {
|
|
|
119
119
|
fs.writeFileSync(globalConfigPath(), JSON.stringify(cfg, null, 2), { mode: 0o600 });
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
function recordSkillInstall(targetPath) {
|
|
123
|
-
const
|
|
124
|
-
const version = extractVersion(bundled) || 'unknown';
|
|
122
|
+
function recordSkillInstall(targetPath, content) {
|
|
123
|
+
const version = extractVersion(content) || 'unknown';
|
|
125
124
|
saveSkillConfig({
|
|
126
125
|
skillLocation: targetPath,
|
|
127
126
|
skillVersion: version,
|
|
@@ -260,7 +259,7 @@ export function skillCmd(wrap) {
|
|
|
260
259
|
})
|
|
261
260
|
.command({
|
|
262
261
|
command: 'install [dir]',
|
|
263
|
-
describe: '
|
|
262
|
+
describe: 'Install the bundled skill matched to this CLI version',
|
|
264
263
|
builder: (y) => y
|
|
265
264
|
.positional('dir', {
|
|
266
265
|
type: 'string',
|
|
@@ -271,9 +270,10 @@ export function skillCmd(wrap) {
|
|
|
271
270
|
describe: 'Target agent(s): hermes, copilot, vscode, claude, cursor, agents, opencode, openclaw, codex, or "all" (comma-separated). Omit for interactive picker.',
|
|
272
271
|
}),
|
|
273
272
|
handler: wrap(async (argv, app) => {
|
|
274
|
-
const
|
|
275
|
-
if (!
|
|
276
|
-
|
|
273
|
+
const content = readBundledSkill();
|
|
274
|
+
if (!content) {
|
|
275
|
+
throw new Error('Skill file not found in package (expected at skills/servicenow/SKILL.md). Reinstall jsn to restore it.');
|
|
276
|
+
}
|
|
277
277
|
|
|
278
278
|
// Resolve target directories
|
|
279
279
|
let targets = [];
|
|
@@ -389,7 +389,7 @@ export function skillCmd(wrap) {
|
|
|
389
389
|
}
|
|
390
390
|
|
|
391
391
|
// Record install location so checkSkill knows where to look
|
|
392
|
-
recordSkillInstall(targets[0].path);
|
|
392
|
+
recordSkillInstall(targets[0].path, content);
|
|
393
393
|
|
|
394
394
|
const installed = targets.reduce((acc, t) => { acc[t.name] = t.path; return acc; }, {});
|
|
395
395
|
const summary = targets.length === 1
|
|
@@ -399,7 +399,7 @@ export function skillCmd(wrap) {
|
|
|
399
399
|
const okOpts = {
|
|
400
400
|
summary,
|
|
401
401
|
breadcrumbs: [
|
|
402
|
-
{ action: 'reinstall', cmd: 'jsn skill install', description: '
|
|
402
|
+
{ action: 'reinstall', cmd: 'jsn skill install', description: 'Reinstall the bundled skill to default target' },
|
|
403
403
|
],
|
|
404
404
|
};
|
|
405
405
|
|
|
@@ -407,7 +407,7 @@ export function skillCmd(wrap) {
|
|
|
407
407
|
okOpts.notice = 'Installed to multiple agents. Restart or reload your agent to pick up the skill.';
|
|
408
408
|
}
|
|
409
409
|
|
|
410
|
-
app.ok({ installed, from:
|
|
410
|
+
app.ok({ installed, from: SKILL_REPO_PATH }, okOpts);
|
|
411
411
|
}),
|
|
412
412
|
});
|
|
413
413
|
},
|