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.
- package/dist/codegen.d.ts +15 -0
- package/dist/codegen.d.ts.map +1 -1
- package/dist/codegen.js +467 -81
- package/dist/codegen.js.map +1 -1
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +1 -0
- package/dist/deploy.js.map +1 -1
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +7 -1
- package/dist/generate.js.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +3 -1
- package/dist/init.js.map +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +43 -0
- package/dist/run.js.map +1 -1
- package/dist/shared/ApiRoutes.d.ts +4 -0
- package/dist/shared/ApiRoutes.d.ts.map +1 -1
- package/dist/shared/ApiRoutes.js +5 -0
- package/dist/shared/ApiRoutes.js.map +1 -1
- package/dist/shared/Configs.d.ts +24 -2
- package/dist/shared/Configs.d.ts.map +1 -1
- package/dist/shared/Configs.js +32 -2
- package/dist/shared/Configs.js.map +1 -1
- package/dist/shared/Integrations.d.ts +18 -1
- package/dist/shared/Integrations.d.ts.map +1 -1
- package/dist/shared/Integrations.js +10 -1
- package/dist/shared/Integrations.js.map +1 -1
- package/dist/shared/InvalidationKeys.d.ts +1 -0
- package/dist/shared/InvalidationKeys.d.ts.map +1 -1
- package/dist/shared/InvalidationKeys.js +3 -0
- package/dist/shared/InvalidationKeys.js.map +1 -1
- package/dist/shared/ModelEvents.d.ts +8 -0
- package/dist/shared/ModelEvents.d.ts.map +1 -1
- package/dist/shared/ModelEvents.js +15 -0
- package/dist/shared/ModelEvents.js.map +1 -1
- package/dist/shared/ToolDisplayUtils.d.ts.map +1 -1
- package/dist/shared/ToolDisplayUtils.js +95 -11
- package/dist/shared/ToolDisplayUtils.js.map +1 -1
- package/dist/shared/types.d.ts +72 -1
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js.map +1 -1
- package/package.json +1 -1
- 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[] }):
|
|
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():
|
|
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():
|
|
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 }):
|
|
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 }):
|
|
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 }):
|
|
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 }):
|
|
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[] }):
|
|
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 }):
|
|
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[] }):
|
|
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[] }):
|
|
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():
|
|
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():
|
|
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():
|
|
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():
|
|
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():
|
|
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[] }):
|
|
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():
|
|
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
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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 }):
|
|
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
|
-
|
|
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.
|
|
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(
|
|
765
|
-
parts.push(
|
|
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) {
|