@ai-ecoverse/slicc-spectrum 1.92.0 → 1.94.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/dist/slicc-ui.js CHANGED
@@ -9349,7 +9349,7 @@ function themeOf(node) {
9349
9349
  }
9350
9350
  return document.querySelector("slicc-app")?.shadowRoot?.querySelector("sp-theme") ?? document.body;
9351
9351
  }
9352
- var dialogStyles = i`
9352
+ var dialogFrame = i`
9353
9353
  dialog {
9354
9354
  box-sizing: border-box;
9355
9355
  max-inline-size: calc(100vw - 2 * var(--swc-spacing-300));
@@ -9369,6 +9369,13 @@ var dialogStyles = i`
9369
9369
  color-mix(in srgb, var(--swc-overlay-color) calc(var(--swc-overlay-opacity-dark) * 100%), transparent)
9370
9370
  );
9371
9371
  }
9372
+ @media (forced-colors: active) {
9373
+ dialog {
9374
+ outline: solid;
9375
+ }
9376
+ }
9377
+ `;
9378
+ var alertStyles = i`
9372
9379
  sp-alert-dialog {
9373
9380
  --mod-alert-dialog-min-width: min(
9374
9381
  var(--swc-alert-dialog-minimum-width),
@@ -9390,9 +9397,6 @@ var dialogStyles = i`
9390
9397
  }
9391
9398
  }
