@project-ajax/create 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.
- package/README.md +15 -0
- package/dist/index.js +13 -46
- package/package.json +3 -3
- package/template/README.md +700 -0
- package/template/docs/custom-agent-add-connection.png +0 -0
- package/template/docs/custom-agent-select-worker.png +0 -0
- package/template/docs/insert-sample-task.png +0 -0
- package/template/docs/inserted-blocks.png +0 -0
- package/template/docs/select-ajax.png +0 -0
- package/template/package.json +26 -0
- package/template/src/index.ts +334 -0
- package/template/tsconfig.json +16 -0
package/README.md
CHANGED
|
@@ -4,6 +4,21 @@ A CLI for creating new Project Ajax workers projects.
|
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
7
|
+
### Interactive Mode
|
|
8
|
+
|
|
7
9
|
```bash
|
|
8
10
|
npm init @project-ajax
|
|
9
11
|
```
|
|
12
|
+
|
|
13
|
+
### Non-Interactive Mode
|
|
14
|
+
|
|
15
|
+
For CI environments or scripted usage:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm init @project-ajax -- --directory my-worker --project my-worker
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
#### Options
|
|
22
|
+
|
|
23
|
+
- `--directory`, `-d` - Path to the new worker project (default: `.`)
|
|
24
|
+
- `--project`, `-p` - Project name (default: `my-worker`)
|
package/dist/index.js
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import fs from "fs";
|
|
5
|
-
import os from "os";
|
|
6
5
|
import path from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
7
|
import { parseArgs } from "util";
|
|
8
8
|
import * as prompts from "@inquirer/prompts";
|
|
9
9
|
import chalk from "chalk";
|
|
10
|
-
import { execa } from "execa";
|
|
11
10
|
import ora from "ora";
|
|
11
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
var __dirname = path.dirname(__filename);
|
|
12
13
|
run().then(() => {
|
|
13
14
|
process.exit(0);
|
|
14
15
|
}).catch((err) => {
|
|
@@ -53,15 +54,12 @@ async function run() {
|
|
|
53
54
|
process.exit(1);
|
|
54
55
|
}
|
|
55
56
|
const spinner = ora("Setting up template...").start();
|
|
56
|
-
let tempDir = null;
|
|
57
57
|
try {
|
|
58
|
-
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "project-ajax-"));
|
|
59
|
-
spinner.text = "Fetching template from repository...";
|
|
60
|
-
await pullCode(tempDir);
|
|
61
58
|
spinner.text = "Preparing destination...";
|
|
62
59
|
const destPath = prepareDestination(directoryName);
|
|
63
60
|
spinner.text = "Copying template files...";
|
|
64
|
-
|
|
61
|
+
const templatePath = getTemplatePath();
|
|
62
|
+
copyTemplate(templatePath, destPath);
|
|
65
63
|
spinner.text = "Customizing package.json...";
|
|
66
64
|
customizePackageJson(destPath, projectName);
|
|
67
65
|
spinner.succeed(chalk.green("Worker project created successfully!"));
|
|
@@ -72,46 +70,11 @@ async function run() {
|
|
|
72
70
|
chalk.red(`
|
|
73
71
|
${err instanceof Error ? err.message : String(err)}`)
|
|
74
72
|
);
|
|
75
|
-
if (err instanceof Error && (err.message.includes("Permission denied") || err.message.includes("publickey"))) {
|
|
76
|
-
console.log(
|
|
77
|
-
chalk.yellow("\n\u26A0\uFE0F SSH authentication failed. Make sure you have:")
|
|
78
|
-
);
|
|
79
|
-
console.log(" 1. SSH keys set up with GitHub");
|
|
80
|
-
console.log(" 2. Added your SSH key to your GitHub account");
|
|
81
|
-
console.log(
|
|
82
|
-
" 3. See: https://docs.github.com/en/authentication/connecting-to-github-with-ssh"
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
73
|
process.exit(1);
|
|
86
|
-
} finally {
|
|
87
|
-
if (tempDir) {
|
|
88
|
-
try {
|
|
89
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
90
|
-
} catch {
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
74
|
}
|
|
94
75
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
await execa(
|
|
98
|
-
"git",
|
|
99
|
-
["remote", "add", "origin", "git@github.com:makenotion/project-ajax.git"],
|
|
100
|
-
{ cwd: tempDir }
|
|
101
|
-
);
|
|
102
|
-
await execa("git", ["config", "core.sparseCheckout", "true"], {
|
|
103
|
-
cwd: tempDir
|
|
104
|
-
});
|
|
105
|
-
const sparseCheckoutPath = path.join(
|
|
106
|
-
tempDir,
|
|
107
|
-
".git",
|
|
108
|
-
"info",
|
|
109
|
-
"sparse-checkout"
|
|
110
|
-
);
|
|
111
|
-
fs.writeFileSync(sparseCheckoutPath, "packages/template/*\n");
|
|
112
|
-
await execa("git", ["pull", "--depth=1", "origin", "main"], {
|
|
113
|
-
cwd: tempDir
|
|
114
|
-
});
|
|
76
|
+
function getTemplatePath() {
|
|
77
|
+
return path.resolve(__dirname, "..", "template");
|
|
115
78
|
}
|
|
116
79
|
function prepareDestination(repoName) {
|
|
117
80
|
const destPath = repoName === "." ? process.cwd() : path.resolve(process.cwd(), repoName);
|
|
@@ -127,8 +90,12 @@ function prepareDestination(repoName) {
|
|
|
127
90
|
}
|
|
128
91
|
return destPath;
|
|
129
92
|
}
|
|
130
|
-
function copyTemplate(
|
|
131
|
-
|
|
93
|
+
function copyTemplate(templatePath, destPath) {
|
|
94
|
+
if (!fs.existsSync(templatePath)) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`Template directory not found at ${templatePath}. This is likely a packaging issue.`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
132
99
|
const templateFiles = fs.readdirSync(templatePath);
|
|
133
100
|
for (const file of templateFiles) {
|
|
134
101
|
const srcPath = path.join(templatePath, file);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@project-ajax/create",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"description": "Initialize a new Notion Project Ajax extensions repo.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-ajax": "dist/index.js"
|
|
@@ -22,12 +22,12 @@
|
|
|
22
22
|
"access": "public"
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
|
-
"dist/"
|
|
25
|
+
"dist/",
|
|
26
|
+
"template/"
|
|
26
27
|
],
|
|
27
28
|
"dependencies": {
|
|
28
29
|
"@inquirer/prompts": "^8.0.1",
|
|
29
30
|
"chalk": "^5.3.0",
|
|
30
|
-
"execa": "^8.0.0",
|
|
31
31
|
"ora": "^8.0.1"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
@@ -0,0 +1,700 @@
|
|
|
1
|
+
# README
|
|
2
|
+
|
|
3
|
+
Use this repo to write extensions to enhance Notion. With Notion extensions, you can write JavaScript that syncs data into Notion collections, adds new slash-commands, or provides new actions to Custom Agents.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
### Node
|
|
8
|
+
|
|
9
|
+
You need Node >= 22, npm >= 10 installed. (The repo will warn you if you don't.)
|
|
10
|
+
|
|
11
|
+
### Access to the feature
|
|
12
|
+
|
|
13
|
+
This feature is behind a feature flag (i.e. Statsig gate) in Notion.
|
|
14
|
+
|
|
15
|
+
See a `#proj-ajax` team member to gain access.
|
|
16
|
+
|
|
17
|
+
## Quickstart
|
|
18
|
+
|
|
19
|
+
In this Quickstart, you’ll:
|
|
20
|
+
|
|
21
|
+
- Clone this extensions template
|
|
22
|
+
- Publish example extensions to your Notion workspace
|
|
23
|
+
|
|
24
|
+
### 1. Create worker project from template
|
|
25
|
+
|
|
26
|
+
Use `npm init` to setup a project template:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
npm init @project-ajax
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
When prompted, enter a path to the new project, e.g. `my-extensions`.
|
|
33
|
+
|
|
34
|
+
### 2. Install packages and connect the repo to Notion
|
|
35
|
+
|
|
36
|
+
Install packages:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
# or whatever you chose for the pathname
|
|
40
|
+
cd my-extensions && npm i
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Now, run `npx workers` to verify the tool has installed:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
npx workers
|
|
47
|
+
|
|
48
|
+
USAGE
|
|
49
|
+
workers auth login|show|logout ...
|
|
50
|
+
workers deploy [--config value] [--debug]
|
|
51
|
+
workers secrets set|list|rm ...
|
|
52
|
+
workers --help
|
|
53
|
+
workers --version
|
|
54
|
+
|
|
55
|
+
A CLI for the Project Ajax platform
|
|
56
|
+
|
|
57
|
+
[ ... ]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Then, connect the repo to Notion to publish the example extensions to your Notion workspace:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
# Connect to Notion Prod
|
|
64
|
+
npx workers auth login
|
|
65
|
+
# Connect to Notion Dev
|
|
66
|
+
npx workers auth login --env=dev
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
This command will open a new page with a command that includes a token. Run that command locally. It will look like this:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
# example
|
|
73
|
+
npx workers auth login v1.user.wAFygwEZ-[...] --env=dev
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
> **Note**
|
|
77
|
+
>
|
|
78
|
+
> If the command opens a page that 404s, you likely do not have Statsig access to extensions. See a #proj-virtual-databases team member for access.
|
|
79
|
+
|
|
80
|
+
### 3. Deploy
|
|
81
|
+
|
|
82
|
+
Run the following command to deploy this repo's example extensions to your Notion workspace:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
npx workers deploy
|
|
86
|
+
|
|
87
|
+
Deploying worker...
|
|
88
|
+
Updating worker...
|
|
89
|
+
Building worker bundle...
|
|
90
|
+
Uploading bundle...
|
|
91
|
+
Fetching and saving worker capabilities...
|
|
92
|
+
✓ Successfully deployed worker
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 4. Try out the sample worker
|
|
96
|
+
|
|
97
|
+
The sample worker installs three capabilities:
|
|
98
|
+
|
|
99
|
+
- [Synced database](#try-the-synced-database)
|
|
100
|
+
- [Agent tool](#try-the-agent-tool)
|
|
101
|
+
- [Slash command](#try-the-slash-command)
|
|
102
|
+
|
|
103
|
+
#### Try the synced database
|
|
104
|
+
|
|
105
|
+
After deploying, the sync runs automatically and creates a database in your private pages - you can find it in your sidebar or by searching "Sample Tasks".
|
|
106
|
+
|
|
107
|
+
Alternatively, run the sync via the CLI to get the URL:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
npx workers exec tasksSync
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
When the command completes you will see the URL for the `Sample Tasks` database.
|
|
114
|
+
|
|
115
|
+
#### Try the agent tool
|
|
116
|
+
|
|
117
|
+
To exercise the sample agent tool, first navigate to a new or existing custom agent in Notion.
|
|
118
|
+
|
|
119
|
+
On the settings tab, choose Add connection:
|
|
120
|
+
|
|
121
|
+
<img src="./docs/custom-agent-add-connection.png" style="width:400px">
|
|
122
|
+
|
|
123
|
+
Scroll down to find the installed worker which has the name used when you deployed the worker. Click Add.
|
|
124
|
+
|
|
125
|
+
<img src="./docs/custom-agent-select-worker.png" style="width:400px">
|
|
126
|
+
|
|
127
|
+
Save settings, then navigate to the **chat** tab. Our sample tool call allows for searching tasks by id or keyword - try asking Agent to find tasks related to "Ajax" or "worker".
|
|
128
|
+
|
|
129
|
+
#### Try the slash command
|
|
130
|
+
|
|
131
|
+
In your Notion workspace, **first refresh**. Then, type forward-slash (`/insert`) and select the "Insert Sample Task" capability:
|
|
132
|
+
|
|
133
|
+
<img src="./docs/insert-sample-task.png" style="width:300px" />
|
|
134
|
+
|
|
135
|
+
In the search field that appears, type `ajax` to preview matching sample tasks:
|
|
136
|
+
|
|
137
|
+
<img src="./docs/select-ajax.png" style="width:400px" />
|
|
138
|
+
|
|
139
|
+
After execution, the function will insert blocks into the page:
|
|
140
|
+
|
|
141
|
+
<img src="./docs/inserted-blocks.png" style="width:400px" />
|
|
142
|
+
|
|
143
|
+
## How-to build your own
|
|
144
|
+
|
|
145
|
+
### 1. Create repo from template
|
|
146
|
+
|
|
147
|
+
If you haven't already, use the GitHub CLI to create a new repo from this template:
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
gh repo create notion-extensions -p makenotion/project-ajax-template --private
|
|
151
|
+
git clone git@github.com:[YOUR-USER]/notion-extensions my-extensions
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Alternatively, you can click "Create from template" on the repo's page [on GitHub](https://github.com/makenotion/project-ajax-template).
|
|
155
|
+
|
|
156
|
+
### 2. Install packages and connect the repo to Notion
|
|
157
|
+
|
|
158
|
+
Install packages:
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
cd my-extensions && npm i
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Then, connect the repo to Notion to publish the example extensions to your Notion workspace:
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
# Connect to Notion Prod
|
|
168
|
+
npx workers auth login
|
|
169
|
+
# Connect to Notion Dev
|
|
170
|
+
npx workers auth login --env=dev
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
This command will open a new page with a command that includes a token. Run that command locally.
|
|
174
|
+
|
|
175
|
+
### 3. Write your extension(s)
|
|
176
|
+
|
|
177
|
+
You can write three types of extensions: **slash commands**, **syncs**, and **tools**.
|
|
178
|
+
|
|
179
|
+
#### Writing a slash command function
|
|
180
|
+
|
|
181
|
+
[Slash commands](#slash-commands) add custom `/` commands to your Notion workspace.
|
|
182
|
+
|
|
183
|
+
Basic structure:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import { slashCommand } from "@project-ajax/sdk/slashCommand";
|
|
187
|
+
|
|
188
|
+
export const myCommand = slashCommand({
|
|
189
|
+
// Title in slash command menu
|
|
190
|
+
menuTitle: "My Command",
|
|
191
|
+
// Description in slash command menu
|
|
192
|
+
menuDescription: "Description shown in menu",
|
|
193
|
+
search: {
|
|
194
|
+
// Placeholder shown in search bar
|
|
195
|
+
placeholder: "Search...",
|
|
196
|
+
// Debounce when user types into the search bar, in milliseconds
|
|
197
|
+
debounce: 300,
|
|
198
|
+
},
|
|
199
|
+
// Called when the user enters a search query. Return results to show to user in search menu.
|
|
200
|
+
executeSearch: async (query: string) => {
|
|
201
|
+
// Return search results
|
|
202
|
+
return { items: [{ id: "1", title: "Result", description: "..." }] };
|
|
203
|
+
},
|
|
204
|
+
// Called when the user has selected a search item from the search menu.
|
|
205
|
+
executeSelect: async (id: string) => {
|
|
206
|
+
// Return Notion blocks to insert
|
|
207
|
+
return [
|
|
208
|
+
/* array of blocks */
|
|
209
|
+
];
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
#### Writing a sync function
|
|
215
|
+
|
|
216
|
+
[Sync functions](#sync) populate Notion collections with data from external sources.
|
|
217
|
+
|
|
218
|
+
Basic structure:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
import { sync } from "@project-ajax/sdk/sync";
|
|
222
|
+
import * as Schema from "@project-ajax/sdk/schema";
|
|
223
|
+
import * as Builder from "@project-ajax/sdk/builder";
|
|
224
|
+
|
|
225
|
+
export const mySync = sync({
|
|
226
|
+
// Which field to use in each object as the primary key. Must be unique.
|
|
227
|
+
primaryKeyProperty: "ID",
|
|
228
|
+
// The schema of the collection to create in Notion.
|
|
229
|
+
schema: {
|
|
230
|
+
// Name of the collection to create in Notion.
|
|
231
|
+
dataSourceTitle: "My Data",
|
|
232
|
+
properties: {
|
|
233
|
+
// See `Schema` for the full list of possible column types.
|
|
234
|
+
Title: Schema.title(),
|
|
235
|
+
ID: Schema.richText(),
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
execute: async () => {
|
|
239
|
+
// Fetch and return data
|
|
240
|
+
return [
|
|
241
|
+
// Each object must match the shape of `properties` above.
|
|
242
|
+
{
|
|
243
|
+
key: "1",
|
|
244
|
+
properties: {
|
|
245
|
+
Title: Builder.title("Item 1"),
|
|
246
|
+
ID: Builder.richText("1"),
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
];
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
#### Writing a tool for custom agents
|
|
255
|
+
|
|
256
|
+
[Tools](#tools) provide custom actions that can be invoked by custom agents in your Notion workspace.
|
|
257
|
+
|
|
258
|
+
Basic structure:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
import { tool } from "@project-ajax/sdk";
|
|
262
|
+
|
|
263
|
+
export const myTool = tool({
|
|
264
|
+
// Description of what this tool does - shown to the AI agent
|
|
265
|
+
description: "Search for items by keyword or ID",
|
|
266
|
+
// JSON Schema for the input the tool accepts
|
|
267
|
+
schema: {
|
|
268
|
+
type: "object",
|
|
269
|
+
properties: {
|
|
270
|
+
query: {
|
|
271
|
+
type: "string",
|
|
272
|
+
nullable: true,
|
|
273
|
+
description: "The search query"
|
|
274
|
+
},
|
|
275
|
+
limit: {
|
|
276
|
+
type: "number",
|
|
277
|
+
nullable: true,
|
|
278
|
+
description: "Maximum number of results"
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
required: [],
|
|
282
|
+
additionalProperties: false,
|
|
283
|
+
},
|
|
284
|
+
// Optional: JSON Schema for the output the tool returns
|
|
285
|
+
outputSchema: {
|
|
286
|
+
type: "object",
|
|
287
|
+
properties: {
|
|
288
|
+
results: {
|
|
289
|
+
type: "array",
|
|
290
|
+
items: { type: "string" },
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
required: ["results"],
|
|
294
|
+
additionalProperties: false,
|
|
295
|
+
},
|
|
296
|
+
// The function that executes when the tool is called
|
|
297
|
+
execute: async (input: { query?: string | null; limit?: number | null }) => {
|
|
298
|
+
// Destructure input with default values
|
|
299
|
+
const { query, limit = 10 } = input;
|
|
300
|
+
|
|
301
|
+
// Perform your logic here
|
|
302
|
+
const results = await searchItems(query, limit);
|
|
303
|
+
|
|
304
|
+
// Return data matching your outputSchema (if provided)
|
|
305
|
+
return { results };
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### 4. Deploy your function
|
|
311
|
+
|
|
312
|
+
Deploy your functions to your Notion workspace with [**deploy**](#npx-workers-deploy):
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
npx workers deploy
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
This will build your worker bundle, upload it to Notion, and make your functions available in your workspace.
|
|
319
|
+
|
|
320
|
+
### 5. Add secrets
|
|
321
|
+
|
|
322
|
+
Your function might require sensitive values, like API tokens, to run. You can add [**secrets**](#npx-workers-secrets) to your function's run context. Secrets are exposed to your function as environment variables.
|
|
323
|
+
|
|
324
|
+
Run the following to add secrets:
|
|
325
|
+
|
|
326
|
+
```
|
|
327
|
+
npx workers secrets set MY_SECRET_1=my-secret-value MY_SECRET_2=my-secret-value2
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Then, you can reference these secret values in your function code using `process.env`, e.g. `process.env.MY_SECRET_1`.
|
|
331
|
+
|
|
332
|
+
### 6. Run your function
|
|
333
|
+
|
|
334
|
+
For slash commands, you can test your function in the Notion workspace you deployed to.
|
|
335
|
+
|
|
336
|
+
For sync commands, your function is run automatically in the background. You can also run it via the CLI with [`exec`](#npx-workers-exec):
|
|
337
|
+
|
|
338
|
+
```
|
|
339
|
+
npx workers exec nameOfSyncCapability
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
When the command has finished executing, you should see a collection in your Notion sidebar that contains the synced data.
|
|
343
|
+
|
|
344
|
+
You can also test slash commands using [`exec`](#npx-workers-exec). Specify which function inside the slash command you want to run (i.e. `executeSearch` or `executeSelect`). Pass in arguments using `arg=value` syntax:
|
|
345
|
+
|
|
346
|
+
```
|
|
347
|
+
npx workers exec mySlashCommand executeSearch query="myquery"
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
## Extensions reference
|
|
351
|
+
|
|
352
|
+
### Slash commands
|
|
353
|
+
|
|
354
|
+
You can write an extension that adds a new **slash command** to a Notion workspace. The slash command currently supported is a "search-then-execute" flow.
|
|
355
|
+
|
|
356
|
+
For example, you can write a slash command to insert a Linear ticket:
|
|
357
|
+
|
|
358
|
+
1. First, after selecting your slash command, the user enters a search query for a particular Linear ticket:
|
|
359
|
+
|
|
360
|
+
<img src="./docs/select-ajax.png" style="width:400px" />
|
|
361
|
+
|
|
362
|
+
2. After they select a ticket from the list, your extension will insert blocks into the page.
|
|
363
|
+
|
|
364
|
+
The search query is powered by `executeSearch` and the select action triggers `executeSelect`.
|
|
365
|
+
|
|
366
|
+
#### Properties
|
|
367
|
+
|
|
368
|
+
- **`menuTitle`** (string, required): The title that appears in the slash command menu when users type `/`.
|
|
369
|
+
- **`menuDescription`** (string, required): A description of what the slash command does, shown in the menu.
|
|
370
|
+
- **`search`** (object, required): Configuration for the search interface.
|
|
371
|
+
- **`placeholder`** (string, required): Placeholder text shown in the search field.
|
|
372
|
+
- **`debounce`** (number, required): Milliseconds to wait before executing search after user stops typing.
|
|
373
|
+
- **`executeSearch`** (function, required): Async function that handles search queries.
|
|
374
|
+
- **Parameters**: `query` (string) - The user's search input.
|
|
375
|
+
- **Returns**: Promise resolving to an object with `items` array, where each item has:
|
|
376
|
+
- `id` (string): Unique identifier for the item.
|
|
377
|
+
- `title` (string): Title displayed in search results.
|
|
378
|
+
- `description` (string, optional): Additional description text.
|
|
379
|
+
- **`executeSelect`** (function, required): Async function that handles when a user selects an item.
|
|
380
|
+
- **Parameters**: `id` (string) - The id of the selected item.
|
|
381
|
+
- **Returns**: Promise resolving to an array of Notion blocks to insert into the page.
|
|
382
|
+
|
|
383
|
+
### Sync
|
|
384
|
+
|
|
385
|
+
The **sync** extension allows you to sync any data to a Notion collection. Every time a sync is run, data is upserted or deleted from the target Notion collection so that the collection matches the source.
|
|
386
|
+
|
|
387
|
+
At the moment, syncs do not run automatically. Use **`exec`** to run a sync.
|
|
388
|
+
|
|
389
|
+
#### Properties
|
|
390
|
+
|
|
391
|
+
- **`primaryKeyProperty`** (string, required): The name of the property that serves as the unique identifier for each entry in the collection (e.g. `"id"`). This property must also be defined in the schema.
|
|
392
|
+
- **`schema`** (object, required): Defines the structure of the Notion collection.
|
|
393
|
+
- **`dataSourceTitle`** (string, required): The name of the collection that will be created in Notion. Collections are instantiated in the user's sidebar.
|
|
394
|
+
- **`properties`** (object, required): An object mapping property names to their schema definitions. Use functions from `@project-ajax/sdk/schema` to define property types (e.g., `Schema.title()`, `Schema.richText()`).
|
|
395
|
+
- **`execute`** (function, required): Async function that fetches and returns data to sync.
|
|
396
|
+
- **Parameters**: None (but can access `process.env` for secrets).
|
|
397
|
+
- **Returns**: Promise resolving to an array of entries, where each entry has:
|
|
398
|
+
- `key` (string): Unique identifier for the entry (maps to the `primaryKeyProperty`).
|
|
399
|
+
- `properties` (object): An object mapping property names to their values. Use functions from `@project-ajax/sdk/builder` to construct property values (e.g., `Builder.title()`, `Builder.richText()`).
|
|
400
|
+
|
|
401
|
+
### Tools
|
|
402
|
+
|
|
403
|
+
The **tool** extension allows custom agents in your Notion workspace to perform actions. When you connect a worker to a custom agent, the agent can invoke your tools based on their descriptions and schemas.
|
|
404
|
+
|
|
405
|
+
Tools use JSON Schema to define their input and output structures, and the SDK validates data automatically.
|
|
406
|
+
|
|
407
|
+
#### Properties
|
|
408
|
+
|
|
409
|
+
- **`description`** (string, required): A clear description of what the tool does. This is shown to the AI agent to help it decide when to use the tool.
|
|
410
|
+
- **`schema`** (JSONSchemaType, required): JSON Schema definition for the tool's input parameters. The input will be validated against this schema before execution.
|
|
411
|
+
- **`outputSchema`** (JSONSchemaType, optional): JSON Schema definition for the tool's output. If provided, the output will be validated against this schema after execution.
|
|
412
|
+
- **`execute`** (function, required): Async function that performs the tool's action.
|
|
413
|
+
- **Parameters**: `input` (typed according to your schema) - The validated input parameters.
|
|
414
|
+
- **Returns**: Promise resolving to output data (validated against `outputSchema` if provided).
|
|
415
|
+
|
|
416
|
+
#### Error handling
|
|
417
|
+
|
|
418
|
+
The tool system automatically handles three types of errors:
|
|
419
|
+
|
|
420
|
+
- **`InvalidToolInputError`**: Thrown when input doesn't match the input schema.
|
|
421
|
+
- **`InvalidToolOutputError`**: Thrown when output doesn't match the output schema (if provided).
|
|
422
|
+
- **`ToolExecutionError`**: Thrown when the execute function throws an error.
|
|
423
|
+
|
|
424
|
+
All errors are automatically caught and returned in a structured format, so you don't need to handle them in your `execute` function.
|
|
425
|
+
|
|
426
|
+
#### JSON Schema types
|
|
427
|
+
|
|
428
|
+
Your schemas should use standard JSON Schema format. Common patterns:
|
|
429
|
+
|
|
430
|
+
**Object with required and optional properties:**
|
|
431
|
+
```typescript
|
|
432
|
+
{
|
|
433
|
+
type: "object",
|
|
434
|
+
properties: {
|
|
435
|
+
name: { type: "string" },
|
|
436
|
+
age: { type: "number", nullable: true },
|
|
437
|
+
active: { type: "boolean", nullable: true },
|
|
438
|
+
},
|
|
439
|
+
// Only 'name' is required; age and active can be omitted
|
|
440
|
+
required: ["name"],
|
|
441
|
+
additionalProperties: false,
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
**Arrays:**
|
|
446
|
+
```typescript
|
|
447
|
+
{
|
|
448
|
+
type: "array",
|
|
449
|
+
items: { type: "string" },
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
**Enums:**
|
|
454
|
+
```typescript
|
|
455
|
+
{
|
|
456
|
+
type: "string",
|
|
457
|
+
enum: ["option1", "option2", "option3"],
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
#### Testing tools
|
|
462
|
+
|
|
463
|
+
You can test tools using [`exec`](#npx-workers-exec):
|
|
464
|
+
|
|
465
|
+
```bash
|
|
466
|
+
# Execute a tool without arguments
|
|
467
|
+
npx workers exec myTool
|
|
468
|
+
|
|
469
|
+
# Note: Currently, tools cannot accept arguments via CLI due to parser limitations.
|
|
470
|
+
# To test tools with specific inputs, use them with a custom agent in Notion.
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
## CLI reference
|
|
474
|
+
|
|
475
|
+
The `workers` CLI provides commands to authenticate, deploy, execute, and manage your Notion functions. All commands support global flags for configuration and debugging.
|
|
476
|
+
|
|
477
|
+
### Global flags
|
|
478
|
+
|
|
479
|
+
These flags are available on all commands:
|
|
480
|
+
|
|
481
|
+
- **`--config <path>`**: Path to the config file to use (defaults to `workers.json`)
|
|
482
|
+
- **`--debug`**: Enable debug logging
|
|
483
|
+
|
|
484
|
+
### `npx workers auth`
|
|
485
|
+
|
|
486
|
+
Commands for managing authentication with the Project Ajax platform.
|
|
487
|
+
|
|
488
|
+
#### `npx workers auth login [token] [--env=<env>]`
|
|
489
|
+
|
|
490
|
+
Login to the Project Ajax platform using a Workers API token.
|
|
491
|
+
|
|
492
|
+
**Arguments:**
|
|
493
|
+
|
|
494
|
+
- `token` (optional): A Workers API token. If not provided, opens a browser to create one.
|
|
495
|
+
|
|
496
|
+
**Flags:**
|
|
497
|
+
|
|
498
|
+
- `--env` (optional): The environment to use (`local`, `staging`, `dev`, or `prod`). Defaults to `prod`.
|
|
499
|
+
|
|
500
|
+
**Examples:**
|
|
501
|
+
|
|
502
|
+
```bash
|
|
503
|
+
# Open browser to create a token
|
|
504
|
+
npx workers auth login
|
|
505
|
+
|
|
506
|
+
# Login with an existing token
|
|
507
|
+
npx workers auth login v1.user.wAFygwEZ-...
|
|
508
|
+
|
|
509
|
+
# Login to dev environment
|
|
510
|
+
npx workers auth login --env=dev
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
#### `npx workers auth show`
|
|
514
|
+
|
|
515
|
+
Display the currently configured authentication token.
|
|
516
|
+
|
|
517
|
+
**Example:**
|
|
518
|
+
|
|
519
|
+
```bash
|
|
520
|
+
npx workers auth show
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
#### `npx workers auth logout`
|
|
524
|
+
|
|
525
|
+
Remove the currently configured authentication token.
|
|
526
|
+
|
|
527
|
+
**Example:**
|
|
528
|
+
|
|
529
|
+
```bash
|
|
530
|
+
npx workers auth logout
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### `npx workers deploy`
|
|
534
|
+
|
|
535
|
+
Deploy your worker to the Project Ajax platform. This command builds your worker bundle, uploads it to Notion, and saves the worker configuration.
|
|
536
|
+
|
|
537
|
+
**Example:**
|
|
538
|
+
|
|
539
|
+
```bash
|
|
540
|
+
npx workers deploy
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
The deploy command will:
|
|
544
|
+
|
|
545
|
+
1. Build your worker bundle from the current directory
|
|
546
|
+
2. Upload the bundle to Notion
|
|
547
|
+
3. Fetch and save worker capabilities
|
|
548
|
+
4. Store the worker ID in your config file
|
|
549
|
+
|
|
550
|
+
### `npx workers exec`
|
|
551
|
+
|
|
552
|
+
Execute a worker capability with optional function and arguments.
|
|
553
|
+
|
|
554
|
+
**Usage:**
|
|
555
|
+
|
|
556
|
+
```bash
|
|
557
|
+
npx workers exec <capabilityName> [functionName] [arg1=value1 arg2=value2...]
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
**Arguments:**
|
|
561
|
+
|
|
562
|
+
- `capabilityName` (required): Name of the capability to execute
|
|
563
|
+
- `functionName` (optional): For slash commands, specify which function to run (`executeSearch` or `executeSelect`)
|
|
564
|
+
- `arguments` (optional): Key-value pairs as arguments. Supports both `key=value` and `key value` formats.
|
|
565
|
+
|
|
566
|
+
**Flags:**
|
|
567
|
+
|
|
568
|
+
- `--output <mode>`: Output mode for results. Options:
|
|
569
|
+
- `none`: No output
|
|
570
|
+
- `full`: Complete output
|
|
571
|
+
- `pager`: Paginate long output (default)
|
|
572
|
+
|
|
573
|
+
**Examples:**
|
|
574
|
+
|
|
575
|
+
```bash
|
|
576
|
+
# Execute a sync capability
|
|
577
|
+
npx workers exec tasksSync
|
|
578
|
+
|
|
579
|
+
# Execute a slash command's search function
|
|
580
|
+
npx workers exec taskSearch executeSearch query="ajax"
|
|
581
|
+
|
|
582
|
+
# Execute with specific output mode
|
|
583
|
+
npx workers exec taskSearch executeSearch query="test" --output=full
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
### `npx workers capabilities`
|
|
587
|
+
|
|
588
|
+
Commands for managing worker capabilities.
|
|
589
|
+
|
|
590
|
+
#### `npx workers capabilities list`
|
|
591
|
+
|
|
592
|
+
List all capabilities for the deployed worker.
|
|
593
|
+
|
|
594
|
+
**Example:**
|
|
595
|
+
|
|
596
|
+
```bash
|
|
597
|
+
npx workers capabilities list
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
This will display all capabilities with their names and types (e.g., `sync`, `slashCommand`).
|
|
601
|
+
|
|
602
|
+
### `npx workers secrets`
|
|
603
|
+
|
|
604
|
+
Commands for managing worker secrets. Secrets are exposed to your functions as environment variables via `process.env`.
|
|
605
|
+
|
|
606
|
+
#### `npx workers secrets set <key> <value> [<key2> <value2>...]`
|
|
607
|
+
|
|
608
|
+
Set one or more secrets for your worker. Supports multiple formats:
|
|
609
|
+
|
|
610
|
+
**Formats:**
|
|
611
|
+
|
|
612
|
+
- Space-separated: `key value`
|
|
613
|
+
- Equals-separated: `key=value`
|
|
614
|
+
- Colon-separated: `key:value`
|
|
615
|
+
|
|
616
|
+
**Examples:**
|
|
617
|
+
|
|
618
|
+
```bash
|
|
619
|
+
# Set a single secret
|
|
620
|
+
npx workers secrets set API_KEY my-secret-value
|
|
621
|
+
|
|
622
|
+
# Set multiple secrets (space-separated)
|
|
623
|
+
npx workers secrets set API_KEY my-key API_SECRET my-secret
|
|
624
|
+
|
|
625
|
+
# Set multiple secrets (equals format)
|
|
626
|
+
npx workers secrets set API_KEY=my-key API_SECRET=my-secret
|
|
627
|
+
|
|
628
|
+
# Mix formats
|
|
629
|
+
npx workers secrets set API_KEY=my-key DATABASE_URL my-db-url
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
#### `npx workers secrets list`
|
|
633
|
+
|
|
634
|
+
List all secret keys for your worker. Note: Secret values are not displayed, only keys and creation timestamps.
|
|
635
|
+
|
|
636
|
+
**Example:**
|
|
637
|
+
|
|
638
|
+
```bash
|
|
639
|
+
npx workers secrets list
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
#### `npx workers secrets rm <key>`
|
|
643
|
+
|
|
644
|
+
Remove a secret from your worker.
|
|
645
|
+
|
|
646
|
+
**Arguments:**
|
|
647
|
+
|
|
648
|
+
- `key` (required): The secret key name to remove
|
|
649
|
+
|
|
650
|
+
**Example:**
|
|
651
|
+
|
|
652
|
+
```bash
|
|
653
|
+
npx workers secrets rm API_KEY
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
### `npx workers connect`
|
|
657
|
+
|
|
658
|
+
Commands for managing OAuth connections that store provider tokens as worker secrets. Connections appear in your runtime as environment variables (e.g., `process.env.NOTION_OAUTH_GOOGLE`).
|
|
659
|
+
|
|
660
|
+
#### `npx workers connect providers`
|
|
661
|
+
|
|
662
|
+
List the OAuth providers that are currently available for your workspace.
|
|
663
|
+
|
|
664
|
+
```bash
|
|
665
|
+
npx workers connect providers
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
#### `npx workers connect add <provider>`
|
|
669
|
+
|
|
670
|
+
Start the OAuth flow in your browser for the specified provider. The command opens your default browser and also prints a fallback link.
|
|
671
|
+
|
|
672
|
+
```bash
|
|
673
|
+
npx workers connect add google
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
#### `npx workers connect list`
|
|
677
|
+
|
|
678
|
+
Display all active OAuth connections for the deployed worker. The output includes the provider name, the environment variable exposed to your code, and when the connection was created.
|
|
679
|
+
|
|
680
|
+
```bash
|
|
681
|
+
npx workers connect list
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
#### `npx workers connect rm <provider>`
|
|
685
|
+
|
|
686
|
+
Remove an OAuth connection (and the stored tokens) for the specified provider.
|
|
687
|
+
|
|
688
|
+
```bash
|
|
689
|
+
npx workers connect rm google
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
### Configuration file
|
|
693
|
+
|
|
694
|
+
The CLI stores configuration in a `workers.json` file in your project directory. This file is automatically created and updated by the CLI commands. It contains:
|
|
695
|
+
|
|
696
|
+
- **`environment`**: The current environment (`local`, `staging`, `dev`, or `prod`)
|
|
697
|
+
- **`token`**: Your authentication token
|
|
698
|
+
- **`workerId`**: The ID of your deployed worker
|
|
699
|
+
|
|
700
|
+
You can specify a different config file using the `--config` flag on any command.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@project-ajax/template",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"private": true,
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc",
|
|
8
|
+
"check": "tsc --noEmit",
|
|
9
|
+
"dev": "tsx --watch src/index.ts",
|
|
10
|
+
"start": "node dist/index.js",
|
|
11
|
+
"bump-sdk": "npm install @project-ajax/sdk@latest"
|
|
12
|
+
},
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=22.0.0",
|
|
15
|
+
"npm": ">=10.9.4"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^22.9.0",
|
|
19
|
+
"tsx": "^4.20.6",
|
|
20
|
+
"typescript": "^5.8.0"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@project-ajax/cli": ">=0.0.0",
|
|
24
|
+
"@project-ajax/sdk": ">=0.0.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { tool } from "@project-ajax/sdk";
|
|
2
|
+
import type {
|
|
3
|
+
Block,
|
|
4
|
+
CalloutBlock,
|
|
5
|
+
ParagraphBlock,
|
|
6
|
+
} from "@project-ajax/sdk/block";
|
|
7
|
+
import * as Builder from "@project-ajax/sdk/builder";
|
|
8
|
+
import * as Schema from "@project-ajax/sdk/schema";
|
|
9
|
+
import { slashCommand } from "@project-ajax/sdk/slashCommand";
|
|
10
|
+
import { sync } from "@project-ajax/sdk/sync";
|
|
11
|
+
|
|
12
|
+
// Sample data for demonstration
|
|
13
|
+
const sampleTasks = [
|
|
14
|
+
{
|
|
15
|
+
id: "task-1",
|
|
16
|
+
title: "Welcome to Project Ajax",
|
|
17
|
+
status: "Completed",
|
|
18
|
+
description: "This is a simple hello world example",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: "task-2",
|
|
22
|
+
title: "Build your first worker",
|
|
23
|
+
status: "In Progress",
|
|
24
|
+
description: "Create a sync or slash command worker",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "task-3",
|
|
28
|
+
title: "Deploy to production",
|
|
29
|
+
status: "Todo",
|
|
30
|
+
description: "Share your worker with your team",
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
// Example sync worker that syncs sample tasks to a database
|
|
35
|
+
export const tasksSync = sync({
|
|
36
|
+
primaryKeyProperty: "Task ID",
|
|
37
|
+
|
|
38
|
+
// Optional: Set to true to delete pages that are not returned from sync executions.
|
|
39
|
+
// By default (false), sync only creates and updates pages, never deletes them.
|
|
40
|
+
// deleteUnreturnedPages: true,
|
|
41
|
+
|
|
42
|
+
schema: {
|
|
43
|
+
dataSourceTitle: "Sample Tasks",
|
|
44
|
+
databaseIcon: Builder.notionIcon("checklist"),
|
|
45
|
+
properties: {
|
|
46
|
+
"Ticket Title": Schema.title(),
|
|
47
|
+
"Task ID": Schema.richText(),
|
|
48
|
+
Description: Schema.richText(),
|
|
49
|
+
Status: Schema.select([
|
|
50
|
+
{ name: "Completed", color: "green" },
|
|
51
|
+
{ name: "In Progress", color: "blue" },
|
|
52
|
+
{ name: "Todo", color: "default" },
|
|
53
|
+
]),
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
execute: async () => {
|
|
58
|
+
const emojiForStatus = (status: string) => {
|
|
59
|
+
switch (status) {
|
|
60
|
+
case "Completed":
|
|
61
|
+
return Builder.notionIcon("checkmark", "green");
|
|
62
|
+
case "In Progress":
|
|
63
|
+
return Builder.notionIcon("arrow-right", "blue");
|
|
64
|
+
case "Todo":
|
|
65
|
+
return Builder.notionIcon("clock", "lightgray");
|
|
66
|
+
default:
|
|
67
|
+
return Builder.notionIcon("question-mark", "lightgray");
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
// Return sample tasks as database entries
|
|
71
|
+
const objects = sampleTasks.map((task) => ({
|
|
72
|
+
key: task.id,
|
|
73
|
+
icon: emojiForStatus(task.status),
|
|
74
|
+
properties: {
|
|
75
|
+
"Ticket Title": Builder.title(task.title),
|
|
76
|
+
"Task ID": Builder.richText(task.id),
|
|
77
|
+
Description: Builder.richText(task.description),
|
|
78
|
+
Status: Builder.select(task.status),
|
|
79
|
+
},
|
|
80
|
+
pageContentMarkdown: `## ${task.title}\n\n${task.description}`,
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
objects,
|
|
85
|
+
done: true,
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Example slash command that searches and inserts sample tasks
|
|
91
|
+
export const taskSearchSlashCommand = slashCommand({
|
|
92
|
+
menuTitle: "Insert Sample Task",
|
|
93
|
+
menuDescription: "Search for a sample task to insert",
|
|
94
|
+
search: {
|
|
95
|
+
placeholder: "Search for tasks...",
|
|
96
|
+
debounce: 300,
|
|
97
|
+
},
|
|
98
|
+
executeSearch: async (query: string) => {
|
|
99
|
+
// Filter tasks based on the search query
|
|
100
|
+
const filtered = sampleTasks.filter(
|
|
101
|
+
(task) =>
|
|
102
|
+
task.title.toLowerCase().includes(query.toLowerCase()) ||
|
|
103
|
+
task.description.toLowerCase().includes(query.toLowerCase()),
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// Transform tasks into menu items
|
|
107
|
+
const items = filtered.map((task) => ({
|
|
108
|
+
id: task.id,
|
|
109
|
+
title: task.title,
|
|
110
|
+
description: `${task.status} • ${task.description}`,
|
|
111
|
+
}));
|
|
112
|
+
|
|
113
|
+
return { items };
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
executeSelect: async (taskId: string) => {
|
|
117
|
+
// Find the selected task
|
|
118
|
+
const task = sampleTasks.find((t) => t.id === taskId);
|
|
119
|
+
|
|
120
|
+
if (!task) {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Create blocks to insert into the page
|
|
125
|
+
const calloutBlock: CalloutBlock = {
|
|
126
|
+
object: "block",
|
|
127
|
+
type: "callout",
|
|
128
|
+
callout: {
|
|
129
|
+
icon: {
|
|
130
|
+
type: "emoji",
|
|
131
|
+
emoji: "✨",
|
|
132
|
+
},
|
|
133
|
+
rich_text: [
|
|
134
|
+
{
|
|
135
|
+
type: "text",
|
|
136
|
+
text: {
|
|
137
|
+
content: `Task: ${task.id}`,
|
|
138
|
+
link: null,
|
|
139
|
+
},
|
|
140
|
+
plain_text: `Task: ${task.id}`,
|
|
141
|
+
href: null,
|
|
142
|
+
annotations: {
|
|
143
|
+
bold: true,
|
|
144
|
+
italic: false,
|
|
145
|
+
strikethrough: false,
|
|
146
|
+
underline: false,
|
|
147
|
+
code: false,
|
|
148
|
+
color: "default",
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
color: "blue_background",
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const titleBlock: ParagraphBlock = {
|
|
157
|
+
object: "block",
|
|
158
|
+
type: "paragraph",
|
|
159
|
+
paragraph: {
|
|
160
|
+
rich_text: [
|
|
161
|
+
{
|
|
162
|
+
type: "text",
|
|
163
|
+
text: {
|
|
164
|
+
content: task.title,
|
|
165
|
+
link: null,
|
|
166
|
+
},
|
|
167
|
+
plain_text: task.title,
|
|
168
|
+
href: null,
|
|
169
|
+
annotations: {
|
|
170
|
+
bold: true,
|
|
171
|
+
italic: false,
|
|
172
|
+
strikethrough: false,
|
|
173
|
+
underline: false,
|
|
174
|
+
code: false,
|
|
175
|
+
color: "default",
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const statusBlock: ParagraphBlock = {
|
|
183
|
+
object: "block",
|
|
184
|
+
type: "paragraph",
|
|
185
|
+
paragraph: {
|
|
186
|
+
rich_text: [
|
|
187
|
+
{
|
|
188
|
+
type: "text",
|
|
189
|
+
text: {
|
|
190
|
+
content: "Status: ",
|
|
191
|
+
link: null,
|
|
192
|
+
},
|
|
193
|
+
plain_text: "Status: ",
|
|
194
|
+
href: null,
|
|
195
|
+
annotations: {
|
|
196
|
+
bold: true,
|
|
197
|
+
italic: false,
|
|
198
|
+
strikethrough: false,
|
|
199
|
+
underline: false,
|
|
200
|
+
code: false,
|
|
201
|
+
color: "default",
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
type: "text",
|
|
206
|
+
text: {
|
|
207
|
+
content: task.status,
|
|
208
|
+
link: null,
|
|
209
|
+
},
|
|
210
|
+
plain_text: task.status,
|
|
211
|
+
href: null,
|
|
212
|
+
annotations: {
|
|
213
|
+
bold: false,
|
|
214
|
+
italic: false,
|
|
215
|
+
strikethrough: false,
|
|
216
|
+
underline: false,
|
|
217
|
+
code: false,
|
|
218
|
+
color:
|
|
219
|
+
task.status === "Completed"
|
|
220
|
+
? "green"
|
|
221
|
+
: task.status === "In Progress"
|
|
222
|
+
? "blue"
|
|
223
|
+
: "default",
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const descriptionBlock: ParagraphBlock = {
|
|
231
|
+
object: "block",
|
|
232
|
+
type: "paragraph",
|
|
233
|
+
paragraph: {
|
|
234
|
+
rich_text: [
|
|
235
|
+
{
|
|
236
|
+
type: "text",
|
|
237
|
+
text: {
|
|
238
|
+
content: task.description,
|
|
239
|
+
link: null,
|
|
240
|
+
},
|
|
241
|
+
plain_text: task.description,
|
|
242
|
+
href: null,
|
|
243
|
+
annotations: {
|
|
244
|
+
bold: false,
|
|
245
|
+
italic: true,
|
|
246
|
+
strikethrough: false,
|
|
247
|
+
underline: false,
|
|
248
|
+
code: false,
|
|
249
|
+
color: "gray",
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const blocks: Block[] = [
|
|
257
|
+
calloutBlock,
|
|
258
|
+
titleBlock,
|
|
259
|
+
statusBlock,
|
|
260
|
+
descriptionBlock,
|
|
261
|
+
];
|
|
262
|
+
|
|
263
|
+
return blocks;
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Example agent tool for retrieving task information
|
|
268
|
+
export const taskSearchTool = tool({
|
|
269
|
+
title: "Task Search",
|
|
270
|
+
description:
|
|
271
|
+
"Look up sample tasks by ID or keyword. Helpful for demonstrating agent tool calls.",
|
|
272
|
+
schema: {
|
|
273
|
+
type: "object",
|
|
274
|
+
properties: {
|
|
275
|
+
taskId: {
|
|
276
|
+
type: "string",
|
|
277
|
+
nullable: true,
|
|
278
|
+
description: "Return a single task that matches the given task ID.",
|
|
279
|
+
},
|
|
280
|
+
query: {
|
|
281
|
+
type: "string",
|
|
282
|
+
nullable: true,
|
|
283
|
+
description:
|
|
284
|
+
"Match search terms against words in the task title or description.",
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
required: [],
|
|
288
|
+
additionalProperties: false,
|
|
289
|
+
},
|
|
290
|
+
execute: async (input: { taskId?: string | null; query?: string | null }) => {
|
|
291
|
+
const { taskId, query } = input;
|
|
292
|
+
|
|
293
|
+
let matchingTasks = sampleTasks;
|
|
294
|
+
|
|
295
|
+
if (taskId) {
|
|
296
|
+
matchingTasks = sampleTasks.filter((task) => task.id === taskId);
|
|
297
|
+
} else if (query) {
|
|
298
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
299
|
+
|
|
300
|
+
const terms = normalizedQuery.split(/\s+/).filter(Boolean);
|
|
301
|
+
|
|
302
|
+
if (terms.length > 0) {
|
|
303
|
+
const scoredTasks = sampleTasks
|
|
304
|
+
.map((task) => {
|
|
305
|
+
const title = task.title.toLowerCase();
|
|
306
|
+
const description = task.description.toLowerCase();
|
|
307
|
+
const matches = terms.reduce((count, term) => {
|
|
308
|
+
return title.includes(term) || description.includes(term)
|
|
309
|
+
? count + 1
|
|
310
|
+
: count;
|
|
311
|
+
}, 0);
|
|
312
|
+
|
|
313
|
+
return { task, matches };
|
|
314
|
+
})
|
|
315
|
+
.filter(({ matches }) => matches > 0)
|
|
316
|
+
.sort((a, b) => b.matches - a.matches);
|
|
317
|
+
|
|
318
|
+
matchingTasks = scoredTasks.map(({ task }) => task);
|
|
319
|
+
} else {
|
|
320
|
+
matchingTasks = [];
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
count: matchingTasks.length,
|
|
326
|
+
tasks: matchingTasks.map((task) => ({
|
|
327
|
+
id: task.id,
|
|
328
|
+
title: task.title,
|
|
329
|
+
status: task.status,
|
|
330
|
+
description: task.description,
|
|
331
|
+
})),
|
|
332
|
+
};
|
|
333
|
+
},
|
|
334
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "nodenext",
|
|
5
|
+
"outDir": "./dist",
|
|
6
|
+
"rootDir": "./src",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"moduleResolution": "nodenext"
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*"],
|
|
15
|
+
"exclude": ["node_modules", "dist"]
|
|
16
|
+
}
|