@customclaw/composio 0.0.1 → 0.0.3

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 CHANGED
@@ -1,17 +1,6 @@
1
- # Composio Tool Router Plugin for OpenClaw
1
+ # @customclaw/composio
2
2
 
3
- Access 1000+ third-party tools through Composio's unified Tool Router interface.
4
-
5
- ## Features
6
-
7
- - **Search Tools**: Find tools by describing what you want to accomplish
8
- - **Execute Tools**: Run any tool with authenticated connections
9
- - **Multi-Execute**: Run up to 50 tools in parallel
10
- - **Connection Management**: Connect to toolkits via OAuth or API keys
11
-
12
- ## Supported Integrations
13
-
14
- Gmail, Slack, GitHub, Notion, Linear, Jira, HubSpot, Salesforce, Google Drive, Asana, Trello, and 1000+ more.
3
+ OpenClaw plugin that connects your agent to Gmail, Sentry, and other services through [Composio](https://composio.dev)'s Tool Router.
15
4
 
16
5
  ## Install
17
6
 
@@ -19,17 +8,11 @@ Gmail, Slack, GitHub, Notion, Linear, Jira, HubSpot, Salesforce, Google Drive, A
19
8
  openclaw plugins install @customclaw/composio
20
9
  ```
21
10
 
22
- ## Configuration
23
-
24
- ### Option 1: Environment Variable
25
-
26
- ```bash
27
- export COMPOSIO_API_KEY=your-api-key
28
- ```
11
+ ## Setup
29
12
 
30
- ### Option 2: OpenClaw Config
13
+ 1. Get an API key from [platform.composio.dev/settings](https://platform.composio.dev/settings)
31
14
 
32
- Add to `~/.openclaw/openclaw.json`:
15
+ 2. Add to `~/.openclaw/openclaw.json`:
33
16
 
34
17
  ```json
35
18
  {
@@ -39,7 +22,6 @@ Add to `~/.openclaw/openclaw.json`:
39
22
  "enabled": true,
40
23
  "config": {
41
24
  "apiKey": "your-api-key",
42
- "defaultUserId": "client-companyname-uuid",
43
25
  "allowedToolkits": ["gmail", "sentry"]
44
26
  }
45
27
  }
@@ -48,112 +30,36 @@ Add to `~/.openclaw/openclaw.json`:
48
30
  }
49
31
  ```
50
32
 
51
- Get your API key from [platform.composio.dev/settings](https://platform.composio.dev/settings).
33
+ Or set `COMPOSIO_API_KEY` as an environment variable.
52
34
 
53
- ## CLI Commands
35
+ 3. Restart the gateway.
54
36
 
55
- ```bash
56
- # List available toolkits
57
- openclaw composio list
58
-
59
- # Check connection status
60
- openclaw composio status
61
- openclaw composio status github
62
-
63
- # Connect to a toolkit (opens auth URL)
64
- openclaw composio connect github
65
- openclaw composio connect gmail
66
-
67
- # Disconnect from a toolkit
68
- openclaw composio disconnect github
37
+ ## What it does
69
38
 