9392
9399
  @media (forced-colors: active) {
9393
- dialog {
9394
- outline: solid;
9395
- }
9396
9400
  sp-alert-dialog {
9397
9401
  border: none;
9398
9402
  }
@@ -9409,6 +9413,7 @@ var dialogStyles = i`
9409
9413
  margin-block-end: var(--swc-spacing-75);
9410
9414
  }
9411
9415
  `;
9416
+ var dialogStyles = [dialogFrame, alertStyles];
9412
9417
  var SliccConfirm = class extends i2 {
9413
9418
  static properties = { heading: {}, body: {}, action: {}, variant: {}, cancel: {} };
9414
9419
  trigger = null;
@@ -22649,6 +22654,34 @@ function markdown(text2) {
22649
22654
  return b`${parse(text2).map(render)}`;
22650
22655
  }
22651
22656
 
22657
+ // src/app/sign-in.ts
22658
+ var continuing = "Continue in the sign-in window that opened.";
22659
+ var closed = "Closed the sign-in window? Cancel, then Log in again.";
22660
+ var signInWait = { closedAfter: 2e4 };
22661
+ function signInText(waited) {
22662
+ return waited ? closed : continuing;
22663
+ }
22664
+ var SignInWaits = class {
22665
+ #since = /* @__PURE__ */ new Map();
22666
+ #update;
22667
+ constructor(update) {
22668
+ this.#update = update;
22669
+ }
22670
+ waited(key2) {
22671
+ const now = Date.now();
22672
+ const since3 = this.#since.get(key2);
22673
+ if (since3 === void 0) {
22674
+ this.#since.set(key2, now);
22675
+ setTimeout(this.#update, signInWait.closedAfter);
22676
+ return false;
22677
+ }
22678
+ return now - since3 >= signInWait.closedAfter;
22679
+ }
22680
+ done(key2) {
22681
+ this.#since.delete(key2);
22682
+ }
22683
+ };
22684
+
22652
22685
  // src/app/messages.ts
22653
22686
  var lickLabels = {
22654
22687
  webhook: ["Webhook", "informative"],
@@ -22843,8 +22876,6 @@ function actionLabel(action, handlers, messageId) {
22843
22876
  return count2 > 1 ? `Drop ${count2} failed turns` : actionLabels[action];
22844
22877
  }
22845
22878
  var signedIn = "Signed in. Retry to send it again.";
22846
- var continuing = "Continue in the sign-in window that opened.";
22847
- var closed = "Closed the sign-in window? Cancel, then Log in again.";
22848
22879
  function stopSigningIn(event2, handlers, account, id) {
22849
22880
  const main2 = event2.currentTarget.nextElementSibling;
22850
22881
  main2.focus();
@@ -22863,7 +22894,7 @@ function errorCard(item2, handlers) {
22863
22894
  const login = fix === "login" && item2.account && !handlers.readOnly ? handlers.login?.(item2.account, id) : void 0;
22864
22895
  const resolved = login?.state === "connected";
22865
22896
  const signing = login?.state === "signing-in" && login.window;
22866
- const text2 = resolved ? signedIn : signing ? login.waited ? closed : continuing : item2.text;
22897
+ const text2 = resolved ? signedIn : signing ? signInText(login.waited) : item2.text;
22867
22898
  const title = resolved && login.provider ? `Signed in to ${login.provider} again.` : item2.title;
22868
22899
  const failure2 = login?.state === "idle" ? login.error : void 0;
22869
22900
  return b`<div class="error-card" role=${resolved ? A : "alert"} aria-atomic=${resolved ? A : "false"} data-id=${id || A} data-login=${login?.state ?? A}>
@@ -24701,7 +24732,7 @@ document.addEventListener(
24701
24732
  },
24702
24733
  true
24703
24734
  );
24704
- var SliccChat = class _SliccChat extends ThemedElement {
24735
+ var SliccChat = class extends ThemedElement {
24705
24736
  static properties = {
24706
24737
  ...ThemedElement.properties,
24707
24738
  agent: {},
@@ -25125,19 +25156,14 @@ var SliccChat = class _SliccChat extends ThemedElement {
25125
25156
  if (!restored) return;
25126
25157
  this.renderRoot.querySelector("slicc-composer")?.restore(latest ? { text: latest.text, attachments: latest.attachments } : restored);
25127
25158
  }
25128
- static closedAfter = 2e4;
25129
- #logins = /* @__PURE__ */ new Map();
25130
- #signing = /* @__PURE__ */ new Map();
25131
- #waited(key2) {
25132
- const now = Date.now();
25133
- const since3 = this.#signing.get(key2);
25134
- if (since3 === void 0) {
25135
- this.#signing.set(key2, now);
25136
- setTimeout(() => this.requestUpdate(), _SliccChat.closedAfter);
25137
- return false;
25138
- }
25139
- return now - since3 >= _SliccChat.closedAfter;
25159
+ static get closedAfter() {
25160
+ return signInWait.closedAfter;
25140
25161
  }
25162
+ static set closedAfter(after) {
25163
+ signInWait.closedAfter = after;
25164
+ }
25165
+ #logins = /* @__PURE__ */ new Map();
25166
+ #waits = new SignInWaits(() => this.requestUpdate());
25141
25167
  #loginState(accountId, messageId) {
25142
25168
  const settings = this.model.settings;
25143
25169
  const account = settings.accounts().find((candidate) => candidate.id === accountId);
@@ -25146,8 +25172,8 @@ var SliccChat = class _SliccChat extends ThemedElement {
25146
25172
  const window2 = account?.auth === "oauth";
25147
25173
  const key2 = `${messageId}:${accountId}`;
25148
25174
  if (account?.status === "signing-in" || local2?.pending)
25149
- return { state: "signing-in", cancel, window: window2, waited: window2 && this.#waited(key2) };
25150
- this.#signing.delete(key2);
25175
+ return { state: "signing-in", cancel, window: window2, waited: window2 && this.#waits.waited(key2) };
25176
+ this.#waits.done(key2);
25151
25177
  if (account?.status === "connected") return { state: "connected", provider: account.provider };
25152
25178
  return { state: "idle", error: local2?.error };
25153
25179
  }
@@ -25988,13 +26014,13 @@ var SliccFileView = class extends ThemedElement {
25988
26014
  async save() {
25989
26015
  const model = this.model;
25990
26016
  if (!model || this.draft === null) return;
25991
- const draft = this.draft;
26017
+ const draft2 = this.draft;
25992
26018
  this.saving = true;
25993
26019
  try {
25994
- await model.files.write(this.path, draft);
25995
- this.text = draft;
26020
+ await model.files.write(this.path, draft2);
26021
+ this.text = draft2;
25996
26022
  this.missing = false;
25997
- if (this.draft === draft) this.draft = null;
26023
+ if (this.draft === draft2) this.draft = null;
25998
26024
  } finally {
25999
26025
  this.saving = false;
26000
26026
  }
@@ -31336,8 +31362,8 @@ var Panels = class {
31336
31362
  };
31337
31363
  }
31338
31364
  find(id) {
31339
- const listed = this.list().find((info) => info.id === id);
31340
- if (listed) return listed;
31365
+ const listed2 = this.list().find((info) => info.id === id);
31366
+ if (listed2) return listed2;
31341
31367
  const document2 = id.match(/^(file|diff):(.+)$/);
31342
31368
  if (!document2) return void 0;
31343
31369
  return this.#state({
@@ -40422,7 +40448,7 @@ var SliccComposer = class extends ThemedElement {
40422
40448
  clearValues(this.renderRoot);
40423
40449
  const held = [
40424
40450
  this.attachments,
40425
- ...[...this.#drafts.values()].map((draft) => draft.attachments)
40451
+ ...[...this.#drafts.values()].map((draft2) => draft2.attachments)
40426
40452
  ];
40427
40453
  if (held.flat().some((attachment2) => attachment2.url?.startsWith("blob:")))
40428
40454
  this.#abandon = setTimeout(() => {
@@ -40478,10 +40504,10 @@ var SliccComposer = class extends ThemedElement {
40478
40504
  #switch(id) {
40479
40505
  if (this.#agent)
40480
40506
  this.#drafts.set(this.#agent, { value: this.value, attachments: this.attachments });
40481
- const draft = this.#drafts.get(id);
40507
+ const draft2 = this.#drafts.get(id);
40482
40508
  this.#agent = id;
40483
- this.value = draft?.value ?? "";
40484
- this.attachments = draft?.attachments ?? [];
40509
+ this.value = draft2?.value ?? "";
40510
+ this.attachments = draft2?.attachments ?? [];
40485
40511
  this.popup = null;
40486
40512
  this.#history = -1;
40487
40513
  }
@@ -40534,8 +40560,8 @@ var SliccComposer = class extends ThemedElement {
40534
40560
  this.failure = null;
40535
40561
  this.#history = -1;
40536
40562
  model.agent.send(this.#agent, text2).catch((error) => {
40537
- const draft = this.value.trim();
40538
- this.value = draft && draft !== text2 ? `${text2}
40563
+ const draft2 = this.value.trim();
40564
+ this.value = draft2 && draft2 !== text2 ? `${text2}
40539
40565
  ${this.value}` : text2;
40540
40566
  this.failure = error instanceof Error ? error.message : String(error);
40541
40567
  });
@@ -40860,8 +40886,8 @@ ${this.value}` : text2;
40860
40886
  }
40861
40887
  async #paste(event2) {
40862
40888
  const data = event2.clipboardData;
40863
- const listed = [...data?.files ?? []];
40864
- const files = listed.length ? listed : [...data?.items ?? []].flatMap((item2) => {
40889
+ const listed2 = [...data?.files ?? []];
40890
+ const files = listed2.length ? listed2 : [...data?.items ?? []].flatMap((item2) => {
40865
40891
  const file = item2.kind === "file" ? item2.getAsFile() : null;
40866
40892
  return file ? [file] : [];
40867
40893
  });
@@ -41839,7 +41865,28 @@ function changes(entry, devices) {
41839
41865
  return entry.changes.map((change) => [change.kind, changeLine(change, devices)]);
41840
41866
  return (entry.widened ?? []).map((change) => [change.kind, widening(change, devices)]);
41841
41867
  }
41842
- function auditLine(entry, devices) {
41868
+ var listed = (items, one2) => items.map((item2, index) => b`${index ? ", " : ""}${one2(item2)}`);
41869
+ function endpointsLine(entry, named2 = (provider) => provider) {
41870
+ const parts2 = [];
41871
+ if (entry.purged?.length)
41872
+ parts2.push(
41873
+ b`<span class="part" data-part="purged">Removed custom model URLs an agent set, automatically: ${listed(entry.purged, (item2) => b`${named2(item2.provider)} at <code>${item2.host ?? item2.url}</code>`)}. Only you can set a model URL now, in Settings › Accounts</span>`
41874
+ );
41875
+ if (entry.adopted?.length)
41876
+ parts2.push(
41877
+ b`<span class="part" data-part="adopted">Kept the MCP servers your tokens use, automatically: ${listed(entry.adopted, (item2) => b`${item2.server} at <code>${item2.host}</code>`)}</span>`
41878
+ );
41879
+ if (entry.pinned)
41880
+ parts2.push(
41881
+ b`<span class="part" data-part="pinned"><code>${entry.pinned.name ?? entry.pinned.server}</code> pinned to <code>${entry.pinned.host}</code> automatically, on first use</span>`
41882
+ );
41883
+ if (entry.unpinned)
41884
+ parts2.push(
41885
+ b`<span class="part" data-part="unpinned"><code>${entry.unpinned.name ?? entry.unpinned.server}</code> unpinned from <code>${entry.unpinned.host}</code>; the next use pins it again</span>`
41886
+ );
41887
+ return b`${parts2.map((part2, index) => b`${index ? ". " : ""}${part2}`)}`;
41888
+ }
41889
+ function auditLine(entry, devices, named2) {
41843
41890
  switch (entry.type) {
41844
41891
  case "inject":
41845
41892
  case "forward":
@@ -41855,7 +41902,7 @@ function auditLine(entry, devices) {
41855
41902
  case "restore":
41856
41903
  return b`Restored`;
41857
41904
  case "endpoints":
41858
- return b`Removed custom model URLs an agent set, automatically: ${entry.purged.map((item2, index) => b`${index ? ", " : ""}${item2.provider} <code>${item2.url}</code>`)}`;
41905
+ return endpointsLine(entry, named2);
41859
41906
  case "refresh":
41860
41907
  return entry.grant ? b`Refreshed automatically with the sign-in <code>${entry.grant}</code>` : b`Refreshed automatically`;
41861
41908
  default:
@@ -41912,6 +41959,9 @@ var SliccSecrets = class extends ThemedElement {
41912
41959
  failure: { state: true },
41913
41960
  form: { state: true },
41914
41961
  busy: { state: true },
41962
+ unpinning: { state: true },
41963
+ unpinned: { state: true },
41964
+ unpinFailure: { state: true },
41915
41965
  problem: { state: true },
41916
41966
  invalid: { state: true },
41917
41967
  said: { state: true },
@@ -41934,6 +41984,9 @@ var SliccSecrets = class extends ThemedElement {
41934
41984
  this.failure = null;
41935
41985
  this.form = null;
41936
41986
  this.busy = false;
41987
+ this.unpinning = null;
41988
+ this.unpinned = /* @__PURE__ */ new Set();
41989
+ this.unpinFailure = null;
41937
41990
  this.problem = null;
41938
41991
  this.invalid = null;
41939
41992
  this.said = null;
@@ -42196,6 +42249,13 @@ var SliccSecrets = class extends ThemedElement {
42196
42249
  .audit .hint {
42197
42250
  display: block;
42198
42251
  }
42252
+ .audit .unpin {
42253
+ display: flex;
42254
+ flex-wrap: wrap;
42255
+ align-items: center;
42256
+ gap: var(--swc-spacing-200);
42257
+ margin-top: var(--swc-spacing-100);
42258
+ }
42199
42259
  .row {
42200
42260
  display: flex;
42201
42261
  flex-wrap: wrap;
@@ -42257,6 +42317,9 @@ var SliccSecrets = class extends ThemedElement {
42257
42317
  this.failure = null;
42258
42318
  this.form = null;
42259
42319
  this.busy = false;
42320
+ this.unpinning = null;
42321
+ this.unpinned = /* @__PURE__ */ new Set();
42322
+ this.unpinFailure = null;
42260
42323
  this.problem = null;
42261
42324
  this.invalid = null;
42262
42325
  this.said = null;
@@ -42741,12 +42804,42 @@ var SliccSecrets = class extends ThemedElement {
42741
42804
  ${refused2.map((item2) => b`<li data-refused-secret=${item2.secret}><span><code>${item2.secret}</code> to ${ruleLine(item2.rule)}</span><span class="muted">${item2.reason}</span></li>`)}
42742
42805
  </ul>`;
42743
42806
  }
42744
- #entry(entry, devices) {
42807
+ #provider(id) {
42808
+ const settings = this.model?.settings;
42809
+ const option2 = settings?.providers?.().find((item2) => item2.id === id);
42810
+ const account = settings?.accounts().find((item2) => item2.id === id);
42811
+ return option2?.name ?? account?.provider ?? id;
42812
+ }
42813
+ #unpin(vault, entry, all2) {
42814
+ const name = entry.pinned?.name;
42815
+ if (!name || !vault.unpin) return A;
42816
+ const key2 = `unpin:${entry.seq}`;
42817
+ const undone = all2.some(
42818
+ (later) => later.type === "endpoints" && later.seq > entry.seq && later.unpinned?.name === name
42819
+ );
42820
+ if (undone || this.unpinned.has(key2)) return A;
42821
+ const busy = this.unpinning === key2;
42822
+ return b`<span class="unpin"><swc-button size="s" variant="secondary" fill-style="outline" data-action="unpin" ?pending=${busy} ?disabled=${!!vault.status()?.locked} @click=${() => this.#unpinNow(vault, name, key2)}>Unpin</swc-button>${this.unpinFailure?.key === key2 ? b`<span class="warning" role="alert" data-error="unpin">${this.unpinFailure.message}</span>` : A}</span>`;
42823
+ }
42824
+ async #unpinNow(vault, name, key2) {
42825
+ if (this.unpinning) return;
42826
+ this.unpinning = key2;
42827
+ this.unpinFailure = null;
42828
+ try {
42829
+ await vault.unpin?.([name]);
42830
+ if (this.#mine(vault)) this.unpinned = /* @__PURE__ */ new Set([...this.unpinned, key2]);
42831
+ } catch (error) {
42832
+ if (this.#mine(vault)) this.unpinFailure = { key: key2, message: failureText(error) };
42833
+ } finally {
42834
+ if (this.#mine(vault)) this.unpinning = null;
42835
+ }
42836
+ }
42837
+ #entry(entry, devices, vault, all2 = []) {
42745
42838
  const date = new Date(entry.at);
42746
42839
  const name = entry.type === "policy" || entry.type === "endpoints" ? null : entry.name;
42747
42840
  return b`<li data-seq=${entry.seq} data-type=${entry.type}>
42748
42841
  <time datetime=${date.toISOString()} title=${date.toLocaleString("en-GB", { dateStyle: "medium", timeStyle: "short" })}>${since(entry.at)}</time>
42749
- <span class="what">${name ? b`<code>${name}</code> · ` : A}${auditLine(entry, devices)}${entry.type === "policy" || entry.type === "refresh" || entry.type === "endpoints" ? A : b` <span class="by">by ${who(entry)}</span>`}</span>
42842
+ <span class="what">${name ? b`<code>${name}</code> · ` : A}${auditLine(entry, devices, (provider) => this.#provider(provider))}${entry.type === "policy" || entry.type === "refresh" || entry.type === "endpoints" ? A : b` <span class="by">by ${who(entry)}</span>`}${entry.type === "endpoints" && vault ? this.#unpin(vault, entry, all2) : A}</span>
42750
42843
  </li>`;
42751
42844
  }
42752
42845
  #audit(vault, list5, entries, devices) {
@@ -42758,7 +42851,7 @@ var SliccSecrets = class extends ThemedElement {
42758
42851
  this.reveals = event2.target.checked;
42759
42852
  }}>Only reveals to programs</sp-switch>
42760
42853
  </div>
42761
- ${shown4.length ? b`<ol class="audit" aria-labelledby="secrets-audit">${shown4.map((entry) => this.#entry(entry, devices))}</ol>` : b`<p class="muted" data-audit-empty>${this.reveals ? more ? "No reveals in the activity loaded so far." : "No program has been given a secret." : "Nothing has used a secret yet."}</p>`}
42854
+ ${shown4.length ? b`<ol class="audit" aria-labelledby="secrets-audit">${shown4.map((entry) => this.#entry(entry, devices, vault, entries))}</ol>` : b`<p class="muted" data-audit-empty>${this.reveals ? more ? "No reveals in the activity loaded so far." : "No program has been given a secret." : "Nothing has used a secret yet."}</p>`}
42762
42855
  ${more ? b`<div class="row"><swc-button size="s" variant="secondary" fill-style="outline" data-action="older" ?pending=${this.loading} @click=${() => this.showOlder(vault, entries)}>Show older</swc-button></div>` : A}`;
42763
42856
  }
42764
42857
  #feedback() {
@@ -42873,6 +42966,7 @@ function recordKind(account) {
42873
42966
  return account.group === "mcp" ? "mcp" : "model";
42874
42967
  }
42875
42968
  function recordName(account) {
42969
+ if (account.vaultName) return account.vaultName;
42876
42970
  if (account.group === "mcp") return `MCP_${upper(serverOf(account))}_TOKEN`;
42877
42971
  return envNames[account.id] ?? `${upper(account.id)}_API_KEY`;
42878
42972
  }
@@ -42887,6 +42981,48 @@ var accountStatus = {
42887
42981
  "needs-sign-in": ["neutral", "Needs sign-in"],
42888
42982
  "sign-in-expired": ["notice", "Sign-in expired"]
42889
42983
  };
42984
+ var authHints = {
42985
+ "api-key": "API key",
42986
+ oauth: "Sign in",
42987
+ fields: "Endpoint and key",
42988
+ local: "On this device"
42989
+ };
42990
+ var listOrder = {
42991
+ connected: 0,
42992
+ expired: 1,
42993
+ "signing-in": 2,
42994
+ disconnected: 3
42995
+ };
42996
+ var corsNotice = {
42997
+ short: "Needs the SLICC extension or CLI",
42998
+ long: "Needs the SLICC extension or CLI, because the browser blocks direct requests to it."
42999
+ };
43000
+ var removeBodies = {
43001
+ oauth: "Its sign-in is deleted from the vault. Models from it stop working.",
43002
+ local: "Models from it stop working."
43003
+ };
43004
+ function draft(option2) {
43005
+ return {
43006
+ id: option2.id,
43007
+ provider: option2.name,
43008
+ identity: "",
43009
+ status: "disconnected",
43010
+ group: "providers",
43011
+ store: "vault",
43012
+ auth: option2.auth === "fields" ? "api-key" : option2.auth,
43013
+ fields: option2.fields,
43014
+ keyUrl: option2.keyUrl,
43015
+ needs: option2.needs,
43016
+ vaultName: option2.vaultName
43017
+ };
43018
+ }
43019
+ function matchProviders(options, query) {
43020
+ const words = query.trim().toLowerCase().split(/\s+/).filter(Boolean);
43021
+ return options.filter((option2) => {
43022
+ const text2 = `${option2.name} ${option2.id} ${option2.description ?? ""}`.toLowerCase();
43023
+ return words.every((word) => text2.includes(word));
43024
+ });
43025
+ }
42890
43026
  var popup = (account) => account.group === "mcp" && account.auth === "oauth";
42891
43027
  var secretNames = { "api-key": "API key", token: "token" };
42892
43028
  var rejectedNames = { "api-key": "API key rejected", token: "Token rejected" };
@@ -42906,8 +43042,15 @@ var SliccSettings = class extends ThemedElement {
42906
43042
  entering: { state: true },
42907
43043
  failure: { state: true },
42908
43044
  problem: { state: true },
42909
- blocked: { state: true }
43045
+ blocked: { state: true },
43046
+ picking: { state: true },
43047
+ adding: { state: true },
43048
+ query: { state: true },
43049
+ filled: { state: true }
42910
43050
  };
43051
+ #waits = new SignInWaits(() => this.requestUpdate());
43052
+ #landing = null;
43053
+ #bases = /* @__PURE__ */ new Map();
42911
43054
  #scroll = new ViewScroll(this, "settings:scroll", { scroller: () => this, rows: "[data-id]" });
42912
43055
  constructor() {
42913
43056
  super();
@@ -42916,9 +43059,16 @@ var SliccSettings = class extends ThemedElement {
42916
43059
  this.failure = null;
42917
43060
  this.problem = null;
42918
43061
  this.blocked = false;
43062
+ this.picking = false;
43063
+ this.adding = null;
43064
+ this.query = "";
43065
+ this.filled = /* @__PURE__ */ new Set();
42919
43066
  }
42920
43067
  static styles = [
42921
43068
  fieldStyles,
43069
+ dialogFrame,
43070
+ searchCss,
43071
+ r(link_default),
42922
43072
  i`
42923
43073
  :host {
42924
43074
  display: block;
@@ -43030,6 +43180,144 @@ var SliccSettings = class extends ThemedElement {
43030
43180
  line-height: var(--swc-line-height-200);
43031
43181
  --swc-icon-color: var(--swc-negative-content-color-default);
43032
43182
  }
43183
+ .actions {
43184
+ display: flex;
43185
+ align-items: center;
43186
+ gap: var(--swc-spacing-75);
43187
+ }
43188
+ .notes {
43189
+ grid-column: 1 / -1;
43190
+ display: grid;
43191
+ gap: var(--swc-spacing-75);
43192
+ margin-block-start: calc(-1 * var(--swc-spacing-200));
43193
+ }
43194
+ .notice {
43195
+ display: flex;
43196
+ align-items: flex-start;
43197
+ gap: var(--swc-spacing-75);
43198
+ margin: 0;
43199
+ color: var(--swc-neutral-subdued-content-color-default);
43200
+ font-size: var(--swc-font-size-75);
43201
+ line-height: var(--swc-line-height-200);
43202
+ --swc-icon-color: var(--swc-notice-visual-color);
43203
+ }
43204
+ .notice swc-icon-alert-triangle {
43205
+ flex: none;
43206
+ margin-block-start: var(--swc-spacing-50);
43207
+ }
43208
+ .add {
43209
+ margin-block-start: var(--swc-spacing-200);
43210
+ margin-inline-start: calc(-1 * var(--swc-spacing-100));
43211
+ }
43212
+ dialog.picker {
43213
+ inline-size: min(520px, calc(100vw - 2 * var(--swc-spacing-300)));
43214
+ }
43215
+ .picker .head {
43216
+ display: flex;
43217
+ align-items: center;
43218
+ justify-content: space-between;
43219
+ gap: var(--swc-spacing-200);
43220
+ padding: var(--swc-spacing-400) var(--swc-spacing-300) var(--swc-spacing-200) var(--swc-spacing-500);
43221
+ }
43222
+ .picker h2 {
43223
+ margin: 0;
43224
+ font-size: var(--swc-heading-size-s);
43225
+ overflow-wrap: anywhere;
43226
+ }
43227
+ .picker .find {
43228
+ display: flex;
43229
+ padding: var(--swc-spacing-100) var(--swc-spacing-500) var(--swc-spacing-200);
43230
+ }
43231
+ .picker .results {
43232
+ block-size: min(400px, calc(100dvh - 280px));
43233
+ overflow-y: auto;
43234
+ overscroll-behavior: contain;
43235
+ padding: var(--swc-spacing-75) var(--swc-spacing-400);
43236
+ border-block: var(--swc-border-width-100) solid var(--swc-gray-200);
43237
+ }
43238
+ .options {
43239
+ display: grid;
43240
+ gap: var(--swc-spacing-50);
43241
+ }
43242
+ .option {
43243
+ display: grid;
43244
+ grid-template-columns: minmax(0, 1fr) max-content;
43245
+ align-items: baseline;
43246
+ gap: var(--swc-spacing-50) var(--swc-spacing-300);
43247
+ padding: var(--swc-spacing-100) var(--swc-spacing-100);
43248
+ border-radius: var(--swc-corner-radius-medium-default);
43249
+ cursor: pointer;
43250
+ }
43251
+ .option:hover,
43252
+ .option:focus {
43253
+ background: var(--swc-gray-200);
43254
+ }
43255
+ .option:focus {
43256
+ outline: none;
43257
+ }
43258
+ .option:focus-visible {
43259
+ outline: var(--swc-focus-indicator-thickness) solid var(--swc-focus-indicator-color);
43260
+ outline-offset: calc(-1 * var(--swc-focus-indicator-thickness));
43261
+ }
43262
+ .option[aria-disabled='true'] {
43263
+ cursor: default;
43264
+ }
43265
+ .option[aria-disabled='true']:hover {
43266
+ background: none;
43267
+ }
43268
+ .option .name {
43269
+ font-weight: var(--swc-bold-font-weight);
43270
+ overflow-wrap: anywhere;
43271
+ }
43272
+ .option[aria-disabled='true'] .name {
43273
+ color: var(--swc-neutral-subdued-content-color-default);
43274
+ }
43275
+ .option .tag {
43276
+ justify-self: end;
43277
+ color: var(--swc-neutral-subdued-content-color-default);
43278
+ font-size: var(--swc-font-size-75);
43279
+ }
43280
+ .added {
43281
+ display: inline-flex;
43282
+ align-items: center;
43283
+ gap: var(--swc-spacing-50);
43284
+ }
43285
+ .option .description,
43286
+ .option .notice {
43287
+ grid-column: 1 / -1;
43288
+ }
43289
+ .option .description,
43290
+ .lead,
43291
+ .empty {
43292
+ margin: 0;
43293
+ color: var(--swc-neutral-subdued-content-color-default);
43294
+ }
43295
+ .empty {
43296
+ padding: var(--swc-spacing-200) var(--swc-spacing-100);
43297
+ overflow-wrap: anywhere;
43298
+ }
43299
+ .picker .end {
43300
+ justify-content: flex-end;
43301
+ padding: var(--swc-spacing-300) var(--swc-spacing-500) var(--swc-spacing-400);
43302
+ }
43303
+ .step {
43304
+ display: grid;
43305
+ gap: var(--swc-spacing-200);
43306
+ padding: var(--swc-spacing-100) var(--swc-spacing-500) var(--swc-spacing-500);
43307
+ }
43308
+ .step .key.stacked {
43309
+ max-width: none;
43310
+ }
43311
+ .step sp-textfield {
43312
+ inline-size: 100%;
43313
+ }
43314
+ .step .buttons {
43315
+ margin-block-start: var(--swc-spacing-200);
43316
+ }
43317
+ .get-key {
43318
+ justify-self: start;
43319
+ font-size: var(--swc-font-size-75);
43320
+ }
43033
43321
  @container (max-width: 480px) {
43034
43322
  .page {
43035
43323
  padding-inline: var(--swc-spacing-300);
@@ -43060,7 +43348,8 @@ var SliccSettings = class extends ThemedElement {
43060
43348
  .state {
43061
43349
  grid-area: state;
43062
43350
  }
43063
- .account swc-button {
43351
+ .account swc-button,
43352
+ .account .actions {
43064
43353
  grid-area: action;
43065
43354
  }
43066
43355
  .key sp-textfield {
@@ -43080,6 +43369,7 @@ var SliccSettings = class extends ThemedElement {
43080
43369
  ...super.subscribe(model),
43081
43370
  model.settings.on("settings", settle2),
43082
43371
  model.settings.on("accounts", settle2),
43372
+ model.settings.on("providers", update),
43083
43373
  ...model.vault ? [model.vault.on("status", update)] : []
43084
43374
  ];
43085
43375
  }
@@ -43126,6 +43416,7 @@ var SliccSettings = class extends ThemedElement {
43126
43416
  try {
43127
43417
  await (fields ? settings?.connect(id, secret, fields) : secret === void 0 ? settings?.connect(id) : settings?.connect(id, secret));
43128
43418
  if (this.entering === id) this.entering = null;
43419
+ if (this.adding?.id === id && this.#added(id)?.status === "connected") this.#finish(id);
43129
43420
  } catch (error) {
43130
43421
  if (!this.#cancelled.has(id)) this.failure = { id, message: error.message };
43131
43422
  } finally {
@@ -43161,7 +43452,82 @@ var SliccSettings = class extends ThemedElement {
43161
43452
  this.#busy(id, false);
43162
43453
  return;
43163
43454
  }
43164
- await this.connect(id, void 0, fields);
43455
+ await this.#connectBases(account, void 0, fields, vault);
43456
+ }
43457
+ #baseOf(value) {
43458
+ const trimmed = value?.trim() ?? "";
43459
+ try {
43460
+ return new URL(trimmed).protocol === "https:" ? trimmed : void 0;
43461
+ } catch {
43462
+ return void 0;
43463
+ }
43464
+ }
43465
+ #oldBase(account, field) {
43466
+ return this.#baseOf(field.value) ?? this.#bases.get(`${account.id} ${field.name}`);
43467
+ }
43468
+ #baseChanges(account, fields) {
43469
+ return (account.fields ?? []).filter((field) => field.customBase).map((field) => ({
43470
+ field: field.name,
43471
+ url: this.#baseOf(fields?.[field.name]),
43472
+ old: this.#oldBase(account, field)
43473
+ })).filter((change) => change.url !== change.old);
43474
+ }
43475
+ #modelChange(name, changes2, from, to) {
43476
+ const entries = (key2) => changes2.flatMap((change) => change[key2] ? [{ name, url: change[key2] }] : []);
43477
+ const remove2 = entries(from);
43478
+ const add = entries(to);
43479
+ if (!remove2.length && !add.length) return null;
43480
+ return {
43481
+ ...remove2.length ? { remove: { model: remove2 } } : {},
43482
+ ...add.length ? { add: { model: add } } : {}
43483
+ };
43484
+ }
43485
+ async #connectBases(account, secret, fields, vault = null) {
43486
+ const model = this.model;
43487
+ const live = vault?.endpoints ? vault : null;
43488
+ const { id } = account;
43489
+ const name = recordName(account);
43490
+ const changes2 = this.#baseChanges(account, fields);
43491
+ const forward = live ? this.#modelChange(name, changes2, "old", "url") : null;
43492
+ if (forward) {
43493
+ this.#busy(id, true);
43494
+ try {
43495
+ await live?.endpoints?.(forward);
43496
+ } catch (error) {
43497
+ this.#busy(id, false);
43498
+ const field = changes2.find((change) => change.url) ?? changes2[0];
43499
+ if (this.model === model) this.#refuse(id, field.field, failureText(error));
43500
+ return;
43501
+ }
43502
+ if (this.model !== model) {
43503
+ this.#busy(id, false);
43504
+ return;
43505
+ }
43506
+ }
43507
+ await this.connect(id, secret, fields);
43508
+ if (this.failure?.id === id) {
43509
+ this.#placeFailure(account, fields);
43510
+ const back = forward ? this.#modelChange(name, changes2, "url", "old") : null;
43511
+ if (back) await live?.endpoints?.(back).catch(() => {
43512
+ });
43513
+ return;
43514
+ }
43515
+ for (const change of changes2) {
43516
+ const key2 = `${id} ${change.field}`;
43517
+ if (change.url) this.#bases.set(key2, change.url);
43518
+ else this.#bases.delete(key2);
43519
+ }
43520
+ }
43521
+ #placeFailure(account, fields) {
43522
+ const message = this.failure.message;
43523
+ const field = (account.fields ?? []).find(
43524
+ (candidate) => candidate.customBase && !!fields?.[candidate.name] && [candidate.name, candidate.label, "https", "url", "port", "userinfo"].some(
43525
+ (word) => new RegExp(`\\b${word.replace(/[^a-z0-9 ]/gi, "\\$&")}\\b`, "i").test(message)
43526
+ )
43527
+ );
43528
+ if (!field) return;
43529
+ this.failure = null;
43530
+ this.#refuse(account.id, field.name, message);
43165
43531
  }
43166
43532
  #refuse(id, field, message) {
43167
43533
  this.problem = { id, field, message };
@@ -43185,13 +43551,25 @@ var SliccSettings = class extends ThemedElement {
43185
43551
  }
43186
43552
  return values;
43187
43553
  }
43554
+ #added(id) {
43555
+ return this.model?.settings.accounts().find((account) => account.id === id);
43556
+ }
43557
+ #offered() {
43558
+ return this.model?.settings.providers?.() ?? null;
43559
+ }
43188
43560
  #start(account) {
