@notionhq/apps 0.0.12 → 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.
- package/README.md +32 -0
- package/dist/box.generated.d.ts +164 -0
- package/dist/box.generated.d.ts.map +1 -0
- package/dist/box.generated.js +24 -0
- package/dist/calendar.generated.d.ts +1036 -0
- package/dist/calendar.generated.d.ts.map +1 -0
- package/dist/calendar.generated.js +42 -0
- package/dist/cli/build.d.ts.map +1 -1
- package/dist/cli/build.js +41 -10
- package/dist/cli/codegen.d.ts +2 -0
- package/dist/cli/codegen.d.ts.map +1 -1
- package/dist/cli/codegen.js +13 -2
- package/dist/cli/discover.d.ts +2 -0
- package/dist/cli/discover.d.ts.map +1 -1
- package/dist/cli/discover.js +4 -2
- package/dist/cli/emit-manifest.d.ts +3 -1
- package/dist/cli/emit-manifest.d.ts.map +1 -1
- package/dist/cli/emit-manifest.js +10 -4
- package/dist/cli/index.js +33 -1
- package/dist/confluence.generated.d.ts +98 -0
- package/dist/confluence.generated.d.ts.map +1 -0
- package/dist/confluence.generated.js +18 -0
- package/dist/connection-actions.d.ts +2 -0
- package/dist/connection-actions.d.ts.map +1 -0
- package/dist/connection-actions.js +0 -0
- package/dist/connection-trigger-definitions.generated.d.ts +34 -0
- package/dist/connection-trigger-definitions.generated.d.ts.map +1 -0
- package/dist/connection-trigger-definitions.generated.js +45 -0
- package/dist/connections.d.ts +15 -0
- package/dist/connections.d.ts.map +1 -0
- package/dist/connections.js +79 -0
- package/dist/connections.test.d.ts +2 -0
- package/dist/connections.test.d.ts.map +1 -0
- package/dist/cursor.generated.d.ts +154 -0
- package/dist/cursor.generated.d.ts.map +1 -0
- package/dist/cursor.generated.js +18 -0
- package/dist/custom-block.d.ts +54 -0
- package/dist/custom-block.d.ts.map +1 -0
- package/dist/custom-block.js +30 -0
- package/dist/custom-block.test.d.ts +2 -0
- package/dist/custom-block.test.d.ts.map +1 -0
- package/dist/custom-blocks.d.ts +2 -0
- package/dist/custom-blocks.d.ts.map +1 -0
- package/dist/custom-blocks.js +1 -0
- package/dist/discord.generated.d.ts +279 -0
- package/dist/discord.generated.d.ts.map +1 -0
- package/dist/discord.generated.js +33 -0
- package/dist/gmail.generated.d.ts +184 -0
- package/dist/gmail.generated.d.ts.map +1 -0
- package/dist/gmail.generated.js +21 -0
- package/dist/googleCalendar.generated.d.ts +83 -0
- package/dist/googleCalendar.generated.d.ts.map +1 -0
- package/dist/googleCalendar.generated.js +15 -0
- package/dist/googleDrive.generated.d.ts +210 -0
- package/dist/googleDrive.generated.d.ts.map +1 -0
- package/dist/googleDrive.generated.js +27 -0
- package/dist/googleDriveOauth.generated.d.ts +851 -0
- package/dist/googleDriveOauth.generated.d.ts.map +1 -0
- package/dist/googleDriveOauth.generated.js +84 -0
- package/dist/manifest.d.ts +1 -1
- package/dist/manifest.d.ts.map +1 -1
- package/dist/nds.css +1 -0
- package/dist/notion-as-code/database.d.ts +4 -3
- package/dist/notion-as-code/database.d.ts.map +1 -1
- package/dist/notion-as-code/database.js +11 -2
- package/dist/notion-as-code/intents.d.ts +4 -1
- package/dist/notion-as-code/intents.d.ts.map +1 -1
- package/dist/notion-as-code/page.d.ts +4 -4
- package/dist/notion-as-code/page.d.ts.map +1 -1
- package/dist/notion-as-code/page.js +11 -2
- package/dist/outlook.generated.d.ts +224 -0
- package/dist/outlook.generated.d.ts.map +1 -0
- package/dist/outlook.generated.js +24 -0
- package/dist/providers.generated.d.ts +117 -0
- package/dist/providers.generated.d.ts.map +1 -0
- package/dist/providers.generated.js +108 -0
- package/dist/react.d.ts +2 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +1 -0
- package/dist/salesforce.generated.d.ts +151 -0
- package/dist/salesforce.generated.d.ts.map +1 -0
- package/dist/salesforce.generated.js +24 -0
- package/dist/slack.generated.d.ts +290 -0
- package/dist/slack.generated.d.ts.map +1 -0
- package/dist/slack.generated.js +30 -0
- package/dist/triggers.generated.d.ts +107 -32
- package/dist/triggers.generated.d.ts.map +1 -1
- package/dist/triggers.generated.js +36 -24
- package/dist/workflow-connections-types.test.d.ts +2 -0
- package/dist/workflow-connections-types.test.d.ts.map +1 -0
- package/dist/workflow-types.test.d.ts +2 -0
- package/dist/workflow-types.test.d.ts.map +1 -0
- package/dist/workflow.d.ts +20 -16
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +14 -17
- package/docs/BUILD.md +83 -14
- package/docs/CONNECTIONS.md +129 -0
- package/package.json +16 -2
- package/src/box.generated.ts +187 -0
- package/src/calendar.generated.ts +1117 -0
- package/src/cli/build.test.ts +87 -0
- package/src/cli/build.ts +47 -9
- package/src/cli/codegen.test.ts +2 -0
- package/src/cli/codegen.ts +25 -7
- package/src/cli/discover.test.ts +12 -0
- package/src/cli/discover.ts +6 -2
- package/src/cli/emit-manifest.ts +13 -3
- package/src/cli/index.ts +33 -1
- package/src/confluence.generated.ts +109 -0
- package/src/connection-actions.ts +2 -0
- package/src/connection-trigger-definitions.generated.ts +48 -0
- package/src/connections.test.ts +157 -0
- package/src/connections.ts +126 -0
- package/src/cursor.generated.ts +167 -0
- package/src/custom-block.test.ts +19 -0
- package/src/custom-block.ts +41 -0
- package/src/custom-blocks.ts +1 -0
- package/src/discord.generated.ts +318 -0
- package/src/gmail.generated.ts +205 -0
- package/src/googleCalendar.generated.ts +95 -0
- package/src/googleDrive.generated.ts +235 -0
- package/src/googleDriveOauth.generated.ts +980 -0
- package/src/manifest.ts +1 -1
- package/src/nds.css +1 -0
- package/src/notion-as-code/database.ts +19 -11
- package/src/notion-as-code/intents.ts +2 -1
- package/src/notion-as-code/page.ts +17 -11
- package/src/outlook.generated.ts +249 -0
- package/src/providers.generated.ts +207 -0
- package/src/react.ts +1 -0
- package/src/salesforce.generated.ts +170 -0
- package/src/slack.generated.ts +330 -0
- package/src/triggers.generated.ts +112 -32
- package/src/workflow-connections-types.test.ts +59 -0
- package/src/workflow-types.test.ts +144 -0
- package/src/workflow.test.ts +112 -2
- package/src/workflow.ts +58 -29
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createCustomBlock } from "./custom-block.js";
|
|
3
|
+
|
|
4
|
+
describe("createCustomBlock", () => {
|
|
5
|
+
it("supports static assets without a build command", () => {
|
|
6
|
+
expect(createCustomBlock({ path: "assets", static: true }).config.source).toEqual({
|
|
7
|
+
type: "static",
|
|
8
|
+
path: "assets",
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
it.each(["//hello", "Hello", "-hello", "", "a".repeat(65)])(
|
|
12
|
+
"rejects invalid slash command %s",
|
|
13
|
+
(slashCommand) => {
|
|
14
|
+
expect(() => createCustomBlock({ path: "blocks/hello", slashCommand })).toThrow(
|
|
15
|
+
/slashCommand/,
|
|
16
|
+
);
|
|
17
|
+
},
|
|
18
|
+
);
|
|
19
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ManifestDataSource } from "@notionhq/custom-blocks";
|
|
2
|
+
|
|
3
|
+
export type CustomBlockOptions = {
|
|
4
|
+
path: string;
|
|
5
|
+
slashCommand?: string;
|
|
6
|
+
dataSources?: Record<string, ManifestDataSource>;
|
|
7
|
+
} & (
|
|
8
|
+
| { static: true; command?: never; output?: never }
|
|
9
|
+
| { static?: false; command?: string; output?: string }
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
/** Declare a browser project. Its filename under src/customBlocks supplies its key. */
|
|
13
|
+
export function createCustomBlock(options: CustomBlockOptions) {
|
|
14
|
+
if (typeof options.path !== "string" || options.path.trim() === "") {
|
|
15
|
+
throw new Error("createCustomBlock requires a non-empty path relative to the app root");
|
|
16
|
+
}
|
|
17
|
+
const slashCommand = options.slashCommand?.replace(/^\//, "");
|
|
18
|
+
if (slashCommand !== undefined && !/^[a-z0-9][a-z0-9_-]{0,63}$/.test(slashCommand)) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
"Custom block slashCommand must be 1–64 lowercase letters, numbers, hyphens or underscores, starting with a letter or number",
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
_tag: "custom_block" as const,
|
|
25
|
+
config: {
|
|
26
|
+
source: options.static
|
|
27
|
+
? { type: "static" as const, path: options.path }
|
|
28
|
+
: {
|
|
29
|
+
type: "project" as const,
|
|
30
|
+
path: options.path,
|
|
31
|
+
command: options.command ?? "npx vite build",
|
|
32
|
+
output: options.output ?? "dist",
|
|
33
|
+
},
|
|
34
|
+
manifest: {
|
|
35
|
+
version: 1 as const,
|
|
36
|
+
...(slashCommand === undefined ? {} : { slashCommand }),
|
|
37
|
+
dataSources: options.dataSources ?? {},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@notionhq/custom-blocks";
|