70
- # Search for tools
71
- openclaw composio search "send email"
72
- openclaw composio search "create issue" --toolkit github
73
- ```
74
-
75
- ## Agent Tools
39
+ The plugin gives your agent two tools:
76
40
 
77
- The plugin provides six tools for agents:
41
+ - `composio_execute_tool` runs a Composio action (e.g. `GMAIL_FETCH_EMAILS`, `SENTRY_LIST_ISSUES`)
42
+ - `composio_manage_connections` — checks connection status and generates OAuth links when a toolkit isn't connected yet
78
43
 
79
- ### `composio_search_tools`
44
+ The agent handles the rest. Ask it to "check my latest emails" and it will call the right tool, prompt you to connect Gmail if needed, and fetch the results.
80
45
 
81
- Search for tools matching a task description.
46
+ ## CLI
82
47
 
83
- ```json
84
- {
85
- "query": "send an email with attachment",
86
- "toolkits": ["gmail"],
87
- "limit": 5
88
- }
89
- ```
90
-
91
- ### `composio_execute_tool`
92
-
93
- Execute a single tool.
94
-
95
- ```json
96
- {
97
- "tool_slug": "GMAIL_SEND_EMAIL",
98
- "arguments": {
99
- "to": "user@example.com",
100
- "subject": "Hello",
101
- "body": "Message content"
102
- }
103
- }
104
- ```
105
-
106
- ### `composio_multi_execute`
107
-
108
- Execute multiple tools in parallel (up to 50).
109
-
110
- ```json
111
- {
112
- "executions": [
113
- { "tool_slug": "GITHUB_CREATE_ISSUE", "arguments": { "title": "Bug", "repo": "org/repo" } },
114
- { "tool_slug": "SLACK_SEND_MESSAGE", "arguments": { "channel": "#dev", "text": "Issue created" } }
115
- ]
116
- }
117
- ```
118
-
119
- ### `composio_manage_connections`
120
-
121
- Manage toolkit connections.
122
-
123
- ```json
124
- {
125
- "action": "status",
126
- "toolkits": ["github", "gmail"]
127
- }
48
+ ```bash
49
+ openclaw composio list # list available toolkits
50
+ openclaw composio status # check what's connected
51
+ openclaw composio connect gmail # open OAuth link
52
+ openclaw composio disconnect gmail # remove a connection
53
+ openclaw composio search "send email" # find tool slugs
128
54
  ```
129
55
 
130
- ### `composio_workbench`
131
-
132
- Execute Python code in a remote Jupyter sandbox.
133
-
134
- ### `composio_bash`
135
-
136
- Execute bash commands in a remote sandbox.
56
+ ## Config options
137
57
 
138
- ## Advanced Configuration
139
-
140
- ```json
141
- {
142
- "plugins": {
143
- "entries": {
144
- "composio": {
145
- "enabled": true,
146
- "config": {
147
- "apiKey": "your-api-key",
148
- "defaultUserId": "user_123",
149
- "allowedToolkits": ["github", "gmail", "slack"],
150
- "blockedToolkits": ["dangerous-toolkit"]
151
- }
152
- }
153
- }
154
- }
155
- }
156
- ```
58
+ | Key | Description |
59
+ |-----|-------------|
60
+ | `apiKey` | Composio API key (required) |
61
+ | `allowedToolkits` | Only allow these toolkits (e.g. `["gmail", "sentry"]`) |
62
+ | `blockedToolkits` | Block specific toolkits |
157
63
 
158
64
  ## Updating
159
65
 
@@ -162,23 +68,16 @@ openclaw plugins update @customclaw/composio
162
68
  openclaw gateway restart
163
69
  ```
164
70
 
165
- Gateway restart is required after updates.
166
-
167
71
  ## Development
168
72
 
169
73
  ```bash
74
+ git clone https://github.com/customclaw/composio.git
75
+ cd composio
170
76
  npm install
171
77
  npm run build
