unbound-cli 1.1.0 → 1.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/package.json +1 -1
- package/src/commands/onboard.js +21 -7
- package/src/commands/setup.js +64 -42
- package/src/index.js +2 -2
package/package.json
CHANGED
package/src/commands/onboard.js
CHANGED
|
@@ -23,7 +23,7 @@ function register(program) {
|
|
|
23
23
|
'One-step user onboarding: install the default AI tools bundle and run device discovery. ' +
|
|
24
24
|
'Runs `setup --all` followed by `discover` in a single command.'
|
|
25
25
|
)
|
|
26
|
-
.option('--api-key <key>', 'User API key (or set UNBOUND_API_KEY env var)')
|
|
26
|
+
.option('--api-key <key>', 'User API key (or set UNBOUND_API_KEY env var, or reuse a stored `unbound login` key)')
|
|
27
27
|
.option('--discovery-key <key>', 'Discovery API key for device scan (or set UNBOUND_DISCOVERY_KEY env var)')
|
|
28
28
|
.option('--domain <url>', 'Backend URL for discovery (defaults to configured backend)')
|
|
29
29
|
.option('--set-cron', 'Set up a daily background job to keep governance up to date')
|
|
@@ -33,7 +33,8 @@ function register(program) {
|
|
|
33
33
|
.addOption(new Option('--gateway-url <url>', 'Override gateway URL for setup scripts (dev only)').hideHelp())
|
|
34
34
|
.addHelpText('after', `
|
|
35
35
|
Runs the full onboarding flow for an end user:
|
|
36
|
-
1. Logs in with --api-key and stores credentials
|
|
36
|
+
1. Logs in with --api-key and stores credentials (or reuses a stored
|
|
37
|
+
\`unbound login\` key when --api-key is omitted).
|
|
37
38
|
2. Installs the default tool bundle: ${ALL_TOOLS.join(', ')}.
|
|
38
39
|
3. Runs device discovery with --discovery-key. With --set-cron, sets up a
|
|
39
40
|
recurring daily scheduled scan (cross-platform) instead of a one-time scan.
|
|
@@ -56,8 +57,10 @@ Examples:
|
|
|
56
57
|
.action(async (opts) => {
|
|
57
58
|
const apiKeyOpt = opts.apiKey || process.env.UNBOUND_API_KEY;
|
|
58
59
|
const discoveryKeyOpt = opts.discoveryKey || process.env.UNBOUND_DISCOVERY_KEY;
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
// A stored `unbound login` key is enough — only demand --api-key when not
|
|
61
|
+
// already logged in. ensureLoggedIn() reuses the stored credential below.
|
|
62
|
+
if (!apiKeyOpt && !config.isLoggedIn()) {
|
|
63
|
+
output.error('--api-key is required (or set UNBOUND_API_KEY env var, or run `unbound login` first)');
|
|
61
64
|
process.exitCode = 1;
|
|
62
65
|
return;
|
|
63
66
|
}
|
|
@@ -116,7 +119,7 @@ Examples:
|
|
|
116
119
|
const { setupScheduledRun } = require('../scheduled');
|
|
117
120
|
await setupScheduledRun({
|
|
118
121
|
command: 'onboard',
|
|
119
|
-
apiKey: apiKeyOpt,
|
|
122
|
+
apiKey: apiKeyOpt || apiKey,
|
|
120
123
|
discoveryKey: discoveryKeyOpt,
|
|
121
124
|
domain: discoveryDomain,
|
|
122
125
|
skipRunAtLoad: true,
|
|
@@ -182,7 +185,7 @@ Examples:
|
|
|
182
185
|
'One-step MDM onboarding: install the default MDM tool bundle and run device discovery. ' +
|
|
183
186
|
'Requires root. Used by organization admins to enroll devices via MDM.'
|
|
184
187
|
)
|
|
185
|
-
.
|
|
188
|
+
.option('--admin-api-key <key>', 'Admin API key for MDM enrollment (falls back to your stored `unbound login` key)')
|
|
186
189
|
.requiredOption('--discovery-key <key>', 'Discovery API key (for device scan)')
|
|
187
190
|
.option('--domain <url>', 'Backend URL for discovery (defaults to configured backend)')
|
|
188
191
|
.option('--backfill', 'Seed historical Claude Code / Codex / Copilot sessions from local transcripts into Unbound analytics (Cursor skipped automatically)')
|
|
@@ -196,11 +199,14 @@ Runs the full MDM onboarding flow for device enrollment:
|
|
|
196
199
|
|
|
197
200
|
Both steps require root. The admin API key and discovery API key are
|
|
198
201
|
separate keys obtained from different parts of the Unbound admin dashboard.
|
|
202
|
+
--admin-api-key may be omitted to reuse the key stored by a prior
|
|
203
|
+
\`unbound login\` (run sudo with HOME preserved so the stored key is found).
|
|
199
204
|
|
|
200
205
|
For end-user onboarding (non-MDM), use \`unbound onboard\` instead.
|
|
201
206
|
|
|
202
207
|
Examples:
|
|
203
208
|
$ sudo unbound onboard-mdm --admin-api-key <ADMIN_KEY> --discovery-key <DISCOVERY_KEY>
|
|
209
|
+
$ sudo unbound onboard-mdm --discovery-key <DISCOVERY_KEY> Reuse the stored \`unbound login\` key
|
|
204
210
|
$ sudo unbound onboard-mdm --admin-api-key <ADMIN_KEY> --discovery-key <DISCOVERY_KEY> --backfill
|
|
205
211
|
`)
|
|
206
212
|
.action(async (opts) => {
|
|
@@ -221,9 +227,17 @@ Examples:
|
|
|
221
227
|
|
|
222
228
|
checkRoot('onboard-mdm');
|
|
223
229
|
|
|
230
|
+
// Reuse the key stored by `unbound login` when --admin-api-key is omitted.
|
|
231
|
+
const adminApiKey = opts.adminApiKey || config.getApiKey();
|
|
232
|
+
if (!adminApiKey) {
|
|
233
|
+
output.error('--admin-api-key is required (or run `unbound login` first).');
|
|
234
|
+
process.exitCode = 1;
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
224
238
|
console.log('');
|
|
225
239
|
output.info('Step 1/2: Installing MDM tool bundle');
|
|
226
|
-
const ok = await runMdmSetupAllBundle(
|
|
240
|
+
const ok = await runMdmSetupAllBundle(adminApiKey, {
|
|
227
241
|
backendUrl, gatewayUrl, backfill: !!opts.backfill,
|
|
228
242
|
});
|
|
229
243
|
if (!ok) return;
|
package/src/commands/setup.js
CHANGED
|
@@ -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.
|
|
@@ -493,7 +510,7 @@ requires authentication.
|
|
|
493
510
|
// No tools specified → interactive multi-select (existing flow)
|
|
494
511
|
if (tools.length === 0) {
|
|
495
512
|
const selected = await output.multiSelect(
|
|
496
|
-
'Select tools to set up with Unbound:',
|
|
513
|
+
opts.clear ? 'Select tools to remove Unbound configuration for:' : 'Select tools to set up with Unbound:',
|
|
497
514
|
SETUP_TOOLS
|
|
498
515
|
);
|
|
499
516
|
|
|
@@ -513,14 +530,15 @@ requires authentication.
|
|
|
513
530
|
const ok = await runBatch(selectedTools, (tool) => {
|
|
514
531
|
const toolArgs = buildScriptArgs(apiKey, {
|
|
515
532
|
...urlOpts,
|
|
533
|
+
clear: opts.clear,
|
|
516
534
|
backfill: opts.backfill && scriptSupportsBackfill(tool.script),
|
|
517
535
|
});
|
|
518
536
|
return runScriptPiped(tool.script, toolArgs);
|
|
519
|
-
});
|
|
537
|
+
}, { clear: opts.clear });
|
|
520
538
|
if (!ok) return;
|
|
521
539
|
|
|
522
540
|
console.log('');
|
|
523
|
-
output.success('All tools configured');
|
|
541
|
+
output.success(opts.clear ? 'All tools cleared' : 'All tools configured');
|
|
524
542
|
return;
|
|
525
543
|
}
|
|
526
544
|
|
|
@@ -688,7 +706,7 @@ requires authentication.
|
|
|
688
706
|
'Used by organization admins to enroll devices via MDM.'
|
|
689
707
|
)
|
|
690
708
|
.argument('[tools...]', 'Tools to set up: ' + mdmToolNames)
|
|
691
|
-
.option('--admin-api-key <key>', 'Admin API key for MDM enrollment (not required with --clear)')
|
|
709
|
+
.option('--admin-api-key <key>', 'Admin API key for MDM enrollment (falls back to your stored `unbound login` key; not required with --clear)')
|
|
692
710
|
.option('--clear', 'Remove Unbound configuration for the specified tools (no API key required)')
|
|
693
711
|
.option('--all', 'Set up all available tools')
|
|
694
712
|
.option('--backfill', 'Seed historical Claude Code / Codex / Copilot sessions from local transcripts into Unbound analytics (subscription/hooks mode only; Cursor unsupported)')
|
|
@@ -708,10 +726,12 @@ Note: claude-code-subscription and claude-code-gateway are mutually exclusive wh
|
|
|
708
726
|
setting up; same for codex. Bare claude-code/codex set up subscription mode.
|
|
709
727
|
When using --all, subscription mode is used by default for Claude Code and Codex.
|
|
710
728
|
|
|
711
|
-
Setup examples (
|
|
729
|
+
Setup examples (need an admin key — pass --admin-api-key, or omit it to reuse the
|
|
730
|
+
key stored by a prior \`unbound login\`):
|
|
712
731
|
$ sudo unbound setup mdm --admin-api-key KEY cursor
|
|
713
732
|
$ sudo unbound setup mdm --admin-api-key KEY cursor claude-code-subscription codex-subscription
|
|
714
733
|
$ sudo unbound setup mdm --admin-api-key KEY --all
|
|
734
|
+
$ sudo unbound setup mdm --all Reuse the stored \`unbound login\` key
|
|
715
735
|
$ sudo unbound setup mdm --admin-api-key KEY claude-code-subscription --backfill
|
|
716
736
|
Install hooks AND backfill local history
|
|
717
737
|
$ sudo unbound setup mdm --admin-api-key KEY copilot --backfill
|
|
@@ -731,9 +751,12 @@ Clear examples (no API key required):
|
|
|
731
751
|
// Use optsWithGlobals() so they all work regardless of position relative to `mdm`.
|
|
732
752
|
const globalOpts = command.optsWithGlobals();
|
|
733
753
|
// Clearing removes config without calling the API, so a key is only
|
|
734
|
-
// required when actually enrolling tools.
|
|
735
|
-
|
|
736
|
-
|
|
754
|
+
// required when actually enrolling tools. Fall back to the API key
|
|
755
|
+
// stored by `unbound login` so admins who are already logged in don't
|
|
756
|
+
// have to pass --admin-api-key again.
|
|
757
|
+
const adminApiKey = opts.adminApiKey || config.getApiKey();
|
|
758
|
+
if (!globalOpts.clear && !adminApiKey) {
|
|
759
|
+
output.error('--admin-api-key is required to set up tools (or run `unbound login` first).');
|
|
737
760
|
process.exitCode = 1;
|
|
738
761
|
return;
|
|
739
762
|
}
|
|
@@ -812,7 +835,7 @@ Clear examples (no API key required):
|
|
|
812
835
|
const ok = await runBatch(
|
|
813
836
|
resolvedTools,
|
|
814
837
|
(tool) => {
|
|
815
|
-
const toolArgs = buildScriptArgs(
|
|
838
|
+
const toolArgs = buildScriptArgs(adminApiKey, {
|
|
816
839
|
backendUrl,
|
|
817
840
|
gatewayUrl,
|
|
818
841
|
clear: globalOpts.clear,
|
|
@@ -839,52 +862,49 @@ Clear examples (no API key required):
|
|
|
839
862
|
.command('nuke')
|
|
840
863
|
.alias('uninstall')
|
|
841
864
|
.description(
|
|
842
|
-
'Remove Unbound and start fresh
|
|
843
|
-
'
|
|
844
|
-
|
|
865
|
+
'Remove Unbound and start fresh: clears AI-tool configuration and deletes ' +
|
|
866
|
+
'stored credentials. Scope follows your privileges — run with sudo to also ' +
|
|
867
|
+
'remove MDM (system-level) config for all users; without root it clears only ' +
|
|
868
|
+
'the current user.'
|
|
845
869
|
)
|
|
846
870
|
.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
871
|
.addHelpText('after', `
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
872
|
+
Scope is chosen automatically from your privileges:
|
|
873
|
+
Run with sudo (root) Clears every user-level AND MDM (system-level) tool
|
|
874
|
+
config for all users on the device, then deletes
|
|
875
|
+
credentials.
|
|
876
|
+
Run without root Clears only the current user's tool config and
|
|
877
|
+
credentials. MDM (system-level) config is skipped —
|
|
878
|
+
re-run with sudo to remove it too.
|
|
856
879
|
|
|
857
880
|
What it clears:
|
|
858
881
|
- USER-level tool config — Cursor, GitHub Copilot, Claude Code (subscription +
|
|
859
|
-
gateway), Gemini CLI, Codex (subscription + gateway).
|
|
860
|
-
- MDM (system-level) tool config across all users
|
|
861
|
-
- Stored credentials and settings (~/.unbound/config.json).
|
|
882
|
+
gateway), Gemini CLI, Codex (subscription + gateway).
|
|
883
|
+
- MDM (system-level) tool config across all users [only when run as root]
|
|
884
|
+
- Stored credentials and settings (~/.unbound/config.json).
|
|
862
885
|
|
|
863
886
|
After it finishes, run \`unbound login\` (or \`unbound onboard\`) to set things up
|
|
864
887
|
fresh. Clearing never contacts the Unbound API, so no API key is needed.
|
|
865
888
|
|
|
866
889
|
Examples:
|
|
867
|
-
$ sudo unbound nuke
|
|
868
|
-
$ sudo unbound nuke --yes
|
|
869
|
-
$ unbound nuke
|
|
870
|
-
$ unbound nuke --
|
|
871
|
-
$
|
|
890
|
+
$ sudo unbound nuke Remove everything on the device (asks to confirm)
|
|
891
|
+
$ sudo unbound nuke --yes Remove everything, no confirmation
|
|
892
|
+
$ unbound nuke Clear just your tools + credentials (no sudo)
|
|
893
|
+
$ unbound nuke --yes Same, no confirmation
|
|
894
|
+
$ unbound uninstall 'uninstall' is an alias for 'nuke'
|
|
872
895
|
`)
|
|
873
896
|
.action(async (opts) => {
|
|
874
897
|
try {
|
|
875
|
-
//
|
|
876
|
-
//
|
|
877
|
-
//
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
process.exitCode = 1;
|
|
881
|
-
return;
|
|
882
|
-
}
|
|
898
|
+
// Scope follows privileges: with root (or Windows Administrator) we also
|
|
899
|
+
// remove system-level MDM config for all users; otherwise we clear only
|
|
900
|
+
// this user. Detecting elevation up front keeps the confirmation honest
|
|
901
|
+
// (no promising MDM removal we can't perform).
|
|
902
|
+
const includeMdm = hasRootPrivileges();
|
|
883
903
|
|
|
884
904
|
if (!opts.yes) {
|
|
885
|
-
output.warn(
|
|
886
|
-
? 'This removes
|
|
887
|
-
: 'This removes
|
|
905
|
+
output.warn(includeMdm
|
|
906
|
+
? 'This removes ALL Unbound tool configuration on this device (user-level and MDM) and deletes your stored credentials.'
|
|
907
|
+
: '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
908
|
const ok = await confirm('Continue?');
|
|
889
909
|
if (!ok) {
|
|
890
910
|
output.info('Cancelled. Nothing was changed.');
|
|
@@ -902,11 +922,13 @@ Examples:
|
|
|
902
922
|
const userTools = Object.keys(SETUP_TOOL_MAP).map(name => ({ name, ...SETUP_TOOL_MAP[name] }));
|
|
903
923
|
const userFailed = await clearToolsBestEffort('user', userTools, { mdm: false, backendUrl, gatewayUrl, frontendUrl });
|
|
904
924
|
|
|
905
|
-
// MDM clears
|
|
925
|
+
// MDM clears need root and touch all users — run them only when we have it.
|
|
906
926
|
let mdmFailed = [];
|
|
907
|
-
if (
|
|
927
|
+
if (includeMdm) {
|
|
908
928
|
const mdmTools = Object.keys(MDM_TOOLS).map(name => ({ name, ...MDM_TOOLS[name] }));
|
|
909
929
|
mdmFailed = await clearToolsBestEffort('mdm', mdmTools, { mdm: true, backendUrl, gatewayUrl });
|
|
930
|
+
} else {
|
|
931
|
+
output.info('Skipped MDM (system-level) config — that needs root. Re-run with sudo to remove it too.');
|
|
910
932
|
}
|
|
911
933
|
|
|
912
934
|
// Wipe credentials + settings last, regardless of tool-clear outcomes.
|
|
@@ -915,7 +937,7 @@ Examples:
|
|
|
915
937
|
|
|
916
938
|
console.log('');
|
|
917
939
|
const failed = [...userFailed, ...mdmFailed];
|
|
918
|
-
const scope =
|
|
940
|
+
const scope = includeMdm ? 'on this device' : 'for your user';
|
|
919
941
|
if (failed.length === 0) {
|
|
920
942
|
output.success(`Unbound removed ${scope}. The CLI is back to a fresh state — run "unbound login" to start over.`);
|
|
921
943
|
} 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)
|
|
86
|
-
$ unbound nuke
|
|
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
|