@jacebenson/jsn 4.2.4 → 4.3.0

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/src/cli.js CHANGED
@@ -30,7 +30,7 @@ import { groupMembersCmd } from './commands/groupmembers.js';
30
30
  import { groupRolesCmd } from './commands/grouproles.js';
31
31
  import { ticketsCmd } from './commands/tickets.js';
32
32
  import { transactionsCmd } from './commands/transactions.js';
33
- import { instanceCmd } from './commands/instance.js';
33
+
34
34
  import { platformCmd } from './commands/platform.js';
35
35
  import { codesearchCmd } from './commands/codesearch.js';
36
36
  import { versionCmd } from './commands/version.js';
@@ -51,11 +51,12 @@ import {
51
51
  catalogscriptsCmd, cataloguipoliciesCmd,
52
52
  uipoliciesCmd,
53
53
  scriptactionsCmd, scheduledjobsCmd, asyncrulesCmd, triggersCmd,
54
- decisiontablesCmd, assignmentsCmd,
54
+ assignmentsCmd,
55
55
  emailCmd,
56
56
  restmessageCmd, soapmessagesCmd,
57
57
  uimacrosCmd, uxlistsCmd, uxapplicabilityCmd,
58
58
  } from './commands/dev/_simple.js';
59
+ import { decisiontablesCmd } from './commands/decisiontables.js';
59
60
  import { flowsCmd } from './commands/dev/flows.js';
60
61
  import { formsCmd } from './commands/dev/forms.js';
61
62
  import { listsCmd } from './commands/dev/lists.js';
@@ -69,6 +70,7 @@ import { scrapiCmd } from './commands/dev/scrapi.js';
69
70
  import { b4rulesCmd } from './commands/dev/b4rules.js';
70
71
  import { atfCmd } from './commands/atf.js';
71
72
  import { approvalsCmd } from './commands/approvals.js';
73
+ import { perfCmd } from './commands/perf.js';
72
74
 
