terse-cli 0.3.7 → 0.3.8

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 (201) hide show
  1. package/dist/_tmp_create_project.d.ts +2 -0
  2. package/dist/_tmp_create_project.d.ts.map +1 -0
  3. package/dist/_tmp_create_project.js +17 -0
  4. package/dist/_tmp_create_project.js.map +1 -0
  5. package/dist/_tmp_delete_agent.d.ts +2 -0
  6. package/dist/_tmp_delete_agent.d.ts.map +1 -0
  7. package/dist/_tmp_delete_agent.js +14 -0
  8. package/dist/_tmp_delete_agent.js.map +1 -0
  9. package/dist/_tmp_manual_trigger.d.ts +2 -0
  10. package/dist/_tmp_manual_trigger.d.ts.map +1 -0
  11. package/dist/_tmp_manual_trigger.js +17 -0
  12. package/dist/_tmp_manual_trigger.js.map +1 -0
  13. package/dist/_tmp_patch_active.d.ts +2 -0
  14. package/dist/_tmp_patch_active.d.ts.map +1 -0
  15. package/dist/_tmp_patch_active.js +21 -0
  16. package/dist/_tmp_patch_active.js.map +1 -0
  17. package/dist/codegen.d.ts +15 -0
  18. package/dist/codegen.d.ts.map +1 -1
  19. package/dist/codegen.js +466 -81
  20. package/dist/codegen.js.map +1 -1
  21. package/dist/commands/auth.js +3 -3
  22. package/dist/commands/auth.js.map +1 -1
  23. package/dist/commands/authOrg.js +3 -3
  24. package/dist/commands/authOrg.js.map +1 -1
  25. package/dist/commands/authStatus.d.ts.map +1 -1
  26. package/dist/commands/authStatus.js +2 -3
  27. package/dist/commands/authStatus.js.map +1 -1
  28. package/dist/commands/deploy.js +1 -2
  29. package/dist/commands/deploy.js.map +1 -1
  30. package/dist/commands/deployTheme.d.ts +28 -0
  31. package/dist/commands/deployTheme.d.ts.map +1 -0
  32. package/dist/commands/deployTheme.js +68 -0
  33. package/dist/commands/deployTheme.js.map +1 -0
  34. package/dist/commands/dev.d.ts +3 -0
  35. package/dist/commands/dev.d.ts.map +1 -0
  36. package/dist/commands/dev.js +47 -0
  37. package/dist/commands/dev.js.map +1 -0
  38. package/dist/commands/fsExec.d.ts +8 -0
  39. package/dist/commands/fsExec.d.ts.map +1 -0
  40. package/dist/commands/fsExec.js +46 -0
  41. package/dist/commands/fsExec.js.map +1 -0
  42. package/dist/commands/install.d.ts.map +1 -1
  43. package/dist/commands/install.js +2 -2
  44. package/dist/commands/install.js.map +1 -1
  45. package/dist/commands/metaAdsScope.d.ts +12 -0
  46. package/dist/commands/metaAdsScope.d.ts.map +1 -0
  47. package/dist/commands/metaAdsScope.js +120 -0
  48. package/dist/commands/metaAdsScope.js.map +1 -0
  49. package/dist/commands/serve.d.ts +1 -0
  50. package/dist/commands/serve.d.ts.map +1 -0
  51. package/dist/commands/serve.js +2 -0
  52. package/dist/commands/serve.js.map +1 -0
  53. package/dist/commands/terseCliTheme.d.ts +28 -0
  54. package/dist/commands/terseCliTheme.d.ts.map +1 -0
  55. package/dist/commands/terseCliTheme.js +68 -0
  56. package/dist/commands/terseCliTheme.js.map +1 -0
  57. package/dist/commands/tool.d.ts +6 -0
  58. package/dist/commands/tool.d.ts.map +1 -0
  59. package/dist/commands/tool.js +34 -0
  60. package/dist/commands/tool.js.map +1 -0
  61. package/dist/data/standardSlackEmoji.d.ts +2 -0
  62. package/dist/data/standardSlackEmoji.d.ts.map +1 -0
  63. package/dist/data/standardSlackEmoji.js +1978 -0
  64. package/dist/data/standardSlackEmoji.js.map +1 -0
  65. package/dist/data/standardSlackEmoji.json +1974 -0
  66. package/dist/deploy.d.ts +2 -1
  67. package/dist/deploy.d.ts.map +1 -1
  68. package/dist/deploy.js +61 -38
  69. package/dist/deploy.js.map +1 -1
  70. package/dist/generate.d.ts +2 -1
  71. package/dist/generate.d.ts.map +1 -1
  72. package/dist/generate.js +67 -69
  73. package/dist/generate.js.map +1 -1
  74. package/dist/init.d.ts +2 -1
  75. package/dist/init.d.ts.map +1 -1
  76. package/dist/init.js +45 -96
  77. package/dist/init.js.map +1 -1
  78. package/dist/integrate.d.ts +1 -0
  79. package/dist/integrate.d.ts.map +1 -1
  80. package/dist/integrate.js +191 -11
  81. package/dist/integrate.js.map +1 -1
  82. package/dist/nonInteractive.d.ts +4 -2
  83. package/dist/nonInteractive.d.ts.map +1 -1
  84. package/dist/nonInteractive.js +5 -2
  85. package/dist/nonInteractive.js.map +1 -1
  86. package/dist/perf.d.ts +9 -0
  87. package/dist/perf.d.ts.map +1 -0
  88. package/dist/perf.js +59 -0
  89. package/dist/perf.js.map +1 -0
  90. package/dist/providers/codegen.test.d.ts +2 -0
  91. package/dist/providers/codegen.test.d.ts.map +1 -0
  92. package/dist/providers/codegen.test.js +45 -0
  93. package/dist/providers/codegen.test.js.map +1 -0
  94. package/dist/providers/python/PythonProvider.js +1 -0
  95. package/dist/providers/python/PythonProvider.js.map +1 -1
  96. package/dist/providers/python/PythonProvider.test.d.ts +2 -0
  97. package/dist/providers/python/PythonProvider.test.d.ts.map +1 -0
  98. package/dist/providers/python/PythonProvider.test.js +169 -0
  99. package/dist/providers/python/PythonProvider.test.js.map +1 -0
  100. package/dist/providers/typescript/TypeScriptProvider.js +1 -1
  101. package/dist/providers/typescript/TypeScriptProvider.js.map +1 -1
  102. package/dist/providers/typescript/attioProjection.d.ts +50 -0
  103. package/dist/providers/typescript/attioProjection.d.ts.map +1 -0
  104. package/dist/providers/typescript/attioProjection.js +358 -0
  105. package/dist/providers/typescript/attioProjection.js.map +1 -0
  106. package/dist/providers/typescript/modules/cursor/CursorModule.d.ts +20 -0
  107. package/dist/providers/typescript/modules/cursor/CursorModule.d.ts.map +1 -0
  108. package/dist/providers/typescript/modules/cursor/CursorModule.js +27 -0
  109. package/dist/providers/typescript/modules/cursor/CursorModule.js.map +1 -0
  110. package/dist/providers/typescript/prepareCodegenData.test.d.ts +2 -0
  111. package/dist/providers/typescript/prepareCodegenData.test.d.ts.map +1 -0
  112. package/dist/providers/typescript/prepareCodegenData.test.js +54 -0
  113. package/dist/providers/typescript/prepareCodegenData.test.js.map +1 -0
  114. package/dist/providers/typescript/projectedTypes.d.ts +11 -0
  115. package/dist/providers/typescript/projectedTypes.d.ts.map +1 -0
  116. package/dist/providers/typescript/projectedTypes.js +53 -0
  117. package/dist/providers/typescript/projectedTypes.js.map +1 -0
  118. package/dist/providers/typescript/projectionPrinter.d.ts +26 -0
  119. package/dist/providers/typescript/projectionPrinter.d.ts.map +1 -0
  120. package/dist/providers/typescript/projectionPrinter.js +110 -0
  121. package/dist/providers/typescript/projectionPrinter.js.map +1 -0
  122. package/dist/providers/typescript/replayLogDedupe.d.ts +6 -0
  123. package/dist/providers/typescript/replayLogDedupe.d.ts.map +1 -0
  124. package/dist/providers/typescript/replayLogDedupe.js +156 -0
  125. package/dist/providers/typescript/replayLogDedupe.js.map +1 -0
  126. package/dist/providers/typescript/triggerPayloads.d.ts +7 -0
  127. package/dist/providers/typescript/triggerPayloads.d.ts.map +1 -0
  128. package/dist/providers/typescript/triggerPayloads.js +60 -0
  129. package/dist/providers/typescript/triggerPayloads.js.map +1 -0
  130. package/dist/providers/typescript/triggerTypeRegistry.d.ts +7 -0
  131. package/dist/providers/typescript/triggerTypeRegistry.d.ts.map +1 -0
  132. package/dist/providers/typescript/triggerTypeRegistry.js +198 -0
  133. package/dist/providers/typescript/triggerTypeRegistry.js.map +1 -0
  134. package/dist/run.d.ts +2 -11
  135. package/dist/run.d.ts.map +1 -1
  136. package/dist/run.js +42 -153
  137. package/dist/run.js.map +1 -1
  138. package/dist/sampleEventId.d.ts +0 -4
  139. package/dist/sampleEventId.d.ts.map +1 -1
  140. package/dist/sampleEventId.js +0 -4
  141. package/dist/sampleEventId.js.map +1 -1
  142. package/dist/shared/ApiRoutes.d.ts +19 -0
  143. package/dist/shared/ApiRoutes.d.ts.map +1 -1
  144. package/dist/shared/ApiRoutes.js +17 -1
  145. package/dist/shared/ApiRoutes.js.map +1 -1
  146. package/dist/shared/Configs.d.ts +24 -2
  147. package/dist/shared/Configs.d.ts.map +1 -1
  148. package/dist/shared/Configs.js +32 -2
  149. package/dist/shared/Configs.js.map +1 -1
  150. package/dist/shared/Integrations.d.ts +18 -1
  151. package/dist/shared/Integrations.d.ts.map +1 -1
  152. package/dist/shared/Integrations.js +10 -1
  153. package/dist/shared/Integrations.js.map +1 -1
  154. package/dist/shared/InvalidationKeys.d.ts +1 -0
  155. package/dist/shared/InvalidationKeys.d.ts.map +1 -1
  156. package/dist/shared/InvalidationKeys.js +3 -0
  157. package/dist/shared/InvalidationKeys.js.map +1 -1
  158. package/dist/shared/ModelEvents.d.ts +9 -0
  159. package/dist/shared/ModelEvents.d.ts.map +1 -1
  160. package/dist/shared/ModelEvents.js +15 -0
  161. package/dist/shared/ModelEvents.js.map +1 -1
  162. package/dist/shared/ToolDisplayUtils.d.ts.map +1 -1
  163. package/dist/shared/ToolDisplayUtils.js +107 -11
  164. package/dist/shared/ToolDisplayUtils.js.map +1 -1
  165. package/dist/shared/types.d.ts +1013 -108
  166. package/dist/shared/types.d.ts.map +1 -1
  167. package/dist/shared/types.js.map +1 -1
  168. package/dist/test.d.ts +2 -1
  169. package/dist/test.d.ts.map +1 -1
  170. package/dist/test.js +43 -23
  171. package/dist/test.js.map +1 -1
  172. package/dist/triggerParseDetail.d.ts +8 -0
  173. package/dist/triggerParseDetail.d.ts.map +1 -0
  174. package/dist/triggerParseDetail.js +30 -0
  175. package/dist/triggerParseDetail.js.map +1 -0
  176. package/dist/util.d.ts +2 -3
  177. package/dist/util.d.ts.map +1 -1
  178. package/dist/util.js +3 -3
  179. package/dist/util.js.map +1 -1
  180. package/package.json +3 -3
  181. package/templates/typescript/codegen/.impeccable/hook.cache.json +1 -0
  182. package/dist/jsonOutput.d.ts +0 -19
  183. package/dist/jsonOutput.d.ts.map +0 -1
  184. package/dist/jsonOutput.js +0 -25
  185. package/dist/jsonOutput.js.map +0 -1
  186. package/dist/providers/typescript/parkSignals.d.ts +0 -7
  187. package/dist/providers/typescript/parkSignals.d.ts.map +0 -1
  188. package/dist/providers/typescript/parkSignals.js +0 -45
  189. package/dist/providers/typescript/parkSignals.js.map +0 -1
  190. package/dist/replay.d.ts +0 -3
  191. package/dist/replay.d.ts.map +0 -1
  192. package/dist/replay.js +0 -20
  193. package/dist/replay.js.map +0 -1
  194. package/dist/testRelay.d.ts +0 -6
  195. package/dist/testRelay.d.ts.map +0 -1
  196. package/dist/testRelay.js +0 -91
  197. package/dist/testRelay.js.map +0 -1
  198. package/dist/urls.d.ts +0 -3
  199. package/dist/urls.d.ts.map +0 -1
  200. package/dist/urls.js +0 -27
  201. package/dist/urls.js.map +0 -1