43189
43561
  if (account.auth !== "api-key") {
43190
43562
  void this.connect(account.id);
43191
43563
  return;
43192
43564
  }
43565
+ this.#enter(account);
43566
+ }
43567
+ #enter(account) {
43193
43568
  this.failure = null;
43194
43569
  this.problem = null;
43570
+ this.filled = new Set(
43571
+ (account.fields ?? []).filter((field) => field.options || field.value?.trim()).map((field) => field.name)
43572
+ );
43195
43573
  this.entering = account.id;
43196
43574
  void this.updateComplete.then(() => this.focusOn(".key sp-textfield", ".key input"));
43197
43575
  }
@@ -43199,6 +43577,7 @@ var SliccSettings = class extends ThemedElement {
43199
43577
  event2.preventDefault();
43200
43578
  const form = event2.currentTarget;
43201
43579
  this.problem = null;
43580
+ this.filled = new Set([...this.filled].filter((name) => name !== "value"));
43202
43581
  if (this.#vault(account)) {
43203
43582
  const value = take(form.querySelector("input[data-field=value]"));
43204
43583
  const fields2 = this.#fields(form, account);
@@ -43209,7 +43588,7 @@ var SliccSettings = class extends ThemedElement {
43209
43588
  const secret = field.value.trim();
43210
43589
  field.value = "";
43211
43590
  const fields = this.#fields(form, account);
43212
- if (secret && fields !== null) void this.connect(account.id, secret, fields);
43591
+ if (secret && fields !== null) void this.#connectBases(account, secret, fields);
43213
43592
  }
43214
43593
  #submitOnEnter(event2) {
43215
43594
  const target2 = event2.target;
@@ -43220,7 +43599,7 @@ var SliccSettings = class extends ThemedElement {
43220
43599
  #send(event2) {
43221
43600
  event2.currentTarget.closest("form")?.requestSubmit();
43222
43601
  }
43223
- #enter(event2) {
43602
+ #enterKey(event2) {
43224
43603
  if (event2.key === "Enter") this.#send(event2);
43225
43604
  }
43226
43605
  #cancel() {
@@ -43248,9 +43627,15 @@ var SliccSettings = class extends ThemedElement {
43248
43627
  (field) => b`<label class="field">
43249
43628
  <span>${field.label}</span>${this.#control(account, field)}
43250
43629
  </label>
43630
+ ${field.help ? b`<p class="hint" id=${`help-${field.name}`}>${field.help}</p>` : A}
43251
43631
  ${this.#problem(account, field.name)}`
43252
43632
  );
43253
43633
  }
