@zackbart/connecta 0.4.1 → 0.6.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.
Files changed (85) hide show
  1. package/CHANGELOG.md +527 -0
  2. package/README.md +83 -7
  3. package/SECURITY.md +10 -6
  4. package/dist/activity.d.ts +8 -0
  5. package/dist/activity.d.ts.map +1 -1
  6. package/dist/activity.js +1 -0
  7. package/dist/activity.js.map +1 -1
  8. package/dist/auth/bearer.d.ts +10 -3
  9. package/dist/auth/bearer.d.ts.map +1 -1
  10. package/dist/auth/bearer.js +21 -0
  11. package/dist/auth/bearer.js.map +1 -1
  12. package/dist/auth/clerk.d.ts +26 -1
  13. package/dist/auth/clerk.d.ts.map +1 -1
  14. package/dist/auth/clerk.js +161 -4
  15. package/dist/auth/clerk.js.map +1 -1
  16. package/dist/connectors/api.d.ts +13 -0
  17. package/dist/connectors/api.d.ts.map +1 -1
  18. package/dist/connectors/api.js +2 -0
  19. package/dist/connectors/api.js.map +1 -1
  20. package/dist/connectors/remote-mcp.d.ts +13 -0
  21. package/dist/connectors/remote-mcp.d.ts.map +1 -1
  22. package/dist/connectors/remote-mcp.js +10 -0
  23. package/dist/connectors/remote-mcp.js.map +1 -1
  24. package/dist/credential-health.d.ts +212 -0
  25. package/dist/credential-health.d.ts.map +1 -0
  26. package/dist/credential-health.js +535 -0
  27. package/dist/credential-health.js.map +1 -0
  28. package/dist/execute.d.ts +4 -4
  29. package/dist/execute.d.ts.map +1 -1
  30. package/dist/execute.js +16 -4
  31. package/dist/execute.js.map +1 -1
  32. package/dist/index.d.ts +77 -2
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +112 -2
  35. package/dist/index.js.map +1 -1
  36. package/dist/meta-tools.d.ts +76 -7
  37. package/dist/meta-tools.d.ts.map +1 -1
  38. package/dist/meta-tools.js +328 -98
  39. package/dist/meta-tools.js.map +1 -1
  40. package/dist/registry.d.ts +245 -2
  41. package/dist/registry.d.ts.map +1 -1
  42. package/dist/registry.js +377 -27
  43. package/dist/registry.js.map +1 -1
  44. package/dist/server.d.ts +7 -1
  45. package/dist/server.d.ts.map +1 -1
  46. package/dist/server.js +342 -27
  47. package/dist/server.js.map +1 -1
  48. package/dist/skills.d.ts +53 -2
  49. package/dist/skills.d.ts.map +1 -1
  50. package/dist/skills.js +162 -2
  51. package/dist/skills.js.map +1 -1
  52. package/dist/timeout.d.ts +16 -0
  53. package/dist/timeout.d.ts.map +1 -0
  54. package/dist/timeout.js +38 -0
  55. package/dist/timeout.js.map +1 -0
  56. package/dist/toolkits.d.ts +138 -0
  57. package/dist/toolkits.d.ts.map +1 -0
  58. package/dist/toolkits.js +319 -0
  59. package/dist/toolkits.js.map +1 -0
  60. package/dist/types.d.ts +90 -1
  61. package/dist/types.d.ts.map +1 -1
  62. package/dist/ui.d.ts +63 -0
  63. package/dist/ui.d.ts.map +1 -1
  64. package/dist/ui.js +176 -11
  65. package/dist/ui.js.map +1 -1
  66. package/dist/version.d.ts +1 -1
  67. package/dist/version.js +1 -1
  68. package/package.json +5 -2
  69. package/src/activity.ts +9 -0
  70. package/src/auth/bearer.ts +35 -1
  71. package/src/auth/clerk.ts +202 -5
  72. package/src/connectors/api.ts +15 -0
  73. package/src/connectors/remote-mcp.ts +24 -0
  74. package/src/credential-health.ts +736 -0
  75. package/src/execute.ts +32 -8
  76. package/src/index.ts +226 -2
  77. package/src/meta-tools.ts +397 -119
  78. package/src/registry.ts +540 -29
  79. package/src/server.ts +431 -25
  80. package/src/skills.ts +185 -2
  81. package/src/timeout.ts +49 -0
  82. package/src/toolkits.ts +450 -0
  83. package/src/types.ts +96 -2
  84. package/src/ui.ts +190 -11
  85. package/src/version.ts +1 -1