172
- npm pack # creates .tgz for local testing
173
- openclaw plugins install ./customclaw-composio-0.0.1.tgz
78
+ npm test
174
79
  ```
175
80
 
176
81
  ## Acknowledgments
177
82
 
178
- This project is based on the Composio plugin from [openclaw-composio](https://github.com/ComposioHQ/openclaw-composio) by ComposioHQ. See [THIRD-PARTY-NOTICES](./THIRD-PARTY-NOTICES) for details.
179
-
180
- ## Links
181
-
182
- - [Composio Documentation](https://docs.composio.dev)
183
- - [Tool Router Overview](https://docs.composio.dev/tool-router/overview)
184
- - [Composio Platform](https://platform.composio.dev)
83
+ Based on the Composio plugin from [openclaw-composio](https://github.com/ComposioHQ/openclaw-composio) by ComposioHQ. See [THIRD-PARTY-NOTICES](./THIRD-PARTY-NOTICES).
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,145 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { ComposioClient } from "./client.js";
3
+ import { parseComposioConfig } from "./config.js";
4
+ // Mock the Composio SDK
5
+ vi.mock("@composio/core", () => ({
6
+ Composio: vi.fn().mockImplementation(() => ({
7
+ toolRouter: {
8
+ create: vi.fn().mockResolvedValue({
9
+ sessionId: "test-session-123",
10
+ tools: vi.fn().mockResolvedValue([]),
11
+ authorize: vi.fn().mockResolvedValue({ url: "https://connect.composio.dev/test" }),
12
+ toolkits: vi.fn().mockResolvedValue({
13
+ items: [
14
+ { slug: "gmail", name: "Gmail", connection: { isActive: true } },
15
+ { slug: "sentry", name: "Sentry", connection: { isActive: false } },
16
+ { slug: "github", name: "GitHub", connection: { isActive: true } },
17
+ ],
18
+ }),
19
+ experimental: { assistivePrompt: "" },
20
+ }),
21
+ },
22
+ client: {
23
+ tools: {
24
+ execute: vi.fn().mockResolvedValue({
25
+ successful: true,
26
+ data: { results: [{ tool_slug: "GMAIL_FETCH_EMAILS", index: 0, response: { successful: true, data: { messages: [] } } }] },
27
+ }),
28
+ },
29
+ },
30
+ connectedAccounts: {
31
+ list: vi.fn().mockResolvedValue({ items: [] }),
32
+ delete: vi.fn().mockResolvedValue({}),
33
+ },
34
+ })),
35
+ }));
36
+ function makeClient(overrides) {
37
+ return new ComposioClient({
38
+ enabled: true,
39
+ apiKey: "test-key",
40
+ ...overrides,
41
+ });
42
+ }
43
+ describe("config parsing", () => {
44
+ it("reads apiKey from config object", () => {
45
+ const config = parseComposioConfig({ config: { apiKey: "from-config" } });
46
+ expect(config.apiKey).toBe("from-config");
47
+ });
48
+ it("reads apiKey from top-level", () => {
49
+ const config = parseComposioConfig({ apiKey: "from-top" });
50
+ expect(config.apiKey).toBe("from-top");
51
+ });
52
+ it("falls back to env var", () => {
53
+ process.env.COMPOSIO_API_KEY = "from-env";
54
+ const config = parseComposioConfig({});
55
+ expect(config.apiKey).toBe("from-env");
56
+ delete process.env.COMPOSIO_API_KEY;
57
+ });
58
+ it("defaults enabled to true", () => {
59
+ const config = parseComposioConfig({});
60
+ expect(config.enabled).toBe(true);
61
+ });
62
+ });
63
+ describe("toolkit filtering", () => {
64
+ it("allows all toolkits when no filter set", async () => {
65
+ const client = makeClient();
66
+ const statuses = await client.getConnectionStatus(["gmail", "sentry", "github"]);
67
+ expect(statuses).toHaveLength(3);
68
+ });
69
+ it("filters by allowedToolkits", async () => {
70
+ const client = makeClient({ allowedToolkits: ["gmail", "sentry"] });
71
+ const statuses = await client.getConnectionStatus(["gmail", "sentry", "github"]);
72
+ expect(statuses).toHaveLength(2);
73
+ expect(statuses.map(s => s.toolkit)).toEqual(["gmail", "sentry"]);
74
+ });
75
+ it("filters by blockedToolkits", async () => {
76
+ const client = makeClient({ blockedToolkits: ["github"] });
77
+ const statuses = await client.getConnectionStatus(["gmail", "sentry", "github"]);
78
+ expect(statuses).toHaveLength(2);
79
+ expect(statuses.find(s => s.toolkit === "github")).toBeUndefined();
80
+ });
81
+ it("blocked takes priority over allowed", async () => {
82
+ const client = makeClient({ allowedToolkits: ["gmail", "github"], blockedToolkits: ["github"] });
83
+ const statuses = await client.getConnectionStatus(["gmail", "github"]);
84
+ expect(statuses).toHaveLength(1);
85
+ expect(statuses[0].toolkit).toBe("gmail");
86
+ });
87
+ });
88
+ describe("connection status", () => {
89
+ it("reports gmail as connected", async () => {
90
+ const client = makeClient();
91
+ const statuses = await client.getConnectionStatus(["gmail"]);
92
+ expect(statuses[0].connected).toBe(true);
93
+ });
94
+ it("reports sentry as not connected", async () => {
95
+ const client = makeClient();
96
+ const statuses = await client.getConnectionStatus(["sentry"]);
97
+ expect(statuses[0].connected).toBe(false);
98
+ });
99
+ it("returns only connected toolkits when no filter", async () => {
100
+ const client = makeClient();
101
+ const statuses = await client.getConnectionStatus();
102
+ expect(statuses.every(s => s.connected)).toBe(true);
103
+ expect(statuses.map(s => s.toolkit)).toEqual(["gmail", "github"]);
104
+ });
105
+ });
106
+ describe("execute tool", () => {
107
+ it("executes and returns result", async () => {
108
+ const client = makeClient();
109
+ const result = await client.executeTool("GMAIL_FETCH_EMAILS", {});
110
+ expect(result.success).toBe(true);
111
+ expect(result.data).toEqual({ messages: [] });
112
+ });
113
+ it("rejects blocked toolkit", async () => {
114
+ const client = makeClient({ allowedToolkits: ["sentry"] });
115
+ const result = await client.executeTool("GMAIL_FETCH_EMAILS", {});
116
+ expect(result.success).toBe(false);
117
+ expect(result.error).toContain("not allowed");
118
+ });
119
+ });
120
+ describe("create connection", () => {
121
+ it("returns auth URL", async () => {
122
+ const client = makeClient();
123
+ const result = await client.createConnection("gmail");
124
+ expect("authUrl" in result).toBe(true);
125
+ if ("authUrl" in result) {
126
+ expect(result.authUrl).toContain("connect.composio.dev");
127
+ }
128
+ });
129
+ it("rejects blocked toolkit", async () => {
130
+ const client = makeClient({ blockedToolkits: ["gmail"] });
131
+ const result = await client.createConnection("gmail");
132
+ expect("error" in result).toBe(true);
133
+ });
134
+ });
135
+ describe("session caching", () => {
136
+ it("reuses session for same user", async () => {
137
+ const client = makeClient();
138
+ await client.getConnectionStatus(["gmail"]);
139
+ await client.getConnectionStatus(["gmail"]);
140
+ // toolRouter.create should only be called once
141
+ const { Composio } = await import("@composio/core");
142
+ const instance = Composio.mock.results[0].value;
143
+ expect(instance.toolRouter.create).toHaveBeenCalledTimes(1);
144
+ });
145
+ });
package/dist/index.js CHANGED
@@ -2,10 +2,7 @@ import { composioPluginConfigSchema, parseComposioConfig } from "./config.js";
2
2
  import { createComposioClient } from "./client.js";
3
3
  import { createComposioSearchTool } from "./tools/search.js";
4
4
  import { createComposioExecuteTool } from "./tools/execute.js";
5
- import { createComposioMultiExecuteTool } from "./tools/multi-execute.js";
6
5
  import { createComposioConnectionsTool } from "./tools/connections.js";
7
- import { createComposioWorkbenchTool } from "./tools/workbench.js";
8
- import { createCompositoBashTool } from "./tools/bash.js";
9
6
  import { registerComposioCli } from "./cli.js";
10
7
  /**
11
8
  * Composio Tool Router Plugin for OpenClaw
@@ -63,30 +60,12 @@ const composioPlugin = {
63
60
  return createComposioExecuteTool(ensureClient(), config).execute(toolCallId, params);
64
61
  },
65
62
  });
66
- api.registerTool({
67
- ...createComposioMultiExecuteTool(ensureClient(), config),
68
- execute: async (toolCallId, params) => {
69
- return createComposioMultiExecuteTool(ensureClient(), config).execute(toolCallId, params);
70
- },
71
- });
72
63
  api.registerTool({
73
64
  ...createComposioConnectionsTool(ensureClient(), config),
74
65
  execute: async (toolCallId, params) => {
75
66
  return createComposioConnectionsTool(ensureClient(), config).execute(toolCallId, params);
76
67
  },
77
68
  });
78
- api.registerTool({
79
- ...createComposioWorkbenchTool(ensureClient(), config),
80
- execute: async (toolCallId, params) => {
81
- return createComposioWorkbenchTool(ensureClient(), config).execute(toolCallId, params);
82
- },
83
- });
84
- api.registerTool({
85
- ...createCompositoBashTool(ensureClient(), config),
86
- execute: async (toolCallId, params) => {
87
- return createCompositoBashTool(ensureClient(), config).execute(toolCallId, params);
88
- },
89
- });
90
69
  // Register CLI commands
91
70
  api.registerCli(({ program }) => registerComposioCli({
92
71
  program,
@@ -98,28 +77,19 @@ const composioPlugin = {
98
77
  api.on("before_agent_start", () => {
99
78
  return {
100
79
  prependContext: `<composio-tools>