43634
+ #describedBy(account, field) {
43635
+ const problem = this.#described(account, field.name);
43636
+ const ids = [problem === A ? "" : problem, field.help ? `help-${field.name}` : ""];
43637
+ return ids.filter(Boolean).join(" ") || A;
43638
+ }
43254
43639
  #control(account, field) {
43255
43640
  if (field.options) {
43256
43641
  const chosen = field.value ?? field.options[0]?.value;
@@ -43259,7 +43644,7 @@ var SliccSettings = class extends ThemedElement {
43259
43644
  ?disabled=${!field.options.length}
43260
43645
  aria-required=${field.required ? "true" : A}
43261
43646
  aria-invalid=${this.#invalid(account, field.name)}
43262
- aria-describedby=${this.#described(account, field.name)}
43647
+ aria-describedby=${this.#describedBy(account, field)}
43263
43648
  >
43264
43649
  ${field.options.map((option2) => b`<option value=${option2.value} ?selected=${option2.value === chosen}>${option2.label}</option>`)}
43265
43650
  </select>`;
@@ -43269,11 +43654,35 @@ var SliccSettings = class extends ThemedElement {
43269
43654
  autocomplete="off"
43270
43655
  spellcheck="false"
43271
43656
  data-account-field=${field.name}
43657
+ value=${field.value ?? A}
43658
+ placeholder=${field.placeholder ?? A}
43272
43659
  aria-required=${field.required ? "true" : A}
43273
43660
  aria-invalid=${this.#invalid(account, field.name)}
43274
- aria-describedby=${this.#described(account, field.name)}
43661
+ aria-describedby=${this.#describedBy(account, field)}
43275
43662
  />`;
43276
43663
  }
43664
+ #getKey(account) {
43665
+ if (!account.keyUrl) return A;
43666
+ return b`<a class="swc-Link get-key" href=${account.keyUrl} target="_blank" rel="noopener noreferrer" data-action="get-key">Get a key</a>`;
43667
+ }
43668
+ #leave(account) {
43669
+ const adding = this.adding?.id === account.id;
43670
+ return b`<swc-button size="m" variant="secondary" data-action=${adding ? "back" : "cancel"} @click=${() => adding ? this.#back() : this.#cancel()}>${adding ? "Back" : "Cancel"}</swc-button>`;
43671
+ }
43672
+ #saveLabel(account) {
43673
+ return this.adding?.id === account.id ? "Connect" : "Save";
43674
+ }
43675
+ #ready(account) {
43676
+ if (!this.#offered()) return true;
43677
+ const required = (account.fields ?? []).filter((field) => field.required);
43678
+ return ["value", ...required.map((field) => field.name)].every((name) => this.filled.has(name));
43679
+ }
43680
+ #fill(event2) {
43681
+ const input = event2.target;
43682
+ const name = input.dataset.accountField ?? "value";
43683
+ const rest = [...this.filled].filter((candidate) => candidate !== name);
43684
+ this.filled = new Set(input.value.trim() ? [...rest, name] : rest);
43685
+ }
43277
43686
  #unlock(vault) {
