@notionhq/apps 0.0.13 → 0.0.14

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 (89) hide show
  1. package/README.md +32 -28
  2. package/dist/box.generated.d.ts +164 -0
  3. package/dist/box.generated.d.ts.map +1 -0
  4. package/dist/box.generated.js +24 -0
  5. package/dist/calendar.generated.d.ts +1036 -0
  6. package/dist/calendar.generated.d.ts.map +1 -0
  7. package/dist/calendar.generated.js +42 -0
  8. package/dist/cli/build.js +7 -6
  9. package/dist/confluence.generated.d.ts +98 -0
  10. package/dist/confluence.generated.d.ts.map +1 -0
  11. package/dist/confluence.generated.js +18 -0
  12. package/dist/connection-actions.d.ts +2 -0
  13. package/dist/connection-actions.d.ts.map +1 -0
  14. package/dist/connection-actions.js +0 -0
  15. package/dist/connection-trigger-definitions.generated.d.ts +34 -0
  16. package/dist/connection-trigger-definitions.generated.d.ts.map +1 -0
  17. package/dist/connection-trigger-definitions.generated.js +45 -0
  18. package/dist/connections.d.ts +15 -0
  19. package/dist/connections.d.ts.map +1 -0
  20. package/dist/connections.js +79 -0
  21. package/dist/connections.test.d.ts +2 -0
  22. package/dist/connections.test.d.ts.map +1 -0
  23. package/dist/cursor.generated.d.ts +154 -0
  24. package/dist/cursor.generated.d.ts.map +1 -0
  25. package/dist/cursor.generated.js +18 -0
  26. package/dist/discord.generated.d.ts +279 -0
  27. package/dist/discord.generated.d.ts.map +1 -0
  28. package/dist/discord.generated.js +33 -0
  29. package/dist/gmail.generated.d.ts +184 -0
  30. package/dist/gmail.generated.d.ts.map +1 -0
  31. package/dist/gmail.generated.js +21 -0
  32. package/dist/googleCalendar.generated.d.ts +83 -0
  33. package/dist/googleCalendar.generated.d.ts.map +1 -0
  34. package/dist/googleCalendar.generated.js +15 -0
  35. package/dist/googleDrive.generated.d.ts +210 -0
  36. package/dist/googleDrive.generated.d.ts.map +1 -0
  37. package/dist/googleDrive.generated.js +27 -0
  38. package/dist/googleDriveOauth.generated.d.ts +851 -0
  39. package/dist/googleDriveOauth.generated.d.ts.map +1 -0
  40. package/dist/googleDriveOauth.generated.js +84 -0
  41. package/dist/outlook.generated.d.ts +224 -0
  42. package/dist/outlook.generated.d.ts.map +1 -0
  43. package/dist/outlook.generated.js +24 -0
  44. package/dist/providers.generated.d.ts +117 -0
  45. package/dist/providers.generated.d.ts.map +1 -0
  46. package/dist/providers.generated.js +108 -0
  47. package/dist/salesforce.generated.d.ts +151 -0
  48. package/dist/salesforce.generated.d.ts.map +1 -0
  49. package/dist/salesforce.generated.js +24 -0
  50. package/dist/slack.generated.d.ts +290 -0
  51. package/dist/slack.generated.d.ts.map +1 -0
  52. package/dist/slack.generated.js +30 -0
  53. package/dist/triggers.generated.d.ts +107 -32
  54. package/dist/triggers.generated.d.ts.map +1 -1
  55. package/dist/triggers.generated.js +36 -24
  56. package/dist/workflow-connections-types.test.d.ts +2 -0
  57. package/dist/workflow-connections-types.test.d.ts.map +1 -0
  58. package/dist/workflow-types.test.d.ts +2 -0
  59. package/dist/workflow-types.test.d.ts.map +1 -0
  60. package/dist/workflow.d.ts +20 -16
  61. package/dist/workflow.d.ts.map +1 -1
  62. package/dist/workflow.js +14 -17
  63. package/docs/BUILD.md +83 -14
  64. package/docs/CONNECTIONS.md +129 -0
  65. package/package.json +1 -1
  66. package/src/box.generated.ts +187 -0
  67. package/src/calendar.generated.ts +1117 -0
  68. package/src/cli/build.test.ts +33 -0
  69. package/src/cli/build.ts +6 -5
  70. package/src/confluence.generated.ts +109 -0
  71. package/src/connection-actions.ts +2 -0
  72. package/src/connection-trigger-definitions.generated.ts +48 -0
  73. package/src/connections.test.ts +157 -0
  74. package/src/connections.ts +126 -0
  75. package/src/cursor.generated.ts +167 -0
  76. package/src/discord.generated.ts +318 -0
  77. package/src/gmail.generated.ts +205 -0
  78. package/src/googleCalendar.generated.ts +95 -0
  79. package/src/googleDrive.generated.ts +235 -0
  80. package/src/googleDriveOauth.generated.ts +980 -0
  81. package/src/outlook.generated.ts +249 -0
  82. package/src/providers.generated.ts +207 -0
  83. package/src/salesforce.generated.ts +170 -0
  84. package/src/slack.generated.ts +330 -0
  85. package/src/triggers.generated.ts +112 -32
  86. package/src/workflow-connections-types.test.ts +59 -0
  87. package/src/workflow-types.test.ts +144 -0
  88. package/src/workflow.test.ts +112 -2
  89. package/src/workflow.ts +58 -29
