@jacobbd/relay-ai 0.6.0 → 0.6.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.
@@ -7,7 +7,7 @@ import {
7
7
  listAddableTemplates,
8
8
  listSupportedTemplates,
9
9
  listVisibleOAuthTemplates
10
- } from "./chunk-MVBA7ABV.js";
10
+ } from "./chunk-EJONCU3B.js";
11
11
  init_provider_templates();
12
12
  export {
13
13
  PROVIDER_TEMPLATES,
@@ -17,4 +17,4 @@ export {
17
17
  listSupportedTemplates,
18
18
  listVisibleOAuthTemplates
19
19
  };
20
- //# sourceMappingURL=provider-templates-6XYKAZB5.js.map
20
+ //# sourceMappingURL=provider-templates-BPGB5V2L.js.map
@@ -53,6 +53,7 @@ const state = {
53
53
  listenMode: 'local', // 'local' | 'network'
54
54
  passwordMode: 'new', // 'saved' | 'new'
55
55
  password: '',
56
+ passwordVisible: false,
56
57
  savePassword: false,
57
58
  },
58
59
  },
@@ -1059,6 +1060,8 @@ function buildProviderBodyContent(provider) {
1059
1060
  const countEl = document.querySelector(`[data-id="${CSS.escape(provider.id)}"] .provider-models-count`);
1060
1061
  if (countEl) countEl.textContent = `${result.count} models`;
1061
1062
  provider.modelCount = result.count;
1063
+ state.modelsLoaded = false;
1064
+ await initModels();
1062
1065
  } else {
1063
1066
  feedback.textContent = result.error ?? 'Refresh failed';
1064
1067
  feedback.className = 'key-feedback error';
@@ -1179,6 +1182,8 @@ function buildOAuthProviderBodyContent(provider) {
1179
1182
  feedback.className = 'key-feedback success';
1180
1183
  const countEl = document.querySelector(`[data-id="${CSS.escape(provider.id)}"] .provider-models-count`);
1181
1184
  if (countEl) countEl.textContent = `${result.count} models`;
1185
+ state.modelsLoaded = false;
1186
+ await initModels();
1182
1187
  } else {
1183
1188
  feedback.textContent = result.error ?? 'Refresh failed';
1184
1189
  feedback.className = 'key-feedback error';
@@ -1612,6 +1617,18 @@ async function initModels() {
1612
1617
  state.modelsError = String(err);
1613
1618
  state.modelsLoaded = true;
1614
1619
  }
1620
+ // Keep the Providers header counts in sync after add / delete / OAuth / refresh.
1621
+ renderProviderStats();
1622
+ // Server tab caches its provider checklist — invalidate so new providers appear without a full reload.
1623
+ invalidateServerProviderCache();
1624
+ }
1625
+
1626
+ function invalidateServerProviderCache() {
1627
+ state.server.form.providersLoaded = false;
1628
+ if (state.server.status?.running) return;
1629
+ if (state.server.form.expose !== 'specific') return;
1630
+ if (!document.getElementById('server-panel')) return;
1631
+ void loadServerProviders().then(() => renderServerPanel());
1615
1632
  }
1616
1633
 
1617
1634
  async function init() {
@@ -2264,7 +2281,14 @@ function seedServerFormFromSaved(saved) {
2264
2281
  f.maskGatewayIds = saved.maskGatewayIds;
2265
2282
  // Published Docker ports only reach 0.0.0.0 inside the container.
2266
2283
  f.listenMode = isServerAdminUi() || saved.listenMode === 'network' ? 'network' : 'local';
2267
- f.passwordMode = saved.hasSavedPassword ? 'saved' : 'new';
2284
+ // Env password (Docker): prefill the field so Start works and clients can copy it.
2285
+ // Keychain-saved password: keep "Use saved" (value never sent to the browser).
2286
+ if (saved.prefillPassword && !f.password) {
2287
+ f.password = saved.prefillPassword;
2288
+ f.passwordMode = 'new';
2289
+ } else {
2290
+ f.passwordMode = saved.hasSavedPassword ? 'saved' : 'new';
2291
+ }
2268
2292
  if (f.expose === 'specific') loadServerProviders().then(renderServerPanel);
2269
2293
  }
2270
2294
 
@@ -2305,6 +2329,10 @@ function renderServerPanel() {
2305
2329
  panel.innerHTML = '<div class="skeleton skeleton-card"></div>';
2306
2330
  return;
2307
2331
  }
2332
+ // Lazy-load (or re-load after invalidate) when the specific-provider checklist is visible.
2333
+ if (!s.status.running && s.form.expose === 'specific' && !s.form.providersLoaded) {
2334
+ void loadServerProviders().then(() => renderServerPanel());
2335
+ }
2308
2336
  panel.innerHTML = s.status.running ? renderServerRunning(s.status) : renderServerSetup(s);
2309
2337
  }
2310
2338
 
@@ -2364,16 +2392,22 @@ function renderServerNetworkOptions() {
2364
2392
  const s = state.server;
2365
2393
  const f = s.form;
2366
2394
  const hasSaved = Boolean(s.status?.saved?.hasSavedPassword);
2395
+ const hasEnv = Boolean(s.status?.saved?.hasEnvPassword);
2367
2396
  const showPasswordInput = f.passwordMode === 'new' || !hasSaved;
2397
+ const inputType = f.passwordVisible ? 'text' : 'password';
2368
2398
 
2369
2399
  return `
2370
2400
  <div class="server-network-options">
2371
- ${hasSaved ? toggleGroupHtml([
2401
+ ${hasSaved && !hasEnv ? toggleGroupHtml([
2372
2402
  { label: 'Use saved password', active: f.passwordMode === 'saved', onClick: "setServerPasswordMode('saved')" },
2373
2403
  { label: 'Enter new password', active: f.passwordMode === 'new', onClick: "setServerPasswordMode('new')" },
2374
2404
  ]) : ''}
2405
+ ${hasEnv && f.passwordMode === 'new' ? '<div class="server-field-hint">Prefilled from RELAY_AI_SERVER_PASSWORD</div>' : ''}
2375
2406
  ${showPasswordInput ? `
2376
- <input type="password" class="key-input" placeholder="Server password" value="${escapeHtml(f.password)}" oninput="setServerPassword(this.value)">
2407
+ <div class="server-password-row">
2408
+ <input type="${inputType}" class="key-input" id="server-password-input" placeholder="Server password" value="${escapeHtml(f.password)}" oninput="setServerPassword(this.value)" autocomplete="off">
2409
+ <button type="button" class="btn btn-ghost server-password-reveal" onclick="toggleServerPasswordVisible()" aria-pressed="${f.passwordVisible ? 'true' : 'false'}">${f.passwordVisible ? 'Hide' : 'Reveal'}</button>
2410
+ </div>
2377
2411
  <label class="server-toggle-row">
2378
2412
  <input type="checkbox" ${f.savePassword ? 'checked' : ''} onchange="setServerSavePassword(this.checked)">
2379
2413
  <span>Save this password for next time</span>
@@ -2581,6 +2615,12 @@ function setServerPassword(value) {
2581
2615
  state.server.form.password = value;
2582
2616
  }
2583
2617
 
2618
+ function toggleServerPasswordVisible() {
2619
+ state.server.form.passwordVisible = !state.server.form.passwordVisible;
2620
+ renderServerPanel();
2621
+ document.getElementById('server-password-input')?.focus();
2622
+ }
2623
+
2584
2624
  function setServerSavePassword(checked) {
2585
2625
  state.server.form.savePassword = checked;
2586
2626
  }
@@ -2676,6 +2716,7 @@ window.setServerFreeModelsOnly = setServerFreeModelsOnly;
2676
2716
  window.setServerListenMode = setServerListenMode;
2677
2717
  window.setServerPasswordMode = setServerPasswordMode;
2678
2718
  window.setServerPassword = setServerPassword;
2719
+ window.toggleServerPasswordVisible = toggleServerPasswordVisible;
2679
2720
  window.setServerSavePassword = setServerSavePassword;
2680
2721
  window.submitServerStart = submitServerStart;
2681
2722
  window.stopServerGateway = stopServerGateway;
@@ -1876,6 +1876,22 @@ input { font-family: inherit; }
1876
1876
  gap: 10px;
1877
1877
  }
1878
1878
 
1879
+ .server-password-row {
1880
+ display: flex;
1881
+ align-items: center;
1882
+ gap: 8px;
1883
+ }
1884
+
1885
+ .server-password-row .key-input {
1886
+ flex: 1;
1887
+ min-width: 0;
1888
+ }
1889
+
1890
+ .server-password-reveal {
1891
+ flex-shrink: 0;
1892
+ white-space: nowrap;
1893
+ }
1894
+
1879
1895
  .server-provider-chips {
1880
1896
  display: flex;
1881
1897
  flex-wrap: wrap;
@@ -25,6 +25,7 @@ import {
25
25
  gatewayProviderLabel,
26
26
  getAppHome,
27
27
  getAppPathOverride,
28
+ getEnvServerPassword,
28
29
  getSavedServerPassword,
29
30
  getServerExposedProviders,
30
31
  getServerFavoritesOnly,
@@ -71,14 +72,14 @@ import {
71
72
  supportsClaudeTransparentMode,
72
73
  validateCustomEndpointUrl,
73
74
  writeSecureLogLine
74
- } from "./chunk-SV2Y6OCD.js";
75
+ } from "./chunk-P4S42QJK.js";
75
76
  import {
76
77
  __toCommonJS,
77
78
  init_provider_templates,
78
79
  listAddableTemplates,
79
80
  listVisibleOAuthTemplates,
80
81
  provider_templates_exports
81
- } from "./chunk-MVBA7ABV.js";
82
+ } from "./chunk-EJONCU3B.js";
82
83
 
83
84
  // src/ui-command.ts
84
85
  import { createServer } from "http";
@@ -385,13 +386,16 @@ function buildModelRows(models, gateway) {
385
386
  return rows.sort((a, b) => a.providerLabel.localeCompare(b.providerLabel) || a.name.localeCompare(b.name));
386
387
  }
387
388
  async function buildSavedConfig() {
389
+ const envPassword = getEnvServerPassword();
388
390
  return {
389
391
  favoritesOnly: getServerFavoritesOnly(),
390
392
  freeModelsOnly: getServerFreeModelsOnly(),
391
393
  exposedProviders: getServerExposedProviders(),
392
394
  maskGatewayIds: getServerMaskGatewayIds(),
393
395
  listenMode: getServerListenMode(),
394
- hasSavedPassword: await hasSavedPasswordCached()
396
+ hasSavedPassword: await hasSavedPasswordCached(),
397
+ hasEnvPassword: Boolean(envPassword),
398
+ ...envPassword ? { prefillPassword: envPassword } : {}
395
399
  };
396
400
  }
397
401
  async function getServerStatus(opts) {
@@ -445,16 +449,26 @@ async function doStartGatewayServer(req, opts) {
445
449
  let serverPassword = null;
446
450
  if (req.listenMode === "network") {
447
451
  if (req.passwordMode === "saved") {
448
- const saved = await getSavedServerPassword();
449
- if (!saved) return { ok: false, error: "No saved password found \u2014 enter a new password." };
450
- serverPassword = saved;
452
+ const configured = await getSavedServerPassword() ?? getEnvServerPassword();
453
+ if (!configured) {
454
+ return {
455
+ ok: false,
456
+ error: "No configured password found \u2014 set RELAY_AI_SERVER_PASSWORD, or enter a new password."
457
+ };
458
+ }
459
+ serverPassword = configured;
451
460
  } else {
452
461
  const trimmed = (req.password ?? "").trim();
453
- if (!trimmed) return { ok: false, error: "A server password is required for network mode." };
454
- serverPassword = trimmed;
455
- if (req.savePassword) {
456
- await setSavedServerPassword(trimmed);
457
- hasSavedPasswordCache = { value: true, expiresAt: Date.now() + SAVED_PASSWORD_CACHE_TTL_MS };
462
+ if (!trimmed) {
463
+ const configured = getEnvServerPassword() ?? await getSavedServerPassword();
464
+ if (!configured) return { ok: false, error: "A server password is required for network mode." };
465
+ serverPassword = configured;
466
+ } else {
467
+ serverPassword = trimmed;
468
+ if (req.savePassword) {
469
+ await setSavedServerPassword(trimmed);
470
+ hasSavedPasswordCache = { value: true, expiresAt: Date.now() + SAVED_PASSWORD_CACHE_TTL_MS };
471
+ }
458
472
  }
459
473
  }
460
474
  }
@@ -807,7 +821,7 @@ async function handleAddProvider(req, res) {
807
821
  sendJson(res, 400, { error: "templateId required" });
808
822
  return;
809
823
  }
810
- const { listSupportedTemplates } = await import("./provider-templates-6XYKAZB5.js");
824
+ const { listSupportedTemplates } = await import("./provider-templates-BPGB5V2L.js");
811
825
  const template = listSupportedTemplates().find((t) => t.id === templateId);
812
826
  if (!template) {
813
827
  sendJson(res, 404, { error: `Template '${templateId}' not found` });
@@ -1564,4 +1578,4 @@ export {
1564
1578
  resolveUiShutdownDecision,
1565
1579
  runUiCommand
1566
1580
  };
1567
- //# sourceMappingURL=ui-command-DJZIIIWC.js.map
1581
+ //# sourceMappingURL=ui-command-EQJIZRZZ.js.map