package/src/skills.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { Connector } from "./types.js";
2
+
1
3
  export const CONNECTA_INSTRUCTIONS =
2
4
  'Connecta exposes many integrations behind meta-tools. When an address is unknown, start with search_tools and includeSchemas="compact"; use describe_tools only when that schema is insufficient. Use call_tool for one explicitly read-only call, batch_call for 2–10 independent explicitly read-only calls, and execute_code (when available) only for dependent read-only steps, loops, joins, or reducing large results. Unannotated, write-capable, and destructive tools must use call_destructive_tool individually. Use authorize_connector only after auth_required and get_result only for truncated results. For the detailed workflow, call skills({ name: "usage" }) once per task.';
3
5
 
@@ -14,7 +16,7 @@ export const USAGE_SKILL = `# Connecta usage
14
16
  - Truncated result: retry with \`fields\` when possible; otherwise page it with \`get_result\`.
15
17
  - \`auth_required\`: use \`authorize_connector\`, have the operator complete consent, then confirm with \`list_connectors\`.
16
18
 
17
- Use \`list_connectors({ probe: false })\` for a fast inventory. Use \`probe: true\` only when diagnosing live health or authorization.
19
+ Use \`list_connectors({ probe: false })\` for a fast inventory. Use \`probe: true\` only when diagnosing live health or authorization. The fast inventory already reports a connector whose stored credential failed a proactive check as \`auth_required\` (with \`credentialCheck\` and the URL to open), so trust it and authorize up front rather than probing to confirm.
18
20
 
19
21
  ## Code mode
20
22
 
@@ -53,11 +55,192 @@ async () => {
53
55
  \`\`\`
54
56
  `;
55
57
 
58
+ /**
59
+ * Appended to USAGE_SKILL only when the deployment actually has at least one
60
+ * connector guide. A deployment with none — every deployment that has not
61
+ * adopted the feature — keeps the base guide byte-for-byte, rather than paying
62
+ * context for an instruction to fetch guides that do not exist.
63
+ */
64
+ export const CONNECTOR_GUIDES_SECTION = `
65
+ ## Per-connector guides
66
+
67
+ Some connectors here ship their own usage guide — preferred tools, address quirks, pagination conventions, rate-limit etiquette, query patterns. \`skills({})\` lists each one as \`connector:<connectorId>\`; fetch it with \`skills({ name: "connector:<connectorId>" })\`. \`search_tools\` and \`describe_tools\` set \`guide\` on matches whose connector has one. Read a connector's guide before working with it for the first time in a task.
68
+ `;
69
+
70
+ /** True when at least one of `connectors` carries a usage guide. */
71
+ export function hasConnectorGuides(connectors: readonly Connector[]): boolean {
72
+ return connectors.some(
73
+ (connector) => connectorGuide(connector) !== undefined,
74
+ );
75
+ }
76
+
77
+ /** The built-in usage guide, plus the guides section when there is one to point at. */
78
+ export function usageSkill(connectors: readonly Connector[]): string {
79
+ return hasConnectorGuides(connectors)
80
+ ? USAGE_SKILL + CONNECTOR_GUIDES_SECTION
81
+ : USAGE_SKILL;
82
+ }
83
+
56
84
  export const AVAILABLE_SKILLS = [
57
85
  {
58
86
  name: "usage",
59
87
  description:
60
88
  "How to choose among Connecta discovery, direct, batch, destructive, and code-mode tools.",
61
- content: USAGE_SKILL,
89
+ content: usageSkill,
62
90
  },
63
91
  ] as const;
