terse-cli 0.1.7 → 0.1.9

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