73
75
  function wrap(handler) {
74
76
  return async (argv) => {
@@ -167,15 +169,19 @@ export function buildCLI() {
167
169
  // / app._overrideInstance change — no more argv._overrideInstance mirror
168
170
  // or duplicated --instance branches across middlewares.
169
171
  (argv) => {
170
- const session = resolveSession(argv, cfg);
171
- if (session.unknownProfile) {
172
- guardExit(argv, {
173
- code: 'unknown_profile',
174
- message: `Unknown profile "${session.unknownProfile}"`,
175
- hint: 'Run "jsn auth status" to list profiles.',
176
- });
172
+ try {
173
+ const session = resolveSession(argv, cfg);
174
+ if (session.unknownProfile) {
175
+ guardExit(argv, {
176
+ code: 'unknown_profile',
177
+ message: `Unknown profile "${session.unknownProfile}"`,
178
+ hint: 'Run "jsn auth status" to list profiles.',
179
+ });
180
+ }
181
+ applySession(app, session);
182
+ } catch (err) {
183
+ guardExit(argv, { code: err.code || 'usage', message: err.message, hint: err.hint });
177
184
  }
178
- applySession(app, session);
179
185
  },
180
186
  // Guard mutation commands: require an instance, block read-only profiles
181
187
  (argv) => {
@@ -358,7 +364,7 @@ export function buildCLI() {
358
364
  .command(cmdbCmd(wrap))
359
365
  .command(recordsCmd(wrap))
360
366
  .command(transactionsCmd(wrap))
361
- .command(instanceCmd(wrap))
367
+
362
368
  .command(platformCmd(wrap))
363
369
  .command(snippetsCmd(wrap))
364
370
  .command(ticketsCmd(wrap))
@@ -366,6 +372,7 @@ export function buildCLI() {
366
372
  // DEVELOPER
367
373
  .command(atfCmd(wrap))
368
374
  .command(approvalsCmd(wrap))
375
+ .command(perfCmd(wrap))
369
376
  .command(evalCmd(wrap))
370
377
  .command(restCmd(wrap))
371
378
  .command(skillCmd(wrap))
@@ -82,25 +82,51 @@ export function resolveInstanceArg(arg, cfg) {
82
82
  return normalizeInstanceURL(`${trimmed}.service-now.com`);
83
83
  }
84
84
 
85
+ /** Resolve a command target by explicit profile name before instance fallback. */
86
+ export function resolveTargetProfile(argv = {}, cfg = {}, instanceURL = '') {
87
+ if (argv.profile && cfg.profiles?.[argv.profile]) {
88
+ return { name: argv.profile, profile: cfg.profiles[argv.profile] };
89
+ }
90
+ const entry = Object.entries(cfg.profiles || {})
91
+ .find(([, profile]) => normalizeInstanceURL(profile.instance_url) === instanceURL);
92
+ return entry ? { name: entry[0], profile: entry[1] } : { name: null, profile: null };
93
+ }
94
+
85
95
  /**
86
96
  * Select the login method. Explicit Basic Auth wins; otherwise reuse the
87
97
  * method recorded on an existing profile. New instances remain OAuth by
88
98
  * default.
89
99
  */
90
- export function shouldUseBasicAuth(argv = {}, cfg = {}, instanceURL = '') {
100
+ export function shouldUseBasicAuth(argv = {}, cfg = {}, instanceURL = '', targetProfile = null) {
91
101
  if (argv.basic || argv.password) return true;
102
+ if (targetProfile) return targetProfile.auth_method === 'basic';
92
103
  return Object.values(cfg.profiles || {}).some(profile =>
93
104
  profile.instance_url === instanceURL && profile.auth_method === 'basic'
94
105
  );
95
106
  }
96
107
 
97
- export function shouldUseGckAuth(argv = {}, cfg = {}, instanceURL = '') {
108
+ export function shouldUseGckAuth(argv = {}, cfg = {}, instanceURL = '', targetProfile = null) {
98
109
  if (argv.gck || argv.headers) return true;
110
+ if (targetProfile) return targetProfile.auth_method === 'gck';
99
111
  return Object.values(cfg.profiles || {}).some(profile =>
100
112
  profile.instance_url === instanceURL && profile.auth_method === 'gck'
101
113
  );
102
114
  }
103
115
 
116
+ export function validateLoginSelectors(argv = {}, targetProfile = null) {
117
+ const basic = argv.basic === true || argv.password === true;
118
+ const gck = argv.gck === true || argv.headers !== undefined;
119
+ if (basic && gck) {
120
+ throw errUsage('Basic and browser-session selectors are mutually exclusive; choose --basic/--password or --gck/--headers.');
121
+ }
122
+ if (basic && targetProfile?.auth_method === 'gck') {
123
+ throw errUsage('This profile is configured for gck authentication; use --gck/--headers or change the profile method.');
124
+ }
125
+ if (gck && targetProfile?.auth_method === 'basic') {
126
+ throw errUsage('This profile is configured for basic authentication; use --basic/--password or change the profile method.');
127
+ }
128
+ }
129
+
104
130
  /**
105
131
  * Pick a profile by name — interactive search picker when ambiguous,
106
132
  * auto-selects when there's exactly one, throws when none configured.
@@ -229,13 +255,13 @@ export async function loginWizard(app, argv = {}) {
229
255
  if (useBasic) {
230
256
  const username = await ask('Username: ');
231
257
  rl.close(); // Close outer readline before askHidden creates its own
232
- const { loadCredentials, askHidden, saveCredentials } = await import('../auth.js');
233
- const existingCreds = loadCredentials(instance) || loadCredentials(instance, username);
258
+ const { askHidden } = await import('../auth.js');
259
+ const existingCreds = app.auth.loadCredentials(instance) || app.auth.loadCredentials(instance, username);
234
260
  if (existingCreds && existingCreds.auth_method === 'basic') {
235
261
  console.log(`Using existing credentials for ${instance}`);
236
262
  } else {
237
263
  const password = await askHidden('Password: ');
238
- saveCredentials(instance, { auth_method: 'basic', username, password }, username);
264
+ app.auth.saveCredentials(instance, { auth_method: 'basic', username, password }, username);
239
265
  console.log('Basic auth credentials saved');
240
266
  }
241
267
  profile.username = username;
@@ -283,16 +309,12 @@ export async function loginWizard(app, argv = {}) {
283
309
  await app.auth.login(instance);
284
310
  // Re-save credentials under <user>@<instance> after verifying username
285
311
  try {
286
- const { SDKClient } = await import('../sdk.js');
287
- const sdk = new SDKClient(instance, app.auth);
312
+ const sdk = app.getSDKForProfile
313
+ ? app.getSDKForProfile(instance, { authMethod: 'oauth' })
314
+ : app.sdk;
288
315
  const user = await sdk.getCurrentUser();
289
316
  if (user && user.user_name) {
290
- const { loadCredentials, saveCredentials } = await import('../auth.js');
291
- const stored = loadCredentials(instance);
292
- if (stored && stored.access_token) {
293
- stored.username = user.user_name;
294
- saveCredentials(instance, stored, user.user_name);
295
- }
317
+ app.auth.migrateLegacyCredential(instance, user.user_name);
296
318
  profile.username = user.user_name;
297
319
  }
298
320
  } catch {
@@ -304,9 +326,14 @@ export async function loginWizard(app, argv = {}) {
304
326
  // Probe domain separation capability once and stamp the profile, so
305
327
  // `jsn domains` (and its help entry) only appear on instances with it.
306
328
  try {
307
- const { SDKClient } = await import('../sdk.js');
308
329
  const domainsMod = await import('./dev/domains.js');
309
- const sdk = new SDKClient(instance, app.auth);
330
+ const sdk = app.getSDKForProfile
331
+ ? app.getSDKForProfile(instance, {
332
+ profile,
333
+ authMethod: profile.auth_method || 'oauth',
334
+ username: profile.username,
335
+ })
336
+ : app.sdk;
310
337
  profile.domain_separation = await domainsMod.isDomainSeparationInstalled({ sdk });
311
338
  await setProfile(app.config, profileName, profile);
312
339
 
@@ -410,8 +437,9 @@ export async function modifyProfile(app, argv = {}) {
410
437
  return;
411
438
  }
412
439
  const { isDomainSeparationInstalled } = await import('./dev/domains.js');
413
- const { SDKClient } = await import('../sdk.js');
414
- const sdk = new SDKClient(profile.instance_url, app.auth);
440
+ const sdk = app.getSDKForProfile
441
+ ? app.getSDKForProfile(profile.instance_url, { profile, authMethod: profile.auth_method, username: profile.username })
442
+ : app.sdk;
415
443
  if (!(await isDomainSeparationInstalled({ sdk }))) {
416
444
  throw new Error(`Domain separation is not installed on ${profile.instance_url}`);
417
445
  }
@@ -436,8 +464,9 @@ export async function modifyProfile(app, argv = {}) {
436
464
  value: 'domain',
437
465
  });
438
466
  }
439
- const { SDKClient } = await import('../sdk.js');
440
- const sdk = new SDKClient(profile.instance_url, app.auth);
467
+ const sdk = app.getSDKForProfile
468
+ ? app.getSDKForProfile(profile.instance_url, { profile, authMethod: profile.auth_method, username: profile.username })
469
+ : app.sdk;
441
470
  flag = await select({
442
471
  message: `Modify ${name} (${profile.instance_url})`,
443
472
  choices,
@@ -482,22 +511,38 @@ export async function removeProfile(app, name) {
482
511
  if (!app.config.profiles[name]) {
483
512
  throw new Error(`Profile not found: ${name}`);
484
513
  }
485
- const instance = app.config.profiles[name].instance_url;
514
+ const profile = app.config.profiles[name];
515
+ const instance = profile.instance_url;
516
+ const otherBareProfiles = Object.entries(app.config.profiles)
517
+ .filter(([profileName, other]) => profileName !== name
518
+ && other.instance_url === instance
519
+ && !other.username)
520
+ .length;
486
521
  delete app.config.profiles[name];
487
522
  if (app.config.defaultProfile === name) app.config.defaultProfile = '';
488
523
  if (app.config.activeProfile === name) app.config.activeProfile = '';
489
524
  saveConfig(app.config);
490
525
 
491
- // Only clear credentials if no other profile uses this instance URL
492
- const stillInUse = instance && Object.values(app.config.profiles || {})
493
- .some(p => p.instance_url === instance);
494
- if (instance && !stillInUse) {
495
- app.auth.logout(instance);
526
+ // Delete only this profile's credential identity. Other profiles may share
527
+ // the instance URL but must retain their separate credentials.
528
+ if (instance && (profile.username || otherBareProfiles === 0)) {
529
+ app.auth.logout(instance, profile.username || null);
496
530
  }
497
531
 
498
532
  app.ok({ removed: name }, { summary: `Removed profile: ${name}` });
499
533
  }
500
534
 
535
+ /** Bare credentials are safe to clear only when their target is identifiable. */
536
+ function canLogoutBareProfile(app, instance, target, explicitlySelected) {
537
+ if (target?.username) return true;
538
+ const sameInstance = Object.entries(app.config.profiles || {})
539
+ .filter(([, profile]) => profile.instance_url === instance);
540
+ if (explicitlySelected) {
541
+ return sameInstance.filter(([, profile]) => !profile.username).length === 1;
542
+ }
543
+ return sameInstance.length <= 1;
544
+ }
545
+
501
546
  export function authCmd(wrap) {
502
547
  return {
503
548
  command: 'auth [subcommand]',
@@ -517,6 +562,14 @@ export function authCmd(wrap) {
517
562
  describe: 'Authenticate with Basic Auth via env vars (SN_USERNAME/SN_PASSWORD)',
518
563
  type: 'boolean',
519
564
  })
565
+ .option('gck', {
566
+ describe: 'Authenticate with a browser session from pasted request headers',
567
+ type: 'boolean',
568
+ })
569
+ .option('headers', {
570
+ describe: 'Browser request headers or cURL command for browser-session auth',
571
+ type: 'string',
572
+ })
520
573
  .option('print-url', {
521
574
  describe: 'Print the OAuth URL and exit (saves PKCE state for --code)',
522
575
  type: 'boolean',
@@ -541,7 +594,15 @@ export function authCmd(wrap) {
541
594
  }),
542
595
  handler: wrap(async (argv, app) => {
543
596
  let instanceURL;
544
- if (argv.instance) {
597
+ if (argv.profile) {
598
+ const namedProfile = app.config.profiles?.[argv.profile];
599
+ if (!namedProfile?.instance_url) {
600
+ throw errUsage(`Profile "${argv.profile}" is not configured.`);
601
+ }
602
+ // An explicit profile is authoritative, including in an
603
+ // interactive terminal. Never let the picker retarget it.
604
+ instanceURL = normalizeInstanceURL(namedProfile.instance_url);
605
+ } else if (argv.instance) {
545
606
  instanceURL = resolveInstanceArg(argv.instance, app.config);
546
607
  } else if (app.isInteractive()) {
547
608
  // gh-faithful: interactive terminals always get a picker —
@@ -579,18 +640,28 @@ Find your instance URL in your browser's address bar when logged into ServiceNow
579
640
  }
580
641
  }
581
642
 
582
- const targetProfile = Object.values(app.config.profiles || {})
583
- .find(profile => profile.instance_url === instanceURL);
584
- const targetUsername = targetProfile?.username || '';
585
- const useBasic = shouldUseBasicAuth(argv, app.config, instanceURL);
643
+ const target = resolveTargetProfile(argv, app.config, instanceURL);
644
+ const targetProfile = target.profile;
645
+ validateLoginSelectors(argv, targetProfile);
646
+ let targetUsername = targetProfile?.username || '';
647
+ const useBasic = shouldUseBasicAuth(argv, app.config, instanceURL, targetProfile);
648
+ const useGck = shouldUseGckAuth(argv, app.config, instanceURL, targetProfile);
586
649
  // --basic (with --password as a compatibility alias): authenticate
587
650
  // with basic auth from env vars and persist it for this profile.
588
- if (useBasic) {
589
- await app.auth.loginWithPassword(instanceURL, targetUsername || undefined);
651
+ if (useGck) {
652
+ const input = argv.headers || await readBrowserSessionInput();
653
+ const credentials = await app.auth.loginWithGck(instanceURL, input, targetUsername || undefined);
654
+ targetUsername = credentials?.username || targetUsername;
655
+ } else if (useBasic) {
656
+ const credentials = await app.auth.loginWithPassword(instanceURL, targetUsername || undefined);
657
+ // A new profile has no configured username yet. The auth
658
+ // manager saves env/stored credentials under creds.username;
659
+ // carry that identity into verification and profile creation.
660
+ targetUsername = credentials?.username || targetUsername;
590
661
  }
591
662
  // --print-url with --wait-file: print URL and wait for code file
592
663
  else if (argv.printUrl && argv.waitFile) {
593
- await app.auth.buildAuthURL(instanceURL, argv.waitFile);
664
+ await app.auth.buildAuthURL(instanceURL, argv.waitFile, targetUsername || undefined);
594
665
  }
595
666
  // --print-url: just print the URL and exit
596
667
  else if (argv.printUrl) {
@@ -600,21 +671,32 @@ Find your instance URL in your browser's address bar when logged into ServiceNow
600
671
  }
601
672
  // --code: exchange the provided authorization code directly
602
673
  else if (argv.code) {
603
- await app.auth.loginWithCode(instanceURL, argv.code);
674
+ await app.auth.loginWithCode(instanceURL, argv.code, targetUsername || undefined);
604
675
  } else {
605
676
  // Interactive: full OAuth flow with browser + prompt
606
- await app.auth.login(instanceURL);
677
+ await app.auth.login(instanceURL, targetUsername || undefined);
607
678
  }
608
679
 
609
680
  // Verify auth by fetching current user
610
681
  let username = '';
611
682
  try {
612
- if (!app.sdk || targetUsername) {
613
- const { SDKClient } = await import('../sdk.js');
614
- const authProvider = targetUsername
615
- ? { getCredentials: () => app.auth.getCredentialsFor(instanceURL, targetUsername) }
616
- : app.auth;
617
- app.sdk = new SDKClient(instanceURL, authProvider);
683
+ const activeName = app.config.activeProfile || app.config.defaultProfile;
684
+ const activeProfile = app.config.profiles?.[activeName];
685
+ const activeInstance = app.getEffectiveInstance?.() || activeProfile?.instance_url || '';
686
+ const activeUsername = activeProfile?.username || '';
687
+ const activeMethod = activeProfile?.auth_method || '';
688
+ const targetMethod = useGck ? 'gck' : useBasic ? 'basic' : 'oauth';
689
+ const targetDiffers = instanceURL !== activeInstance
690
+ || targetUsername !== activeUsername
691
+ || targetMethod !== activeMethod;
692
+ if (!app.sdk || targetDiffers) {
693
+ app.sdk = app.getSDKForProfile
694
+ ? app.getSDKForProfile(instanceURL, {
695
+ authMethod: targetMethod,
696
+ username: targetUsername,
697
+ profile: targetProfile,
698
+ })
699
+ : app.sdk;
618
700
  }
619
701
  const user = await app.sdk.getCurrentUser();
620
702
  username = user?.user_name || user?.name || '';
@@ -622,17 +704,24 @@ Find your instance URL in your browser's address bar when logged into ServiceNow
622
704
  // Non-fatal — token is saved, just couldn't verify
623
705
  }
624
706
 
707
+ // Basic/GCK auth establishes the target identity before the
708
+ // profile exists; preserve it when verification is unavailable.
709
+ username = username || targetUsername;
710
+
625
711
  // Save to profiles — deduplicate by instance URL
626
712
  if (!app.config.profiles) {
627
713
  app.config.profiles = {};
628
714
  }
629
715
 
630
- // Check if a profile already exists for this instance
631
- let profileName = null;
632
- for (const [existingName, existingProfile] of Object.entries(app.config.profiles)) {
633
- if (existingProfile.instance_url === instanceURL) {
634
- profileName = existingName;
635
- break;
716
+ // Preserve an explicitly selected profile even when another profile
717
+ // shares the same instance URL.
718
+ let profileName = target.name;
719
+ if (!profileName) {
720
+ for (const [existingName, existingProfile] of Object.entries(app.config.profiles)) {
721
+ if (existingProfile.instance_url === instanceURL) {
722
+ profileName = existingName;
723
+ break;
724
+ }
636
725
  }
637
726
  }
638
727
 
@@ -644,7 +733,7 @@ Find your instance URL in your browser's address bar when logged into ServiceNow
644
733
  app.config.profiles[profileName] = {
645
734
  ...(app.config.profiles[profileName] || {}),
646
735
  instance_url: instanceURL,
647
- auth_method: useBasic ? 'basic' : 'oauth',
736
+ auth_method: useGck ? 'gck' : useBasic ? 'basic' : 'oauth',
648
737
  username: username || undefined,
649
738
  read_only: argv['read-only'] || undefined,
650
739
  skip_confirmations: argv['skip-confirmations'] || undefined,
@@ -656,7 +745,7 @@ Find your instance URL in your browser's address bar when logged into ServiceNow
656
745
  // The legacy bare-instance key is cleaned up behind the
657
746
  // AuthManager seam (credential-store internals don't leak here).
658
747
  if (!useBasic && username) {
659
- app.auth.migrateLegacyCredential(instanceURL, username);
748
+ app.auth.migrateLegacyCredential?.(instanceURL, username);
660
749
  }
661
750
 
662
751
  // Set as default if this is the first one
@@ -699,40 +788,69 @@ Examples:
699
788
  jsn auth logout https://dev12345.service-now.com`);
700
789
  }
701
790
  }
702
- app.auth.logout(instanceURL);
791
+ const target = resolveTargetProfile(argv, app.config, instanceURL);
792
+ const username = target.profile?.username ?? null;
793
+ if (username || canLogoutBareProfile(app, instanceURL, target.profile, !!argv.profile)) {
794
+ app.auth.logout(instanceURL, username);
795
+ }
703
796
  app.ok({ logged_out: true, instance: instanceURL }, { summary: `✓ Logged out from ${instanceURL}` });
704
797
  }),
705
798
  })
706
799
  .command({
707
800
  command: 'status',
708
801
  describe: 'Show detailed authentication status',
709
- handler: wrap(async (_argv, app) => {
710
- const defaultInstance = getEffectiveInstance(app.config);
802
+ handler: wrap(async (argv, app) => {
803
+ const configuredDefault = app.config.profiles?.[app.config.defaultProfile];
804
+ const defaultInstance = configuredDefault?.instance_url || app.config.instanceURL || '';
711
805
 
712
806
  // Check environment auth
713
807
  const envToken = process.env.SERVICENOW_OAUTH_TOKEN || '';
714
808
 
715
809
  const profiles = [];
716
- for (const [name, profile] of Object.entries(app.config.profiles || {})) {
810
+ const configuredProfiles = Object.entries(app.config.profiles || {});
811
+ const selectedProfiles = argv.profile
812
+ ? configuredProfiles.filter(([name]) => name === argv.profile)
813
+ : configuredProfiles;
814
+ for (const [name, profile] of selectedProfiles) {
717
815
  const instance = profile.instance_url;
718
- const isAuth = app.auth.isAuthenticatedFor(instance);
719
- const lastSeen = app.auth.getLastSeen(instance);
720
- const authSource = isAuth ? app.auth.getAuthSource(instance) : null;
721
- const legacy = !isAuth && app.auth.hasLegacyCredentials(instance) ? true : undefined;
816
+ const authOptions = { authMethod: profile.auth_method, username: profile.username };
817
+ const identityOptions = { username: profile.username, authMethod: profile.auth_method };
818
+ const isAuth = app.auth.isAuthenticatedFor(instance, identityOptions);
819
+ const lastSeen = app.auth.getLastSeen(instance, identityOptions);
820
+ const authState = app.auth.getAuthState(instance, authOptions);
821
+ const legacy = !isAuth && app.auth.hasLegacyCredentials(instance, identityOptions) ? true : undefined;
822
+ // Keep the established status field contract: unauthenticated
823
+ // profiles omit auth_source, except legacy credentials retain
824
+ // their historical `legacy` marker. getAuthState remains the
825
+ // safe metadata seam for authenticated profiles.
826
+ const authSource = isAuth ? authState.auth_source : (legacy ? 'legacy' : undefined);
722
827
 
723
- // Try live verification
828
+ // Try live verification through AuthManager's read-only probe seam.
724
829
  let verified = null;
725
830
  let verifiedAt = null;
726
- if (isAuth && instance) {
831
+ let probe;
832
+ if (!instance) {
833
+ probe = app.auth.probeUnavailable?.('missing_instance') || { status: 'not_attempted', code: 'missing_instance', classification: 'unavailable' };
834
+ } else {
727
835
  try {
728
- const { SDKClient } = await import('../sdk.js');
729
- const sdk = new SDKClient(instance, app.auth);
730
- const user = await sdk.getCurrentUser();
731
- if (user && user.user_name) {
836
+ const sdk = app.getSDKForProfile
837
+ ? app.getSDKForProfile(instance, { ...authOptions, profile })
838
+ : app.sdk || { getCurrentUser: async () => null };
839
+ let user;
840
+ probe = await app.auth.probeCurrentUser(instance, {
841
+ getCurrentUser: async (options) => {
842
+ user = await sdk.getCurrentUser(options);
843
+ return user;
844
+ },
845
+ }, authOptions);
846
+ if (probe.status === 'succeeded' && user?.user_name) {
732
847
  verified = true;
733
848
  verifiedAt = user.user_name;
849
+ } else if (probe.status !== 'not_attempted') {
850
+ verified = false;
734
851
  }
735
852
  } catch {
853
+ probe = app.auth.probeUnavailable?.('sdk_construction_failed') || { status: 'not_attempted', code: 'sdk_construction_failed', classification: 'configuration_error' };
736
854
  verified = false;
737
855
  }
738
856
  }
@@ -748,16 +866,22 @@ Examples:
748
866
  instance,
749
867
  authenticated: isAuth,
750
868
  legacy,
751
- auth_source: authSource || (legacy ? 'legacy' : undefined),
869
+ auth_source: authSource,
752
870
  verified,
753
871
  verified_as: verifiedAt || undefined,
754
872
  last_seen: lastSeen || undefined,
755
873
  days_since_last_seen: daysSinceLastSeen,
756
874
  stale: daysSinceLastSeen > 7,
757
- default: instance === defaultInstance,
875
+ default: name === app.config.defaultProfile,
758
876
  read_only: profile.read_only || false,
759
877
  skip_confirmations: profile.skip_confirmations || false,
760
878
  include_counts: profile.include_counts !== false,
879
+ diagnostics: {
880
+ auth_method: authState.auth_method,
881
+ auth_source: authState.auth_source,
882
+ state: authState.state,
883
+ probe,
884
+ },
761
885
  });
762
886
  }
763
887
 
@@ -808,17 +932,31 @@ Examples:
808
932
  }
809
933
  }
810
934
 
811
- const creds = await app.auth.getCredentialsFor(instanceURL);
812
- const refreshed = await app.auth.refreshToken(instanceURL, creds);
935
+ const target = resolveTargetProfile(argv, app.config, instanceURL);
936
+ const targetProfile = target.profile;
937
+ const targetAuthOptions = {
938
+ authMethod: targetProfile ? targetProfile.auth_method : 'oauth',
939
+ username: targetProfile?.username,
940
+ };
941
+ const creds = await app.auth.getCredentialsFor(
942
+ instanceURL,
943
+ targetProfile?.username,
944
+ targetAuthOptions
945
+ );
946
+ const refreshed = await app.auth.refreshToken(instanceURL, creds, targetProfile?.username ?? null);
813
947
 
814
948
  // Re-probe domain separation on refresh so the capability flag
815
949
  // stays in sync if the plugin was installed/removed since setup.
816
950
  try {
817
- const { SDKClient } = await import('../sdk.js');
818
951
  const { isDomainSeparationInstalled } = await import('./dev/domains.js');
819
- const sdk = new SDKClient(instanceURL, app.auth);
952
+ const sdk = app.getSDKForProfile
953
+ ? app.getSDKForProfile(instanceURL, {
954
+ ...targetAuthOptions,
955
+ profile: targetProfile,
956
+ })
957
+ : app.sdk;
820
958
  const hasDS = await isDomainSeparationInstalled({ sdk });
821
- const name = app.config.activeProfile || app.config.defaultProfile;
959
+ const name = target.name || app.config.activeProfile || app.config.defaultProfile;
822
960
  if (name && app.config.profiles[name] && app.config.profiles[name].instance_url === instanceURL) {
823
961
  app.config.profiles[name].domain_separation = hasDS;
824
962
  await saveConfig(app.config);