@zackbart/connecta 0.7.6 → 0.7.7

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/dist/activity.d.ts +17 -0
  3. package/dist/activity.d.ts.map +1 -1
  4. package/dist/activity.js.map +1 -1
  5. package/dist/auth/clerk.d.ts.map +1 -1
  6. package/dist/auth/clerk.js +101 -0
  7. package/dist/auth/clerk.js.map +1 -1
  8. package/dist/auth/downstream-oauth.d.ts +57 -20
  9. package/dist/auth/downstream-oauth.d.ts.map +1 -1
  10. package/dist/auth/downstream-oauth.js +275 -67
  11. package/dist/auth/downstream-oauth.js.map +1 -1
  12. package/dist/connectors/remote-mcp.d.ts.map +1 -1
  13. package/dist/connectors/remote-mcp.js +165 -103
  14. package/dist/connectors/remote-mcp.js.map +1 -1
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/registry.d.ts +12 -0
  19. package/dist/registry.d.ts.map +1 -1
  20. package/dist/registry.js +70 -15
  21. package/dist/registry.js.map +1 -1
  22. package/dist/server.d.ts.map +1 -1
  23. package/dist/server.js +238 -19
  24. package/dist/server.js.map +1 -1
  25. package/dist/storage/file.d.ts.map +1 -1
  26. package/dist/storage/file.js +8 -0
  27. package/dist/storage/file.js.map +1 -1
  28. package/dist/types.d.ts +21 -0
  29. package/dist/types.d.ts.map +1 -1
  30. package/dist/ui.d.ts +7 -1
  31. package/dist/ui.d.ts.map +1 -1
  32. package/dist/ui.js +118 -4
  33. package/dist/ui.js.map +1 -1
  34. package/dist/version.d.ts +1 -1
  35. package/dist/version.js +1 -1
  36. package/package.json +1 -1
  37. package/src/activity.ts +20 -0
  38. package/src/auth/clerk.ts +124 -0
  39. package/src/auth/downstream-oauth.ts +359 -68
  40. package/src/connectors/remote-mcp.ts +172 -104
  41. package/src/index.ts +3 -0
  42. package/src/registry.ts +79 -19
  43. package/src/server.ts +306 -16
  44. package/src/storage/file.ts +7 -0
  45. package/src/types.ts +23 -0
  46. package/src/ui.ts +124 -3
  47. package/src/version.ts +1 -1
package/src/ui.ts CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  import type { Registry } from "./registry.js";
12
12
  import type { Toolkit } from "./toolkits.js";
13
13
  import type { ConnectaBranding, UiAuthConfig } from "./types.js";
14
+ import { CONNECTA_VERSION } from "./version.js";
14
15
 
15
16
  /** Connecta's default monochrome "C" mark. */
16
17
  export const CONNECTA_FAVICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
@@ -256,6 +257,8 @@ export interface UiConnector {
256
257
  authorizationUrl?: string;
257
258
  toolCount: number;
258
259
  tools: UiTool[];
260
+ /** This connector exposes operator-managed downstream OAuth lifecycle hooks. */
261
+ oauth?: boolean;
259
262
  /**
260
263
  * Verdict of the last proactive credential liveness check (issue #24), for the
261
264
  * connectors that hold a credential connecta stores. Shown beside the live
@@ -299,11 +302,15 @@ export interface UiConnector {
299
302
 
300
303
  export interface UiData {
301
304
  serverInfo: { name: string; version: string };
305
+ /** Version of the installed @zackbart/connecta package. */
306
+ connectaVersion: string;
302
307
  connectors: UiConnector[];
303
308
  /** Read-only projection of the validated deployment config. */
304
309
  toolkits: UiToolkit[];
305
310
  activityEnabled: boolean;
306
311
  credentialManagement: CredentialManagementCapability;
312
+ /** True only for an eligible, unrestricted Clerk operator. */
313
+ oauthManagement: boolean;
307
314
  }
308
315
 