92
+
93
+ /**
94
+ * Namespace for operator-authored per-connector guides. Built-in skill names
95
+ * are bare identifiers and never contain ":", so `connector:<id>` cannot
96
+ * collide with one — not even when a connector's id is literally "usage".
97
+ * The prefixed form is the ONLY way to reach a connector guide: a bare
98
+ * connector id is never resolved, so nothing shadows anything silently.
99
+ */
100
+ export const CONNECTOR_SKILL_PREFIX = "connector:";
101
+
102
+ /** The skill name that fetches `connector`'s guide. */
103
+ export function connectorSkillName(connectorId: string): string {
104
+ return `${CONNECTOR_SKILL_PREFIX}${connectorId}`;
105
+ }
106
+
107
+ /** The connector's guide, or undefined when it declares none (or a blank one). */
108
+ export function connectorGuide(connector: Connector): string | undefined {
109
+ const guide = connector.usageGuide;
110
+ return guide && guide.trim() !== "" ? guide : undefined;
111
+ }
112
+
113
+ const SUMMARY_LENGTH = 120;
114
+
115
+ /** A `---`/`***`/`___` rule, which also opens and closes YAML frontmatter. */
116
+ const RULE_RE = /^\s*(?:-{3,}|\*{3,}|_{3,})\s*$/;
117
+
118
+ /** A fenced code block's delimiter. */
119
+ const FENCE_RE = /^\s*(?:```|~~~)/;
120
+
121
+ /**
122
+ * Markup that carries no summary text of its own: horizontal rules, HTML
123
+ * comments, and table rows. Skipped so a guide that opens with one is
124
+ * summarized by its first real line instead of by punctuation.
125
+ */
126
+ const NOT_SUMMARY_RE = /^\s*(?:<!--|\|)|^\s*(?:-{3,}|\*{3,}|_{3,})\s*$/;
127
+
128
+ /** Drop a leading YAML frontmatter block — metadata, not summary text. */
129
+ function withoutFrontmatter(lines: string[]): string[] {
130
+ let start = 0;
131
+ while (start < lines.length && lines[start].trim() === "") start++;
132
+ if (start >= lines.length || !RULE_RE.test(lines[start])) return lines;
133
+ const close = lines.findIndex((line, i) => i > start && RULE_RE.test(line));
134
+ return close === -1 ? lines : lines.slice(close + 1);
135
+ }
136
+
137
+ /**
138
+ * One line describing a guide, for the cheap list view: the guide's first
139
+ * meaningful line (heading marks and list bullets stripped), falling back to
140
+ * the connector's own description when the guide opens with nothing but
141
+ * markup.
142
+ */
143
+ function summarizeGuide(connector: Connector, guide: string): string {
144
+ let inFence = false;
145
+ for (const raw of withoutFrontmatter(guide.split("\n"))) {
146
+ if (FENCE_RE.test(raw)) {
147
+ inFence = !inFence;
148
+ continue;
149
+ }
150
+ if (inFence) continue;
151
+ if (raw.trim() === "" || NOT_SUMMARY_RE.test(raw)) continue;
152
+ const line = raw
153
+ // `\s*` (not `\s+`) so a bare `#` strips to nothing and is skipped, and
154
+ // an unspaced `#Heading` is still read as a heading.
155
+ .replace(/^\s*#{1,6}\s*/, "")
156
+ .replace(/^\s*[-*+]\s+/, "")
157
+ .replace(/\s+/g, " ")
158
+ .trim();
159
+ if (line === "") continue;
160
+ return line.length <= SUMMARY_LENGTH
161
+ ? line
162
+ : `${line.slice(0, SUMMARY_LENGTH - 1).trimEnd()}…`;
163
+ }
164
+ return connector.description ?? `Usage guide for "${connector.id}".`;
165
+ }
166
+
167
+ export interface SkillListing {
168
+ name: string;
169
+ description: string;
170
+ }
171
+
172
+ /**
173
+ * Every fetchable skill: the built-in guides plus one entry per connector that
174
+ * carries a usage guide. Derived from the connector list passed in — the single
175
+ * place guide visibility is decided. The `skills` meta-tool passes its
176
+ * connection's `registry.listConnectors()`, so a toolkit-scoped session lists
177
+ * only in-scope guides, and `resolveSkill` below reports an out-of-scope
178
+ * `connector:<id>` exactly as it reports an unknown connector.
179
+ */
180
+ export function listSkills(connectors: readonly Connector[]): SkillListing[] {
181
+ const listing: SkillListing[] = AVAILABLE_SKILLS.map((skill) => ({
182
+ name: skill.name,
183
+ description: skill.description,
184
+ }));
185
+ for (const connector of connectors) {
186
+ const guide = connectorGuide(connector);
187
+ if (!guide) continue;
188
+ listing.push({
189
+ name: connectorSkillName(connector.id),
190
+ description: summarizeGuide(connector, guide),
191
+ });
192
+ }
193
+ return listing;
194
+ }
195
+
196
+ export type SkillLookup =
197
+ { found: true; content: string } | { found: false; message: string };
198
+
199
+ /**
200
+ * Resolve one skill name. Built-in names match exactly; connector guides are
201
+ * reachable only through the `connector:` prefix. Every miss — unknown name,
202
+ * unknown connector, connector without a guide — is an explicit error, never a
203
+ * silent fallback to the generic guide.
204
+ */
205
+ export function resolveSkill(
206
+ name: string,
207
+ connectors: readonly Connector[],
208
+ ): SkillLookup {
209
+ const builtIn = AVAILABLE_SKILLS.find((skill) => skill.name === name);
210
+ if (builtIn) return { found: true, content: builtIn.content(connectors) };
211
+ const available = () =>
212
+ listSkills(connectors)
213
+ .map((skill) => skill.name)
214
+ .join(", ");
215
+ if (name.startsWith(CONNECTOR_SKILL_PREFIX)) {
216
+ const id = name.slice(CONNECTOR_SKILL_PREFIX.length);
217
+ const connector = connectors.find((c) => c.id === id);
218
+ if (!connector) {
219
+ return {
220
+ found: false,
221
+ message: `Unknown connector "${id}". Available skills: ${available()}.`,
222
+ };
223
+ }
224
+ const guide = connectorGuide(connector);
225
+ if (!guide) {
226
+ return {
227
+ found: false,
228
+ message: `Connector "${id}" has no usage guide. Available skills: ${available()}.`,
229
+ };
230
+ }
231
+ return { found: true, content: guide };
232
+ }
233
+ const bare = connectors.find((c) => c.id === name);
234
+ if (bare) {
235
+ return {
236
+ found: false,
237
+ message: connectorGuide(bare)
238
+ ? `Unknown skill "${name}". Connector guides are fetched as "${connectorSkillName(name)}". Available: ${available()}.`
239
+ : `Connector "${name}" has no usage guide. Available skills: ${available()}.`,
240
+ };
241
+ }
242
+ return {
243
+ found: false,
244
+ message: `Unknown skill "${name}". Available: ${available()}.`,
245
+ };
246
+ }
package/src/timeout.ts ADDED
@@ -0,0 +1,49 @@
1
+ // The deadline vocabulary shared by every non-call downstream probe: the
2
+ // discovery meta-tools' catalog fan-out (src/meta-tools.ts) and the credential
3
+ // liveness checks (src/credential-health.ts). One definition so a "probe" means
4
+ // the same thing, and is bounded the same way, wherever one is issued.
5
+
6
+ /**
7
+ * Generous default bound for a single downstream probe/catalog call. High enough
8
+ * to trip only on a pathological hang, not a realistically slow probe.
9
+ */
10
+ export const DEFAULT_PROBE_TIMEOUT_MS = 30_000;
11
+
12
+ /** A finite, positive integer number of milliseconds, or undefined. */
13
+ export function normalizeTimeoutMs(
14
+ value: number | undefined,
15
+ ): number | undefined {
16
+ if (value === undefined || !Number.isFinite(value) || !(value > 0)) {
17
+ return undefined;
18
+ }
19
+ return Math.max(1, Math.trunc(value));
20
+ }
21
+
22
+ /**
23
+ * Reject `promise` after `ms` if it has not settled, so one hung downstream
24
+ * cannot stall a whole fan-out. NOTE: this bounds only the caller-facing wait —
25
+ * the registry probe methods take no AbortSignal, so the underlying fetch is
26
+ * NOT cancelled and keeps running in the background. Real cancellation
27
+ * (AbortSignal plumbed through the registry) is a deferred follow-up.
28
+ */
29
+ export function withTimeout<T>(
30
+ promise: Promise<T>,
31
+ ms: number,
32
+ label: string,
33
+ ): Promise<T> {
34
+ return new Promise<T>((resolve, reject) => {
35
+ const timer = setTimeout(() => {
36
+ reject(new Error(`${label} timed out after ${ms}ms`));
37
+ }, ms);
38
+ promise.then(
39
+ (value) => {
40
+ clearTimeout(timer);
41
+ resolve(value);
42
+ },
43
+ (err) => {
44
+ clearTimeout(timer);
45
+ reject(err);
46
+ },
47
+ );
48
+ });
49
+ }