101
- You have access to Composio Tool Router, which provides 1000+ third-party integrations (Gmail, Slack, GitHub, Notion, Linear, Jira, HubSpot, Google Drive, etc.).
102
-
103
- ## How to use Composio tools
104
-
105
- 1. **Search first**: Use \`composio_search_tools\` to find tools matching the user's task. Search by describing what you want to do (e.g., "send email", "create github issue").
106
-
107
- 2. **Check connections**: Before executing, use \`composio_manage_connections\` with action="status" to verify the required toolkit is connected. If not connected, use action="create" to generate an auth URL for the user.
108
-
109
- 3. **Execute tools**: Use \`composio_execute_tool\` with the tool_slug from search results and arguments matching the tool's schema. For multiple operations, use \`composio_multi_execute\` to run up to 50 tools in parallel.
80
+ You have access to Composio tools for third-party integrations (Gmail, Sentry, etc.).
110
81
 
111
- 4. **Remote processing**: For large responses or bulk operations, use \`composio_workbench\` to run Python code in a remote Jupyter sandbox with helpers like run_composio_tool(), invoke_llm(), etc. Use \`composio_bash\` for shell commands in the remote sandbox.
82
+ ## Usage
83
+ 1. Use \`composio_search_tools\` to find tools and their parameter schemas.
84
+ 2. Use \`composio_manage_connections\` with action="status" to check if a toolkit is connected. Use action="create" to generate an auth URL if needed.
85
+ 3. Use \`composio_execute_tool\` with the tool_slug and arguments from search results.
112
86
 
113
- ## Important notes
114
- - Tool slugs are uppercase (e.g., GMAIL_SEND_EMAIL, GITHUB_CREATE_ISSUE)
115
- - Always use exact tool_slug values from search results - do not invent slugs
116
- - Check the parameters schema from search results before executing
117
- - If a tool fails with auth errors, prompt the user to connect the toolkit
118
- - Use workbench/bash tools when processing data stored in remote files or scripting bulk operations
87
+ Always search first to get the correct parameter schema before executing a tool.
88
+ Tool slugs are uppercase. If a tool fails with auth errors, prompt the user to connect the toolkit.
119
89
  </composio-tools>`,
