unbound-cli 1.1.0 → 1.1.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unbound-cli",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "CLI tool for Unbound - AI Gateway management",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -332,6 +332,23 @@ function checkRoot(commandHint = 'setup mdm') {
332
332
  }
333
333
  }
334
334
 
335
+ /**
336
+ * Returns true when the process has the privileges needed to touch system-level
337
+ * (MDM) configuration. On Windows, `net session` succeeds only when elevated, so
338
+ * it doubles as an Administrator check — this keeps nuke's scope (and the copy it
339
+ * shows) accurate instead of assuming admin.
340
+ */
341
+ function hasRootPrivileges() {
342
+ if (process.platform === 'win32') {
343
+ try {
344
+ return spawnSync('net', ['session'], { stdio: 'ignore', windowsHide: true }).status === 0;
345
+ } catch {
346
+ return false;
347
+ }
348
+ }
349
+ return typeof process.getuid === 'function' && process.getuid() === 0;
350
+ }
351
+
335
352
  /**
336
353
  * Runs a batch of tools sequentially with spinners.
337
354
  * Stops on first failure. Returns true if all succeeded.
@@ -839,52 +856,49 @@ Clear examples (no API key required):
839
856
  .command('nuke')
840
857
  .alias('uninstall')
841
858
  .description(
842
- 'Remove Unbound and start fresh. By default clears every user-level AND ' +
843
- 'MDM (system-level) tool configuration plus stored credentials (requires ' +
844
- "root). Use --user to clear only this user's tools and credentials (no root)."
859
+ 'Remove Unbound and start fresh: clears AI-tool configuration and deletes ' +
860
+ 'stored credentials. Scope follows your privileges run with sudo to also ' +
861
+ 'remove MDM (system-level) config for all users; without root it clears only ' +
862
+ 'the current user.'
845
863
  )
846
864
  .option('-y, --yes', 'Skip the confirmation prompt')
847
- .option('--user', "Clear only THIS user's tool config and credentials — skip MDM (no root required)")
848
865
  .addHelpText('after', `
849
- Two modes:
850
- Default (requires root) Clears every user-level and MDM (system-level) tool
851
- config for all users on the device, then deletes
852
- credentials.
853
- --user (no root) Clears only the current user's tool config, then
854
- deletes credentials. Leaves MDM config untouched
855
- (removing system-level config needs root).
866
+ Scope is chosen automatically from your privileges:
867
+ Run with sudo (root) Clears every user-level AND MDM (system-level) tool
868
+ config for all users on the device, then deletes
869
+ credentials.
870
+ Run without root Clears only the current user's tool config and
871
+ credentials. MDM (system-level) config is skipped —
872
+ re-run with sudo to remove it too.
856
873
 
857
874
  What it clears:
858
875
  - USER-level tool config — Cursor, GitHub Copilot, Claude Code (subscription +
859
- gateway), Gemini CLI, Codex (subscription + gateway). [both modes]
860
- - MDM (system-level) tool config across all users on the device. [default only]
861
- - Stored credentials and settings (~/.unbound/config.json). [both modes]
876
+ gateway), Gemini CLI, Codex (subscription + gateway).
877
+ - MDM (system-level) tool config across all users [only when run as root]
878
+ - Stored credentials and settings (~/.unbound/config.json).
862
879
 
863
880
  After it finishes, run \`unbound login\` (or \`unbound onboard\`) to set things up
864
881
  fresh. Clearing never contacts the Unbound API, so no API key is needed.
865
882
 
866
883
  Examples:
867
- $ sudo unbound nuke Remove everything on the device (asks to confirm)
868
- $ sudo unbound nuke --yes Remove everything, no confirmation
869
- $ unbound nuke --user Clear only your tools + credentials (no sudo)
870
- $ unbound nuke --user --yes Same, no confirmation
871
- $ sudo unbound uninstall 'uninstall' is an alias for 'nuke'
884
+ $ sudo unbound nuke Remove everything on the device (asks to confirm)
885
+ $ sudo unbound nuke --yes Remove everything, no confirmation
886
+ $ unbound nuke Clear just your tools + credentials (no sudo)
887
+ $ unbound nuke --yes Same, no confirmation
888
+ $ unbound uninstall 'uninstall' is an alias for 'nuke'
872
889
  `)
873
890
  .action(async (opts) => {
874
891
  try {
875
- // Root is only required for the MDM clears (system-level, all users).
876
- // --user mode skips them, so it doesn't need root. On native Windows the
877
- // Python MDM scripts do their own admin check, so skip the uid check there.
878
- if (!opts.user && process.platform !== 'win32' && (typeof process.getuid !== 'function' || process.getuid() !== 0)) {
879
- output.error('nuke removes system-level MDM config for all users, so it must run as root. Run with: sudo unbound nuke — or use `unbound nuke --user` to clear just your own tools and credentials without root.');
880
- process.exitCode = 1;
881
- return;
882
- }
892
+ // Scope follows privileges: with root (or Windows Administrator) we also
893
+ // remove system-level MDM config for all users; otherwise we clear only
894
+ // this user. Detecting elevation up front keeps the confirmation honest
895
+ // (no promising MDM removal we can't perform).
896
+ const includeMdm = hasRootPrivileges();
883
897
 
884
898
  if (!opts.yes) {
885
- output.warn(opts.user
886
- ? 'This removes your user-level Unbound tool configuration and deletes your stored credentials.'
887
- : 'This removes ALL Unbound tool configuration on this device (user-level and MDM) and deletes your stored credentials.');
899
+ output.warn(includeMdm
900
+ ? 'This removes ALL Unbound tool configuration on this device (user-level and MDM) and deletes your stored credentials.'
901
+ : 'This removes your user-level Unbound tool configuration and deletes your stored credentials. (MDM/system-level config needs root and will be skipped — re-run with sudo to remove it too.)');
888
902
  const ok = await confirm('Continue?');
889
903
  if (!ok) {
890
904
  output.info('Cancelled. Nothing was changed.');
@@ -902,11 +916,13 @@ Examples:
902
916
  const userTools = Object.keys(SETUP_TOOL_MAP).map(name => ({ name, ...SETUP_TOOL_MAP[name] }));
903
917
  const userFailed = await clearToolsBestEffort('user', userTools, { mdm: false, backendUrl, gatewayUrl, frontendUrl });
904
918
 
905
- // MDM clears are skipped in --user mode (they need root and touch all users).
919
+ // MDM clears need root and touch all users run them only when we have it.
906
920
  let mdmFailed = [];
907
- if (!opts.user) {
921
+ if (includeMdm) {
908
922
  const mdmTools = Object.keys(MDM_TOOLS).map(name => ({ name, ...MDM_TOOLS[name] }));
909
923
  mdmFailed = await clearToolsBestEffort('mdm', mdmTools, { mdm: true, backendUrl, gatewayUrl });
924
+ } else {
925
+ output.info('Skipped MDM (system-level) config — that needs root. Re-run with sudo to remove it too.');
910
926
  }
911
927
 
912
928
  // Wipe credentials + settings last, regardless of tool-clear outcomes.
@@ -915,7 +931,7 @@ Examples:
915
931
 
916
932
  console.log('');
917
933
  const failed = [...userFailed, ...mdmFailed];
918
- const scope = opts.user ? 'for your user' : 'on this device';
934
+ const scope = includeMdm ? 'on this device' : 'for your user';
919
935
  if (failed.length === 0) {
920
936
  output.success(`Unbound removed ${scope}. The CLI is back to a fresh state — run "unbound login" to start over.`);
921
937
  } else {
package/src/index.js CHANGED
@@ -82,8 +82,8 @@ TOOL SETUP
82
82
  $ unbound setup --all --clear Remove config for every tool
83
83
 
84
84
  Full uninstall (all tools + credentials):
85
- $ sudo unbound nuke Wipe everything on the device (MDM + user); requires root
86
- $ unbound nuke --user Wipe just your tools + credentials (no sudo)
85
+ $ sudo unbound nuke Wipe everything on the device (MDM + user)
86
+ $ unbound nuke Wipe just your tools + credentials (no sudo)
87
87
 
88
88
  MDM SETUP (admin, requires root)
89
89
  $ sudo unbound setup mdm --admin-api-key KEY --all