43278
43687
  return b`<div class="locked" data-locked>
43279
43688
  <span>Unlock the vault first</span>
@@ -43292,7 +43701,7 @@ var SliccSettings = class extends ThemedElement {
43292
43701
  const busy = this.pending.has(account.id);
43293
43702
  const locked = !!vault.status()?.locked;
43294
43703
  const name = secretNames[account.secret ?? "api-key"];
43295
- return b`<form class="key stacked" data-id=${account.id} data-store="vault" @submit=${(event2) => this.#submit(event2, account)} @keydown=${(event2) => this.#submitOnEnter(event2)}>
43704
+ return b`<form class="key stacked" data-id=${account.id} data-store="vault" @submit=${(event2) => this.#submit(event2, account)} @keydown=${(event2) => this.#submitOnEnter(event2)} @input=${(event2) => this.#fill(event2)}>
43296
43705
  <label class="field"><span>${account.provider} ${name}</span><input
43297
43706
  type="password"
43298
43707
  autocomplete="off"
@@ -43303,11 +43712,12 @@ var SliccSettings = class extends ThemedElement {
43303
43712
  aria-describedby=${this.#described(account, "value")}
43304
43713
  /></label>
43305
43714
  ${this.#problem(account, "value")}
43306
- ${this.#extra(account)}
43307
43715
  ${locked ? this.#unlock(vault) : b`<p class="hint">Saved in the vault</p>`}
43716
+ ${this.#getKey(account)}
43717
+ ${this.#extra(account)}
43308
43718
  <div class="buttons">
43309
- <swc-button size="m" variant="accent" data-action="save" ?pending=${busy} ?disabled=${locked} @click=${(event2) => this.#send(event2)}>Save</swc-button>
43310
- <swc-button size="m" variant="secondary" data-action="cancel" @click=${() => this.#cancel()}>Cancel</swc-button>
43719
+ <swc-button size="m" variant="accent" data-action="save" ?pending=${busy} ?disabled=${locked || !busy && !this.#ready(account)} @click=${(event2) => this.#send(event2)}>${this.#saveLabel(account)}</swc-button>
43720
+ ${this.#leave(account)}
43311
43721
  </div>
43312
43722
  </form>`;
43313
43723
  }