@@ -216,6 +216,39 @@ describe("buildApp", () => {
216
216
  });
217
217
  });
218
218
 
219
+ it("removes provisioning when a rebuild no longer declares Notion resources", async () => {
220
+ const root = await fs.promises.mkdtemp(path.join(FIXTURES, "provisioning-rebuild-"));
221
+ try {
222
+ await fs.promises.cp(
223
+ path.join(FIXTURES, "notion-as-code", "src"),
224
+ path.join(root, "src"),
225
+ {
226
+ recursive: true,
227
+ },
228
+ );
229
+ await buildApp(root);
230
+ const provisioningPath = path.join(root, "dist", "provisioning.json");
231
+ const provisioning = JSON.parse(await fs.promises.readFile(provisioningPath, "utf8"));
232
+ expect(provisioning.intents).toEqual(
233
+ expect.arrayContaining([expect.objectContaining({ resourceId: "issues-db" })]),
234
+ );
235
+
236
+ await fs.promises.rm(path.join(root, "src"), { recursive: true });
237
+ await fs.promises.cp(path.join(EXAMPLES, "workflow", "src"), path.join(root, "src"), {
238
+ recursive: true,
239
+ });
240
+ const { manifest } = await buildApp(root);
241
+ expect(manifest.capabilities).toEqual([
242
+ expect.objectContaining({ type: "workflow", key: "onPageCreated" }),
243
+ ]);
244
+ await expect(fs.promises.readFile(provisioningPath)).rejects.toMatchObject({
245
+ code: "ENOENT",
246
+ });
247
+ } finally {
248
+ await fs.promises.rm(root, { recursive: true, force: true });
249
+ }
250
+ });
251
+
219
252
  it("builds a workflow bundle and manifest", async () => {
220
253
  const { manifest, bundlePath, manifestPath } = await buildApp(example("workflow"));
221
254
 
package/src/cli/build.ts CHANGED
@@ -72,13 +72,14 @@ async function emitProvisioningArtifact(
72
72
  capabilities: readonly DiscoveredCapability[],
73
73
  ): Promise<void> {
74
74
  const entityProvisioning = await buildMetadata(projectRoot, capabilities);
75
- if (entityProvisioning.intents.length === 0) return;
75
+ const provisioningPath = path.join(projectRoot, "dist", "provisioning.json");
76
+ if (entityProvisioning.intents.length === 0) {
77
+ await fs.promises.rm(provisioningPath, { force: true });
78
+ return;
79
+ }
76
80
 
77
81
  const artifact = createProvisioningArtifact(entityProvisioning.intents);
78
- await fs.promises.writeFile(
79
- path.join(projectRoot, "dist", "provisioning.json"),
80
- `${JSON.stringify(artifact, null, "\t")}\n`,
81
- );
82
+ await fs.promises.writeFile(provisioningPath, `${JSON.stringify(artifact, null, "\t")}\n`);
82
83
  }
83
84
 
84
85
  /** Bundle capabilities separately to record their Notion-as-Code declarations without shipping them. */
@@ -0,0 +1,109 @@
1
+ // Generated by notion tool-core codegen-script-types --connections. Do not edit.
2
+ import type { ConnectionActionInvoker } from "./connection-actions.js"
3
+ /**
4
+ * Script input for the "Query Confluence with CQL" effect (`confluence.cqlQuery`),
5
+ * derived from its Tool Core definition's call_function input projection.
6
+ *
7
+ * Run a CQL query and return results.
8
+ */
9
+ export type CqlQueryScriptInput = {
10
+ /**
11
+ * CQL query string. Only read-only queries are supported. Example: type=page AND space=DEV AND text~"deployment"
12
+ */
13
+ query: string
14
+ /**
15
+ * Maximum number of results to return. Defaults to 20.
16
+ */
17
+ maxResults?: number
18
+ }
19
+
20
+ /**
21
+ * Canonical Tool Core output for the "Query Confluence with CQL" effect (`confluence.cqlQuery`).
22
+ * This is the definition's output schema; the effect's script surface may
23
+ * re-encode it before returning.
24
+ */
25
+ export type CqlQueryScriptOutput = {
26
+ results: Array<{
27
+ id: string
28
+ title: string
29
+ blocks: Array<string>
30
+ }>
31
+ query: string
32
+ baseUrl: string
33
+ }
34
+
35
+ /**
36
+ * Script input for the "Load Confluence page" effect (`confluence.loadPage`),
37
+ * derived from its Tool Core definition's call_function input projection.
38
+ *
39
+ * Load a Confluence page by ID.
40
+ */
41
+ export type LoadPageScriptInput = {
42
+ /**
43
+ * The Confluence page ID to load.
44
+ */
45
+ pageId: string
46
+ }
47
+
48
+ /**
49
+ * Canonical Tool Core output for the "Load Confluence page" effect (`confluence.loadPage`).
50
+ * This is the definition's output schema; the effect's script surface may
51
+ * re-encode it before returning.
52
+ */
53
+ export type LoadPageScriptOutput = {
54
+ type: "confluence-page"
55
+ title: string
56
+ blocks: Array<string>
57
+ pageId: string
58
+ }
59
+
60
+ /**
61
+ * Script input for the "Search Confluence" effect (`confluence.search`),
62
+ * derived from its Tool Core definition's call_function input projection.
63
+ *
64
+ * Search Confluence via the configured connector.
65
+ */
66
+ export type SearchScriptInput = {
67
+ /**
68
+ * A single, focused question to search for.
69
+ */
70
+ question: string
71
+ /**
72
+ * Brief keywords capturing the core entities.
73
+ */
74
+ keywords?: string
75
+ /**
76
+ * Optional time window. Use only "default", "all_time", a duration matching /^\d+[dwmy]$/ (for example, "7d", "2w", "3m", or "1y"), or a date in "YYYY-MM-DD" format. Do not use natural-language durations or combine units.
77
+ */
78
+ lookback?: string
79
+ }
80
+
81
+ /**
82
+ * Canonical Tool Core output for the "Search Confluence" effect (`confluence.search`).
83
+ * This is the definition's output schema; the effect's script surface may
84
+ * re-encode it before returning.
85
+ */
86
+ export type SearchScriptOutput = {
87
+ results: Array<{
88
+ id: string
89
+ title: string
90
+ path: string
91
+ text: string
92
+ lastEdited: string
93
+ isPrivate: boolean
94
+ pageId: string
95
+ }>
96
+ }
97
+
98
+ export class ConfluenceConnectionClient {
99
+ constructor(private readonly invoke: ConnectionActionInvoker) {}
100
+ cqlQuery(args: CqlQueryScriptInput): Promise<CqlQueryScriptOutput> {
101
+ return this.invoke<CqlQueryScriptOutput>("cqlQuery", args)
102
+ }
103
+ loadPage(args: LoadPageScriptInput): Promise<LoadPageScriptOutput> {
104
+ return this.invoke<LoadPageScriptOutput>("loadPage", args)
105
+ }
106
+ search(args: SearchScriptInput): Promise<SearchScriptOutput> {
107
+ return this.invoke<SearchScriptOutput>("search", args)
108
+ }
109
+ }
@@ -0,0 +1,2 @@
1
+ /** @internal Transport used by generated Tool Core provider clients. */
2
+ export type ConnectionActionInvoker = <Result>(action: string, args: object) => Promise<Result>;
@@ -0,0 +1,48 @@
1
+ // Generated by notion tool-core codegen-script-types --connections. Do not edit.
2
+ export const connectionTriggerDefinitions = [
3
+ {
4
+ type: "calendar.event.created",
5
+ provider: "calendar",
6
+ description: "Triggers immediately when a calendar event is created",
7
+ },
8
+ {
9
+ type: "calendar.event.updated",
10
+ provider: "calendar",
11
+ description: "Triggers immediately when a calendar event is updated",
12
+ },
13
+ {
14
+ type: "calendar.event.canceled",
15
+ provider: "calendar",
16
+ description: "Triggers immediately when a calendar event is canceled",
17
+ },
18
+ {
19
+ type: "slack.message",
20
+ provider: "slack",
21
+ description:
22
+ "Triggers immediately after a new message is posted in Slack (includes new threads and replies)",
23
+ },
24
+ {
25
+ type: "slack.reaction.added",
26
+ provider: "slack",
27
+ description:
28
+ "Triggers immediately after a reaction is added to a message in Slack",
29
+ },
30
+ {
31
+ type: "slack.app.mention",
32
+ provider: "slack",
33
+ description:
34
+ "Triggers when the agent is mentioned in a Slack channel or direct message",
35
+ },
36
+ {
37
+ type: "googleDriveOauth.filesChanged",
38
+ provider: "googleDriveOauth",
39
+ description:
40
+ "Watches for file changes in a specific Google Drive folder (direct children only) or shared drive. The My Drive root is not supported. Fires on a schedule and provides change tokens for fetching the list of changes.",
41
+ },
42
+ {
43
+ type: "discord.interaction",
44
+ provider: "discord",
45
+ description:
46
+ "Triggers when a user invokes the /ai slash command in a configured Discord channel",
47
+ },
48
+ ] as const
@@ -0,0 +1,157 @@
1
+ import { Client } from "@notionhq/client";
2
+ import { afterEach, describe, expect, it, vi } from "vitest";
3
+ import {
4
+ connections,
5
+ createWorkflowConnections,
6
+ validateConnectionRequirements,
7
+ } from "./connections.js";
8
+
9
+ afterEach(() => {
10
+ vi.unstubAllEnvs();
11
+ vi.restoreAllMocks();
12
+ });
13
+
14
+ describe("connection requirements", () => {
15
+ it("declares generated providers", () => {
16
+ expect(connections.calendar()).toEqual({ type: "calendar" });
17
+ expect(connections.box({ key: "documents" })).toEqual({ type: "box", key: "documents" });
18
+ expect(connections.googleDriveOauth()).toEqual({ type: "googleDriveOauth" });
19
+ expect(connections.discord({ key: "community" })).toEqual({
20
+ type: "discord",
21
+ key: "community",
22
+ });
23
+ });
24
+
25
+ it("rejects duplicate keys across providers", () => {
26
+ expect(() =>
27
+ validateConnectionRequirements([
28
+ connections.calendar({ key: "work" }),
29
+ connections.slack({ key: "work" }),
30
+ ]),
31
+ ).toThrow("Duplicate");
32
+ });
33
+ it("rejects invalid keys and excessive requirements", () => {
34
+ expect(() =>
35
+ validateConnectionRequirements([connections.slack({ key: "bad/key" })]),
36
+ ).toThrow("Invalid");
37
+ expect(() =>
38
+ validateConnectionRequirements(
39
+ Array.from({ length: 101 }, (_, i) => connections.slack({ key: `team${i}` })),
40
+ ),
41
+ ).toThrow("100");
42
+ });
43
+ });
44
+
45
+ describe("workflow connection runtime", () => {
46
+ it("preserves pagination through a generated OAuth provider method", async () => {
47
+ vi.stubEnv(
48
+ "NOTION_CONNECTION_BINDINGS",
49
+ '[{"key":"documents","type":"googleDriveOauth","id":"drive-id"}]',
50
+ );
51
+ const notion = new Client({ auth: "test" });
52
+ const request = vi
53
+ .spyOn(notion, "request")
54
+ .mockResolvedValue({ result: { files: [], nextPageToken: "next" } });
55
+ const result = await createWorkflowConnections(notion)
56
+ .googleDriveOauth("documents")
57
+ .listFiles({ folderId: "folder", pageToken: "previous" });
58
+ expect(result.nextPageToken).toBe("next");
59
+ expect(request).toHaveBeenCalledWith({
60
+ path: "tools/run/internal",
61
+ method: "post",
62
+ body: {
63
+ connection: "drive-id",
64
+ action: "listFiles",
65
+ args: { folderId: "folder", pageToken: "previous" },
66
+ },
67
+ });
68
+ });
69
+ it("uses the configured connector binding without choosing a user in the SDK", async () => {
70
+ vi.stubEnv(
71
+ "NOTION_CONNECTION_BINDINGS",
72
+ '[{"key":"documents","type":"box","id":"box-id"}]',
73
+ );
74
+ const notion = new Client({ auth: "test" });
75
+ const request = vi.spyOn(notion, "request").mockResolvedValue({ result: [] });
76
+ await createWorkflowConnections(notion)
77
+ .box("documents")
78
+ .search({ question: "Quarterly report" });
79
+ expect(request).toHaveBeenCalledWith({
80
+ path: "tools/run/internal",
81
+ method: "post",
82
+ body: {
83
+ connection: "box-id",
84
+ action: "search",
85
+ args: { question: "Quarterly report" },
86
+ },
87
+ });
88
+ });
89
+
90
+ it("rejects a mismatched provider before sending requests", () => {
91
+ vi.stubEnv("NOTION_CONNECTION_BINDINGS", '[{"key":"support","type":"calendar","id":"a"}]');
92
+ const notion = new Client({ auth: "test" });
93
+ const request = vi.spyOn(notion, "request");
94
+ expect(() => createWorkflowConnections(notion).slack("support")).toThrow("not slack");
95
+ expect(request).not.toHaveBeenCalled();
96
+ });
97
+ it("invokes a typed Calendar method with the default binding", async () => {
98
+ vi.stubEnv(
99
+ "NOTION_CONNECTION_BINDINGS",
100
+ '[{"key":"calendar","type":"calendar","id":"calendar-id"}]',
101
+ );
102
+ const notion = new Client({ auth: "test" });
103
+ const request = vi.spyOn(notion, "request").mockResolvedValue({ result: { accounts: [] } });
104
+ const result = await createWorkflowConnections(notion).calendar().listCalendars({});
105
+ expect(result.accounts).toEqual([]);
106
+ expect(request).toHaveBeenCalledWith({
107
+ path: "tools/run/internal",
108
+ method: "post",
109
+ body: { connection: "calendar-id", action: "listCalendars", args: {} },
110
+ });
111
+ });
112
+
113
+ it("invokes the exact named binding and unwraps its result", async () => {
114
+ vi.stubEnv(
115
+ "NOTION_CONNECTION_BINDINGS",
116
+ JSON.stringify([{ key: "support", type: "slack", id: "configured-id" }]),
117
+ );
118
+ const notion = new Client({ auth: "test" });
119
+ const request = vi.spyOn(notion, "request").mockResolvedValue({ result: { ok: true } });
120
+ const runtime = createWorkflowConnections(notion);
121
+ await expect(
122
+ runtime.slack("support").findUserByEmail({ email: "person@example.com" }),
123
+ ).resolves.toEqual({
124
+ ok: true,
125
+ });
126
+ expect(request).toHaveBeenCalledWith({
127
+ path: "tools/run/internal",
128
+ method: "post",
129
+ body: {
130
+ connection: "configured-id",
131
+ action: "findUserByEmail",
132
+ args: { email: "person@example.com" },
133
+ },
134
+ });
135
+ expect(() => runtime.slack("slack")).toThrow("not bound");
136
+ expect(request).toHaveBeenCalledTimes(1);
137
+ });
138
+ it.each([
139
+ "{}",
140
+ '[{"key":"support","type":"slack","id":1}]',
141
+ '[{"key":"support","type":"slack","id":"a"},{"key":"support","type":"slack","id":"b"}]',
142
+ ])("rejects malformed or ambiguous bindings: %s", (metadata) => {
143
+ vi.stubEnv("NOTION_CONNECTION_BINDINGS", metadata);
144
+ expect(() => createWorkflowConnections(new Client()).slack("support")).toThrow();
145
+ });
146
+ it("propagates permission failures without retrying an alternate connection", async () => {
147
+ vi.stubEnv("NOTION_CONNECTION_BINDINGS", '[{"key":"support","type":"slack","id":"a"}]');
148
+ const notion = new Client({ auth: "test" });
149
+ const request = vi.spyOn(notion, "request").mockRejectedValue(new Error("forbidden"));
150
+ await expect(
151
+ createWorkflowConnections(notion)
152
+ .slack("support")
153
+ .findUserByEmail({ email: "person@example.com" }),
154
+ ).rejects.toThrow("forbidden");
155
+ expect(request).toHaveBeenCalledTimes(1);
156
+ });
157
+ });
@@ -0,0 +1,126 @@
1
+ import { connectionTriggerDefinitions } from "./connection-trigger-definitions.generated.js";
2
+ import type { WorkflowTrigger } from "./triggers.generated.js";
3
+ import type { Client } from "@notionhq/client";
4
+ import { createConnectionProviders, type ConnectionProviders } from "./providers.generated.js";
5
+ import type { ConnectionActionInvoker } from "./connection-actions.js";
6
+
7
+ /** A setup requirement. The server decides which providers are available. */
8
+ export type WorkflowConnection<TProvider extends string = string, TKey extends string = string> = {
9
+ type: TProvider;
10
+ key?: TKey;
11
+ };
12
+
13
+ /** Keys declared for one provider in a workflow. */
14
+ export type WorkflowConnectionKeys<
15
+ TConnections extends readonly WorkflowConnection[],
16
+ TProvider extends string,
17
+ > = WorkflowConnectionKey<Extract<TConnections[number], { type: TProvider }>>;
18
+
19
+ type WorkflowConnectionKey<TConnection extends WorkflowConnection> =
20
+ TConnection extends WorkflowConnection
21
+ ? "key" extends keyof TConnection
22
+ ? Exclude<TConnection["key"], undefined>
23
+ : TConnection["type"]
24
+ : never;
25
+ type ConnectionBinding = { key: string; type: string; id: string };
26
+
27
+ /** Requirements are declarations, not credentials or permission grants. */
28
+ export { connections } from "./providers.generated.js";
29
+
30
+ export type WorkflowConnections<
31
+ TConnections extends readonly WorkflowConnection[] = readonly WorkflowConnection[],
32
+ > = Pick<ConnectionProviders, Extract<keyof ConnectionProviders, TConnections[number]["type"]>>;
33
+
34
+ /** @internal */
35
+ export function validateConnectionRequirements(requirements: readonly WorkflowConnection[]): void {
36
+ if (requirements.length > 100) throw new Error("Workflows support at most 100 connections.");
37
+ const keys = new Set<string>();
38
+ for (const requirement of requirements) {
39
+ if (!requirement.type) throw new Error("A workflow connection needs a provider type.");
40
+ const key = requirement.key ?? requirement.type;
41
+ if (!/^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(key)) {
42
+ throw new Error("Invalid workflow connection key: " + key);
43
+ }
44
+ if (keys.has(key)) throw new Error("Duplicate workflow connection: " + key);
45
+ keys.add(key);
46
+ }
47
+ }
48
+
49
+ /** @internal */
50
+ export function validateTriggerConnections(
51
+ triggers: readonly WorkflowTrigger[],
52
+ requirements: readonly WorkflowConnection[],
53
+ ): void {
54
+ const seen = new Set<string>();
55
+ for (const trigger of triggers) {
56
+ if (!("connectionKey" in trigger) || trigger.connectionKey === undefined) continue;
57
+ const requirement = requirements.find(
58
+ (connection) => (connection.key ?? connection.type) === trigger.connectionKey,
59
+ );
60
+ const definition = connectionTriggerDefinitions.find(
61
+ (definition) => definition.type === trigger.type,
62
+ );
63
+ if (!requirement || requirement.type !== definition?.provider) {
64
+ throw new Error(
65
+ `Trigger "${trigger.type}" requires a declared ${definition?.provider ?? "supported"} connection: ${trigger.connectionKey}`,
66
+ );
67
+ }
68
+ const identity = JSON.stringify([trigger.type, trigger.connectionKey]);
69
+ if (seen.has(identity))
70
+ throw new Error(
71
+ `Duplicate connection trigger: ${trigger.type} (${trigger.connectionKey})`,
72
+ );
73
+ seen.add(identity);
74
+ }
75
+ }
76
+
77
+ /** @internal */
78
+ export function createWorkflowConnections(notion: Client): WorkflowConnections {
79
+ // Capture the execution's bindings; defer parsing so non-connection workflows
80
+ // remain usable without runtime connection metadata.
81
+ const rawBindings = process.env.NOTION_CONNECTION_BINDINGS;
82
+ function resolveBinding(key: string): ConnectionBinding {
83
+ const bindings: unknown = JSON.parse(rawBindings ?? "[]");
84
+ if (!Array.isArray(bindings) || !bindings.every(isConnectionBinding)) {
85
+ throw new Error("Invalid NOTION_CONNECTION_BINDINGS metadata.");
86
+ }
87
+ const matches = bindings.filter((binding: ConnectionBinding) => binding.key === key);
88
+ if (matches.length !== 1 || !matches[0]) {
89
+ throw new Error(
90
+ `Workflow connection "${key}" is not bound. Deploy and configure this requirement before running it.`,
91
+ );
92
+ }
93
+ return { ...matches[0] };
94
+ }
95
+ function invoker(provider: string, key: string): ConnectionActionInvoker {
96
+ const binding = resolveBinding(key);
97
+ if (binding.type !== provider) {
98
+ throw new Error(`Workflow connection "${key}" is ${binding.type}, not ${provider}.`);
99
+ }
100
+ return async <Result>(action: string, args: object): Promise<Result> => {
101
+ const response = await notion.request<{ result: Result }>({
102
+ path: "tools/run/internal",
103
+ method: "post",
104
+ body: { connection: binding.id, action, args },
105
+ });
106
+ return response.result;
107
+ };
108
+ }
109
+ return createConnectionProviders(invoker);
110
+ }
111
+
112
+ function isConnectionBinding(value: unknown): value is ConnectionBinding {
113
+ return (
114
+ typeof value === "object" &&
115
+ value !== null &&
116
+ "key" in value &&
117
+ typeof value.key === "string" &&
118
+ value.key.length > 0 &&
119
+ "type" in value &&
120
+ typeof value.type === "string" &&
121
+ value.type.length > 0 &&
122
+ "id" in value &&
123
+ typeof value.id === "string" &&
124
+ value.id.length > 0
125
+ );
126
+ }
@@ -0,0 +1,167 @@
1
+ // Generated by notion tool-core codegen-script-types --connections. Do not edit.
2
+ import type { ConnectionActionInvoker } from "./connection-actions.js"
3
+ /**
4
+ * Script input for the "Create Cursor agent" effect (`cursor.createAgent`),
5
+ * derived from its Tool Core definition's call_function input projection.
6
+ *
7
+ * Create a new Cursor agent
8
+ */
9
+ export type CreateAgentScriptInput = {
10
+ /**
11
+ * What you want the agent to do
12
+ */
13
+ prompt: string
14
+ /**
15
+ * GitHub repository URL or owner/repo format (e.g. 'owner/repo' or 'https://github.com/owner/repo')
16
+ */
17
+ repository: string
18
+ /**
19
+ * Optional branch or ref to work from (e.g. 'main', 'develop')
20
+ */
21
+ ref?: string
22
+ /**
23
+ * Custom branch name for the agent to create in the target repository.
24
+ */
25
+ branchName?: string
26
+ /**
27
+ * Whether to open the pull request as the Cursor GitHub App instead of as the user (default: true). Only applies when a PR is auto-created.
28
+ */
29
+ openAsCursorGithubApp?: boolean
30
+ /**
31
+ * Whether to skip adding the user as a reviewer on the pull request. Only applies if autoCreatePr is true and the PR is opened as the Cursor GitHub App (default: false).
32
+ */
33
+ skipReviewerRequest?: boolean
34
+ webhook?: {
35
+ /**
36
+ * Webhook URL to receive agent status updates.
37
+ */
38
+ url: string
39
+ /**
40
+ * Optional shared secret for verifying webhook signatures.
41
+ */
42
+ secret?: string
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Canonical Tool Core output for the "Create Cursor agent" effect (`cursor.createAgent`).
48
+ * This is the definition's output schema; the effect's script surface may
49
+ * re-encode it before returning.
50
+ */
51
+ export type CreateAgentScriptOutput = {
52
+ id: string
53
+ name: string
54
+ /**
55
+ * One of: `CREATING`, `RUNNING`, `FINISHED`, `ERROR`, `EXPIRED`
56
+ */
57
+ status: "CREATING" | "RUNNING" | "FINISHED" | "ERROR" | "EXPIRED"
58
+ source: {
59
+ repository: string
60
+ ref?: string
61
+ }
62
+ target: {
63
+ url: string
64
+ prUrl?: string
65
+ branchName?: string
66
+ autoCreatePr?: boolean
67
+ autoBranch?: boolean
68
+ openAsCursorGithubApp?: boolean
69
+ skipReviewerRequest?: boolean
70
+ }
71
+ createdAt: string
72
+ summary?: string
73
+ conversation?: {
74
+ id: string
75
+ messages: Array<{
76
+ id: string
77
+ type: string
78
+ text: string
79
+ }>
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Script input for the "Get Cursor agent" effect (`cursor.getAgent`),
85
+ * derived from its Tool Core definition's call_function input projection.
86
+ *
87
+ * Get a Cursor agent by ID
88
+ */
89
+ export type GetAgentScriptInput = {
90
+ /**
91
+ * The ID of the Cursor agent to get (e.g. 'agent-123')
92
+ */
93
+ agentId: string
94
+ }
95
+
96
+ /**
97
+ * Canonical Tool Core output for the "Get Cursor agent" effect (`cursor.getAgent`).
98
+ * This is the definition's output schema; the effect's script surface may
99
+ * re-encode it before returning.
100
+ */
101
+ export type GetAgentScriptOutput = {
102
+ id: string
103
+ name: string
104
+ /**
105
+ * One of: `CREATING`, `RUNNING`, `FINISHED`, `ERROR`, `EXPIRED`
106
+ */
107
+ status: "CREATING" | "RUNNING" | "FINISHED" | "ERROR" | "EXPIRED"
108
+ source: {
109
+ repository: string
110
+ ref?: string
111
+ }
112
+ target: {
113
+ url: string
114
+ prUrl?: string
115
+ branchName?: string
116
+ autoCreatePr?: boolean
117
+ autoBranch?: boolean
118
+ openAsCursorGithubApp?: boolean
119
+ skipReviewerRequest?: boolean
120
+ }
121
+ createdAt: string
122
+ summary?: string
123
+ conversation?: {
124
+ id: string
125
+ messages: Array<{
126
+ id: string
127
+ type: string
128
+ text: string
129
+ }>
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Script input for the "List Cursor repositories" effect (`cursor.listRepositories`),
135
+ * derived from its Tool Core definition's call_function input projection.
136
+ *
137
+ * List accessible Cursor repositories
138
+ */
139
+ export type ListRepositoriesScriptInput = Record<string, never>
140
+
141
+ /**
142
+ * Canonical Tool Core output for the "List Cursor repositories" effect (`cursor.listRepositories`).
143
+ * This is the definition's output schema; the effect's script surface may
144
+ * re-encode it before returning.
145
+ */
146
+ export type ListRepositoriesScriptOutput = {
147
+ repositories: Array<{
148
+ owner: string
149
+ name: string
150
+ repository: string
151
+ }>
152
+ }
153
+
154
+ export class CursorConnectionClient {
155
+ constructor(private readonly invoke: ConnectionActionInvoker) {}
156
+ createAgent(args: CreateAgentScriptInput): Promise<CreateAgentScriptOutput> {
157
+ return this.invoke<CreateAgentScriptOutput>("createAgent", args)
158
+ }
159
+ getAgent(args: GetAgentScriptInput): Promise<GetAgentScriptOutput> {
160
+ return this.invoke<GetAgentScriptOutput>("getAgent", args)
161
+ }
162
+ listRepositories(
163
+ args: ListRepositoriesScriptInput,
164
+ ): Promise<ListRepositoriesScriptOutput> {
165
+ return this.invoke<ListRepositoriesScriptOutput>("listRepositories", args)
166
+ }
167
+ }