@zackbart/connecta 0.33.0 → 0.34.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.
Files changed (131) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +100 -128
  3. package/SECURITY.md +1 -1
  4. package/bin/version.mjs +1 -1
  5. package/dist/activity.d.ts +3 -0
  6. package/dist/branding.d.ts +7 -0
  7. package/dist/branding.js +76 -9
  8. package/dist/catalog-drift.d.ts +30 -26
  9. package/dist/catalog-drift.js +169 -84
  10. package/dist/catalog-service.js +16 -2
  11. package/dist/config-value-sources.js +6 -1
  12. package/dist/config.d.ts +13 -2
  13. package/dist/config.js +36 -2
  14. package/dist/connector-caller.js +2 -1
  15. package/dist/connectors/api-connector.d.ts +3 -0
  16. package/dist/connectors/api-connector.js +5 -1
  17. package/dist/connectors/option-shapes.js +1 -1
  18. package/dist/connectors/remote-mcp.d.ts +2 -0
  19. package/dist/connectors/remote-mcp.js +5 -1
  20. package/dist/describe-config.d.ts +5 -1
  21. package/dist/describe-config.js +2 -0
  22. package/dist/execute.js +1 -11
  23. package/dist/fix-prompt.js +2 -2
  24. package/dist/index.js +1 -0
  25. package/dist/invocation.js +3 -2
  26. package/dist/membership.d.ts +2 -0
  27. package/dist/meta-tools.js +3 -2
  28. package/dist/oauth-callback-outcome.js +2 -2
  29. package/dist/operator-ui/agents.d.ts +1 -1
  30. package/dist/operator-ui/agents.js +16 -16
  31. package/dist/operator-ui/config-summary.js +565 -0
  32. package/dist/operator-ui/connection-map.js +376 -0
  33. package/dist/operator-ui/endpoints.js +45 -16
  34. package/dist/operator-ui/fix-prompts.js +1 -1
  35. package/dist/operator-ui/generated.js +4 -4
  36. package/dist/operator-ui/health.js +52 -0
  37. package/dist/operator-ui/member-page.js +13 -28
  38. package/dist/operator-ui/overview.js +123 -0
  39. package/dist/operator-ui/view.d.ts +26 -22
  40. package/dist/operator-ui/view.js +130 -96
  41. package/dist/page-styles.js +5 -3
  42. package/dist/provider.d.ts +6 -1
  43. package/dist/provider.js +18 -1
  44. package/dist/providers/_shared/google/workspace.d.ts +9 -4
  45. package/dist/providers/_shared/google/workspace.js +7 -3
  46. package/dist/providers/basecamp/index.d.ts +2 -0
  47. package/dist/providers/breeze/index.d.ts +2 -0
  48. package/dist/providers/ccb/index.d.ts +2 -0
  49. package/dist/providers/ccb/index.js +2 -2
  50. package/dist/providers/cloudflare/index.d.ts +2 -0
  51. package/dist/providers/cloudflare/index.js +5 -1
  52. package/dist/providers/cloudflare/openapi.generated.js +3296 -3206
  53. package/dist/providers/cloudflare/rest.js +19 -0
  54. package/dist/providers/cloudflare/skill.generated.js +2 -2
  55. package/dist/providers/cloudflare/value-safety.js +51 -18
  56. package/dist/providers/docs/index.js +1 -1
  57. package/dist/providers/docs/skill.generated.js +2 -2
  58. package/dist/providers/drive/index.js +1 -1
  59. package/dist/providers/drive/skill.generated.js +2 -2
  60. package/dist/providers/forms/index.js +1 -1
  61. package/dist/providers/forms/skill.generated.js +2 -2
  62. package/dist/providers/gmail/index.js +1 -1
  63. package/dist/providers/gmail/skill.generated.js +2 -2
  64. package/dist/providers/intercom/index.d.ts +24 -0
  65. package/dist/providers/intercom/index.js +121 -0
  66. package/dist/providers/intercom/skill.generated.js +8 -0
  67. package/dist/providers/linear/index.js +204 -19
  68. package/dist/providers/linear/skill.generated.js +2 -2
  69. package/dist/providers/mixpanel/index.d.ts +2 -0
  70. package/dist/providers/mixpanel/index.js +21 -17
  71. package/dist/providers/mixpanel/skill.generated.js +3 -3
  72. package/dist/providers/notion/index.d.ts +2 -0
  73. package/dist/providers/notion/index.js +1 -1
  74. package/dist/providers/notion/skill.generated.js +1 -1
  75. package/dist/providers/overflow/index.d.ts +2 -0
  76. package/dist/providers/overflow/index.js +1 -1
  77. package/dist/providers/overflow/skill.generated.js +2 -2
  78. package/dist/providers/planning-center/index.d.ts +2 -0
  79. package/dist/providers/planning-center/index.js +1 -1
  80. package/dist/providers/planning-center/skill.generated.js +4 -4
  81. package/dist/providers/revenuecat/index.d.ts +5 -3
  82. package/dist/providers/revenuecat/index.js +147 -7
  83. package/dist/providers/revenuecat/skill.generated.js +3 -3
  84. package/dist/providers/sheets/index.js +1 -1
  85. package/dist/providers/sheets/skill.generated.js +2 -2
  86. package/dist/providers/slides/index.js +1 -1
  87. package/dist/providers/slides/skill.generated.js +2 -2
  88. package/dist/providers/stripe/index.d.ts +2 -0
  89. package/dist/providers/stripe/index.js +50 -30
  90. package/dist/providers/stripe/skill.generated.js +2 -1
  91. package/dist/providers/tithely/index.d.ts +2 -0
  92. package/dist/providers/tithely/index.js +1 -1
  93. package/dist/providers/vercel/index.d.ts +2 -0
  94. package/dist/providers/vercel/index.js +2 -2
  95. package/dist/providers/vercel/openapi.generated.js +391 -374
  96. package/dist/providers/vercel/skill.generated.js +2 -2
  97. package/dist/providers/vercel/value-safety.js +14 -4
  98. package/dist/registry.d.ts +10 -14
  99. package/dist/registry.js +77 -62
  100. package/dist/routes/mcp.js +20 -13
  101. package/dist/routes/oauth.js +2 -2
  102. package/dist/routes/ui-data.js +8 -2
  103. package/dist/skills.js +18 -2
  104. package/dist/storage/sql.js +5 -1
  105. package/dist/types.d.ts +36 -11
  106. package/dist/ui.js +9 -4
  107. package/dist/usage-guide.js +1 -1
  108. package/dist/version.d.ts +1 -1
  109. package/dist/version.js +1 -1
  110. package/documentation/README.md +2 -2
  111. package/documentation/architecture.md +66 -18
  112. package/documentation/auth.md +183 -67
  113. package/documentation/code-mode.md +30 -10
  114. package/documentation/deploying.md +5 -1
  115. package/documentation/integrating.md +64 -7
  116. package/documentation/meta-tools.md +38 -24
  117. package/documentation/migration-0.33.md +21 -13
  118. package/documentation/migration-0.34.md +67 -0
  119. package/documentation/operating.md +1 -1
  120. package/documentation/operator-tests.md +52 -20
  121. package/documentation/operator-ui.md +341 -104
  122. package/documentation/provider-auth-migration.md +22 -0
  123. package/examples/worker/AGENTS.md +15 -9
  124. package/examples/worker/README.md +41 -14
  125. package/examples/worker/src/connecta.config.ts +5 -4
  126. package/examples/worker/wrangler.jsonc +1 -1
  127. package/package.json +23 -19
  128. package/templates/node/AGENTS.md +13 -7
  129. package/templates/node/README.md +14 -11
  130. package/templates/node/package.json +1 -1
  131. package/templates/node/src/connecta.config.ts +2 -2
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The next steady evidence. A settled connector takes its current row; one
3
+ * being read again keeps its previous evidence when this identity has any.
4
+ * Returns `previous` itself when nothing changed, so it can live in React
5
+ * state. A new identity starts empty.
6
+ */
7
+ export function steadyHealth(state, previous) {
8
+ const same = previous?.generation === state.generation ? previous : null;
9
+ const contract = state.contract?.role === "maintainer" && state.data ? state.contract : null;
10
+ // No contract on screen: keep what was shown until the next one arrives.
11
+ if (!contract || !state.data)
12
+ return same ?? { generation: state.generation, rows: Object.create(null) };
13
+ const reading = new Set(state.data.connectors.filter((c) => c.status === "loading").map((c) => c.id));
14
+ const held = same?.rows ?? Object.create(null);
15
+ const rows = Object.create(null);
16
+ let changed = !same || Object.keys(held).length !== contract.live.connectors.length;
17
+ for (const live of contract.live.connectors) {
18
+ const before = Object.hasOwn(held, live.id) ? held[live.id] : undefined;
19
+ if (before && reading.has(live.id)) {
20
+ rows[live.id] = before;
21
+ continue;
22
+ }
23
+ const failed = Object.hasOwn(state.connectorFailures, live.id) && Boolean(state.connectorFailures[live.id]);
24
+ if (before?.live === live && before.failed === failed)
25
+ rows[live.id] = before;
26
+ else {
27
+ rows[live.id] = { live, failed };
28
+ changed = true;
29
+ }
30
+ }
31
+ return changed ? { generation: state.generation, rows } : same;
32
+ }
33
+ /** The contract each view renders health from, with every live row replaced by its steady evidence. */
34
+ export function shownHealth(contract, health) {
35
+ const signals = Object.create(null);
36
+ if (!contract)
37
+ return { contract, signals };
38
+ let replaced = false;
39
+ const connectors = contract.live.connectors.map((live) => {
40
+ const row = Object.hasOwn(health.rows, live.id) ? health.rows[live.id] : undefined;
41
+ if (row?.failed)
42
+ signals[live.id] = { failed: true };
43
+ if (!row || row.live === live)
44
+ return live;
45
+ replaced = true;
46
+ return row.live;
47
+ });
48
+ return {
49
+ contract: replaced ? { ...contract, live: { ...contract.live, connectors } } : contract,
50
+ signals,
51
+ };
52
+ }
@@ -35,23 +35,17 @@ function controlsFor(manage, connection) {
35
35
  }