package/dist/codegen.js CHANGED
@@ -19,6 +19,82 @@ function sectionHeader(name) {
19
19
  function escapeString(s) {
20
20
  return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
21
21
  }
22
+ function toGeneratedIdentifier(raw, fallback) {
23
+ let name = toPascalCase(raw || fallback);
24
+ if (!name)
25
+ name = fallback;
26
+ if (/^\d/.test(name))
27
+ name = `_${name}`;
28
+ return name;
29
+ }
30
+ function isProbablyAttioMultiValue(attr) {
31
+ const slug = (attr.api_slug || "").toLowerCase();
32
+ const type = (attr.type || "").toLowerCase();
33
+ return (type.includes("multi") ||
34
+ type.includes("array") ||
35
+ type.includes("list") ||
36
+ slug === "email_addresses" ||
37
+ slug === "domains" ||
38
+ slug === "phone_numbers" ||
39
+ slug === "social_profiles" ||
40
+ slug === "links" ||
41
+ slug === "tags" ||
42
+ slug.endsWith("_addresses") ||
43
+ slug.endsWith("_ids"));
44
+ }
45
+ function attioAttributeBaseType(attr) {
46
+ const slug = (attr.api_slug || "").toLowerCase();
47
+ const type = (attr.type || "").toLowerCase();
48
+ if (type.includes("checkbox") || type.includes("boolean"))
49
+ return "boolean";
50
+ if (type.includes("number") || type.includes("currency") || type.includes("rating") || type.includes("percent"))
51
+ return "number";
52
+ if (type.includes("date") || type.includes("time"))
53
+ return "string";
54
+ if (type.includes("email") ||
55
+ type.includes("domain") ||
56
+ type.includes("phone") ||
57
+ type.includes("url") ||
58
+ type.includes("select") ||
59
+ type.includes("status") ||
60
+ type.includes("text") ||
61
+ type.includes("string") ||
62
+ type.includes("name")) {
63
+ return "string";
64
+ }
65
+ if (type.includes("location") ||
66
+ type.includes("address") ||
67
+ type.includes("reference") ||
68
+ type.includes("record") ||
69
+ type.includes("actor")) {
70
+ return "Record<string, unknown>";
71
+ }
72
+ if (slug === "email_addresses" || slug === "domains" || slug === "phone_numbers" || slug === "name") {
73
+ return "string";
74
+ }
75
+ return "unknown";
76
+ }
77
+ function attioAttributeInputTsType(attr) {
78
+ const baseType = attioAttributeBaseType(attr);
79
+ if (!isProbablyAttioMultiValue(attr))
80
+ return baseType;
81
+ return baseType.includes("|") ? `(${baseType})[]` : `${baseType}[]`;
82
+ }
83
+ function attioAttributeRecordTsType(attr) {
84
+ const baseType = attioAttributeBaseType(attr);
85
+ if (!isProbablyAttioMultiValue(attr))
86
+ return baseType;
87
+ return baseType.includes("|") ? `(${baseType})[]` : `${baseType}[]`;
88
+ }
89
+ function renderAttioObjectValueShape(attributes, mode) {
90
+ if (attributes.length === 0)
91
+ return "Record<string, unknown>";
92
+ const lines = attributes.map(attr => {
93
+ const valueType = mode === "input" ? attioAttributeInputTsType(attr) : attioAttributeRecordTsType(attr);
94
+ return ` "${escapeString(attr.api_slug || "")}"?: ${valueType}`;
95
+ });
96
+ return `{\n${lines.join("\n")}\n}`;
97
+ }
22
98
  /** Map a tool's integration key back to the IntegrationType enum value used by ConfigInstance. */
23
99
  function toolIntegrationToIntegrationType(toolIntegration) {
24
100
  switch (toolIntegration) {
@@ -29,6 +105,19 @@ function toolIntegrationToIntegrationType(toolIntegration) {
29
105
  return toolIntegration;
30
106
  }
31
107
  }
108
+ function renderStringLiteralUnion(values) {
109
+ if (values.length === 0)
110
+ return "never";
111
+ return values.map(value => `"${escapeString(value)}"`).join(" | ");
112
+ }
113
+ function buildSkillToolTypeForIntegration(tools, integrationType) {
114
+ const toolNames = tools
115
+ .filter(tool => toolIntegrationToIntegrationType(tool.integration.toLowerCase()) === integrationType)
116
+ .filter(tool => !tool.isReadOnly || tool.supportsApproval)
117
+ .map(tool => tool.name)
118
+ .sort();
119
+ return renderStringLiteralUnion(toolNames);
120
+ }
32
121
  function generateResourceClass(className, fields, staticNameField, items) {
33
122
  const lines = [];
34
123
  lines.push(`export class ${className} {`);
@@ -57,13 +146,14 @@ function generateResourceClass(className, fields, staticNameField, items) {
57
146
  return lines.join("\n");
58
147
  }
59
148
  // ── GitHub ────────────────────────────────────────────────────────────
60
- function generateGitHubSection(instances) {
149
+ function generateGitHubSection(instances, tools) {
61
150
  if (instances.length === 0)
62
151
  return EMPTY_SECTION;
63
152
  const inst = instances[0];
64
153
  const imports = new Set(["GitHubConfig"]);
65
154
  const parts = [sectionHeader("GitHub"), ""];
66
155
  const id = inst.integration.id;
156
+ const skillToolType = buildSkillToolTypeForIntegration(tools, "github");
67
157
  const toStaticName = (raw, fallback) => {
68
158
  let name = toPascalCase(raw || fallback);
69
159
  if (!name)
@@ -125,6 +215,7 @@ function generateGitHubSection(instances) {
125
215
  parts.push("");
126
216
  imports.add("GitHubEventType");
127
217
  imports.add("TypedTrigger");
218
+ imports.add("TypedSkill");
128
219
  imports.add("GithubPRInputEvent");
129
220
  imports.add("GithubPushInputEvent");
130
221
  imports.add("GithubInputEvent");
@@ -155,20 +246,21 @@ function generateGitHubSection(instances) {
155
246
  parts.push(` return new GitHubConfig("${id}", opts.repos.map(r => r.repositoryId), opts.eventTypes) as TypedTrigger<GithubInputEvent>`);
156
247
  parts.push(" },");
157
248
  parts.push(` /** Use in \`skills[]\` */`);
158
- parts.push(` skill(opts: { repos: Repos[] }): GitHubConfig {`);
159
- parts.push(` return new GitHubConfig("${id}", opts.repos.map(r => r.repositoryId))`);
249
+ parts.push(` skill(opts: { repos: Repos[] }): TypedSkill<${skillToolType}> {`);
250
+ parts.push(` return new GitHubConfig("${id}", opts.repos.map(r => r.repositoryId)) as TypedSkill<${skillToolType}>`);
160
251
  parts.push(" },");
161
252
  parts.push("}");
162
253
  parts.push("");
163
254
  return { code: parts.join("\n"), imports };
164
255
  }
165
256
  // ── Gmail ─────────────────────────────────────────────────────────────
166
- function generateGmailSection(instances) {
257
+ function generateGmailSection(instances, tools) {
167
258
  if (instances.length === 0)
168
259
  return EMPTY_SECTION;
169
- const imports = new Set(["GmailConfig", "GmailOutputConfig", "GmailDraftOutputConfig"]);
260
+ const imports = new Set(["GmailConfig", "GmailOutputConfig", "GmailDraftOutputConfig", "TypedSkill"]);
170
261
  const parts = [sectionHeader("Gmail"), ""];
171
262
  const id = instances[0].id;
263
+ const skillToolType = buildSkillToolTypeForIntegration(tools, "gmail");
172
264
  imports.add("GmailEventType");
173
265
  parts.push("export const Gmail = {");
174
266
  parts.push(` /** Trigger when a new email is received */`);
@@ -180,25 +272,26 @@ function generateGmailSection(instances) {
180
272
  parts.push(` return new GmailConfig("${id}", opts?.eventTypes)`);
181
273
  parts.push(" },");
182
274
  parts.push(` /** Use in \`skills[]\` */`);
183
- parts.push(` skill(): GmailOutputConfig {`);
184
- parts.push(` return new GmailOutputConfig("${id}")`);
275
+ parts.push(` skill(): TypedSkill<${skillToolType}> {`);
276
+ parts.push(` return new GmailOutputConfig("${id}") as TypedSkill<${skillToolType}>`);
185
277
  parts.push(" },");
186
278
  parts.push(` /** Use in \`skills[]\` — creates draft emails */`);
187
- parts.push(` draftSkill(): GmailDraftOutputConfig {`);
188
- parts.push(` return new GmailDraftOutputConfig("${id}")`);
279
+ parts.push(` draftSkill(): TypedSkill<${skillToolType}> {`);
280
+ parts.push(` return new GmailDraftOutputConfig("${id}") as TypedSkill<${skillToolType}>`);
189
281
  parts.push(" },");
190
282
  parts.push("}");
191
283
  parts.push("");
192
284
  return { code: parts.join("\n"), imports };
193
285
  }
194
286
  // ── Slack ──────────────────────────────────────────────────────────────
195
- function generateSlackSection(instances) {
287
+ function generateSlackSection(instances, tools) {
196
288
  if (instances.length === 0)
197
289
  return EMPTY_SECTION;
198
290
  const inst = instances[0];
199
- const imports = new Set(["SlackConfig", "SlackOutputConfig"]);
291
+ const imports = new Set(["SlackConfig", "SlackOutputConfig", "TypedSkill"]);
200
292
  const parts = [sectionHeader("Slack"), ""];
201
293
  const id = inst.id;
294
+ const skillToolType = buildSkillToolTypeForIntegration(tools, "slack");
202
295
  parts.push(generateResourceClass("SlackChannel", [
203
296
  { classField: "channelId", type: "string", sourceField: "id" },
204
297
  { classField: "name", type: "string", sourceField: "name" },
@@ -220,8 +313,8 @@ function generateSlackSection(instances) {
220
313
  parts.push(` return new SlackConfig("${id}", opts?.channel?.channelId, opts?.channel?.name, opts?.listenToUserDms, opts?.userIds, opts?.eventTypes)`);
221
314
  parts.push(" },");
222
315
  parts.push(` /** Use in \`skills[]\` */`);
223
- parts.push(` skill(opts?: { channel?: SlackChannel; userIds?: string[]; userNames?: string[]; listenToUserDms?: boolean }): SlackOutputConfig {`);
224
- parts.push(` return new SlackOutputConfig("${id}", opts?.channel?.channelId, opts?.channel?.name, opts?.userIds, opts?.userNames, opts?.listenToUserDms)`);
316
+ parts.push(` skill(opts?: { channel?: SlackChannel; userIds?: string[]; userNames?: string[]; listenToUserDms?: boolean }): TypedSkill<${skillToolType}> {`);
317
+ parts.push(` return new SlackOutputConfig("${id}", opts?.channel?.channelId, opts?.channel?.name, opts?.userIds, opts?.userNames, opts?.listenToUserDms) as TypedSkill<${skillToolType}>`);
225
318
  parts.push(" },");
226
319
  parts.push("}");
227
320
  parts.push("");
@@ -249,13 +342,14 @@ function generateFigmaSection(instances) {
249
342
  return { code: parts.join("\n"), imports };
250
343
  }
251
344
  // ── Linear ────────────────────────────────────────────────────────────
252
- function generateLinearSection(instances) {
345
+ function generateLinearSection(instances, tools) {
253
346
  if (instances.length === 0)
254
347
  return EMPTY_SECTION;
255
348
  const inst = instances[0];
256
- const imports = new Set(["LinearInputConfig", "LinearOutputConfig"]);
349
+ const imports = new Set(["LinearInputConfig", "LinearOutputConfig", "TypedSkill"]);
257
350
  const parts = [sectionHeader("Linear"), ""];
258
351
  const id = inst.id;
352
+ const skillToolType = buildSkillToolTypeForIntegration(tools, "linear");
259
353
  parts.push(generateResourceClass("LinearTeam", [
260
354
  { classField: "teamId", type: "string", sourceField: "id" },
261
355
  { classField: "name", type: "string", sourceField: "name" },
@@ -281,21 +375,22 @@ function generateLinearSection(instances) {
281
375
  parts.push(` return new LinearInputConfig("${id}", opts?.projectId, opts?.projectName, opts?.eventTypes)`);
282
376
  parts.push(" },");
283
377
  parts.push(` /** Use in \`skills[]\` */`);
284
- parts.push(` skill(opts?: { team?: LinearTeam; projectId?: string; projectName?: string }): LinearOutputConfig {`);
285
- parts.push(` return new LinearOutputConfig("${id}", opts?.team?.teamId, opts?.team?.name, opts?.projectId, opts?.projectName)`);
378
+ parts.push(` skill(opts?: { team?: LinearTeam; projectId?: string; projectName?: string }): TypedSkill<${skillToolType}> {`);
379
+ parts.push(` return new LinearOutputConfig("${id}", opts?.team?.teamId, opts?.team?.name, opts?.projectId, opts?.projectName) as TypedSkill<${skillToolType}>`);
286
380
  parts.push(" },");
287
381
  parts.push("}");
288
382
  parts.push("");
289
383
  return { code: parts.join("\n"), imports };
290
384
  }
291
385
  // ── Jira & Confluence (Atlassian) ─────────────────────────────────────
292
- function generateAtlassianSection(instances) {
386
+ function generateAtlassianSection(instances, tools) {
293
387
  if (instances.length === 0)
294
388
  return EMPTY_SECTION;
295
389
  const inst = instances[0];
296
- const imports = new Set(["JiraConfig", "ConfluenceConfig"]);
390
+ const imports = new Set(["JiraConfig", "ConfluenceConfig", "TypedSkill"]);
297
391
  const parts = [sectionHeader("Jira & Confluence"), ""];
298
392
  const id = inst.id;
393
+ const atlassianSkillToolType = buildSkillToolTypeForIntegration(tools, "atlassian");
299
394
  parts.push(generateResourceClass("JiraProject", [
300
395
  { classField: "projectKey", type: "string", sourceField: "key" },
301
396
  { classField: "projectId", type: "string", sourceField: "id" },
@@ -324,28 +419,29 @@ function generateAtlassianSection(instances) {
324
419
  parts.push(` return new JiraConfig("${id}", opts?.project?.projectKey, opts?.project?.projectId, opts?.eventTypes)`);
325
420
  parts.push(" },");
326
421
  parts.push(` /** Use in \`skills[]\` */`);
327
- parts.push(` skill(opts?: { project?: JiraProject }): JiraConfig {`);
328
- parts.push(` return new JiraConfig("${id}", opts?.project?.projectKey, opts?.project?.projectId)`);
422
+ parts.push(` skill(opts?: { project?: JiraProject }): TypedSkill<${atlassianSkillToolType}> {`);
423
+ parts.push(` return new JiraConfig("${id}", opts?.project?.projectKey, opts?.project?.projectId) as TypedSkill<${atlassianSkillToolType}>`);
329
424
  parts.push(" },");
330
425
  parts.push("}");
331
426
  parts.push("");
332
427
  parts.push("export const Confluence = {");
333
428
  parts.push(` /** Use in \`skills[]\` */`);
334
- parts.push(` skill(opts: { page: ConfluencePage }): ConfluenceConfig {`);
335
- parts.push(` return new ConfluenceConfig("${id}", opts.page.spaceName, opts.page.spaceId, opts.page.pageId, opts.page.title)`);
429
+ parts.push(` skill(opts: { page: ConfluencePage }): TypedSkill<${atlassianSkillToolType}> {`);
430
+ parts.push(` return new ConfluenceConfig("${id}", opts.page.spaceName, opts.page.spaceId, opts.page.pageId, opts.page.title) as TypedSkill<${atlassianSkillToolType}>`);
336
431
  parts.push(" },");
337
432
  parts.push("}");
338
433
  parts.push("");
339
434
  return { code: parts.join("\n"), imports };
340
435
  }
341
436
  // ── Notion ────────────────────────────────────────────────────────────
342
- function generateNotionSection(instances) {
437
+ function generateNotionSection(instances, tools) {
343
438
  if (instances.length === 0)
344
439
  return EMPTY_SECTION;
345
440
  const inst = instances[0];
346
- const imports = new Set(["NotionConfig"]);
441
+ const imports = new Set(["NotionConfig", "TypedSkill"]);
347
442
  const parts = [sectionHeader("Notion"), ""];
348
443
  const id = inst.id;
444
+ const skillToolType = buildSkillToolTypeForIntegration(tools, "notion");
349
445
  parts.push(generateResourceClass("NotionDatabase", [
350
446
  { classField: "databaseId", type: "string", sourceField: "id" },
351
447
  { classField: "title", type: "string", sourceField: "title" },
@@ -358,23 +454,24 @@ function generateNotionSection(instances) {
358
454
  parts.push("");
359
455
  parts.push("export const Notion = {");
360
456
  parts.push(` /** Use in \`skills[]\` */`);
361
- parts.push(` skill(opts?: { databases?: NotionDatabase[]; pages?: NotionPage[] }): NotionConfig {`);
457
+ parts.push(` skill(opts?: { databases?: NotionDatabase[]; pages?: NotionPage[] }): TypedSkill<${skillToolType}> {`);
362
458
  parts.push(` return new NotionConfig("${id}",`);
363
459
  parts.push(` opts?.databases?.map(d => d.databaseId), opts?.databases?.map(d => d.title),`);
364
- parts.push(` opts?.pages?.map(p => p.pageId), opts?.pages?.map(p => p.title))`);
460
+ parts.push(` opts?.pages?.map(p => p.pageId), opts?.pages?.map(p => p.title)) as TypedSkill<${skillToolType}>`);
365
461
  parts.push(" },");
366
462
  parts.push("}");
367
463
  parts.push("");
368
464
  return { code: parts.join("\n"), imports };
369
465
  }
370
466
  // ── PostHog ───────────────────────────────────────────────────────────
371
- function generatePosthogSection(instances) {
467
+ function generatePosthogSection(instances, tools) {
372
468
  if (instances.length === 0)
373
469
  return EMPTY_SECTION;
374
470
  const inst = instances[0];
375
- const imports = new Set(["PosthogConfig"]);
471
+ const imports = new Set(["PosthogConfig", "TypedSkill"]);
376
472
  const parts = [sectionHeader("PostHog"), ""];
377
473
  const id = inst.id;
474
+ const skillToolType = buildSkillToolTypeForIntegration(tools, "posthog");
378
475
  parts.push(generateResourceClass("PosthogProject", [
379
476
  { classField: "projectId", type: "string", sourceField: "id" },
380
477
  { classField: "name", type: "string", sourceField: "name" },
@@ -382,42 +479,44 @@ function generatePosthogSection(instances) {
382
479
  parts.push("");
383
480
  parts.push("export const Posthog = {");
384
481
  parts.push(` /** Use in \`skills[]\` */`);
385
- parts.push(` skill(opts: { project: PosthogProject }): PosthogConfig {`);
386
- parts.push(` return new PosthogConfig("${id}", opts.project.projectId, opts.project.name)`);
482
+ parts.push(` skill(opts: { project: PosthogProject }): TypedSkill<${skillToolType}> {`);
483
+ parts.push(` return new PosthogConfig("${id}", opts.project.projectId, opts.project.name) as TypedSkill<${skillToolType}>`);
387
484
  parts.push(" },");
388
485
  parts.push("}");
389
486
  parts.push("");
390
487
  return { code: parts.join("\n"), imports };
391
488
  }
392
489
  // ── Datadog ───────────────────────────────────────────────────────────
393
- function generateDatadogSection(instances) {
490
+ function generateDatadogSection(instances, tools) {
394
491
  if (instances.length === 0)
395
492
  return EMPTY_SECTION;
396
493
  const inst = instances[0];
397
- const imports = new Set(["DatadogConfig"]);
494
+ const imports = new Set(["DatadogConfig", "TypedSkill"]);
398
495
  const parts = [sectionHeader("Datadog"), ""];
399
496
  const id = inst.id;
497
+ const skillToolType = buildSkillToolTypeForIntegration(tools, "datadog");
400
498
  parts.push(generateResourceClass("DatadogIndex", [
401
499
  { classField: "name", type: "string", sourceField: "name" },
402
500
  ], "name", inst.indexes));
403
501
  parts.push("");
404
502
  parts.push("export const Datadog = {");
405
503
  parts.push(` /** Use in \`skills[]\` */`);
406
- parts.push(` skill(opts?: { indexes?: DatadogIndex[] }): DatadogConfig {`);
407
- parts.push(` return new DatadogConfig("${id}", opts?.indexes?.map(i => i.name))`);
504
+ parts.push(` skill(opts?: { indexes?: DatadogIndex[] }): TypedSkill<${skillToolType}> {`);
505
+ parts.push(` return new DatadogConfig("${id}", opts?.indexes?.map(i => i.name)) as TypedSkill<${skillToolType}>`);
408
506
  parts.push(" },");
409
507
  parts.push("}");
410
508
  parts.push("");
411
509
  return { code: parts.join("\n"), imports };
412
510
  }
413
511
  // ── LaunchDarkly ──────────────────────────────────────────────────────
414
- function generateLaunchDarklySection(instances) {
512
+ function generateLaunchDarklySection(instances, tools) {
415
513
  if (instances.length === 0)
416
514
  return EMPTY_SECTION;
417
515
  const inst = instances[0];
418
- const imports = new Set(["LaunchDarklyConfig"]);
516
+ const imports = new Set(["LaunchDarklyConfig", "TypedSkill"]);
419
517
  const parts = [sectionHeader("LaunchDarkly"), ""];
420
518
  const id = inst.id;
519
+ const skillToolType = buildSkillToolTypeForIntegration(tools, "launchdarkly");
421
520
  parts.push(generateResourceClass("LaunchDarklyProject", [
422
521
  { classField: "projectKey", type: "string", sourceField: "key" },
423
522
  { classField: "name", type: "string", sourceField: "name" },
@@ -425,70 +524,160 @@ function generateLaunchDarklySection(instances) {
425
524
  parts.push("");
426
525
  parts.push("export const LaunchDarkly = {");
427
526
  parts.push(` /** Use in \`skills[]\` */`);
428
- parts.push(` skill(opts: { project: LaunchDarklyProject; environmentKeys: string[] }): LaunchDarklyConfig {`);
429
- parts.push(` return new LaunchDarklyConfig("${id}", opts.project.projectKey, opts.environmentKeys)`);
527
+ parts.push(` skill(opts: { project: LaunchDarklyProject; environmentKeys: string[] }): TypedSkill<${skillToolType}> {`);
528
+ parts.push(` return new LaunchDarklyConfig("${id}", opts.project.projectKey, opts.environmentKeys) as TypedSkill<${skillToolType}>`);
430
529
  parts.push(" },");
431
530
  parts.push("}");
432
531
  parts.push("");
433
532
  return { code: parts.join("\n"), imports };
434
533
  }
435
534
  // ── WorkOS ────────────────────────────────────────────────────────────
436
- function generateWorkOSSection(instances) {
535
+ function generateWorkOSSection(instances, tools) {
437
536
  if (instances.length === 0)
438
537
  return EMPTY_SECTION;
439
- const imports = new Set(["WorkOSInputConfig", "WorkOSOutputConfig", "WorkOSEventType"]);
538
+ const imports = new Set(["WorkOSInputConfig", "WorkOSOutputConfig", "WorkOSEventType", "TypedSkill"]);
440
539
  const parts = [sectionHeader("WorkOS"), ""];
441
540
  const id = instances[0].id;
541
+ const skillToolType = buildSkillToolTypeForIntegration(tools, "workos");
542
+ imports.add("TypedTrigger");
543
+ imports.add("WorkOSInputEvent");
544
+ imports.add("WorkOSUserInputEvent");
545
+ imports.add("WorkOSMembershipInputEvent");
546
+ imports.add("WorkOSInvitationInputEvent");
547
+ imports.add("WorkOSOrganizationInputEvent");
442
548
  parts.push("export const WorkOS = {");
443
549
  parts.push(` /** Trigger on user creation */`);
444
- parts.push(` onUserCreated(): WorkOSInputConfig {`);
445
- parts.push(` return new WorkOSInputConfig("${id}", [WorkOSEventType.USER_CREATED])`);
550
+ parts.push(` onUserCreated(): TypedTrigger<WorkOSUserInputEvent> {`);
551
+ parts.push(` return new WorkOSInputConfig("${id}", [WorkOSEventType.USER_CREATED]) as TypedTrigger<WorkOSUserInputEvent>`);
446
552
  parts.push(" },");
447
553
  parts.push(` /** Trigger on user update */`);
448
- parts.push(` onUserUpdated(): WorkOSInputConfig {`);
449
- parts.push(` return new WorkOSInputConfig("${id}", [WorkOSEventType.USER_UPDATED])`);
554
+ parts.push(` onUserUpdated(): TypedTrigger<WorkOSUserInputEvent> {`);
555
+ parts.push(` return new WorkOSInputConfig("${id}", [WorkOSEventType.USER_UPDATED]) as TypedTrigger<WorkOSUserInputEvent>`);
450
556
  parts.push(" },");
451
557
  parts.push(` /** Trigger on user deletion */`);
452
- parts.push(` onUserDeleted(): WorkOSInputConfig {`);
453
- parts.push(` return new WorkOSInputConfig("${id}", [WorkOSEventType.USER_DELETED])`);
558
+ parts.push(` onUserDeleted(): TypedTrigger<WorkOSUserInputEvent> {`);
559
+ parts.push(` return new WorkOSInputConfig("${id}", [WorkOSEventType.USER_DELETED]) as TypedTrigger<WorkOSUserInputEvent>`);
454
560
  parts.push(" },");
455
561
  parts.push(` /** Trigger on organization membership change */`);
456
- parts.push(` onMembershipChanged(): WorkOSInputConfig {`);
457
- parts.push(` return new WorkOSInputConfig("${id}", [WorkOSEventType.ORGANIZATION_MEMBERSHIP_CREATED, WorkOSEventType.ORGANIZATION_MEMBERSHIP_UPDATED, WorkOSEventType.ORGANIZATION_MEMBERSHIP_DELETED])`);
562
+ parts.push(` onMembershipChanged(): TypedTrigger<WorkOSMembershipInputEvent> {`);
563
+ parts.push(` return new WorkOSInputConfig("${id}", [WorkOSEventType.ORGANIZATION_MEMBERSHIP_CREATED, WorkOSEventType.ORGANIZATION_MEMBERSHIP_UPDATED, WorkOSEventType.ORGANIZATION_MEMBERSHIP_DELETED]) as TypedTrigger<WorkOSMembershipInputEvent>`);
564
+ parts.push(" },");
565
+ parts.push(` /** Trigger when an invitation is sent */`);
566
+ parts.push(` onInvitationSent(): TypedTrigger<WorkOSInvitationInputEvent> {`);
567
+ parts.push(` return new WorkOSInputConfig("${id}", [WorkOSEventType.INVITATION_CREATED, WorkOSEventType.INVITATION_RESENT]) as TypedTrigger<WorkOSInvitationInputEvent>`);
568
+ parts.push(" },");
569
+ parts.push(` /** Trigger when an invitation is first created */`);
570
+ parts.push(` onInvitationCreated(): TypedTrigger<WorkOSInvitationInputEvent> {`);
571
+ parts.push(` return new WorkOSInputConfig("${id}", [WorkOSEventType.INVITATION_CREATED]) as TypedTrigger<WorkOSInvitationInputEvent>`);
572
+ parts.push(" },");
573
+ parts.push(` /** Trigger when an invitation is resent */`);
574
+ parts.push(` onInvitationResent(): TypedTrigger<WorkOSInvitationInputEvent> {`);
575
+ parts.push(` return new WorkOSInputConfig("${id}", [WorkOSEventType.INVITATION_RESENT]) as TypedTrigger<WorkOSInvitationInputEvent>`);
458
576
  parts.push(" },");
459
577
  parts.push(` /** Trigger on invitation accepted */`);
460
- parts.push(` onInvitationAccepted(): WorkOSInputConfig {`);
461
- parts.push(` return new WorkOSInputConfig("${id}", [WorkOSEventType.INVITATION_ACCEPTED])`);
578
+ parts.push(` onInvitationAccepted(): TypedTrigger<WorkOSInvitationInputEvent> {`);
579
+ parts.push(` return new WorkOSInputConfig("${id}", [WorkOSEventType.INVITATION_ACCEPTED]) as TypedTrigger<WorkOSInvitationInputEvent>`);
580
+ parts.push(" },");
581
+ parts.push(` /** Trigger when an invitation is revoked */`);
582
+ parts.push(` onInvitationRevoked(): TypedTrigger<WorkOSInvitationInputEvent> {`);
583
+ parts.push(` return new WorkOSInputConfig("${id}", [WorkOSEventType.INVITATION_REVOKED]) as TypedTrigger<WorkOSInvitationInputEvent>`);
584
+ parts.push(" },");
585
+ parts.push(` /** Trigger on organization creation */`);
586
+ parts.push(` onOrganizationCreated(): TypedTrigger<WorkOSOrganizationInputEvent> {`);
587
+ parts.push(` return new WorkOSInputConfig("${id}", [WorkOSEventType.ORGANIZATION_CREATED]) as TypedTrigger<WorkOSOrganizationInputEvent>`);
462
588
  parts.push(" },");
463
589
  parts.push(` /** Trigger on specific WorkOS event types */`);
464
- parts.push(` trigger(opts?: { eventTypes?: WorkOSEventType[] }): WorkOSInputConfig {`);
465
- parts.push(` return new WorkOSInputConfig("${id}", opts?.eventTypes)`);
590
+ parts.push(` trigger(opts?: { eventTypes?: WorkOSEventType[] }): TypedTrigger<WorkOSInputEvent | WorkOSUserInputEvent | WorkOSMembershipInputEvent | WorkOSInvitationInputEvent | WorkOSOrganizationInputEvent> {`);
591
+ parts.push(` return new WorkOSInputConfig("${id}", opts?.eventTypes) as TypedTrigger<WorkOSInputEvent | WorkOSUserInputEvent | WorkOSMembershipInputEvent | WorkOSInvitationInputEvent | WorkOSOrganizationInputEvent>`);
466
592
  parts.push(" },");
467
593
  parts.push(` /** Use in \`skills[]\` */`);
468
- parts.push(` skill(): WorkOSOutputConfig {`);
469
- parts.push(` return new WorkOSOutputConfig("${id}")`);
594
+ parts.push(` skill(): TypedSkill<${skillToolType}> {`);
595
+ parts.push(` return new WorkOSOutputConfig("${id}") as TypedSkill<${skillToolType}>`);
470
596
  parts.push(" },");
471
597
  parts.push("}");
472
598
  parts.push("");
473
599
  return { code: parts.join("\n"), imports };
474
600
  }
475
601
  // ── Attio ─────────────────────────────────────────────────────────────
476
- function generateAttioSection(instances) {
602
+ function generateAttioSection(instances, tools) {
477
603
  if (instances.length === 0)
478
604
  return EMPTY_SECTION;
479
605
  const inst = instances[0];
480
- const imports = new Set(["AttioOutputConfig"]);
606
+ const imports = new Set(["AttioOutputConfig", "TypedSkill"]);
481
607
  const parts = [sectionHeader("Attio"), ""];
482
608
  const id = inst.id;
483
- parts.push(generateResourceClass("AttioObject", [
484
- { classField: "apiSlug", type: "string", sourceField: "api_slug" },
485
- { classField: "name", type: "string", sourceField: "singular_noun" },
486
- ], "singular_noun", inst.objects));
609
+ const skillToolType = buildSkillToolTypeForIntegration(tools, "attio");
610
+ parts.push("export type AttioAttributeDefinition<TSlug extends string = string, TType extends string = string> = {");
611
+ parts.push(" apiSlug: TSlug");
612
+ parts.push(" title?: string");
613
+ parts.push(" type?: TType");
614
+ parts.push(" isRequired?: boolean");
615
+ parts.push(" isUnique?: boolean");
616
+ parts.push("}");
617
+ parts.push("");
618
+ parts.push("export class AttioObject<");
619
+ parts.push(" TSlug extends string = string,");
620
+ parts.push(" TRecordValues extends Record<string, unknown> = Record<string, unknown>,");
621
+ parts.push(" TInputValues extends Record<string, unknown> = TRecordValues");
622
+ parts.push("> {");
623
+ parts.push(" constructor(");
624
+ parts.push(" public readonly apiSlug: TSlug,");
625
+ parts.push(" public readonly name: string,");
626
+ parts.push(" public readonly attributes: readonly AttioAttributeDefinition[] = []");
627
+ parts.push(" ) {}");
628
+ parts.push("");
629
+ parts.push(" declare readonly __recordValues: TRecordValues");
630
+ parts.push(" declare readonly __inputValues: TInputValues");
631
+ if (inst.objects.length > 0) {
632
+ parts.push("");
633
+ const usedNames = new Set();
634
+ for (const object of inst.objects) {
635
+ let staticName = toGeneratedIdentifier(object.singular_noun || object.api_slug || "Object", "AttioObject");
636
+ while (usedNames.has(staticName))
637
+ staticName += "_";
638
+ usedNames.add(staticName);
639
+ const attributes = (object.attributes || []).filter((attr) => !!attr.api_slug);
640
+ const attributeSource = attributes.length === 0
641
+ ? "[]"
642
+ : `[\n${attributes.map(attr => {
643
+ const fields = [
644
+ `apiSlug: "${escapeString(attr.api_slug)}"`,
645
+ attr.title ? `title: "${escapeString(attr.title)}"` : undefined,
646
+ attr.type ? `type: "${escapeString(attr.type)}"` : undefined,
647
+ attr.is_required !== undefined ? `isRequired: ${attr.is_required ? "true" : "false"}` : undefined,
648
+ attr.is_unique !== undefined ? `isUnique: ${attr.is_unique ? "true" : "false"}` : undefined,
649
+ ].filter(Boolean).join(", ");
650
+ return ` { ${fields} }`;
651
+ }).join(",\n")}\n ]`;
652
+ const recordValuesType = renderAttioObjectValueShape(attributes, "record");
653
+ const inputValuesType = renderAttioObjectValueShape(attributes, "input");
654
+ parts.push(` static ${staticName} = new AttioObject<"${escapeString(object.api_slug)}", ${recordValuesType}, ${inputValuesType}>("${escapeString(object.api_slug)}", "${escapeString(object.singular_noun)}", ${attributeSource})`);
655
+ }
656
+ }
657
+ parts.push("}");
487
658
  parts.push("");
488
659
  parts.push("export const Attio = {");
489
660
  parts.push(` /** Use in \`skills[]\` */`);
490
- parts.push(` skill(opts?: { object?: AttioObject }): AttioOutputConfig {`);
491
- parts.push(` return new AttioOutputConfig("${id}", opts?.object?.apiSlug)`);
661
+ parts.push(` skill(opts: { object: AttioObject<any> }): TypedSkill<${skillToolType}> {`);
662
+ parts.push(` return new AttioOutputConfig("${id}", opts.object.apiSlug) as TypedSkill<${skillToolType}>`);
663
+ parts.push(" },");
664
+ parts.push("}");
665
+ parts.push("");
666
+ return { code: parts.join("\n"), imports };
667
+ }
668
+ // ── Snowflake ─────────────────────────────────────────────────────────
669
+ function generateSnowflakeSection(instances, tools) {
670
+ if (instances.length === 0)
671
+ return EMPTY_SECTION;
672
+ const inst = instances[0];
673
+ const imports = new Set(["SnowflakeOutputConfig", "TypedSkill"]);
674
+ const parts = [sectionHeader("Snowflake"), ""];
675
+ const id = inst.id;
676
+ const skillToolType = buildSkillToolTypeForIntegration(tools, "snowflake");
677
+ parts.push("export const Snowflake = {");
678
+ parts.push(` /** Use in \`skills[]\` */`);
679
+ parts.push(` skill(): TypedSkill<${skillToolType}> {`);
680
+ parts.push(` return new SnowflakeOutputConfig("${id}") as TypedSkill<${skillToolType}>`);
492
681
  parts.push(" },");
493
682
  parts.push("}");
494
683
  parts.push("");
@@ -584,6 +773,7 @@ function generateToolsSection(tools, input) {
584
773
  instanceMap.set("launchdarkly", input.launchdarkly.map(l => ({ id: l.id, displayName: l.displayName })));
585
774
  instanceMap.set("workos", input.workos.map(w => ({ id: w.id, displayName: w.displayName })));
586
775
  instanceMap.set("attio", input.attio.map(a => ({ id: a.id, displayName: a.displayName })));
776
+ instanceMap.set("snowflake", input.snowflake.map(s => ({ id: s.id, displayName: s.name })));
587
777
  // Group tools by integration
588
778
  const byIntegration = new Map();
589
779
  for (const tool of tools) {
@@ -625,8 +815,178 @@ function generateToolsSection(tools, input) {
625
815
  return "params";
626
816
  }
627
817
  };
818
+ const isAttioTool = (tool) => tool.integration.toLowerCase() === "attio";
819
+ const attioObjects = input.attio[0]?.objects ?? [];
820
+ const attioGeneratedObjects = (() => {
821
+ const usedNames = new Set();
822
+ return attioObjects.map(object => {
823
+ let staticName = toGeneratedIdentifier(object.singular_noun || object.api_slug || "Object", "AttioObject");
824
+ while (usedNames.has(staticName))
825
+ staticName += "_";
826
+ usedNames.add(staticName);
827
+ return {
828
+ ...object,
829
+ staticName,
830
+ attributes: (object.attributes || []).filter((attr) => !!attr.api_slug),
831
+ };
832
+ });
833
+ })();
834
+ if (tools.some(isAttioTool)) {
835
+ parts.push("type __AttioPrimitive = string | number | boolean | null");
836
+ parts.push("type __AttioStructuredValue = Record<string, unknown>");
837
+ parts.push("type __AttioValue = __AttioPrimitive | __AttioStructuredValue | (__AttioPrimitive | __AttioStructuredValue)[]");
838
+ parts.push("type __AttioFilterShorthand<T> = T extends (infer U)[] ? U | T : T");
839
+ parts.push('type __AttioFilterValue<T> = __AttioFilterShorthand<T> | { $eq?: __AttioFilterShorthand<T>; $contains?: string; $starts_with?: string; $ends_with?: string } | Record<string, unknown>');
840
+ parts.push('type __AttioFilterExpression<TValues extends Record<string, unknown>> = Partial<{ [K in keyof TValues]: __AttioFilterValue<TValues[K]> }> & { $and?: Array<__AttioFilterExpression<TValues>>; $or?: Array<__AttioFilterExpression<TValues>> }');
841
+ parts.push('type __AttioRecordBase = NonNullable<NonNullable<ToolOutputByName["attio_upsert_record"]["records"]>[number]>');
842
+ parts.push('type __AttioRecordWithValues<TValues extends Record<string, unknown>> = Omit<__AttioRecordBase, "values"> & TValues & { values: TValues; attributes: TValues }');
843
+ parts.push("");
844
+ if (attioGeneratedObjects.length > 0) {
845
+ parts.push("export type GeneratedAttioObject =");
846
+ for (const object of attioGeneratedObjects) {
847
+ parts.push(` | typeof AttioObject.${object.staticName}`);
848
+ }
849
+ parts.push("");
850
+ parts.push("export type AttioInputValuesByObject = {");
851
+ for (const object of attioGeneratedObjects) {
852
+ const inputShape = object.attributes.length === 0 ? "Record<string, __AttioValue>" : renderAttioObjectValueShape(object.attributes, "input");
853
+ parts.push(` "${escapeString(object.api_slug)}": ${inputShape}`);
854
+ }
855
+ parts.push("}");
856
+ parts.push("");
857
+ parts.push("export type AttioRecordValuesByObject = {");
858
+ for (const object of attioGeneratedObjects) {
859
+ const recordShape = object.attributes.length === 0 ? "Record<string, __AttioValue>" : renderAttioObjectValueShape(object.attributes, "record");
860
+ parts.push(` "${escapeString(object.api_slug)}": ${recordShape}`);
861
+ }
862
+ parts.push("}");
863
+ parts.push("");
864
+ parts.push("export type AttioFilterByObject = {");
865
+ for (const object of attioGeneratedObjects) {
866
+ parts.push(` "${escapeString(object.api_slug)}": __AttioFilterExpression<AttioRecordValuesByObject["${escapeString(object.api_slug)}"]>`);
867
+ }
868
+ parts.push("}");
869
+ parts.push("");
870
+ parts.push("export type AttioRecordByObject = {");
871
+ for (const object of attioGeneratedObjects) {
872
+ parts.push(` "${escapeString(object.api_slug)}": __AttioRecordWithValues<AttioRecordValuesByObject["${escapeString(object.api_slug)}"]>`);
873
+ }
874
+ parts.push("}");
875
+ }
876
+ else {
877
+ parts.push("export type GeneratedAttioObject = AttioObject<string, Record<string, __AttioValue>, Record<string, __AttioValue>>");
878
+ parts.push('export type AttioInputValuesByObject = Record<string, Record<string, __AttioValue>>');
879
+ parts.push('export type AttioRecordValuesByObject = Record<string, Record<string, __AttioValue>>');
880
+ parts.push('export type AttioFilterByObject = Record<string, __AttioFilterExpression<Record<string, __AttioValue>>>');
881
+ parts.push('export type AttioRecordByObject = Record<string, __AttioRecordWithValues<Record<string, __AttioValue>>>');
882
+ }
883
+ parts.push("");
884
+ parts.push('export type AttioValuesFor<TObject extends GeneratedAttioObject = GeneratedAttioObject> = TObject extends { __inputValues: infer TInputValues } ? TInputValues : AttioInputValuesByObject[TObject["apiSlug"]]');
885
+ parts.push('export type AttioRecordValuesFor<TObject extends GeneratedAttioObject = GeneratedAttioObject> = TObject extends { __recordValues: infer TRecordValues } ? TRecordValues : AttioRecordValuesByObject[TObject["apiSlug"]]');
886
+ parts.push('export type AttioAttributeSlug<TObject extends GeneratedAttioObject = GeneratedAttioObject> = Extract<keyof AttioValuesFor<TObject>, string>');
887
+ parts.push('export type AttioFilterFor<TObject extends GeneratedAttioObject = GeneratedAttioObject> = __AttioFilterExpression<AttioRecordValuesFor<TObject>>');
888
+ parts.push('export type AttioRecordFor<TObject extends GeneratedAttioObject = GeneratedAttioObject> = __AttioRecordWithValues<AttioRecordValuesFor<TObject>>');
889
+ parts.push('export type AttioQueryRecordsParams<TObject extends GeneratedAttioObject = GeneratedAttioObject> = { object: TObject; filter?: AttioFilterFor<TObject> | null; limit?: number | null }');
890
+ parts.push('export type AttioUpsertRecordParams<TObject extends GeneratedAttioObject = GeneratedAttioObject> = { object: TObject; matchingAttribute: AttioAttributeSlug<TObject>; records: AttioValuesFor<TObject>[] }');
891
+ parts.push("export type AttioListObjectsParams = Record<string, never>");
892
+ parts.push('export type AttioQueryRecordsResult<TObject extends GeneratedAttioObject = GeneratedAttioObject> = Omit<ToolOutputByName["attio_query_records"], "records"> & { records: Array<AttioRecordFor<TObject>> }');
893
+ parts.push('export type AttioUpsertRecordResult<TObject extends GeneratedAttioObject = GeneratedAttioObject> = Omit<ToolOutputByName["attio_upsert_record"], "records"> & { records?: Array<AttioRecordFor<TObject>> }');
894
+ parts.push("");
895
+ parts.push("const __attioMetadataKeys = new Set([");
896
+ parts.push(' "active_from",');
897
+ parts.push(' "active_until",');
898
+ parts.push(' "attribute_type",');
899
+ parts.push(' "created_by_actor",');
900
+ parts.push("])");
901
+ parts.push("");
902
+ parts.push("const __attioMultiValueAttributeSlugsByObject: Record<string, readonly string[]> = {");
903
+ for (const object of attioGeneratedObjects) {
904
+ const multiValueSlugs = object.attributes.filter(isProbablyAttioMultiValue).map(attr => `"${escapeString(attr.api_slug)}"`).join(", ");
905
+ parts.push(` "${escapeString(object.api_slug)}": [${multiValueSlugs}],`);
906
+ }
907
+ parts.push("}");
908
+ parts.push("");
909
+ parts.push("function __normalizeAttioObjectSlug(object: unknown): string {");
910
+ parts.push(' if (object && typeof object === "object" && "apiSlug" in (object as Record<string, unknown>)) {');
911
+ parts.push(' const apiSlug = (object as { apiSlug?: unknown }).apiSlug');
912
+ parts.push(' if (typeof apiSlug === "string" && apiSlug.length > 0) return apiSlug');
913
+ parts.push(" }");
914
+ parts.push(' return typeof object === "string" ? object : ""');
915
+ parts.push("}");
916
+ parts.push("");
917
+ parts.push("function __serializeAttioFilter(filter: unknown): string | null {");
918
+ parts.push(" if (filter === undefined || filter === null) return null");
919
+ parts.push(" return JSON.stringify(filter)");
920
+ parts.push("}");
921
+ parts.push("");
922
+ parts.push("function __serializeAttioRecords(records: unknown): string {");
923
+ parts.push(" if (!Array.isArray(records)) return JSON.stringify([])");
924
+ parts.push(' return JSON.stringify(records.filter((record): record is Record<string, unknown> => typeof record === "object" && record !== null && !Array.isArray(record)))');
925
+ parts.push("}");
926
+ parts.push("");
927
+ parts.push("function __isAttioMultiValueAttribute(objectSlug: string, attributeSlug: string): boolean {");
928
+ parts.push(" return (__attioMultiValueAttributeSlugsByObject[objectSlug] || []).includes(attributeSlug)");
929
+ parts.push("}");
930
+ parts.push("");
931
+ parts.push("function __flattenAttioLeafValue(value: unknown): unknown {");
932
+ parts.push(" if (value === null || value === undefined) return value");
933
+ parts.push(" if (Array.isArray(value)) return value.map(entry => __flattenAttioLeafValue(entry))");
934
+ parts.push(' if (typeof value !== "object") return value');
935
+ parts.push(" const rawObject = value as Record<string, unknown>");
936
+ parts.push(' if (typeof rawObject.full_name === "string") return rawObject.full_name');
937
+ parts.push(' if (typeof rawObject.email_address === "string") return rawObject.email_address');
938
+ parts.push(' if (typeof rawObject.domain === "string") return rawObject.domain');
939
+ parts.push(' if (typeof rawObject.phone_number === "string") return rawObject.phone_number');
940
+ parts.push(' if ("value" in rawObject) return __flattenAttioLeafValue(rawObject.value)');
941
+ parts.push(" const dataEntries = Object.entries(rawObject).filter(([key]) => !__attioMetadataKeys.has(key))");
942
+ parts.push(" if (dataEntries.length === 0) return rawObject");
943
+ parts.push(" if (dataEntries.length === 1) return __flattenAttioLeafValue(dataEntries[0][1])");
944
+ parts.push(" return Object.fromEntries(dataEntries.map(([key, entryValue]) => [key, __flattenAttioLeafValue(entryValue)]))");
945
+ parts.push("}");
946
+ parts.push("");
947
+ parts.push("function __flattenAttioAttributeValue(rawValue: unknown, preferArray: boolean): unknown {");
948
+ parts.push(" if (!Array.isArray(rawValue)) return __flattenAttioLeafValue(rawValue)");
949
+ parts.push(" const flattened = rawValue");
950
+ parts.push(" .map(entry => __flattenAttioLeafValue(entry))");
951
+ parts.push(" .filter(entry => entry !== undefined)");
952
+ parts.push(" return preferArray ? flattened : flattened[0]");
953
+ parts.push("}");
954
+ parts.push("");
955
+ parts.push("function __getAttioRecordValues<TValues extends Record<string, unknown>>(objectSlug: string, record: unknown): TValues {");
956
+ parts.push(' const rawValues = record && typeof record === "object" ? (record as { values?: unknown }).values : undefined');
957
+ parts.push(' if (!rawValues || typeof rawValues !== "object" || Array.isArray(rawValues)) return {} as TValues');
958
+ parts.push(" const flattenedValues: Record<string, unknown> = {}");
959
+ parts.push(" for (const [attributeSlug, rawValue] of Object.entries(rawValues)) {");
960
+ parts.push(" flattenedValues[attributeSlug] = __flattenAttioAttributeValue(rawValue, __isAttioMultiValueAttribute(objectSlug, attributeSlug))");
961
+ parts.push(" }");
962
+ parts.push(" return flattenedValues as TValues");
963
+ parts.push("}");
964
+ parts.push("");
965
+ parts.push("function __enhanceAttioRecord<TObject extends GeneratedAttioObject>(object: TObject, record: unknown): __AttioRecordWithValues<AttioRecordValuesFor<TObject>> | undefined {");
966
+ parts.push(' if (!record || typeof record !== "object") return undefined');
967
+ parts.push(" const values = __getAttioRecordValues<AttioRecordValuesFor<TObject>>(__normalizeAttioObjectSlug(object), record)");
968
+ parts.push(' return { ...values, ...(record as __AttioRecordBase), values, attributes: values } as __AttioRecordWithValues<AttioRecordValuesFor<TObject>>');
969
+ parts.push("}");
970
+ parts.push("");
971
+ parts.push('function __enhanceAttioQueryResult<TObject extends GeneratedAttioObject>(object: TObject, result: ToolOutputByName["attio_query_records"]): AttioQueryRecordsResult<TObject> {');
972
+ parts.push(" return {");
973
+ parts.push(" ...result,");
974
+ parts.push(" records: (result.records || []).map(record => __enhanceAttioRecord(object, record)).filter(Boolean) as Array<AttioRecordFor<TObject>>,");
975
+ parts.push(" }");
976
+ parts.push("}");
977
+ parts.push("");
978
+ parts.push('function __enhanceAttioUpsertResult<TObject extends GeneratedAttioObject>(object: TObject, result: ToolOutputByName["attio_upsert_record"]): AttioUpsertRecordResult<TObject> {');
979
+ parts.push(" return {");
980
+ parts.push(" ...result,");
981
+ parts.push(" records: (result.records || []).map(record => __enhanceAttioRecord(object, record)).filter(Boolean) as Array<AttioRecordFor<TObject>>,");
982
+ parts.push(" }");
983
+ parts.push("}");
984
+ parts.push("");
985
+ }
628
986
  // Generate per-tool param types (with integrationId omitted where applicable)
629
987
  for (const tool of tools) {
988
+ if (isAttioTool(tool))
989
+ continue;
630
990
  const typeName = toolNameToInterfaceName(tool.name);
631
991
  const schema = hasAutoFillId(tool) ? omitIntegrationId(tool.parameters) : tool.parameters;
632
992
  const tsType = jsonSchemaToTs(schema, 0);
@@ -700,7 +1060,18 @@ function generateToolsSection(tools, input) {
700
1060
  if (tool.description) {
701
1061
  parts.push(` /** ${tool.description} */`);
702
1062
  }
703
- parts.push(` ${methodName}(params: ${paramsType}): Promise<ToolOutputByName["${escapeString(tool.name)}"]>`);
1063
+ if (group.integration === "attio" && tool.name === "attio_query_records") {
1064
+ parts.push(` ${methodName}<TObject extends GeneratedAttioObject>(params: AttioQueryRecordsParams<TObject>): Promise<AttioQueryRecordsResult<TObject>>`);
1065
+ }
1066
+ else if (group.integration === "attio" && tool.name === "attio_upsert_record") {
1067
+ parts.push(` ${methodName}<TObject extends GeneratedAttioObject>(params: AttioUpsertRecordParams<TObject>): Promise<AttioUpsertRecordResult<TObject>>`);
1068
+ }
1069
+ else if (group.integration === "attio" && tool.name === "attio_list_objects") {
1070
+ parts.push(` ${methodName}(params?: AttioListObjectsParams): Promise<ToolOutputByName["${escapeString(tool.name)}"]>`);
1071
+ }
1072
+ else {
1073
+ parts.push(` ${methodName}(params: ${paramsType}): Promise<ToolOutputByName["${escapeString(tool.name)}"]>`);
1074
+ }
704
1075
  }
705
1076
  parts.push(" }");
706
1077
  }
@@ -725,7 +1096,19 @@ function generateToolsSection(tools, input) {
725
1096
  const methodName = toCamelCase(tool.displayName);
726
1097
  const paramsType = toolNameToInterfaceName(tool.name);
727
1098
  const normalizedParamsExpr = group.integration === "github" ? normalizeGitHubReposParams(tool.name) : "params";
728
- if (group.integrationId && hasAutoFillId(tool)) {
1099
+ if (group.integration === "attio" && tool.name === "attio_query_records" && group.integrationId) {
1100
+ parts.push(` ${methodName}: <TObject extends GeneratedAttioObject>(params: AttioQueryRecordsParams<TObject>) =>`);
1101
+ parts.push(` agent.executeTool<ToolOutputByName["${escapeString(tool.name)}"]>("${escapeString(tool.name)}", { objectSlug: __normalizeAttioObjectSlug(params.object), filter: __serializeAttioFilter(params.filter), limit: params.limit ?? null, integrationId: "${escapeString(group.integrationId)}" }).then(result => __enhanceAttioQueryResult(params.object, result)),`);
1102
+ }
1103
+ else if (group.integration === "attio" && tool.name === "attio_upsert_record" && group.integrationId) {
1104
+ parts.push(` ${methodName}: <TObject extends GeneratedAttioObject>(params: AttioUpsertRecordParams<TObject>) =>`);
1105
+ parts.push(` agent.executeTool<ToolOutputByName["${escapeString(tool.name)}"]>("${escapeString(tool.name)}", { objectSlug: __normalizeAttioObjectSlug(params.object), matchingAttribute: params.matchingAttribute, records: __serializeAttioRecords(params.records), integrationId: "${escapeString(group.integrationId)}" }).then(result => __enhanceAttioUpsertResult(params.object, result)),`);
1106
+ }
1107
+ else if (group.integration === "attio" && tool.name === "attio_list_objects" && group.integrationId) {
1108
+ parts.push(` ${methodName}: (params: AttioListObjectsParams = {}) =>`);
1109
+ parts.push(` agent.executeTool<ToolOutputByName["${escapeString(tool.name)}"]>("${escapeString(tool.name)}", { ...params, integrationId: "${escapeString(group.integrationId)}" }),`);
1110
+ }
1111
+ else if (group.integrationId && hasAutoFillId(tool)) {
729
1112
  parts.push(` ${methodName}: (params: ${paramsType}) =>`);
730
1113
  parts.push(` agent.executeTool<ToolOutputByName["${escapeString(tool.name)}"]>("${escapeString(tool.name)}", { ...(${normalizedParamsExpr}), integrationId: "${escapeString(group.integrationId)}" }),`);
731
1114
  }
@@ -745,9 +1128,10 @@ function generateToolsSection(tools, input) {
745
1128
  return { code: parts.join("\n"), imports };
746
1129
  }
747
1130
  // ── System (always included) ──────────────────────────────────────────
748
- function generateSystemSection() {
749
- const imports = new Set(["TimeTriggerConfig", "TerseConfig"]);
1131
+ function generateSystemSection(tools) {
1132
+ const imports = new Set(["TimeTriggerConfig", "TerseConfig", "TypedSkill"]);
750
1133
  const parts = [];
1134
+ const skillToolType = buildSkillToolTypeForIntegration(tools, "terse");
751
1135
  parts.push(sectionHeader("Schedule"));
752
1136
  parts.push("");
753
1137
  parts.push("export const Schedule = {");
@@ -761,8 +1145,8 @@ function generateSystemSection() {
761
1145
  parts.push("");
762
1146
  parts.push("export const Terse = {");
763
1147
  parts.push(` /** Use in \`skills[]\` — built-in web search */`);
764
- parts.push(" skill(): TerseConfig {");
765
- parts.push(" return new TerseConfig()");
1148
+ parts.push(` skill(): TypedSkill<${skillToolType}> {`);
1149
+ parts.push(` return new TerseConfig() as TypedSkill<${skillToolType}>`);
766
1150
  parts.push(" },");
767
1151
  parts.push("}");
768
1152
  parts.push("");
@@ -773,20 +1157,21 @@ export function generateCode(input) {
773
1157
  const allImports = new Set();
774
1158
  const sections = [];
775
1159
  const sectionGenerators = [
776
- generateGitHubSection(input.github),
777
- generateGmailSection(input.gmail),
778
- generateSlackSection(input.slack),
1160
+ generateGitHubSection(input.github, input.tools),
1161
+ generateGmailSection(input.gmail, input.tools),
1162
+ generateSlackSection(input.slack, input.tools),
779
1163
  generateFigmaSection(input.figma),
780
- generateLinearSection(input.linear),
781
- generateAtlassianSection(input.atlassian),
782
- generateNotionSection(input.notion),
783
- generatePosthogSection(input.posthog),
784
- generateDatadogSection(input.datadog),
785
- generateLaunchDarklySection(input.launchdarkly),
786
- generateWorkOSSection(input.workos),
787
- generateAttioSection(input.attio),
1164
+ generateLinearSection(input.linear, input.tools),
1165
+ generateAtlassianSection(input.atlassian, input.tools),
1166
+ generateNotionSection(input.notion, input.tools),
1167
+ generatePosthogSection(input.posthog, input.tools),
1168
+ generateDatadogSection(input.datadog, input.tools),
1169
+ generateLaunchDarklySection(input.launchdarkly, input.tools),
1170
+ generateWorkOSSection(input.workos, input.tools),
1171
+ generateAttioSection(input.attio, input.tools),
1172
+ generateSnowflakeSection(input.snowflake, input.tools),
788
1173
  generateToolsSection(input.tools, input),
789
- generateSystemSection(),
1174
+ generateSystemSection(input.tools),
790
1175
  ];
791
1176
  for (const section of sectionGenerators) {
792
1177
  if (section.code) {