309
316
  export interface UiToolkit {
@@ -408,6 +415,7 @@ export async function buildUiData(
408
415
  : "requires_clerk",
409
416
  toolkits?: ReadonlyMap<string, Toolkit>,
410
417
  defer?: DeferredWork,
418
+ oauthManagement = false,
411
419
  ): Promise<UiData> {
412
420
  const requestScope = {};
413
421
  const connectorSet = registry.listConnectors();
@@ -533,6 +541,7 @@ export async function buildUiData(
533
541
  : {}),
534
542
  toolCount: tools.length,
535
543
  tools,
544
+ ...(c.disconnectAuth && c.startAuth ? { oauth: true } : {}),
536
545
  ...(credentialCheck
537
546
  ? {
538
547
  credentialCheck: {
@@ -581,10 +590,12 @@ export async function buildUiData(
581
590
  );
582
591
  return {
583
592
  serverInfo,
593
+ connectaVersion: CONNECTA_VERSION,
584
594
  connectors,
585
595
  toolkits: toolkitData,
586
596
  activityEnabled,
587
597
  credentialManagement,
598
+ oauthManagement,
588
599
  };
589
600
  }
590
601
 
@@ -860,6 +871,8 @@ ${clerkScript}
860
871
  #notice { margin-bottom: .75rem; }
861
872
  #notice:empty { display: none; }
862
873
  #notice:not(:empty) { text-decoration: underline; }
874
+ #oauthNotice { margin-bottom: .75rem; }
875
+ #oauthNotice:empty { display: none; }
863
876
  .error-notice, .msg { text-decoration: underline; }
864
877
  .card,
865
878
  .credential-card,
@@ -1047,6 +1060,7 @@ ${clerkScript}
1047
1060
  .activity-time,
1048
1061
  .activity-actor,
1049
1062
  .activity-detail { color: var(--muted); font-size: .82rem; }
1063
+ .activity-actor-id { color: var(--muted); margin-top: .1rem; }
1050
1064
  .activity-address { font-family: var(--mono); font-size: .78rem; overflow-wrap: anywhere; }
1051
1065
  .activity-outcome { font-size: .9em; }
1052
1066
  .activity-item.error .activity-outcome,
@@ -1148,6 +1162,7 @@ ${clerkScript}
1148
1162
  </div>
1149
1163
  </div>
1150
1164
  <p class="cap" id="serverInfo">${escapeHtmlAttr(brand.productName)} operator</p>
1165
+ <p id="oauthNotice" class="meta" role="status" aria-live="polite" tabindex="-1"></p>
1151
1166
  </div>
1152
1167
  </div>
1153
1168
  <section class="section pgrid" aria-labelledby="connectorLedgerHeading">
@@ -1260,6 +1275,12 @@ function setNotice(message, isError) {
1260
1275
  $("credentialNotice").setAttribute("role", isError ? "alert" : "status");
1261
1276
  }
1262
1277
 
1278
+ function setOauthNotice(message, isError) {
1279
+ $("oauthNotice").textContent = message || "";
1280
+ $("oauthNotice").classList.toggle("error-notice", Boolean(isError));
1281
+ $("oauthNotice").setAttribute("role", isError ? "alert" : "status");
1282
+ }
1283
+
1263
1284
  async function sessionToken() {
1264
1285
  return AUTH.kind === "clerk"
1265
1286
  ? await Clerk.session?.getToken()
@@ -1287,6 +1308,7 @@ function clearIdentityState() {
1287
1308
  DATA = null;
1288
1309
  clearActivityState();
1289
1310
  $("list").innerHTML = "";
1311
+ setOauthNotice("");
1290
1312
  $("toolkitList").innerHTML = "";
1291
1313
  $("filter").value = "";
1292
1314
  $("credentialList").innerHTML = "";
@@ -1431,14 +1453,16 @@ async function load() {
1431
1453
  $("app").classList.remove("hidden");
1432
1454
  $("appNav").classList.remove("hidden");
1433
1455
  const si = DATA.serverInfo || {};
1434
- $("serverInfo").textContent = (si.name || ${escapeScriptString(brand.productName)}) + " v" + (si.version || "?");
1456
+ $("serverInfo").textContent = (si.name || ${escapeScriptString(brand.productName)}) + " v" + (DATA.connectaVersion || "?");
1435
1457
  updateCapabilities();
1436
1458
  activatePage(CURRENT_PAGE);
1437
1459
  }
1438
1460
 
1439
1461
  function actorLabel(actor) {
1440
1462
  if (!actor || !actor.kind) return "unknown";
1441
- return actor.id ? actor.kind + " · " + actor.id : actor.kind;
1463
+ return actor.label
1464
+ ? actor.kind + " · " + actor.label
1465
+ : actor.id ? actor.kind + " · " + actor.id : actor.kind;
1442
1466
  }
1443
1467
 
1444
1468
  function renderActivity() {
@@ -1457,6 +1481,8 @@ function renderActivity() {
1457
1481
  event.errorCode,
1458
1482
  actor.kind,
1459
1483
  actor.id,
1484
+ actor.namespace,
1485
+ actor.label,
1460
1486
  ].some((value) => String(value || "").toLowerCase().includes(query));
1461
1487
  });
1462
1488
  const uniqueTools = new Set(ACTIVITY.map((event) => event.address)).size;
@@ -1480,13 +1506,23 @@ function renderActivity() {
1480
1506
  ? " · " + esc(event.attempts) + " attempts"
1481
1507
  : "";
1482
1508
  const errorCopy = event.errorCode ? " · " + esc(event.errorCode) : "";
1509
+ const actorId = event.actor?.id
1510
+ ? (event.actor.namespace
1511
+ ? event.actor.namespace + " · " + event.actor.id
1512
+ : event.actor.id)
1513
+ : "";
1514
+ const stableActorId = actorId &&
1515
+ (event.actor?.label || event.actor?.namespace)
1516
+ ? '<div class="activity-actor-id mono">' + esc(actorId) + "</div>"
1517
+ : "";
1483
1518
  item.innerHTML =
1484
1519
  '<div class="activity-stamp"><span class="dot ' +
1485
1520
  (outcomeClass === "success" ? "ok" : "") +
1486
1521
  '" aria-hidden="true"></span><div><time class="activity-time" datetime="' +
1487
1522
  esc(event.occurredAt) + '">' +
1488
1523
  esc(formatDate(event.occurredAt)) +
1489
- '</time><div class="activity-actor">' + esc(actorLabel(event.actor)) + '</div></div></div>' +
1524
+ '</time><div class="activity-actor">' + esc(actorLabel(event.actor)) +
1525
+ "</div>" + stableActorId + "</div></div>" +
1490
1526
  '<div><div class="activity-address">' + esc(event.address) +
1491
1527
  '</div><div class="activity-detail">' + esc(event.source) + retryCopy +
1492
1528
  errorCopy + '</div></div>' +
@@ -1680,6 +1716,20 @@ function renderConnections() {
1680
1716
  : '<p class="connector-auth meta">Authorization URL: ' +
1681
1717
  esc(c.authorizationUrl) + "</p>";
1682
1718
  }
1719
+ if (c.oauth && DATA.oauthManagement) {
1720
+ const oauthName = c.title || c.id;
1721
+ head += '<div class="credential-actions">';
1722
+ head += '<button type="button" class="linklike danger" ' +
1723
+ 'aria-label="Disconnect OAuth for ' + esc(oauthName) + '" ' +
1724
+ 'data-oauth-action="disconnect" data-connector="' + esc(c.id) +
1725
+ '">Disconnect OAuth</button>';
1726
+ head += '<button type="button" class="linklike" ' +
1727
+ 'aria-label="' + (c.status === "ok" ? "Reconnect OAuth for " :
1728
+ "Restart authorization for ") + esc(oauthName) + '" ' +
1729
+ 'data-oauth-action="reconnect" data-connector="' + esc(c.id) +
1730
+ '">' + (c.status === "ok" ? "Reconnect OAuth" : "Restart authorization") +
1731
+ "</button></div>";
1732
+ }
1683
1733
  if (c.credential) {
1684
1734
  head += '<p class="connector-auth"><a class="linklike" href="/credentials" ' +
1685
1735
  'data-operator-page="credentials">Manage credential →</a></p>';
@@ -1709,6 +1759,77 @@ function renderConnections() {
1709
1759
  renderToolkits();
1710
1760
  }
1711
1761
 
1762
+ async function oauthRequest(connector, method, generation) {
1763
+ const token = await sessionToken();
1764
+ if (generation !== SESSION_GENERATION) {
1765
+ throw new Error("The operator session changed.");
1766
+ }
1767
+ if (!token) throw new Error("Your Clerk session has expired.");
1768
+ const res = await fetch("/ui/oauth/" + encodeURIComponent(connector), {
1769
+ method,
1770
+ headers: { Authorization: "Bearer " + token },
1771
+ });
1772
+ if (res.status === 204) return null;
1773
+ let payload = {};
1774
+ try { payload = await res.json(); } catch (e) {}
1775
+ if (!res.ok) throw new Error(payload.error || "Request failed (" + res.status + ").");
1776
+ return payload;
1777
+ }
1778
+
1779
+ $("list").onclick = async (event) => {
1780
+ const button = event.target.closest("[data-oauth-action]");
1781
+ if (!button) return;
1782
+ const connector = button.dataset.connector;
1783
+ const action = button.dataset.oauthAction;
1784
+ const generation = SESSION_GENERATION;
1785
+ const question = action === "disconnect"
1786
+ ? "Disconnect OAuth for " + connector +
1787
+ "? Stored credentials and any pending authorization will be removed."
1788
+ : "Restart OAuth for " + connector +
1789
+ "? Stored credentials and any pending authorization will be replaced.";
1790
+ if (!window.confirm(question)) return;
1791
+
1792
+ setOauthNotice("");
1793
+ const buttons = [...document.querySelectorAll(
1794
+ '[data-connector="' + CSS.escape(connector) + '"]'
1795
+ )];
1796
+ buttons.forEach((item) => { item.disabled = true; });
1797
+ try {
1798
+ const result = await oauthRequest(
1799
+ connector,
1800
+ action === "disconnect" ? "DELETE" : "POST",
1801
+ generation,
1802
+ );
1803
+ if (generation !== SESSION_GENERATION) return;
1804
+ await load();
1805
+ if (generation !== SESSION_GENERATION) return;
1806
+ setOauthNotice(
1807
+ action === "disconnect"
1808
+ ? "OAuth disconnected. Restart authorization when you are ready to reconnect."
1809
+ : result?.message ||
1810
+ "Authorization restarted. Open the authorization link to reconnect.",
1811
+ );
1812
+ $("oauthNotice").focus();
1813
+ } catch (e) {
1814
+ if (generation !== SESSION_GENERATION) return;
1815
+ // A reset can publish its durable epoch before best-effort physical cleanup
1816
+ // reports failure. Refresh so stale tools/health/actions never survive that
1817
+ // partial success, while preserving the original error for the operator.
1818
+ try {
1819
+ await load();
1820
+ } catch (refreshError) {
1821
+ // The mutation error is the actionable one; load() already owns its gate.
1822
+ }
1823
+ if (generation !== SESSION_GENERATION) return;
1824
+ setOauthNotice(e.message || "OAuth action failed.", true);
1825
+ $("oauthNotice").focus();
1826
+ } finally {
1827
+ if (generation === SESSION_GENERATION) {
1828
+ buttons.forEach((item) => { item.disabled = false; });
1829
+ }
1830
+ }
1831
+ };
1832
+
1712
1833
  function renderCredentials() {
1713
1834
  const list = $("credentialList");
1714
1835
  list.innerHTML = "";
package/src/version.ts CHANGED
@@ -4,4 +4,4 @@
4
4
  * a bump that forgets this file fails the build rather than shipping a stale
5
5
  * version to `/health` and to downstream MCP handshakes.
6
6
  */
7
- export const CONNECTA_VERSION = "0.7.6";
7
+ export const CONNECTA_VERSION = "0.7.7";