36
36
  return [];
37
37
  }
38
+ /**
39
+ * A few words under the account's name, only where the badge alone would
40
+ * leave something out: a saved credential that no longer fits, or one saved
41
+ * but not working.
42
+ */
38
43
  function summaryFor(manage, connection) {
39
- if (manage.kind === "credential") {
40
- if (!credentialStored(manage)) {
41
- return manage.actions.includes("save") ? "Add your credential to use its tools." : "No credential saved.";
42
- }
43
- if (connection.problem === "credential_mismatch")
44
- return "Your saved credential no longer fits. Replace it.";
45
- return connection.state === "ok" ? "Your credential is saved." : "Your credential is saved, but it isn't working.";
46
- }
47
- if (connection.state === "ok")
48
- return "Connected with your account.";
49
- if (connection.state === "auth_required") {
50
- return manage.kind === "oauth" && manage.actions.includes("connect")
51
- ? "Not connected. Its tools won't work for you until you connect."
52
- : "Not connected.";
53
- }
54
- return "Unavailable right now.";
44
+ if (manage.kind !== "credential" || !credentialStored(manage))
45
+ return null;
46
+ if (connection.problem === "credential_mismatch")
47
+ return "Saved credential no longer fits";
48
+ return connection.state === "ok" ? null : "Saved, but not working";
55
49
  }
