unbound-cli 1.1.6 → 1.1.8

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.6",
3
+ "version": "1.1.8",
4
4
  "description": "CLI tool for Unbound - AI Gateway management",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -11,6 +11,13 @@ const LAUNCH_AGENT_LABEL = 'ai.getunbound.discovery';
11
11
  // (e.g. Linux). Treated as a skipped scan, not a failure.
12
12
  const DISCOVERY_EXIT_UNSUPPORTED_OS = 3;
13
13
 
14
+ // Self-imposed discovery run timeout (seconds), forwarded through install.sh to
15
+ // the discovery process so `unbound onboard` / `unbound discover` can't hang
16
+ // indefinitely. Discovery enforces this itself — on expiry it releases its lock,
17
+ // reports the run as failed, and exits non-zero. Kept in sync with
18
+ // setup/mdm/onboard.py's DISCOVERY_TIMEOUT_SECONDS.
19
+ const DISCOVERY_TIMEOUT_SECONDS = 1800;
20
+
14
21
  // Classifies a discovery subprocess exit code:
15
22
  // 'success' (scan ran), 'unsupported' (skipped on this OS), or 'failure'.
16
23
  function classifyDiscoveryExit(code) {
@@ -146,7 +153,16 @@ async function runDiscoveryScan({ apiKey, domain }) {
146
153
  console.log('');
147
154
  }
148
155
 
156
+ // Don't pass --timeout: install.sh is fetched from coding-discovery-tool/main,
157
+ // and an older discovery there would reject the unknown flag (argparse exits
158
+ // non-zero) and fail the scan. Discovery self-bounds via its own default (kept
159
+ // equal to DISCOVERY_TIMEOUT_SECONDS), so this is correct regardless of merge
160
+ // order. Surface the bound so the wait isn't a mystery; on expiry the discovery
161
+ // process prints its own line (stdio is inherited) and exits non-zero.
149
162
  const args = `--api-key ${shellEscape(apiKey)} --domain ${shellEscape(domain)}`;
163
+ if (!isWindowsNative()) {
164
+ output.info(`Discovery stops on its own after ${Math.round(DISCOVERY_TIMEOUT_SECONDS / 60)} minutes if it can't finish.`);
165
+ }
150
166
  await runDiscoveryScript('install.sh', args);
151
167
  }
152
168
 
@@ -221,9 +221,11 @@ Examples:
221
221
  // user's explicit --*-url flag.
222
222
  const written = config.setUrls({
223
223
  backend: opts.backendUrl,
224
+ frontend: opts.frontendUrl,
224
225
  gateway: opts.gatewayUrl,
225
226
  });
226
227
  const backendUrl = written.base_url || config.getBaseUrl();
228
+ const frontendUrl = written.frontend_url || config.getFrontendUrl();
227
229
  const gatewayUrl = written.gateway_url || config.getGatewayUrl();
228
230
  discoveryDomain = opts.domain || backendUrl;
229
231
 
@@ -240,7 +242,7 @@ Examples:
240
242
  console.log('');
241
243
  output.info('Step 1/2: Installing MDM tool bundle');
242
244
  const { ok, skipped } = await runMdmSetupAllBundle(adminApiKey, {
243
- backendUrl, gatewayUrl, backfill: !!opts.backfill,
245
+ backendUrl, frontendUrl, gatewayUrl, backfill: !!opts.backfill,
244
246
  });
245
247
  if (!ok) return;
246
248
  setupSucceeded = true;