120
90
  };
121
91
  });
122
- api.logger.info("[composio] Plugin registered with 6 tools and CLI commands");
92
+ api.logger.info("[composio] Plugin registered with 3 tools and CLI commands");
123
93
  },
124
94
  };
125
95
  export default composioPlugin;
package/package.json CHANGED
@@ -1,15 +1,18 @@
1
1
  {
2
2
  "name": "@customclaw/composio",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "type": "module",
5
5
  "description": "Composio Tool Router plugin for OpenClaw — access 1000+ third-party integrations",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "openclaw": {
9
- "extensions": ["./dist/index.js"]
9
+ "extensions": [
10
+ "./dist/index.js"
11
+ ]
10
12
  },
11
13
  "scripts": {
12
14
  "build": "tsc",
15
+ "test": "vitest run",
13
16
  "prepublishOnly": "npm run build"
14
17
  },
15
18
  "files": [
@@ -25,12 +28,13 @@
25
28
  ],
26
29
  "license": "MIT",
27
30
  "dependencies": {
28
- "@composio/core": "^0.5.5",
31
+ "@composio/core": "^0.6.3",
29
32
  "@sinclair/typebox": "0.34.47",
30
33
  "zod": "^4.3.6"
31
34
  },
32
35
  "devDependencies": {
33
36
  "@types/node": "^25.2.3",
34
- "typescript": "^5.4.0"
37
+ "typescript": "^5.4.0",
38
+ "vitest": "^3.2.4"
35
39
  }
36
40
  }