terse-cli 0.1.0 → 0.1.2

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 (62) hide show
  1. package/dist/codegen.d.ts.map +1 -1
  2. package/dist/codegen.js +358 -333
  3. package/dist/codegen.js.map +1 -1
  4. package/dist/deploy.d.ts +1 -1
  5. package/dist/deploy.d.ts.map +1 -1
  6. package/dist/deploy.js +35 -58
  7. package/dist/deploy.js.map +1 -1
  8. package/dist/index.js +3 -5
  9. package/dist/index.js.map +1 -1
  10. package/dist/run.d.ts.map +1 -1
  11. package/dist/run.js +7 -0
  12. package/dist/run.js.map +1 -1
  13. package/dist/shared/ApiRoutes.d.ts +55 -0
  14. package/dist/shared/ApiRoutes.d.ts.map +1 -1
  15. package/dist/shared/ApiRoutes.js +40 -0
  16. package/dist/shared/ApiRoutes.js.map +1 -1
  17. package/dist/shared/ApprovalTypes.d.ts +29 -0
  18. package/dist/shared/ApprovalTypes.d.ts.map +1 -0
  19. package/dist/shared/ApprovalTypes.js +8 -0
  20. package/dist/shared/ApprovalTypes.js.map +1 -0
  21. package/dist/shared/Configs.d.ts +50 -9
  22. package/dist/shared/Configs.d.ts.map +1 -1
  23. package/dist/shared/Configs.js +62 -10
  24. package/dist/shared/Configs.js.map +1 -1
  25. package/dist/shared/FrontendRoutes.d.ts +2 -0
  26. package/dist/shared/FrontendRoutes.d.ts.map +1 -1
  27. package/dist/shared/FrontendRoutes.js +2 -0
  28. package/dist/shared/FrontendRoutes.js.map +1 -1
  29. package/dist/shared/InvalidationKeys.d.ts +7 -0
  30. package/dist/shared/InvalidationKeys.d.ts.map +1 -1
  31. package/dist/shared/InvalidationKeys.js +13 -0
  32. package/dist/shared/InvalidationKeys.js.map +1 -1
  33. package/dist/shared/ModelEvents.d.ts +1 -0
  34. package/dist/shared/ModelEvents.d.ts.map +1 -1
  35. package/dist/shared/Notifications.d.ts +11 -0
  36. package/dist/shared/Notifications.d.ts.map +1 -1
  37. package/dist/shared/Notifications.js.map +1 -1
  38. package/dist/shared/RunHistoryTypes.d.ts +2 -1
  39. package/dist/shared/RunHistoryTypes.d.ts.map +1 -1
  40. package/dist/shared/RunHistoryTypes.js +1 -0
  41. package/dist/shared/RunHistoryTypes.js.map +1 -1
  42. package/dist/shared/SentNotifications.d.ts +30 -0
  43. package/dist/shared/SentNotifications.d.ts.map +1 -0
  44. package/dist/shared/SentNotifications.js +13 -0
  45. package/dist/shared/SentNotifications.js.map +1 -0
  46. package/dist/shared/ToolDisplayUtils.d.ts +26 -0
  47. package/dist/shared/ToolDisplayUtils.d.ts.map +1 -0
  48. package/dist/shared/ToolDisplayUtils.js +1024 -0
  49. package/dist/shared/ToolDisplayUtils.js.map +1 -0
  50. package/dist/shared/types.d.ts +84 -1
  51. package/dist/shared/types.d.ts.map +1 -1
  52. package/dist/shared/types.js +1 -0
  53. package/dist/shared/types.js.map +1 -1
  54. package/dist/test.js +2 -2
  55. package/dist/test.js.map +1 -1
  56. package/dist/util.d.ts +1 -9
  57. package/dist/util.d.ts.map +1 -1
  58. package/dist/util.js +2 -17
  59. package/dist/util.js.map +1 -1
  60. package/package.json +1 -6
  61. package/templates/package.json.tmpl +1 -1
  62. package/templates/src/index.ts.tmpl +24 -24