@@ -43316,23 +43726,24 @@ var SliccSettings = class extends ThemedElement {
43316
43726
  const vault = this.#vault(account);
43317
43727
  if (vault) return this.#kept(account, vault);
43318
43728
  const busy = this.pending.has(account.id);
43319
- const extra = account.fields?.length;
43320
- return b`<form class=${extra ? "key stacked" : "key"} data-id=${account.id} @submit=${(event2) => this.#submit(event2, account)} @keydown=${(event2) => this.#submitOnEnter(event2)}>
43729
+ const extra = account.fields?.length || account.keyUrl;
43730
+ return b`<form class=${extra ? "key stacked" : "key"} data-id=${account.id} @submit=${(event2) => this.#submit(event2, account)} @keydown=${(event2) => this.#submitOnEnter(event2)} @input=${(event2) => this.#fill(event2)}>
43321
43731
  <sp-textfield
43322
43732
  size="m"
43323
43733
  type="password"
43324
43734
  autocomplete="off"
43325
43735
  label=${`${account.provider} ${secretNames[account.secret ?? "api-key"]}`}
43326
43736
  placeholder=${`Paste the ${secretNames[account.secret ?? "api-key"]}`}
43327
- @keydown=${(event2) => this.#enter(event2)}
43737
+ @keydown=${(event2) => this.#enterKey(event2)}
43328
43738
  ></sp-textfield>
43739
+ ${this.#getKey(account)}
43329
43740
  ${extra ? this.#extra(account) : A}
43330
- ${extra ? b`<div class="buttons">${this.#buttons(busy)}</div>` : this.#buttons(busy)}
43741
+ ${extra ? b`<div class="buttons">${this.#buttons(account, busy)}</div>` : this.#buttons(account, busy)}
43331
43742
  </form>`;
43332
43743
  }
43333
- #buttons(busy) {
43334
- return b`<swc-button size="m" variant="accent" data-action="save" ?pending=${busy} @click=${(event2) => this.#send(event2)}>Save</swc-button>
43335
- <swc-button size="m" variant="secondary" data-action="cancel" @click=${() => this.#cancel()}>Cancel</swc-button>`;
43744
+ #buttons(account, busy) {
43745
+ return b`<swc-button size="m" variant="accent" data-action="save" ?pending=${busy} ?disabled=${!busy && !this.#ready(account)} @click=${(event2) => this.#send(event2)}>${this.#saveLabel(account)}</swc-button>
43746
+ ${this.#leave(account)}`;
43336
43747
  }
43337
43748
  #inVault(account) {
43338
43749
  const vault = this.model?.vault;
@@ -43384,7 +43795,8 @@ var SliccSettings = class extends ThemedElement {
43384
43795
  get busyReason() {
43385
43796
  const accounts = this.model?.settings.accounts() ?? [];
43386
43797
  if (accounts.some((account) => this.#signingIn(account))) return "signing in";
43387
- return this.entering === null ? null : "a key is being entered";
43798
+ if (this.entering !== null) return "a key is being entered";
43799
+ return this.picking ? "a provider is being added" : null;
43388
43800
  }
43389
43801
  #signingIn(account) {
43390
43802
  return account.status === "signing-in" || account.auth !== "api-key" && account.status !== "connected" && this.pending.has(account.id);
@@ -43441,10 +43853,266 @@ var SliccSettings = class extends ThemedElement {
43441
43853
  <span class="provider">${account.provider}</span>
43442
43854
  <span class="identity">${account.identity}${this.#inVault(account) ? b`<span class="kept" data-store="vault">${account.identity ? " \xB7 " : ""}${kept2(account)}</span>` : A}</span>
43443
43855
  <swc-status-light class="state" variant=${variant}>${label}</swc-status-light>
43444
- ${this.#action(account)}
43856
+ ${this.#removable(account) ? b`<div class="actions">${this.#action(account)}${this.#removeButton(account)}</div>` : this.#action(account)}
43445
43857
  </div>
43446
- ${this.#key(account)}
43447
- ${failure2}`;
43858
+ ${this.#offered() ? this.#rowNotes(account) : A}
43859
+ ${this.adding?.id === account.id ? A : b`${this.#key(account)}${failure2}`}`;
43860
+ }
43861
+ #removable(account) {
43862
+ return !!this.#offered() && !!this.model?.settings.remove && account.group !== "mcp";
43863
+ }
43864
+ #removeButton(account) {
43865
+ const id = `remove-${account.id}`;
43866
+ const label = `Remove ${account.provider}`;
43867
+ return b`<swc-action-button
43868
+ id=${id}
43869
+ size="m"
43870
+ quiet
43871
+ data-action="remove-account"
43872
+ accessible-label=${label}
43873
+ ?disabled=${this.pending.has(account.id)}
43874
+ @click=${() => this.#remove(account)}
43875
+ ><swc-icon-delete slot="icon"></swc-icon-delete></swc-action-button
43876
+ ><swc-tooltip for=${id} placement="top">${label}</swc-tooltip>`;
43877
+ }
43878
+ async #remove(account) {
43879
+ const model = this.model;
43880
+ const body4 = removeBodies[account.auth ?? ""] ?? "Its key is deleted from the vault. Models from it stop working.";
43881
+ const title = `Remove ${account.provider}?`;
43882
+ if (!await confirm({ title, body: body4, action: "Remove" })) return;
43883
+ if (this.model !== model) return;
43884
+ clearValues(this.renderRoot);
43885
+ if (this.entering === account.id) this.entering = null;
43886
+ const vault = model.vault;
43887
+ const keyed = account.store === "vault" && account.auth === "api-key" && !!vault?.remove;
43888
+ if (keyed && !await this.#forget(account, vault)) return;
43889
+ if (this.model !== model) return;
43890
+ this.#busy(account.id, true);
43891
+ this.failure = null;
43892
+ try {
43893
+ await model.settings.remove?.(account.id);
43894
+ } catch (error) {
43895
+ if (this.model === model) this.failure = { id: account.id, message: failureText(error) };
43896
+ return;
43897
+ } finally {
43898
+ this.#busy(account.id, false);
43899
+ }
43900
+ this.focusOn("[data-action=add-provider]");
43901
+ for (const field of account.fields ?? []) this.#bases.delete(`${account.id} ${field.name}`);
43902
+ }
43903
+ #notice(needs, size4 = "long") {
43904
+ if (needs !== "cors-free-transport") return A;
43905
+ return b`<p class="notice" data-notice=${needs}><swc-icon-alert-triangle size="s" aria-hidden="true"></swc-icon-alert-triangle><span>${corsNotice[size4]}</span></p>`;
43906
+ }
43907
+ #signingNote(account) {
43908
+ const key2 = `settings:${account.id}`;
43909
+ if (account.auth !== "oauth" || !this.#signingIn(account)) {
43910
+ this.#waits.done(key2);
43911
+ return A;
43912
+ }
43913
+ return b`<p class="hint" role="status" data-signing>${signInText(this.#waits.waited(key2))}</p>`;
43914
+ }
43915
+ #rowNotes(account) {
43916
+ const notice = this.#notice(account.needs);
43917
+ const signing = this.adding?.id === account.id ? A : this.#signingNote(account);
43918
+ if (notice === A && signing === A) return A;
43919
+ return b`<div class="notes">${notice}${signing}</div>`;
43920
+ }
43921
+ #openPicker() {
43922
+ this.query = "";
43923
+ this.adding = null;
43924
+ this.picking = true;
43925
+ }
43926
+ updated(changed) {
43927
+ super.updated(changed);
43928
+ const dialog = this.#dialog;
43929
+ if (!changed.has("picking") || !dialog || dialog.open) return;
43930
+ dialog.showModal();
43931
+ const field = this.renderRoot.querySelector("sp-search");
43932
+ void field.updateComplete.then(() => field.focus());
43933
+ }
43934
+ get #dialog() {
43935
+ return this.renderRoot.querySelector("dialog.picker");
43936
+ }
43937
+ #closePicker() {
43938
+ this.#dialog?.close();
43939
+ }
43940
+ #closed() {
43941
+ const id = this.adding?.id;
43942
+ if (id !== void 0 && this.entering === id) {
43943
+ clearValues(this.renderRoot);
43944
+ this.entering = null;
43945
+ this.problem = null;
43946
+ }
43947
+ if (id !== void 0 && this.failure?.id === id) this.failure = null;
43948
+ this.picking = false;
43949
+ this.adding = null;
43950
+ this.query = "";
43951
+ const landing = this.#landing;
43952
+ this.#landing = null;
43953
+ void this.updateComplete.then(() => {
43954
+ const root2 = this.renderRoot;
43955
+ const row = landing ? root2.querySelector(`.account[data-id="${CSS.escape(landing)}"] swc-button`) : null;
43956
+ (row ?? root2.querySelector("[data-action=add-provider]"))?.focus();
43957
+ });
43958
+ }
43959
+ #finish(id) {
43960
+ this.#landing = id;
43961
+ this.#closePicker();
43962
+ }
43963
+ #choose(option2) {
43964
+ if (this.#added(option2.id)) return;
43965
+ this.adding = option2;
43966
+ this.failure = null;
43967
+ if (option2.auth === "api-key" || option2.auth === "fields") {
43968
+ this.#enter(draft(option2));
43969
+ return;
43970
+ }
43971
+ void this.updateComplete.then(
43972
+ () => this.focusOn("[data-step] [data-action=sign-in]", "[data-step] [data-action=connect]")
43973
+ );
43974
+ }
43975
+ #back() {
43976
+ const id = this.adding.id;
43977
+ clearValues(this.renderRoot);
43978
+ if (this.entering === id) this.entering = null;
43979
+ if (this.failure?.id === id) this.failure = null;
43980
+ this.problem = null;
43981
+ this.adding = null;
43982
+ void this.updateComplete.then(
43983
+ () => this.renderRoot.querySelector(`.option[data-provider="${CSS.escape(id)}"]`)?.focus()
43984
+ );
43985
+ }
43986
+ #options() {
43987
+ return [...this.renderRoot.querySelectorAll(".option")];
43988
+ }
43989
+ #pickerKey(event2) {
43990
+ if (event2.key !== "Escape") return;
43991
+ event2.preventDefault();
43992
+ this.#closePicker();
43993
+ }
43994
+ #searchKey(event2) {
43995
+ if (event2.key === "ArrowDown") {
43996
+ event2.preventDefault();
43997
+ this.#options()[0]?.focus();
43998
+ return;
43999
+ }
44000
+ if (event2.key !== "Enter") return;
44001
+ event2.preventDefault();
44002
+ const first2 = matchProviders(this.#offered() ?? [], this.query).find(
44003
+ (option2) => !this.#added(option2.id)
44004
+ );
44005
+ if (first2) this.#choose(first2);
44006
+ }
44007
+ #optionKey(event2, option2) {
44008
+ const options = this.#options();
44009
+ const at = options.indexOf(event2.currentTarget);
44010
+ const moves = {
44011
+ ArrowDown: Math.min(at + 1, options.length - 1),
44012
+ ArrowUp: at - 1,
44013
+ Home: 0,
44014
+ End: options.length - 1
44015
+ };
44016
+ const typed3 = event2.key.length === 1 && !event2.ctrlKey && !event2.metaKey && !event2.altKey;
44017
+ if (event2.key in moves) {
44018
+ const to = moves[event2.key];
44019
+ if (to < 0) this.focusOn("sp-search");
44020
+ else options[to].focus();
44021
+ } else if (event2.key === "Enter" || event2.key === " ") this.#choose(option2);
44022
+ else if (typed3 || event2.key === "Backspace") {
44023
+ this.query = typed3 ? this.query + event2.key : this.query.slice(0, -1);
44024
+ this.focusOn("sp-search");
44025
+ } else return;
44026
+ event2.preventDefault();
44027
+ }
44028
+ #option(option2) {
44029
+ const added = !!this.#added(option2.id);
44030
+ return b`<div
44031
+ class="option"
44032
+ role="option"
44033
+ tabindex="-1"
44034
+ data-provider=${option2.id}
44035
+ data-auth=${option2.auth}
44036
+ aria-disabled=${added ? "true" : "false"}
44037
+ @click=${() => this.#choose(option2)}
44038
+ @keydown=${(event2) => this.#optionKey(event2, option2)}
44039
+ >
44040
+ <span class="name">${option2.name}</span>
44041
+ <span class="tag">${added ? b`<span class="added" data-added><swc-icon-checkmark size="s" aria-hidden="true"></swc-icon-checkmark>Added</span>` : authHints[option2.auth]}</span>
44042
+ ${option2.description ? b`<span class="description">${option2.description}</span>` : A}
44043
+ ${this.#notice(option2.needs, "short")}
44044
+ </div>`;
44045
+ }
44046
+ #pickStep(offered) {
44047
+ const matches2 = matchProviders(offered, this.query);
44048
+ return b`<div class="find">${search({
44049
+ label: "Search providers",
44050
+ value: this.query,
44051
+ input: (value) => {
44052
+ this.query = value;
44053
+ },
44054
+ keydown: (event2) => this.#searchKey(event2)
44055
+ })}</div>
44056
+ <div class="results">${matches2.length ? b`<div class="options" role="listbox" aria-label="Providers">${matches2.map((option2) => this.#option(option2))}</div>` : b`<p class="empty" role="status">No provider matches “${this.query.trim()}”.</p>`}</div>
44057
+ <div class="buttons end">
44058
+ <swc-button size="m" variant="secondary" data-action="close" @click=${() => this.#closePicker()}>Cancel</swc-button>
44059
+ </div>`;
44060
+ }
44061
+ #signInStep(account) {
44062
+ const signing = this.#signingIn(account);
44063
+ const cancel = signing && !!this.model?.settings.cancel;
44064
+ return b`<p class="hint">Sign in to ${account.provider} in a new window.</p>
44065
+ ${signing ? this.#signingNote(account) : A}
44066
+ <div class="buttons">
44067
+ ${cancel ? b`<swc-button size="m" variant="secondary" data-action="cancel-sign-in" @click=${() => this.#stopSigningIn(account)}>Cancel</swc-button>` : A}
44068
+ <swc-button size="m" variant="accent" data-action="sign-in" ?pending=${signing} @click=${() => this.connect(account.id)}>${signing ? "Signing in\u2026" : "Sign in"}</swc-button>
44069
+ ${signing ? A : this.#leave(account)}
44070
+ </div>`;
44071
+ }
44072
+ #localStep(account) {
44073
+ return b`<p class="hint">SLICC connects to ${account.provider} on this device.</p>
44074
+ <div class="buttons">
44075
+ <swc-button size="m" variant="accent" data-action="connect" ?pending=${this.pending.has(account.id)} @click=${() => this.connect(account.id)}>Connect</swc-button>
44076
+ ${this.#leave(account)}
44077
+ </div>`;
44078
+ }
44079
+ #connectStep(option2) {
44080
+ const account = this.#added(option2.id) ?? draft(option2);
44081
+ const step = option2.auth === "oauth" ? this.#signInStep(account) : option2.auth === "local" ? this.#localStep(account) : this.#key(account);
44082
+ return b`<div class="step" data-step=${option2.auth} data-provider=${option2.id}>
44083
+ ${option2.description ? b`<p class="lead">${option2.description}</p>` : A}
44084
+ ${this.#notice(option2.needs)}
44085
+ ${step}
44086
+ ${this.failure?.id === option2.id ? b`<div class="failure" role="alert"><swc-icon-alert-triangle size="s" aria-hidden="true"></swc-icon-alert-triangle><span>${this.failure.message}</span></div>` : A}
44087
+ </div>`;
44088
+ }
44089
+ #picker(offered) {
44090
+ if (!this.picking) return A;
44091
+ const adding = this.adding;
44092
+ return b`<dialog class="picker" aria-labelledby="picker-title" data-picker=${adding ? "connect" : "pick"} @keydown=${(event2) => this.#pickerKey(event2)} @close=${() => this.#closed()}>
44093
+ <div class="head">
44094
+ <h2 id="picker-title">${adding ? `Add ${adding.name}` : "Add a provider"}</h2>
44095
+ <swc-close-button size="m" data-action="close" accessible-label="Close" @click=${() => this.#closePicker()}></swc-close-button>
44096
+ </div>
44097
+ ${adding ? this.#connectStep(adding) : this.#pickStep(offered)}
44098
+ </dialog>`;
44099
+ }
44100
+ #providerList(accounts) {
44101
+ const offered = this.#offered();
44102
+ if (!offered)
44103
+ return b`<div class="accounts">${accounts.map((account) => this.#account(account))}</div>`;
44104
+ const sorted = [...accounts].sort((a23, b13) => listOrder[a23.status] - listOrder[b13.status]);
44105
+ return b`<div class="accounts" data-group="providers">${c(
44106
+ sorted,
44107
+ (account) => account.id,
44108
+ (account) => this.#account(account)
44109
+ )}</div>
44110
+ <div class="add">
44111
+ <swc-action-button size="m" quiet data-action="add-provider" @click=${() => this.#openPicker()}>
44112
+ <swc-icon-add slot="icon"></swc-icon-add>Add a provider
44113
+ </swc-action-button>
44114
+ </div>
44115
+ ${this.#picker(offered)}`;
43448
44116
  }
43449
44117
  render() {
43450
44118
  const settings = this.model?.settings;
@@ -43500,7 +44168,7 @@ var SliccSettings = class extends ThemedElement {
43500
44168
  </div>
43501
44169
  </div>
43502
44170
  <h2 data-section="accounts">Accounts</h2>
43503
- <div class="accounts">${providers.map((account) => this.#account(account))}</div>
44171
+ ${this.#providerList(providers)}
43504
44172
  ${servers.length ? b`<h2>MCP servers</h2>
43505
44173
  <div class="accounts" data-group="mcp">${servers.map((account) => this.#account(account))}</div>` : A}
43506
44174
  ${this.model?.vault ? b`<slicc-secrets .model=${this.model}></slicc-secrets>` : A}