56
50
  const toolCount = (connector) => connector.tools.length;
57
51
  export function memberPage(contract) {
@@ -94,19 +88,10 @@ export function toolCountLabel(count) {
94
88
  /** The in-page question before a member's action that takes something away. */
95
89
  export function memberConfirmCopy(action, name) {
96
90
  if (action === "oauth_restart") {
97
- return {
98
- question: `Reconnect ${name}? Your current connection stops working until you finish authorizing in the new tab.`,
99
- confirm: "Reconnect",
100
- };
91
+ return { question: `Reconnect ${name}? It stops working until you finish in the new tab.`, confirm: "Reconnect" };
101
92
  }
102
93
  if (action === "oauth_disconnect") {
103
- return {
104
- question: `Disconnect your ${name} account? Its tools stop working for you until you connect again.`,
105
- confirm: "Disconnect",
106
- };
94
+ return { question: `Disconnect your ${name} account? Its tools stop working for you.`, confirm: "Disconnect" };
107
95
  }
108
- return {
109
- question: `Clear your ${name} credential? Its tools stop working for you until you add one again.`,
110
- confirm: "Clear",
111
- };
96
+ return { question: `Clear your ${name} credential? Its tools stop working for you.`, confirm: "Clear" };
112
97
  }
@@ -0,0 +1,123 @@
1
+ import { endpointRows } from "./endpoints.js";
2
+ import { joinList } from "./connection-map.js";
3
+ import { connectorVerdict } from "./view.js";
4
+ const PROBLEM_TITLE = {
5
+ connector_unavailable: "is unavailable",
6
+ oauth_required: "needs authorization",
7
+ credential_required: "needs a credential",
8
+ auth_required: "needs a secret in the deployment",
9
+ credential_mismatch: "can't use its saved credential",
10
+ catalog_failed: "couldn't load its tools",
11
+ };
12
+ function affects(paths) {
13
+ return paths.length ? `Affects ${joinList(paths)}` : "No endpoint serves it";
14
+ }
15
+ /**
16
+ * One row per connector that is not healthy, in config order, then one per
17
+ * agent that sits in an endpoint without a working token. Each row carries
18
+ * the fix the rest of the UI already offers for it.
19
+ */
20
+ export function attentionItems(contract, options = {}) {
21
+ if (contract.role !== "maintainer")
22
+ return [];
23
+ const rows = endpointRows(contract);
24
+ const connectors = [];
25
+ for (const config of contract.config?.connectors ?? []) {
26
+ const live = contract.live.connectors.find((c) => c.id === config.id);
27
+ if (!live)
28
+ continue;
29
+ const signals = options.signals && Object.hasOwn(options.signals, config.id) ? options.signals[config.id] : {};
30
+ const verdict = connectorVerdict(live, signals);
31
+ if ((verdict.tone !== "warn" && verdict.tone !== "danger") || !verdict.action)
32
+ continue;
33
+ const title = config.title ?? config.id;
34
+ const id = encodeURIComponent(config.id);
35
+ const paths = rows.filter((row) => row.groups.some((group) => group.config.id === config.id)).map((r) => r.path);
36
+ const connection = `/connectors/${id}#connection`;
37
+ const base = {
38
+ key: `connector:${config.id}`,
39
+ kind: "connector",
40
+ tone: verdict.tone,
41
+ subject: title,
42
+ action: { label: verdict.action, href: connection, ariaLabel: `${verdict.action} ${title}` },
43
+ href: connection,
44
+ };
45
+ // The server's problem code outranks a failed read on this page: the row
46
+ // keeps its fix prompt and says the details didn't load. "Unavailable"
47
+ // beside a failed read is the page's own (see connectorVerdict).
48
+ const reported = live.problem ?? (live.status === "error" ? "connector_unavailable" : undefined);
49
+ const problem = signals.failed && reported === "connector_unavailable" ? undefined : reported;
50
+ if (problem) {
51
+ connectors.push({
52
+ ...base,
53
+ problem: PROBLEM_TITLE[problem],
54
+ body: signals.failed ? `Details didn't load · ${affects(paths)}` : affects(paths),
55
+ fix: { kind: problem, connectorId: config.id },
56
+ });
57
+ }
58
+ else if (signals.failed) {
59
+ connectors.push({
60
+ ...base,
61
+ problem: "didn't load on this page",
62
+ body: affects(paths),
63
+ fix: null,
64
+ });
65
+ }
66
+ else if (live.status !== "ok") {
67
+ connectors.push({
68
+ ...base,
69
+ problem: verdict.label === "Authorization needed" ? "needs authorization" : "isn't connected",
70
+ body: affects(paths),
71
+ fix: null,
72
+ });
73
+ }
74
+ }
75
+ const agents = contract.agents
76
+ .filter((agent) => agent.pools.length && agent.token.state !== "issued")
77
+ .map((agent) => {
78
+ // An agent connects only at a pool's agent URL.
79
+ const paths = agent.pools.map((name) => contract.pools.find((pool) => pool.name === name)?.agentPath ?? `/agent/${name}`);
80
+ const revoked = agent.token.state === "revoked";
81
+ return {
82
+ key: `agent:${agent.name}`,
83
+ kind: "agent",
84
+ tone: "warn",
85
+ subject: agent.name,
86
+ problem: revoked ? "has a revoked token" : "has no token",
87
+ body: `Can't connect at ${joinList(paths)}`,
88
+ action: options.tokensPage
89
+ ? { label: "Issue token", href: "/tokens", ariaLabel: `Issue a token for ${agent.name}` }
90
+ : null,
91
+ href: options.tokensPage ? "/tokens" : null,
92
+ fix: null,
93
+ };
94
+ });
95
+ // Severity changes as individual details settle. Config order keeps surviving
96
+ // rows in place without waiting for the slowest connector's read.
97
+ return [...connectors, ...agents];
98
+ }
99
+ /** The numbers above the list, counted from the same map the page draws. */
100
+ export function overviewHeadline(contract, map, items, signals = {}) {
101
+ const bar = (contract.config?.connectors ?? []).flatMap((config) => {
102
+ const live = contract.live.connectors.find((c) => c.id === config.id);
103
+ if (!live)
104
+ return [];
105
+ const health = connectorVerdict(live, Object.hasOwn(signals, config.id) ? signals[config.id] : undefined);
106
+ return [{ id: config.id, title: config.title ?? config.id, tone: health.tone, label: health.label }];
107
+ });
108
+ const healthy = bar.filter((entry) => entry.tone === "ok").length;
109
+ const warn = bar.filter((entry) => entry.tone === "warn").length;
110
+ const danger = bar.filter((entry) => entry.tone === "danger").length;
111
+ const count = (kind) => map.nodes.filter((node) => node.kind === kind).length;
112
+ return {
113
+ connectors: bar.length,
114
+ healthy,
115
+ warn,
116
+ danger,
117
+ bar,
118
+ endpoints: count("endpoint"),
119
+ readOnlyEndpoints: map.nodes.filter((node) => node.kind === "endpoint" && node.readOnly).length,
120
+ people: count("person"),
121
+ agents: contract.agents.length,
122
+ };
123
+ }
@@ -4,16 +4,7 @@ import type { CredentialManagementCapability, UiConnector, UiData, UiProblem } f
4
4
  import type { FixPromptKind } from "./fix-prompts.js";
5
5
  import type { AgentSlot } from "./agents.js";
6
6
  export declare function registrationPathLabel(path: NonNullable<UiConnector["registrationPath"]>): string;
7
- /**
8
- * A connector's source column: the maintained provider with the
9
- * implementation it uses, since a dual provider's `auth` selects either the
10
- * vendor's hosted MCP server or Connecta's REST connector.
11
- */
12
- export declare function connectorSourceLabel(source: {
13
- kind: string;
14
- provider?: string;
15
- }): string;
16
- export type OperatorPage = "endpoints" | "settings" | "connector" | "tokens" | "connections" | "activity";
7
+ export type OperatorPage = "overview" | "endpoints" | "settings" | "connector" | "tokens" | "connections" | "activity";
17
8
  /** Pages the nav lists, in dashboard order. */
18
9
  export declare const OPERATOR_PAGES: readonly OperatorPage[];
19
10
  export declare const PAGE_META: Readonly<Record<OperatorPage, {
@@ -134,7 +125,7 @@ export declare function credentialTestNotice(connectorId: string, answer: {
134
125
  */
135
126
  export declare function refusedNotice(action: DownstreamAction, connectorId: string, problem?: unknown): Notice;
136
127
  /** An action whose permission or target changed before it was sent. */
137
- export declare const STALE_COPY = "This changed before the request was sent, so nothing was sent. Check it and try again.";
128
+ export declare const STALE_COPY = "This changed before sending, so nothing was sent. Try again.";
138
129
  /**
139
130
  * Every row action's failure, in one voice. The page's own failures — a
140
131
  * lapsed session, a denied identity, a dropped connection — read the same for
@@ -327,12 +318,30 @@ export declare function problemCopy(problem: UiProblem | undefined): string | nu
327
318
  * error. The two used to share the red box, so the row contradicted itself.
328
319
  */
329
320
  export declare function problemTone(problem: UiProblem): "warn" | "danger";
321
+ /** What the page knows about a connector beyond its live status: a failed detail read. */
322
+ export interface ConnectorSignals {
323
+ failed?: boolean;
324
+ }
325
+ /** One connector's health everywhere it is shown: badge tone, status word, and the one action beside it. */
326
+ export interface ConnectorVerdict {
327
+ tone: "ok" | "warn" | "danger" | "neutral";
328
+ label: string;
329
+ /** The link to the Connection tab, or null when the connector is fine. */
330
+ action: "Connect" | "Replace key" | "Inspect" | null;
331
+ }
330
332
  /**
331
- * What this identity may do with a connector, in one line. Being able to use it
332
- * is implied by seeing it at all, so the sentence covers authentication, which
333
- * is the part that differs between operators.
333
+ * The verdict the Connectors list, the Overview, Endpoints and a connector's
334
+ * page all show. The server's problem code comes first, then a failed detail
335
+ * read on this page, then a non-ok status. Catalog drift is informational. A problem
336
+ * outranks the status, so a connector whose catalog failed is never
337
+ * "Connected". A failed read marks its connector `connector_unavailable` in
338
+ * the store, so that one code beside a failed read is the page's own and
339
+ * reads "Couldn't load" instead of blaming the downstream.
334
340
  */
335
- export declare function permissionLabel(connector: UiConnector): string;
341
+ export declare function connectorVerdict(live: {
342
+ status: string;
343
+ problem?: UiProblem;
344
+ }, signals?: ConnectorSignals): ConnectorVerdict;
336
345
  /**
337
346
  * Hosted-provider catalog drift, as an operator reads it
338
347
  * ([#343](https://github.com/zackbart/connecta/issues/343)).
@@ -378,6 +387,8 @@ export declare function activityOutcomeBadge(outcome: string): {
378
387
  label: string;
379
388
  tone: Tone;
380
389
  };
390
+ /** Why a call stalled or failed, in words; "" when nothing was recorded. */
391
+ export declare function activityReason(event: UiActivityEvent): string;
381
392
  /** The one-line detail under an address: where it ran, retries, and why it stalled. */
382
393
  export declare function activityDetail(event: UiActivityEvent): string;
383
394
  /**
@@ -385,13 +396,6 @@ export declare function activityDetail(event: UiActivityEvent): string;
385
396
  * beside it is not shown: it can carry a vault's own words.
386
397
  */
387
398
  export declare function credentialProblemCopy(problem: string | undefined): string;
388
- export declare function credentialStateLabel(credential: {
389
- configured: boolean | null;
390
- problem?: string;
391
- fields?: unknown[];
392
- lastFour?: string;
393
- updatedAt?: string;
394
- }): string;
395
399
  /** Gate copy for each browser-auth shape, so the sign-in state is never a blank page. */
396
400
  export declare function gateCopy(kind: string, signedIn: boolean): string;
397
401
  export {};