package/dist/codegen.js CHANGED
@@ -19,9 +19,6 @@ function sectionHeader(name) {
19
19
  function escapeString(s) {
20
20
  return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
21
21
  }
22
- function suffix(instances, i) {
23
- return instances.length > 1 ? toPascalCase(instances[i].displayName || `Instance${i + 1}`) : "";
24
- }
25
22
  /** Map a tool's integration key back to the IntegrationType enum value used by ConfigInstance. */
26
23
  function toolIntegrationToIntegrationType(toolIntegration) {
27
24
  switch (toolIntegration) {
@@ -63,90 +60,106 @@ function generateResourceClass(className, fields, staticNameField, items) {
63
60
  function generateGitHubSection(instances) {
64
61
  if (instances.length === 0)
65
62
  return EMPTY_SECTION;
63
+ const inst = instances[0];
66
64
  const imports = new Set(["GitHubConfig"]);
67
65
  const parts = [sectionHeader("GitHub"), ""];
68
- for (let i = 0; i < instances.length; i++) {
69
- const inst = instances[i];
70
- const sfx = instances.length > 1
71
- ? toPascalCase(inst.integration.account_name || `Instance${i + 1}`)
72
- : "";
73
- const ownerClass = `GithubOwner${sfx}`;
74
- const repoClass = `Repository${sfx}`;
75
- const id = inst.integration.id;
76
- const toStaticName = (raw, fallback) => {
77
- let name = toPascalCase(raw || fallback);
78
- if (!name)
79
- name = fallback;
80
- if (/^\d/.test(name))
81
- name = `_${name}`;
82
- return name;
83
- };
84
- const repositoriesWithFullName = inst.repositories.map(repo => {
85
- const owner = repo.owner || "UnknownOwner";
86
- const fullName = repo.owner && repo.name ? `${repo.owner}/${repo.name}` : repo.name;
87
- return { ...repo, owner, fullName };
88
- });
89
- const ownerEntries = new Map();
90
- const usedOwnerNames = new Set();
91
- for (const repo of repositoriesWithFullName) {
92
- if (!ownerEntries.has(repo.owner)) {
93
- let ownerStaticName = toStaticName(repo.owner, "UnknownOwner");
94
- while (usedOwnerNames.has(ownerStaticName))
95
- ownerStaticName += "_";
96
- usedOwnerNames.add(ownerStaticName);
97
- ownerEntries.set(repo.owner, { staticName: ownerStaticName, repos: [] });
98
- }
99
- ownerEntries.get(repo.owner).repos.push(repo);
100
- }
101
- parts.push(`export class ${ownerClass} {`);
102
- parts.push(` constructor(public readonly name: string) {}`);
103
- if (ownerEntries.size > 0) {
104
- parts.push("");
105
- for (const [owner, data] of ownerEntries) {
106
- parts.push(` static ${data.staticName} = new ${ownerClass}("${escapeString(owner)}")`);
107
- }
66
+ const id = inst.integration.id;
67
+ const toStaticName = (raw, fallback) => {
68
+ let name = toPascalCase(raw || fallback);
69
+ if (!name)
70
+ name = fallback;
71
+ if (/^\d/.test(name))
72
+ name = `_${name}`;
73
+ return name;
74
+ };
75
+ const repositoriesWithFullName = inst.repositories.map(repo => {
76
+ const owner = repo.owner || "UnknownOwner";
77
+ const fullName = repo.owner && repo.name ? `${repo.owner}/${repo.name}` : repo.name;
78
+ return { ...repo, owner, fullName };
79
+ });
80
+ const ownerEntries = new Map();
81
+ const usedOwnerNames = new Set();
82
+ for (const repo of repositoriesWithFullName) {
83
+ if (!ownerEntries.has(repo.owner)) {
84
+ let ownerStaticName = toStaticName(repo.owner, "UnknownOwner");
85
+ while (usedOwnerNames.has(ownerStaticName))
86
+ ownerStaticName += "_";
87
+ usedOwnerNames.add(ownerStaticName);
88
+ ownerEntries.set(repo.owner, { staticName: ownerStaticName, repos: [] });
108
89
  }
109
- parts.push("}");
90
+ ownerEntries.get(repo.owner).repos.push(repo);
91
+ }
92
+ parts.push("export class GithubOwner {");
93
+ parts.push(" constructor(public readonly name: string) {}");
94
+ if (ownerEntries.size > 0) {
110
95
  parts.push("");
111
- parts.push(`export class ${repoClass} {`);
112
- parts.push(` constructor(`);
113
- parts.push(` public readonly repositoryId: number,`);
114
- parts.push(` public readonly name: string,`);
115
- parts.push(` public readonly owner: ${ownerClass},`);
116
- parts.push(` public readonly fullName: string`);
117
- parts.push(` ) {}`);
118
- if (ownerEntries.size > 0) {
119
- parts.push("");
120
- for (const [owner, data] of ownerEntries) {
121
- const usedRepoNames = new Set();
122
- parts.push(` static ${data.staticName} = {`);
123
- for (const repo of data.repos) {
124
- let repoStaticName = toStaticName(repo.name, "Repository");
125
- while (usedRepoNames.has(repoStaticName))
126
- repoStaticName += "_";
127
- usedRepoNames.add(repoStaticName);
128
- parts.push(` ${repoStaticName}: new ${repoClass}(${repo.id}, "${escapeString(repo.name)}", ${ownerClass}.${data.staticName}, "${escapeString(repo.fullName)}"),`);
129
- }
130
- parts.push(" } as const");
96
+ for (const [owner, data] of ownerEntries) {
97
+ parts.push(` static ${data.staticName} = new GithubOwner("${escapeString(owner)}")`);
98
+ }
99
+ }
100
+ parts.push("}");
101
+ parts.push("");
102
+ parts.push("export class Repos {");
103
+ parts.push(" constructor(");
104
+ parts.push(" public readonly repositoryId: number,");
105
+ parts.push(" public readonly name: string,");
106
+ parts.push(" public readonly owner: GithubOwner,");
107
+ parts.push(" public readonly fullName: string");
108
+ parts.push(" ) {}");
109
+ if (ownerEntries.size > 0) {
110
+ parts.push("");
111
+ for (const [, data] of ownerEntries) {
112
+ const usedRepoNames = new Set();
113
+ parts.push(` static ${data.staticName} = {`);
114
+ for (const repo of data.repos) {
115
+ let repoStaticName = toStaticName(repo.name, "Repos");
116
+ while (usedRepoNames.has(repoStaticName))
117
+ repoStaticName += "_";
118
+ usedRepoNames.add(repoStaticName);
119
+ parts.push(` ${repoStaticName}: new Repos(${repo.id}, "${escapeString(repo.name)}", GithubOwner.${data.staticName}, "${escapeString(repo.fullName)}"),`);
131
120
  }
121
+ parts.push(" } as const");
132
122
  }
133
- parts.push("}");
134
- parts.push("");
135
- parts.push(`/** Use in \`triggers[]\` */`);
136
- parts.push(`export class GithubTrigger${sfx} extends GitHubConfig {`);
137
- parts.push(` constructor(repositories: ${repoClass}[]) {`);
138
- parts.push(` super("${id}", repositories.map(r => r.repositoryId))`);
139
- parts.push(" }");
140
- parts.push("}");
141
- parts.push("");
142
- parts.push(`/** Use in \`skills[]\` */`);
143
- parts.push(`export class GithubSkill${sfx} extends GitHubConfig {`);
144
- parts.push(` constructor(repositories: ${repoClass}[]) {`);
145
- parts.push(` super("${id}", repositories.map(r => r.repositoryId))`);
146
- parts.push(" }");
147
- parts.push("}");
148
- parts.push("");
149
123
  }
124
+ parts.push("}");
125
+ parts.push("");
126
+ imports.add("GitHubEventType");
127
+ imports.add("TypedTrigger");
128
+ imports.add("GithubPRInputEvent");
129
+ imports.add("GithubPushInputEvent");
130
+ imports.add("GithubInputEvent");
131
+ // Namespace object with typed event triggers
132
+ parts.push("export const GitHub = {");
133
+ parts.push(` /** Trigger on push to a repository */`);
134
+ parts.push(` onPush(opts: { repo: Repos }): TypedTrigger<GithubPushInputEvent> {`);
135
+ parts.push(` return new GitHubConfig("${id}", [opts.repo.repositoryId], [GitHubEventType.PUSH]) as TypedTrigger<GithubPushInputEvent>`);
136
+ parts.push(" },");
137
+ parts.push(` /** Trigger when a pull request is opened */`);
138
+ parts.push(` onPROpened(opts: { repo: Repos }): TypedTrigger<GithubPRInputEvent> {`);
139
+ parts.push(` return new GitHubConfig("${id}", [opts.repo.repositoryId], [GitHubEventType.PR_OPENED]) as TypedTrigger<GithubPRInputEvent>`);
140
+ parts.push(" },");
141
+ parts.push(` /** Trigger when a pull request is merged */`);
142
+ parts.push(` onPRMerged(opts: { repo: Repos }): TypedTrigger<GithubPRInputEvent> {`);
143
+ parts.push(` return new GitHubConfig("${id}", [opts.repo.repositoryId], [GitHubEventType.PR_MERGED]) as TypedTrigger<GithubPRInputEvent>`);
144
+ parts.push(" },");
145
+ parts.push(` /** Trigger when a pull request is closed */`);
146
+ parts.push(` onPRClosed(opts: { repo: Repos }): TypedTrigger<GithubPRInputEvent> {`);
147
+ parts.push(` return new GitHubConfig("${id}", [opts.repo.repositoryId], [GitHubEventType.PR_CLOSED]) as TypedTrigger<GithubPRInputEvent>`);
148
+ parts.push(" },");
149
+ parts.push(` /** Trigger on any pull request event */`);
150
+ parts.push(` onPR(opts: { repo: Repos }): TypedTrigger<GithubPRInputEvent> {`);
151
+ parts.push(` return new GitHubConfig("${id}", [opts.repo.repositoryId], [GitHubEventType.PR_OPENED, GitHubEventType.PR_MERGED, GitHubEventType.PR_CLOSED, GitHubEventType.PR_SYNCHRONIZE]) as TypedTrigger<GithubPRInputEvent>`);
152
+ parts.push(" },");
153
+ parts.push(` /** Trigger on specific GitHub events for the given repositories */`);
154
+ parts.push(` trigger(opts: { repos: Repos[]; eventTypes?: GitHubEventType[] }): TypedTrigger<GithubInputEvent> {`);
155
+ parts.push(` return new GitHubConfig("${id}", opts.repos.map(r => r.repositoryId), opts.eventTypes) as TypedTrigger<GithubInputEvent>`);
156
+ parts.push(" },");
157
+ 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))`);
160
+ parts.push(" },");
161
+ parts.push("}");
162
+ parts.push("");
150
163
  return { code: parts.join("\n"), imports };
151
164
  }
152
165
  // ── Gmail ─────────────────────────────────────────────────────────────
@@ -155,57 +168,63 @@ function generateGmailSection(instances) {
155
168
  return EMPTY_SECTION;
156
169
  const imports = new Set(["GmailConfig", "GmailOutputConfig", "GmailDraftOutputConfig"]);
157
170
  const parts = [sectionHeader("Gmail"), ""];
158
- for (let i = 0; i < instances.length; i++) {
159
- const sfx = suffix(instances, i);
160
- const id = instances[i].id;
161
- parts.push(`/** Use in \`triggers[]\` */`);
162
- parts.push(`export class GmailTrigger${sfx} extends GmailConfig {`);
163
- parts.push(` constructor() { super("${id}") }`);
164
- parts.push("}");
165
- parts.push("");
166
- parts.push(`/** Use in \`skills[]\` */`);
167
- parts.push(`export class GmailSkill${sfx} extends GmailOutputConfig {`);
168
- parts.push(` constructor() { super("${id}") }`);
169
- parts.push("}");
170
- parts.push("");
171
- parts.push(`/** Use in \`skills[]\` — creates draft emails */`);
172
- parts.push(`export class GmailDraftSkill${sfx} extends GmailDraftOutputConfig {`);
173
- parts.push(` constructor() { super("${id}") }`);
174
- parts.push("}");
175
- parts.push("");
176
- }
171
+ const id = instances[0].id;
172
+ imports.add("GmailEventType");
173
+ parts.push("export const Gmail = {");
174
+ parts.push(` /** Trigger when a new email is received */`);
175
+ parts.push(` onEmail(): GmailConfig {`);
176
+ parts.push(` return new GmailConfig("${id}", [GmailEventType.EMAIL_RECEIVED])`);
177
+ parts.push(" },");
178
+ parts.push(` /** Trigger on all Gmail events */`);
179
+ parts.push(` trigger(opts?: { eventTypes?: GmailEventType[] }): GmailConfig {`);
180
+ parts.push(` return new GmailConfig("${id}", opts?.eventTypes)`);
181
+ parts.push(" },");
182
+ parts.push(` /** Use in \`skills[]\` */`);
183
+ parts.push(` skill(): GmailOutputConfig {`);
184
+ parts.push(` return new GmailOutputConfig("${id}")`);
185
+ parts.push(" },");
186
+ parts.push(` /** Use in \`skills[]\` — creates draft emails */`);
187
+ parts.push(` draftSkill(): GmailDraftOutputConfig {`);
188
+ parts.push(` return new GmailDraftOutputConfig("${id}")`);
189
+ parts.push(" },");
190
+ parts.push("}");
191
+ parts.push("");
177
192
  return { code: parts.join("\n"), imports };
178
193
  }
179
194
  // ── Slack ──────────────────────────────────────────────────────────────
180
195
  function generateSlackSection(instances) {
181
196
  if (instances.length === 0)
182
197
  return EMPTY_SECTION;
198
+ const inst = instances[0];
183
199
  const imports = new Set(["SlackConfig", "SlackOutputConfig"]);
184
200
  const parts = [sectionHeader("Slack"), ""];
185
- for (let i = 0; i < instances.length; i++) {
186
- const inst = instances[i];
187
- const sfx = suffix(instances, i);
188
- const channelClass = `SlackChannel${sfx}`;
189
- parts.push(generateResourceClass(channelClass, [
190
- { classField: "channelId", type: "string", sourceField: "id" },
191
- { classField: "name", type: "string", sourceField: "name" },
192
- ], "name", inst.channels));
193
- parts.push("");
194
- parts.push(`/** Use in \`triggers[]\` */`);
195
- parts.push(`export class SlackTrigger${sfx} extends SlackConfig {`);
196
- parts.push(` constructor(channel?: ${channelClass}, listenToUserDms?: boolean, userIds?: string[]) {`);
197
- parts.push(` super("${inst.id}", channel?.channelId, channel?.name, listenToUserDms, userIds)`);
198
- parts.push(" }");
199
- parts.push("}");
200
- parts.push("");
201
- parts.push(`/** Use in \`skills[]\` */`);
202
- parts.push(`export class SlackSkill${sfx} extends SlackOutputConfig {`);
203
- parts.push(` constructor(channel?: ${channelClass}, userIds?: string[], userNames?: string[], listenToUserDms?: boolean) {`);
204
- parts.push(` super("${inst.id}", channel?.channelId, channel?.name, userIds, userNames, listenToUserDms)`);
205
- parts.push(" }");
206
- parts.push("}");
207
- parts.push("");
208
- }
201
+ const id = inst.id;
202
+ parts.push(generateResourceClass("SlackChannel", [
203
+ { classField: "channelId", type: "string", sourceField: "id" },
204
+ { classField: "name", type: "string", sourceField: "name" },
205
+ ], "name", inst.channels));
206
+ parts.push("");
207
+ imports.add("SlackEventType");
208
+ // Namespace object with typed event triggers
209
+ parts.push("export const Slack = {");
210
+ parts.push(` /** Trigger on any message in a channel */`);
211
+ parts.push(` onMessage(opts: { channel: SlackChannel; userIds?: string[] }): SlackConfig {`);
212
+ parts.push(` return new SlackConfig("${id}", opts.channel.channelId, opts.channel.name, false, opts.userIds, [SlackEventType.MESSAGE])`);
213
+ parts.push(" },");
214
+ parts.push(` /** Trigger on direct messages to the bot */`);
215
+ parts.push(` onDm(opts?: { userIds?: string[] }): SlackConfig {`);
216
+ parts.push(` return new SlackConfig("${id}", undefined, undefined, true, opts?.userIds, [SlackEventType.MESSAGE])`);
217
+ parts.push(" },");
218
+ parts.push(` /** Trigger on all Slack events (messages + DMs) for a channel */`);
219
+ parts.push(` trigger(opts?: { channel?: SlackChannel; listenToUserDms?: boolean; userIds?: string[]; eventTypes?: SlackEventType[] }): SlackConfig {`);
220
+ parts.push(` return new SlackConfig("${id}", opts?.channel?.channelId, opts?.channel?.name, opts?.listenToUserDms, opts?.userIds, opts?.eventTypes)`);
221
+ parts.push(" },");
222
+ 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)`);
225
+ parts.push(" },");
226
+ parts.push("}");
227
+ parts.push("");
209
228
  return { code: parts.join("\n"), imports };
210
229
  }
211
230
  // ── Figma ─────────────────────────────────────────────────────────────
@@ -214,255 +233,265 @@ function generateFigmaSection(instances) {
214
233
  return EMPTY_SECTION;
215
234
  const imports = new Set(["FigmaConfig"]);
216
235
  const parts = [sectionHeader("Figma"), ""];
217
- for (let i = 0; i < instances.length; i++) {
218
- const sfx = suffix(instances, i);
219
- parts.push(`/** Use in \`triggers[]\` */`);
220
- parts.push(`export class FigmaTrigger${sfx} extends FigmaConfig {`);
221
- parts.push(` constructor(fileKey: string, fileName: string, teamId: string) {`);
222
- parts.push(` super("${instances[i].id}", fileKey, fileName, teamId)`);
223
- parts.push(" }");
224
- parts.push("}");
225
- parts.push("");
226
- }
236
+ const id = instances[0].id;
237
+ imports.add("FigmaEventType");
238
+ parts.push("export const Figma = {");
239
+ parts.push(` /** Trigger when a comment is added to a Figma file */`);
240
+ parts.push(` onComment(opts: { fileKey: string; fileName: string; teamId: string }): FigmaConfig {`);
241
+ parts.push(` return new FigmaConfig("${id}", opts.fileKey, opts.fileName, opts.teamId, [FigmaEventType.FILE_COMMENT])`);
242
+ parts.push(" },");
243
+ parts.push(` /** Trigger on all Figma events */`);
244
+ parts.push(` trigger(opts: { fileKey: string; fileName: string; teamId: string; eventTypes?: FigmaEventType[] }): FigmaConfig {`);
245
+ parts.push(` return new FigmaConfig("${id}", opts.fileKey, opts.fileName, opts.teamId, opts.eventTypes)`);
246
+ parts.push(" },");
247
+ parts.push("}");
248
+ parts.push("");
227
249
  return { code: parts.join("\n"), imports };
228
250
  }
229
251
  // ── Linear ────────────────────────────────────────────────────────────
230
252
  function generateLinearSection(instances) {
231
253
  if (instances.length === 0)
232
254
  return EMPTY_SECTION;
255
+ const inst = instances[0];
233
256
  const imports = new Set(["LinearInputConfig", "LinearOutputConfig"]);
234
257
  const parts = [sectionHeader("Linear"), ""];
235
- for (let i = 0; i < instances.length; i++) {
236
- const inst = instances[i];
237
- const sfx = suffix(instances, i);
238
- const teamClass = `LinearTeam${sfx}`;
239
- parts.push(generateResourceClass(teamClass, [
240
- { classField: "teamId", type: "string", sourceField: "id" },
241
- { classField: "name", type: "string", sourceField: "name" },
242
- { classField: "key", type: "string", sourceField: "key" },
243
- ], "name", inst.teams));
244
- parts.push("");
245
- parts.push(`/** Use in \`triggers[]\` */`);
246
- parts.push(`export class LinearTrigger${sfx} extends LinearInputConfig {`);
247
- parts.push(` constructor(projectId?: string, projectName?: string) {`);
248
- parts.push(` super("${inst.id}", projectId, projectName)`);
249
- parts.push(" }");
250
- parts.push("}");
251
- parts.push("");
252
- parts.push(`/** Use in \`skills[]\` */`);
253
- parts.push(`export class LinearSkill${sfx} extends LinearOutputConfig {`);
254
- parts.push(` constructor(team?: ${teamClass}, projectId?: string, projectName?: string) {`);
255
- parts.push(` super("${inst.id}", team?.teamId, team?.name, projectId, projectName)`);
256
- parts.push(" }");
257
- parts.push("}");
258
- parts.push("");
259
- }
258
+ const id = inst.id;
259
+ parts.push(generateResourceClass("LinearTeam", [
260
+ { classField: "teamId", type: "string", sourceField: "id" },
261
+ { classField: "name", type: "string", sourceField: "name" },
262
+ { classField: "key", type: "string", sourceField: "key" },
263
+ ], "name", inst.teams));
264
+ parts.push("");
265
+ imports.add("LinearEventType");
266
+ parts.push("export const Linear = {");
267
+ parts.push(` /** Trigger when a new issue is created */`);
268
+ parts.push(` onIssueCreated(opts?: { projectId?: string; projectName?: string }): LinearInputConfig {`);
269
+ parts.push(` return new LinearInputConfig("${id}", opts?.projectId, opts?.projectName, [LinearEventType.ISSUE_CREATED])`);
270
+ parts.push(" },");
271
+ parts.push(` /** Trigger when an issue is updated */`);
272
+ parts.push(` onIssueUpdated(opts?: { projectId?: string; projectName?: string }): LinearInputConfig {`);
273
+ parts.push(` return new LinearInputConfig("${id}", opts?.projectId, opts?.projectName, [LinearEventType.ISSUE_UPDATED])`);
274
+ parts.push(" },");
275
+ parts.push(` /** Trigger when a comment is added to an issue */`);
276
+ parts.push(` onComment(opts?: { projectId?: string; projectName?: string }): LinearInputConfig {`);
277
+ parts.push(` return new LinearInputConfig("${id}", opts?.projectId, opts?.projectName, [LinearEventType.COMMENT_CREATED])`);
278
+ parts.push(" },");
279
+ parts.push(` /** Trigger on all Linear events */`);
280
+ parts.push(` trigger(opts?: { projectId?: string; projectName?: string; eventTypes?: LinearEventType[] }): LinearInputConfig {`);
281
+ parts.push(` return new LinearInputConfig("${id}", opts?.projectId, opts?.projectName, opts?.eventTypes)`);
282
+ parts.push(" },");
283
+ 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)`);
286
+ parts.push(" },");
287
+ parts.push("}");
288
+ parts.push("");
260
289
  return { code: parts.join("\n"), imports };
261
290
  }
262
291
  // ── Jira & Confluence (Atlassian) ─────────────────────────────────────
263
292
  function generateAtlassianSection(instances) {
264
293
  if (instances.length === 0)
265
294
  return EMPTY_SECTION;
295
+ const inst = instances[0];
266
296
  const imports = new Set(["JiraConfig", "ConfluenceConfig"]);
267
297
  const parts = [sectionHeader("Jira & Confluence"), ""];
268
- for (let i = 0; i < instances.length; i++) {
269
- const inst = instances[i];
270
- const sfx = suffix(instances, i);
271
- const projectClass = `JiraProject${sfx}`;
272
- const pageClass = `ConfluencePage${sfx}`;
273
- // Jira projects
274
- parts.push(generateResourceClass(projectClass, [
275
- { classField: "projectKey", type: "string", sourceField: "key" },
276
- { classField: "projectId", type: "string", sourceField: "id" },
277
- { classField: "name", type: "string", sourceField: "name" },
278
- ], "name", inst.jiraProjects));
279
- parts.push("");
280
- parts.push(`/** Use in \`triggers[]\` */`);
281
- parts.push(`export class JiraTrigger${sfx} extends JiraConfig {`);
282
- parts.push(` constructor(project?: ${projectClass}) {`);
283
- parts.push(` super("${inst.id}", project?.projectKey, project?.projectId)`);
284
- parts.push(" }");
285
- parts.push("}");
286
- parts.push("");
287
- parts.push(`/** Use in \`skills[]\` */`);
288
- parts.push(`export class JiraSkill${sfx} extends JiraConfig {`);
289
- parts.push(` constructor(project?: ${projectClass}) {`);
290
- parts.push(` super("${inst.id}", project?.projectKey, project?.projectId)`);
291
- parts.push(" }");
292
- parts.push("}");
293
- parts.push("");
294
- // Confluence pages
295
- parts.push(generateResourceClass(pageClass, [
296
- { classField: "pageId", type: "string", sourceField: "id" },
297
- { classField: "title", type: "string", sourceField: "title" },
298
- { classField: "spaceId", type: "string", sourceField: "spaceId" },
299
- { classField: "spaceName", type: "string", sourceField: "spaceName" },
300
- ], "title", inst.confluencePages));
301
- parts.push("");
302
- parts.push(`/** Use in \`skills[]\` */`);
303
- parts.push(`export class ConfluenceSkill${sfx} extends ConfluenceConfig {`);
304
- parts.push(` constructor(page: ${pageClass}) {`);
305
- parts.push(` super("${inst.id}", page.spaceName, page.spaceId, page.pageId, page.title)`);
306
- parts.push(" }");
307
- parts.push("}");
308
- parts.push("");
309
- }
298
+ const id = inst.id;
299
+ parts.push(generateResourceClass("JiraProject", [
300
+ { classField: "projectKey", type: "string", sourceField: "key" },
301
+ { classField: "projectId", type: "string", sourceField: "id" },
302
+ { classField: "name", type: "string", sourceField: "name" },
303
+ ], "name", inst.jiraProjects));
304
+ parts.push("");
305
+ parts.push(generateResourceClass("ConfluencePage", [
306
+ { classField: "pageId", type: "string", sourceField: "id" },
307
+ { classField: "title", type: "string", sourceField: "title" },
308
+ { classField: "spaceId", type: "string", sourceField: "spaceId" },
309
+ { classField: "spaceName", type: "string", sourceField: "spaceName" },
310
+ ], "title", inst.confluencePages));
311
+ parts.push("");
312
+ imports.add("JiraEventType");
313
+ parts.push("export const Jira = {");
314
+ parts.push(` /** Trigger when a Jira issue is created */`);
315
+ parts.push(` onIssueCreated(opts?: { project?: JiraProject }): JiraConfig {`);
316
+ parts.push(` return new JiraConfig("${id}", opts?.project?.projectKey, opts?.project?.projectId, [JiraEventType.ISSUE_CREATED])`);
317
+ parts.push(" },");
318
+ parts.push(` /** Trigger when a Jira issue is updated */`);
319
+ parts.push(` onIssueUpdated(opts?: { project?: JiraProject }): JiraConfig {`);
320
+ parts.push(` return new JiraConfig("${id}", opts?.project?.projectKey, opts?.project?.projectId, [JiraEventType.ISSUE_UPDATED])`);
321
+ parts.push(" },");
322
+ parts.push(` /** Trigger on all Jira events */`);
323
+ parts.push(` trigger(opts?: { project?: JiraProject; eventTypes?: JiraEventType[] }): JiraConfig {`);
324
+ parts.push(` return new JiraConfig("${id}", opts?.project?.projectKey, opts?.project?.projectId, opts?.eventTypes)`);
325
+ parts.push(" },");
326
+ 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)`);
329
+ parts.push(" },");
330
+ parts.push("}");
331
+ parts.push("");
332
+ parts.push("export const Confluence = {");
333
+ 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)`);
336
+ parts.push(" },");
337
+ parts.push("}");
338
+ parts.push("");
310
339
  return { code: parts.join("\n"), imports };
311
340
  }
312
341
  // ── Notion ────────────────────────────────────────────────────────────
313
342
  function generateNotionSection(instances) {
314
343
  if (instances.length === 0)
315
344
  return EMPTY_SECTION;
345
+ const inst = instances[0];
316
346
  const imports = new Set(["NotionConfig"]);
317
347
  const parts = [sectionHeader("Notion"), ""];
318
- for (let i = 0; i < instances.length; i++) {
319
- const inst = instances[i];
320
- const sfx = suffix(instances, i);
321
- const dbClass = `NotionDatabase${sfx}`;
322
- const pageClass = `NotionPage${sfx}`;
323
- parts.push(generateResourceClass(dbClass, [
324
- { classField: "databaseId", type: "string", sourceField: "id" },
325
- { classField: "title", type: "string", sourceField: "title" },
326
- ], "title", inst.databases));
327
- parts.push("");
328
- parts.push(generateResourceClass(pageClass, [
329
- { classField: "pageId", type: "string", sourceField: "id" },
330
- { classField: "title", type: "string", sourceField: "title" },
331
- ], "title", inst.pages));
332
- parts.push("");
333
- parts.push(`/** Use in \`skills[]\` */`);
334
- parts.push(`export class NotionSkill${sfx} extends NotionConfig {`);
335
- parts.push(` constructor(databases?: ${dbClass}[], pages?: ${pageClass}[]) {`);
336
- parts.push(` super("${inst.id}",`);
337
- parts.push(` databases?.map(d => d.databaseId), databases?.map(d => d.title),`);
338
- parts.push(` pages?.map(p => p.pageId), pages?.map(p => p.title))`);
339
- parts.push(" }");
340
- parts.push("}");
341
- parts.push("");
342
- }
348
+ const id = inst.id;
349
+ parts.push(generateResourceClass("NotionDatabase", [
350
+ { classField: "databaseId", type: "string", sourceField: "id" },
351
+ { classField: "title", type: "string", sourceField: "title" },
352
+ ], "title", inst.databases));
353
+ parts.push("");
354
+ parts.push(generateResourceClass("NotionPage", [
355
+ { classField: "pageId", type: "string", sourceField: "id" },
356
+ { classField: "title", type: "string", sourceField: "title" },
357
+ ], "title", inst.pages));
358
+ parts.push("");
359
+ parts.push("export const Notion = {");
360
+ parts.push(` /** Use in \`skills[]\` */`);
361
+ parts.push(` skill(opts?: { databases?: NotionDatabase[]; pages?: NotionPage[] }): NotionConfig {`);
362
+ parts.push(` return new NotionConfig("${id}",`);
363
+ 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))`);
365
+ parts.push(" },");
366
+ parts.push("}");
367
+ parts.push("");
343
368
  return { code: parts.join("\n"), imports };
344
369
  }
345
370
  // ── PostHog ───────────────────────────────────────────────────────────
346
371
  function generatePosthogSection(instances) {
347
372
  if (instances.length === 0)
348
373
  return EMPTY_SECTION;
374
+ const inst = instances[0];
349
375
  const imports = new Set(["PosthogConfig"]);
350
376
  const parts = [sectionHeader("PostHog"), ""];
351
- for (let i = 0; i < instances.length; i++) {
352
- const inst = instances[i];
353
- const sfx = suffix(instances, i);
354
- const projClass = `PosthogProject${sfx}`;
355
- parts.push(generateResourceClass(projClass, [
356
- { classField: "projectId", type: "string", sourceField: "id" },
357
- { classField: "name", type: "string", sourceField: "name" },
358
- ], "name", inst.projects));
359
- parts.push("");
360
- parts.push(`/** Use in \`skills[]\` */`);
361
- parts.push(`export class PosthogSkill${sfx} extends PosthogConfig {`);
362
- parts.push(` constructor(project: ${projClass}) {`);
363
- parts.push(` super("${inst.id}", project.projectId, project.name)`);
364
- parts.push(" }");
365
- parts.push("}");
366
- parts.push("");
367
- }
377
+ const id = inst.id;
378
+ parts.push(generateResourceClass("PosthogProject", [
379
+ { classField: "projectId", type: "string", sourceField: "id" },
380
+ { classField: "name", type: "string", sourceField: "name" },
381
+ ], "name", inst.projects));
382
+ parts.push("");
383
+ parts.push("export const Posthog = {");
384
+ 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)`);
387
+ parts.push(" },");
388
+ parts.push("}");
389
+ parts.push("");
368
390
  return { code: parts.join("\n"), imports };
369
391
  }
370
392
  // ── Datadog ───────────────────────────────────────────────────────────
371
393
  function generateDatadogSection(instances) {
372
394
  if (instances.length === 0)
373
395
  return EMPTY_SECTION;
396
+ const inst = instances[0];
374
397
  const imports = new Set(["DatadogConfig"]);
375
398
  const parts = [sectionHeader("Datadog"), ""];
376
- for (let i = 0; i < instances.length; i++) {
377
- const inst = instances[i];
378
- const sfx = suffix(instances, i);
379
- const indexClass = `DatadogIndex${sfx}`;
380
- parts.push(generateResourceClass(indexClass, [
381
- { classField: "name", type: "string", sourceField: "name" },
382
- ], "name", inst.indexes));
383
- parts.push("");
384
- parts.push(`/** Use in \`skills[]\` */`);
385
- parts.push(`export class DatadogSkill${sfx} extends DatadogConfig {`);
386
- parts.push(` constructor(indexes?: ${indexClass}[]) {`);
387
- parts.push(` super("${inst.id}", indexes?.map(i => i.name))`);
388
- parts.push(" }");
389
- parts.push("}");
390
- parts.push("");
391
- }
399
+ const id = inst.id;
400
+ parts.push(generateResourceClass("DatadogIndex", [
401
+ { classField: "name", type: "string", sourceField: "name" },
402
+ ], "name", inst.indexes));
403
+ parts.push("");
404
+ parts.push("export const Datadog = {");
405
+ 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))`);
408
+ parts.push(" },");
409
+ parts.push("}");
410
+ parts.push("");
392
411
  return { code: parts.join("\n"), imports };
393
412
  }
394
413
  // ── LaunchDarkly ──────────────────────────────────────────────────────
395
414
  function generateLaunchDarklySection(instances) {
396
415
  if (instances.length === 0)
397
416
  return EMPTY_SECTION;
417
+ const inst = instances[0];
398
418
  const imports = new Set(["LaunchDarklyConfig"]);
399
419
  const parts = [sectionHeader("LaunchDarkly"), ""];
400
- for (let i = 0; i < instances.length; i++) {
401
- const inst = instances[i];
402
- const sfx = suffix(instances, i);
403
- const projClass = `LaunchDarklyProject${sfx}`;
404
- parts.push(generateResourceClass(projClass, [
405
- { classField: "projectKey", type: "string", sourceField: "key" },
406
- { classField: "name", type: "string", sourceField: "name" },
407
- ], "name", inst.projects));
408
- parts.push("");
409
- parts.push(`/** Use in \`skills[]\` */`);
410
- parts.push(`export class LaunchDarklySkill${sfx} extends LaunchDarklyConfig {`);
411
- parts.push(` constructor(project: ${projClass}, environmentKeys: string[]) {`);
412
- parts.push(` super("${inst.id}", project.projectKey, environmentKeys)`);
413
- parts.push(" }");
414
- parts.push("}");
415
- parts.push("");
416
- }
420
+ const id = inst.id;
421
+ parts.push(generateResourceClass("LaunchDarklyProject", [
422
+ { classField: "projectKey", type: "string", sourceField: "key" },
423
+ { classField: "name", type: "string", sourceField: "name" },
424
+ ], "name", inst.projects));
425
+ parts.push("");
426
+ parts.push("export const LaunchDarkly = {");
427
+ 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)`);
430
+ parts.push(" },");
431
+ parts.push("}");
432
+ parts.push("");
417
433
  return { code: parts.join("\n"), imports };
418
434
  }
419
435
  // ── WorkOS ────────────────────────────────────────────────────────────
420
436
  function generateWorkOSSection(instances) {
421
437
  if (instances.length === 0)
422
438
  return EMPTY_SECTION;
423
- const imports = new Set(["WorkOSInputConfig", "WorkOSOutputConfig"]);
439
+ const imports = new Set(["WorkOSInputConfig", "WorkOSOutputConfig", "WorkOSEventType"]);
424
440
  const parts = [sectionHeader("WorkOS"), ""];
425
- for (let i = 0; i < instances.length; i++) {
426
- const sfx = suffix(instances, i);
427
- const id = instances[i].id;
428
- parts.push(`/** Use in \`triggers[]\` */`);
429
- parts.push(`export class WorkOSTrigger${sfx} extends WorkOSInputConfig {`);
430
- parts.push(` constructor(eventTypes?: string[]) {`);
431
- parts.push(` super("${id}", eventTypes)`);
432
- parts.push(" }");
433
- parts.push("}");
434
- parts.push("");
435
- parts.push(`/** Use in \`skills[]\` */`);
436
- parts.push(`export class WorkOSSkill${sfx} extends WorkOSOutputConfig {`);
437
- parts.push(` constructor() { super("${id}") }`);
438
- parts.push("}");
439
- parts.push("");
440
- }
441
+ const id = instances[0].id;
442
+ parts.push("export const WorkOS = {");
443
+ parts.push(` /** Trigger on user creation */`);
444
+ parts.push(` onUserCreated(): WorkOSInputConfig {`);
445
+ parts.push(` return new WorkOSInputConfig("${id}", [WorkOSEventType.USER_CREATED])`);
446
+ parts.push(" },");
447
+ parts.push(` /** Trigger on user update */`);
448
+ parts.push(` onUserUpdated(): WorkOSInputConfig {`);
449
+ parts.push(` return new WorkOSInputConfig("${id}", [WorkOSEventType.USER_UPDATED])`);
450
+ parts.push(" },");
451
+ parts.push(` /** Trigger on user deletion */`);
452
+ parts.push(` onUserDeleted(): WorkOSInputConfig {`);
453
+ parts.push(` return new WorkOSInputConfig("${id}", [WorkOSEventType.USER_DELETED])`);
454
+ parts.push(" },");
455
+ 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])`);
458
+ parts.push(" },");
459
+ parts.push(` /** Trigger on invitation accepted */`);
460
+ parts.push(` onInvitationAccepted(): WorkOSInputConfig {`);
461
+ parts.push(` return new WorkOSInputConfig("${id}", [WorkOSEventType.INVITATION_ACCEPTED])`);
462
+ parts.push(" },");
463
+ 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)`);
466
+ parts.push(" },");
467
+ parts.push(` /** Use in \`skills[]\` */`);
468
+ parts.push(` skill(): WorkOSOutputConfig {`);
469
+ parts.push(` return new WorkOSOutputConfig("${id}")`);
470
+ parts.push(" },");
471
+ parts.push("}");
472
+ parts.push("");
441
473
  return { code: parts.join("\n"), imports };
442
474
  }
443
475
  // ── Attio ─────────────────────────────────────────────────────────────
444
476
  function generateAttioSection(instances) {
445
477
  if (instances.length === 0)
446
478
  return EMPTY_SECTION;
479
+ const inst = instances[0];
447
480
  const imports = new Set(["AttioOutputConfig"]);
448
481
  const parts = [sectionHeader("Attio"), ""];
449
- for (let i = 0; i < instances.length; i++) {
450
- const inst = instances[i];
451
- const sfx = suffix(instances, i);
452
- const objClass = `AttioObject${sfx}`;
453
- parts.push(generateResourceClass(objClass, [
454
- { classField: "apiSlug", type: "string", sourceField: "api_slug" },
455
- { classField: "name", type: "string", sourceField: "singular_noun" },
456
- ], "singular_noun", inst.objects));
457
- parts.push("");
458
- parts.push(`/** Use in \`skills[]\` */`);
459
- parts.push(`export class AttioSkill${sfx} extends AttioOutputConfig {`);
460
- parts.push(` constructor(object?: ${objClass}) {`);
461
- parts.push(` super("${inst.id}", object?.apiSlug)`);
462
- parts.push(" }");
463
- parts.push("}");
464
- parts.push("");
465
- }
482
+ 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));
487
+ parts.push("");
488
+ parts.push("export const Attio = {");
489
+ parts.push(` /** Use in \`skills[]\` */`);
490
+ parts.push(` skill(opts?: { object?: AttioObject }): AttioOutputConfig {`);
491
+ parts.push(` return new AttioOutputConfig("${id}", opts?.object?.apiSlug)`);
492
+ parts.push(" },");
493
+ parts.push("}");
494
+ parts.push("");
466
495
  return { code: parts.join("\n"), imports };
467
496
  }
468
497
  // ── Typed Tools ───────────────────────────────────────────────────────
@@ -578,20 +607,20 @@ function generateToolsSection(tools, input) {
578
607
  };
579
608
  // Ensure GitHub repository params are full "owner/repo".
580
609
  // Supports:
581
- // - Repository objects (uses .fullName)
610
+ // - Repos objects (uses .fullName)
582
611
  // - full string "owner/repo" (passthrough)
583
612
  // - short repo name string "repo" (resolved via known generated repositories when unique)
584
- const normalizeGitHubRepositoryParams = (toolName) => {
613
+ const normalizeGitHubReposParams = (toolName) => {
585
614
  switch (toolName) {
586
615
  case "readGitHubFile":
587
616
  case "listGitHubPullRequests":
588
617
  case "listGitHubDirectory":
589
618
  case "listGitHubCommits":
590
619
  case "summarizeGitHubPullRequestDiff":
591
- return "{ ...params, repository: __normalizeGitHubRepository((params as any).repository) }";
620
+ return "{ ...params, repository: __normalizeGitHubRepos((params as any).repository) }";
592
621
  case "searchGitHubCode":
593
622
  case "grepGitHubCode":
594
- return "{ ...params, repositoryNames: __normalizeGitHubRepositoryNames((params as any).repositoryNames) }";
623
+ return "{ ...params, repositoryNames: __normalizeGitHubReposNames((params as any).repositoryNames) }";
595
624
  default:
596
625
  return "params";
597
626
  }
@@ -608,18 +637,14 @@ function generateToolsSection(tools, input) {
608
637
  parts.push(`export type ${typeName} = ${tsType}`);
609
638
  parts.push("");
610
639
  }
611
- // Build tool groups: each group has a key, raw integration, tools, and optional baked-in integrationId
612
640
  const groups = [];
613
641
  for (const [integration, integrationTools] of byIntegration) {
614
642
  const needsAutoFill = integrationTools.some(hasAutoFillId);
615
643
  if (needsAutoFill) {
616
644
  const instances = instanceMap.get(integration) || [];
617
645
  if (instances.length === 0)
618
- continue; // Skip tools with no matching instances
619
- for (let i = 0; i < instances.length; i++) {
620
- const sfx = suffix(instances, i);
621
- groups.push({ key: integration + sfx, integration, tools: integrationTools, integrationId: instances[i].id });
622
- }
646
+ continue;
647
+ groups.push({ key: integration, integration, tools: integrationTools, integrationId: instances[0].id });
623
648
  }
624
649
  else {
625
650
  groups.push({ key: integration, integration, tools: integrationTools });
@@ -647,7 +672,7 @@ function generateToolsSection(tools, input) {
647
672
  }
648
673
  parts.push(`])`);
649
674
  parts.push("");
650
- parts.push(`function __normalizeGitHubRepository(repo: unknown): string {`);
675
+ parts.push(`function __normalizeGitHubRepos(repo: unknown): string {`);
651
676
  parts.push(` if (repo && typeof repo === "object" && "fullName" in (repo as Record<string, unknown>)) {`);
652
677
  parts.push(` const fullName = (repo as { fullName?: unknown }).fullName`);
653
678
  parts.push(` if (typeof fullName === "string" && fullName.length > 0) return fullName`);
@@ -659,9 +684,9 @@ function generateToolsSection(tools, input) {
659
684
  parts.push(` return String(repo ?? "")`);
660
685
  parts.push(`}`);
661
686
  parts.push("");
662
- parts.push(`function __normalizeGitHubRepositoryNames(repositories: unknown): string[] {`);
687
+ parts.push(`function __normalizeGitHubReposNames(repositories: unknown): string[] {`);
663
688
  parts.push(` if (!Array.isArray(repositories)) return []`);
664
- parts.push(` return repositories.map(repo => __normalizeGitHubRepository(repo))`);
689
+ parts.push(` return repositories.map(repo => __normalizeGitHubRepos(repo))`);
665
690
  parts.push(`}`);
666
691
  parts.push("");
667
692
  }
@@ -699,7 +724,7 @@ function generateToolsSection(tools, input) {
699
724
  for (const tool of group.tools) {
700
725
  const methodName = toCamelCase(tool.displayName);
701
726
  const paramsType = toolNameToInterfaceName(tool.name);
702
- const normalizedParamsExpr = group.integration === "github" ? normalizeGitHubRepositoryParams(tool.name) : "params";
727
+ const normalizedParamsExpr = group.integration === "github" ? normalizeGitHubReposParams(tool.name) : "params";
703
728
  if (group.integrationId && hasAutoFillId(tool)) {
704
729
  parts.push(` ${methodName}: (params: ${paramsType}) =>`);
705
730
  parts.push(` agent.executeTool<ToolOutputByName["${escapeString(tool.name)}"]>("${escapeString(tool.name)}", { ...(${normalizedParamsExpr}), integrationId: "${escapeString(group.integrationId)}" }),`);
@@ -725,20 +750,20 @@ function generateSystemSection() {
725
750
  const parts = [];
726
751
  parts.push(sectionHeader("Schedule"));
727
752
  parts.push("");
728
- parts.push(`/** Use in \`triggers[]\` — run on a cron schedule */`);
729
- parts.push("export class ScheduleTrigger extends TimeTriggerConfig {");
730
- parts.push(" constructor(cronExpression: string) {");
731
- parts.push(" super(cronExpression)");
732
- parts.push(" }");
753
+ parts.push("export const Schedule = {");
754
+ parts.push(` /** Use in \`triggers[]\` — run on a cron schedule */`);
755
+ parts.push(" cron(opts: { expression: string }): TimeTriggerConfig {");
756
+ parts.push(" return new TimeTriggerConfig(opts.expression)");
757
+ parts.push(" },");
733
758
  parts.push("}");
734
759
  parts.push("");
735
760
  parts.push(sectionHeader("Terse"));
736
761
  parts.push("");
737
- parts.push(`/** Use in \`skills[]\` — built-in web search */`);
738
- parts.push("export class TerseSkill extends TerseConfig {");
739
- parts.push(" constructor() {");
740
- parts.push(" super()");
741
- parts.push(" }");
762
+ parts.push("export const Terse = {");
763
+ parts.push(` /** Use in \`skills[]\` — built-in web search */`);
764
+ parts.push(" skill(): TerseConfig {");
765
+ parts.push(" return new TerseConfig()");
766
+ parts.push(" },");
742
767
  parts.push("}");
743
768
  parts.push("");
744
769
  return { code: parts.join("\n"), imports };