@jacebenson/jsn 4.3.13-dev.93.1 → 4.3.13-dev.94.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 +14 -0
- package/package.json +1 -1
- package/src/cli-adapter.js +4 -0
- package/src/cli.js +17 -3
- package/src/command-audience.js +35 -0
- package/src/commands/auth.js +15 -0
- package/src/commands/discover.js +1 -1
- package/src/help.js +2 -0
package/README.md
CHANGED
|
@@ -78,3 +78,17 @@ npm test
|
|
|
78
78
|
## License
|
|
79
79
|
|
|
80
80
|
MIT
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
### Commands for the connected account
|
|
84
|
+
|
|
85
|
+
Login and OAuth refresh check for the current account's `admin` role. Profiles
|
|
86
|
+
without that role show a compact command list focused on tickets, generic
|
|
87
|
+
records, people/groups, documentation, and connection management. If the role
|
|
88
|
+
check is unavailable, the profile also uses compact help; this does not assert
|
|
89
|
+
that the account lacks admin. Existing profiles keep full help until renewed.
|
|
90
|
+
|
|
91
|
+
This applies to root help, root JSON, `discover`, and root shell completion.
|
|
92
|
+
Use `jsn --all-commands --help` or `jsn discover --all-commands --json` to inspect
|
|
93
|
+
every registered command. Explicit command paths remain available and ServiceNow
|
|
94
|
+
ACLs decide access. Reauthenticate (or refresh OAuth) after roles change.
|
package/package.json
CHANGED
package/src/cli-adapter.js
CHANGED
|
@@ -332,6 +332,10 @@ export class LocalCLI {
|
|
|
332
332
|
}
|
|
333
333
|
|
|
334
334
|
_help(node, path = []) {
|
|
335
|
+
if (!path.length && this.rootHelpRenderer) {
|
|
336
|
+
process.stdout.write(this.rootHelpRenderer() + "\n");
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
335
339
|
const lines = [node.usageText || `Usage: ${this.script} ${path.join(' ') || '<command>'}`];
|
|
336
340
|
if (node.definition.describe) lines.push(`\n${node.definition.describe}`);
|
|
337
341
|
if (node.children.length) lines.push('\nCommands:\n' + node.children.filter((n) => n.name !== this.completionCommand).map((n) => ` ${n.definition.command} ${n.definition.describe || ''}`).join('\n'));
|
package/src/cli.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { visibleCommandRegistry } from './command-audience.js';
|
|
1
2
|
// Root CLI using the local yargs-compatible adapter
|
|
2
3
|
|
|
3
4
|
import yargs, { hideBin } from './cli-adapter.js';
|
|
@@ -148,6 +149,7 @@ export function buildCLI() {
|
|
|
148
149
|
},
|
|
149
150
|
])
|
|
150
151
|
.options({
|
|
152
|
+
'all-commands': { type: 'boolean', describe: 'Show the full command list, including admin and developer tools' },
|
|
151
153
|
reason: { type: 'string', describe: 'Optional non-sensitive reason saved in local mutation history' },
|
|
152
154
|
|
|
153
155
|
force: {
|
|
@@ -432,7 +434,7 @@ export function buildCLI() {
|
|
|
432
434
|
.command(skillCmd(wrap))
|
|
433
435
|
.command(docsCmd(wrap))
|
|
434
436
|
.command(versionCmd(wrap))
|
|
435
|
-
.command(discoverCmd(wrap, () => cliInstance.commandRegistry()))
|
|
437
|
+
.command(discoverCmd(wrap, (argv) => visibleCommandRegistry(cliInstance.commandRegistry(), app.config, argv)))
|
|
436
438
|
.demandCommand(1, 'You must specify a command')
|
|
437
439
|
// Completion plumbing under a hidden command name, so the explicit
|
|
438
440
|
// 'completion' command below owns the UX. (#176)
|
|
@@ -452,6 +454,12 @@ export function buildCLI() {
|
|
|
452
454
|
if (current && !current.startsWith('-') && depth === 0) {
|
|
453
455
|
out = [...new Set([...out, ...rootCommands])];
|
|
454
456
|
}
|
|
457
|
+
const options = cliInstance.parseGlobalOptions(process.argv.slice(2));
|
|
458
|
+
const visible = visibleCommandRegistry(cliInstance.commandRegistry(), loadConfig(options), options);
|
|
459
|
+
if (!depth) {
|
|
460
|
+
const names = new Set(visible.commands.flatMap(command => [command.name, ...command.aliases]));
|
|
461
|
+
out = out.filter(value => value.startsWith('-') || names.has(value));
|
|
462
|
+
}
|
|
455
463
|
done([...new Set(out)]);
|
|
456
464
|
});
|
|
457
465
|
})
|
|
@@ -557,7 +565,7 @@ export function buildCLI() {
|
|
|
557
565
|
if (typeof formatArgv?.get === 'string' && formatArgv.get) {
|
|
558
566
|
app.output.setJqFilter(formatArgv.get);
|
|
559
567
|
}
|
|
560
|
-
const { commands } = cliInstance.commandRegistry();
|
|
568
|
+
const { commands } = visibleCommandRegistry(cliInstance.commandRegistry(), loadConfig(formatArgv), formatArgv);
|
|
561
569
|
try {
|
|
562
570
|
app.ok({ commands }, {
|
|
563
571
|
summary: `${commands.length} registered commands — run "jsn <command> --json" for a command's subcommands.`,
|
|
@@ -567,7 +575,7 @@ export function buildCLI() {
|
|
|
567
575
|
}
|
|
568
576
|
process.exit(0);
|
|
569
577
|
}
|
|
570
|
-
process.stdout.write(renderHelp(loadConfig(), cliInstance.commandRegistry()) + '\n');
|
|
578
|
+
process.stdout.write(renderHelp(loadConfig(formatArgv), visibleCommandRegistry(cliInstance.commandRegistry(), loadConfig(formatArgv), formatArgv)) + '\n');
|
|
571
579
|
process.exit(0);
|
|
572
580
|
}
|
|
573
581
|
// Every other failure here (unknown command, missing required argument,
|
|
@@ -587,6 +595,12 @@ export function buildCLI() {
|
|
|
587
595
|
exitWithError(new AppError(CodeUsage, msg, hint), { app });
|
|
588
596
|
});
|
|
589
597
|
|
|
598
|
+
cliInstance.rootHelpRenderer = () => {
|
|
599
|
+
const options = cliInstance.parseGlobalOptions(process.argv.slice(2));
|
|
600
|
+
const config = loadConfig(options);
|
|
601
|
+
return renderHelp(config, visibleCommandRegistry(cliInstance.commandRegistry(), config, options));
|
|
602
|
+
};
|
|
603
|
+
|
|
590
604
|
// Snapshot the root command names (plus aliases) for the completion
|
|
591
605
|
// filter. Done before any parse — command builders mutate the registry.
|
|
592
606
|
rootCommands = cliInstance.getInternalMethods().getCommandInstance().getCommands();
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Presentation policy only. ServiceNow ACLs still authorize every request.
|
|
2
|
+
// Explicit command paths and --all-commands remain available to every account.
|
|
3
|
+
const BASIC_COMMANDS = new Set([
|
|
4
|
+
'incidents', 'changes', 'requests', 'tasks', 'tickets', 'approvals', 'records',
|
|
5
|
+
'groups', 'groupmembers', 'grouproles', 'users', 'setup', 'auth', 'skill',
|
|
6
|
+
'version', 'docs', 'history', 'discover', 'completion',
|
|
7
|
+
]);
|
|
8
|
+
|
|
9
|
+
export async function detectCommandAudience(sdk, username, instanceURL = sdk?.baseURL) {
|
|
10
|
+
if (!username || typeof sdk?.listStrict !== 'function') return undefined;
|
|
11
|
+
try {
|
|
12
|
+
const roles = await sdk.listStrict('sys_user_has_role', {
|
|
13
|
+
sysparm_query: 'user=javascript:gs.getUserID()^role.name=admin',
|
|
14
|
+
sysparm_fields: 'sys_id', sysparm_limit: '1',
|
|
15
|
+
}, { touchLastSeen: false });
|
|
16
|
+
return { username, instance_url: instanceURL, mode: roles.length ? 'admin' : 'basic', role_check: 'verified' };
|
|
17
|
+
} catch {
|
|
18
|
+
// Missing role access must not expand help. This is a presentation fallback,
|
|
19
|
+
// not a claim that the role query succeeded or an authorization boundary.
|
|
20
|
+
return { username, instance_url: instanceURL, mode: 'basic', role_check: 'unavailable' };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function commandAudience(config, { instance, allCommands = false } = {}) {
|
|
25
|
+
if (allCommands || instance) return 'all';
|
|
26
|
+
const name = config?.activeProfile || config?.defaultProfile;
|
|
27
|
+
const profile = config?.profiles?.[name];
|
|
28
|
+
const cached = profile?.command_audience;
|
|
29
|
+
return cached?.username === profile?.username && cached?.instance_url === profile?.instance_url && cached?.mode === 'basic' ? 'basic' : 'all';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function visibleCommandRegistry(registry, config, options = {}) {
|
|
33
|
+
if (commandAudience(config, options) !== 'basic') return registry;
|
|
34
|
+
return { ...registry, commands: registry.commands.filter(command => BASIC_COMMANDS.has(command.name)) };
|
|
35
|
+
}
|
package/src/commands/auth.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { detectCommandAudience } from '../command-audience.js';
|
|
1
2
|
import { getEffectiveInstance, normalizeInstanceURL, saveConfig, setActiveProfile } from '../config.js';
|
|
2
3
|
import { errUsage, errUsageHint, errConfirmationRequired } from '../errors.js';
|
|
3
4
|
import { canUsePicker, formatCommandList, quoteShellArg } from '../helpers.js';
|
|
@@ -710,6 +711,8 @@ Find your instance URL in your browser's address bar when logged into ServiceNow
|
|
|
710
711
|
// profile exists; preserve it when verification is unavailable.
|
|
711
712
|
username = username || targetUsername;
|
|
712
713
|
|
|
714
|
+
const commandAudience = await detectCommandAudience(app.sdk, username, instanceURL);
|
|
715
|
+
|
|
713
716
|
// Save to profiles — deduplicate by instance URL
|
|
714
717
|
if (!app.config.profiles) {
|
|
715
718
|
app.config.profiles = {};
|
|
@@ -740,7 +743,9 @@ Find your instance URL in your browser's address bar when logged into ServiceNow
|
|
|
740
743
|
read_only: preferences.read_only,
|
|
741
744
|
skip_confirmations: preferences.skip_confirmations,
|
|
742
745
|
include_counts: preferences.include_counts,
|
|
746
|
+
...(commandAudience ? { command_audience: commandAudience } : {}),
|
|
743
747
|
};
|
|
748
|
+
if (!commandAudience) delete app.config.profiles[profileName].command_audience;
|
|
744
749
|
if (!targetProfile && interactive) {
|
|
745
750
|
try {
|
|
746
751
|
const domains = await import('./domains.js');
|
|
@@ -1050,12 +1055,22 @@ Examples:
|
|
|
1050
1055
|
const name = target.name || app.config.activeProfile || app.config.defaultProfile;
|
|
1051
1056
|
if (name && app.config.profiles[name] && app.config.profiles[name].instance_url === instanceURL) {
|
|
1052
1057
|
app.config.profiles[name].domain_separation = hasDS;
|
|
1058
|
+
|
|
1053
1059
|
await saveConfig(app.config);
|
|
1054
1060
|
}
|
|
1055
1061
|
} catch {
|
|
1056
1062
|
// Non-fatal — capability probe is best-effort
|
|
1057
1063
|
}
|
|
1058
1064
|
|
|
1065
|
+
const audienceName = target.name || app.config.activeProfile || app.config.defaultProfile;
|
|
1066
|
+
const audienceProfile = app.config.profiles?.[audienceName];
|
|
1067
|
+
if (audienceProfile?.instance_url === instanceURL) {
|
|
1068
|
+
const sdk = app.getSDKForProfile
|
|
1069
|
+
? app.getSDKForProfile(instanceURL, { ...targetAuthOptions, profile: audienceProfile }) : app.sdk;
|
|
1070
|
+
audienceProfile.command_audience = await detectCommandAudience(sdk, audienceProfile.username, instanceURL);
|
|
1071
|
+
await saveConfig(app.config);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1059
1074
|
app.ok({
|
|
1060
1075
|
refreshed: true,
|
|
1061
1076
|
instance: instanceURL,
|
package/src/commands/discover.js
CHANGED
|
@@ -35,7 +35,7 @@ export function discoverCmd(wrap, getRegistry) {
|
|
|
35
35
|
'Use --json for structured results or --json --get data.commands for only matches.',
|
|
36
36
|
].join('\n')),
|
|
37
37
|
handler: wrap(async (argv, app) => {
|
|
38
|
-
const commands = discoverCommands(getRegistry(), { query: argv.query, table: argv.table });
|
|
38
|
+
const commands = discoverCommands(getRegistry(argv), { query: argv.query, table: argv.table });
|
|
39
39
|
const lines = commands.flatMap(command => [
|
|
40
40
|
`${command.name}: ${command.description}`,
|
|
41
41
|
...(command.discovery ? [
|
package/src/help.js
CHANGED
|
@@ -203,6 +203,8 @@ function renderFlags(options) {
|
|
|
203
203
|
export function renderTips() {
|
|
204
204
|
return `
|
|
205
205
|
TIPS
|
|
206
|
+
Show every command, including admin and developer tools: "jsn --all-commands --help"
|
|
207
|
+
Command visibility follows the selected profile at authentication; explicit commands still use ServiceNow ACLs.
|
|
206
208
|
Find commands by task words: "jsn discover catalog"; by configured table: "jsn discover --table incident"
|
|
207
209
|
Cross-table task searches and custom tables: "jsn records list --help"
|
|
208
210
|
Specialized commands offer configured columns, lookup, and table-specific views; inspect discovery results for details
|