@@ -241,8 +241,9 @@ async function runPythonScriptWindows(scriptPath, args, { capture }) {
241
241
  /**
242
242
  * Builds the shared argv tail for setup.py invocations. Always passes the
243
243
  * tenant URL flags so the script's behavior is not sensitive to drift in its
244
- * own defaults. `mdm: true` skips --domain (frontend URL) since MDM scripts
245
- * have no browser-auth flow.
244
+ * own defaults. The frontend URL goes to non-MDM scripts as --domain (also
245
+ * their browser-auth host) and to MDM scripts as --frontend-url (persist-only,
246
+ * no auth flow). Either way it lands in ~/.unbound/config.json as frontend_url.
246
247
  */
247
248
  function buildScriptArgs(apiKey, { backendUrl, frontendUrl, gatewayUrl, clear, mdm, backfill } = {}) {
248
249
  // --clear runs no auth in the Python scripts, so the key may be absent. Omit
@@ -250,7 +251,7 @@ function buildScriptArgs(apiKey, { backendUrl, frontendUrl, gatewayUrl, clear, m
250
251
  let args = apiKey ? `--api-key ${shellEscape(apiKey)}` : '';
251
252
  if (backendUrl) args += ` --backend-url ${shellEscape(backendUrl)}`;
252
253
  if (gatewayUrl) args += ` --gateway-url ${shellEscape(gatewayUrl)}`;
253
- if (!mdm && frontendUrl) args += ` --domain ${shellEscape(frontendUrl)}`;
254
+ if (frontendUrl) args += mdm ? ` --frontend-url ${shellEscape(frontendUrl)}` : ` --domain ${shellEscape(frontendUrl)}`;
254
255
  if (clear) args += ' --clear';
255
256
  if (backfill) args += ' --backfill';
256
257
  return args.trim();
@@ -819,9 +820,11 @@ Clear examples (no API key required):
819
820
  // UNBOUND_*_URL can't shadow the explicit --*-url flag.
820
821
  const written = config.setUrls({
821
822
  backend: globalOpts.backendUrl,
823
+ frontend: globalOpts.frontendUrl,
822
824
  gateway: globalOpts.gatewayUrl,
823
825
  });
824
826
  const backendUrl = written.base_url || config.getBaseUrl();
827
+ const frontendUrl = written.frontend_url || config.getFrontendUrl();
825
828
  const gatewayUrl = written.gateway_url || config.getGatewayUrl();
826
829
 
827
830
  if (globalOpts.all && tools.length > 0) {
@@ -890,6 +893,7 @@ Clear examples (no API key required):
890
893
  (tool) => {
891
894
  const toolArgs = buildScriptArgs(adminApiKey, {
892
895
  backendUrl,
896
+ frontendUrl,
893
897
  gatewayUrl,
894
898
  clear: globalOpts.clear,
895
899
  mdm: true,
@@ -1030,7 +1034,7 @@ async function runSetupAllBundle(apiKey, { backendUrl, frontendUrl, gatewayUrl,
1030
1034
  * Caller must ensure the process is running as root.
1031
1035
  * Returns true on success, false on failure.
1032
1036
  */
1033
- async function runMdmSetupAllBundle(adminApiKey, { backendUrl, gatewayUrl, backfill = false } = {}) {
1037
+ async function runMdmSetupAllBundle(adminApiKey, { backendUrl, frontendUrl, gatewayUrl, backfill = false } = {}) {
1034
1038
  const resolvedTools = MDM_ALL_TOOLS.map(name => ({ name, ...MDM_TOOLS[name] }));
1035
1039
  if (backfill) {
1036
1040
  for (const tool of resolvedTools) {
@@ -1039,7 +1043,7 @@ async function runMdmSetupAllBundle(adminApiKey, { backendUrl, gatewayUrl, backf
1039
1043
  }
1040
1044
  return runBatch(resolvedTools, (tool) => {
1041
1045
  const args = buildScriptArgs(adminApiKey, {
1042
- backendUrl, gatewayUrl, mdm: true,
1046
+ backendUrl, frontendUrl, gatewayUrl, mdm: true,
1043
1047
  backfill: backfill && scriptSupportsBackfill(tool.script),
1044
1048
  });
1045
1049
  return runScriptPiped(tool.script, args);
@@ -70,14 +70,16 @@ test('scriptSupportsBackfill: cursor and gateway-mode scripts are unsupported',
70
70
  assert.ok(!scriptSupportsBackfill('gemini-cli/gateway/setup.py'));
71
71
  });
72
72
 
73
- // MDM scripts have no browser-auth flow, so --domain (frontend URL) is
74
- // suppressed even when a frontendUrl is supplied.
75
- test('buildScriptArgs: mdm:true suppresses --domain even with frontendUrl', () => {
73
+ // MDM scripts have no browser-auth flow, so the frontend URL goes via
74
+ // --frontend-url (persist-only), never --domain. It must still be passed so
75
+ // frontend_url lands in each user's ~/.unbound/config.json.
76
+ test('buildScriptArgs: mdm:true passes frontend as --frontend-url, not --domain', () => {
76
77
  const args = buildScriptArgs('sk-x', {
77
78
  mdm: true,
78
79
  frontendUrl: 'https://gateway.acme.com',
79
80
  });
80
81
  assert.ok(!args.includes('--domain'), args);
82
+ assert.ok(args.includes("--frontend-url 'https://gateway.acme.com'"), args);
81
83
  });
82
84
 
83
85
  test('buildScriptArgs: non-mdm includes --domain when frontendUrl passed', () => {