@troykelly/openclaw-projects 0.0.1
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/LICENSE +21 -0
- package/README.md +389 -0
- package/dist/api-client.d.ts +81 -0
- package/dist/api-client.d.ts.map +1 -0
- package/dist/api-client.js +216 -0
- package/dist/api-client.js.map +1 -0
- package/dist/cli.d.ts +112 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +233 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +324 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +287 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +87 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +144 -0
- package/dist/context.js.map +1 -0
- package/dist/gateway/rpc-methods.d.ts +93 -0
- package/dist/gateway/rpc-methods.d.ts.map +1 -0
- package/dist/gateway/rpc-methods.js +145 -0
- package/dist/gateway/rpc-methods.js.map +1 -0
- package/dist/hooks.d.ts +86 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +314 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +106 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +221 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +22 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +78 -0
- package/dist/logger.js.map +1 -0
- package/dist/register-openclaw.d.ts +43 -0
- package/dist/register-openclaw.d.ts.map +1 -0
- package/dist/register-openclaw.js +1838 -0
- package/dist/register-openclaw.js.map +1 -0
- package/dist/secrets.d.ts +56 -0
- package/dist/secrets.d.ts.map +1 -0
- package/dist/secrets.js +161 -0
- package/dist/secrets.js.map +1 -0
- package/dist/services/notification-service.d.ts +60 -0
- package/dist/services/notification-service.d.ts.map +1 -0
- package/dist/services/notification-service.js +145 -0
- package/dist/services/notification-service.js.map +1 -0
- package/dist/tools/contacts.d.ts +139 -0
- package/dist/tools/contacts.d.ts.map +1 -0
- package/dist/tools/contacts.js +333 -0
- package/dist/tools/contacts.js.map +1 -0
- package/dist/tools/email-send.d.ts +71 -0
- package/dist/tools/email-send.d.ts.map +1 -0
- package/dist/tools/email-send.js +132 -0
- package/dist/tools/email-send.js.map +1 -0
- package/dist/tools/file-share.d.ts +64 -0
- package/dist/tools/file-share.d.ts.map +1 -0
- package/dist/tools/file-share.js +133 -0
- package/dist/tools/file-share.js.map +1 -0
- package/dist/tools/index.d.ts +22 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +33 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/memory-forget.d.ts +69 -0
- package/dist/tools/memory-forget.d.ts.map +1 -0
- package/dist/tools/memory-forget.js +224 -0
- package/dist/tools/memory-forget.js.map +1 -0
- package/dist/tools/memory-recall.d.ts +82 -0
- package/dist/tools/memory-recall.d.ts.map +1 -0
- package/dist/tools/memory-recall.js +161 -0
- package/dist/tools/memory-recall.js.map +1 -0
- package/dist/tools/memory-store.d.ts +80 -0
- package/dist/tools/memory-store.d.ts.map +1 -0
- package/dist/tools/memory-store.js +172 -0
- package/dist/tools/memory-store.js.map +1 -0
- package/dist/tools/message-search.d.ts +85 -0
- package/dist/tools/message-search.d.ts.map +1 -0
- package/dist/tools/message-search.js +137 -0
- package/dist/tools/message-search.js.map +1 -0
- package/dist/tools/notebooks.d.ts +155 -0
- package/dist/tools/notebooks.d.ts.map +1 -0
- package/dist/tools/notebooks.js +287 -0
- package/dist/tools/notebooks.js.map +1 -0
- package/dist/tools/notes.d.ts +272 -0
- package/dist/tools/notes.d.ts.map +1 -0
- package/dist/tools/notes.js +530 -0
- package/dist/tools/notes.js.map +1 -0
- package/dist/tools/projects.d.ts +139 -0
- package/dist/tools/projects.d.ts.map +1 -0
- package/dist/tools/projects.js +280 -0
- package/dist/tools/projects.js.map +1 -0
- package/dist/tools/relationships.d.ts +133 -0
- package/dist/tools/relationships.d.ts.map +1 -0
- package/dist/tools/relationships.js +281 -0
- package/dist/tools/relationships.js.map +1 -0
- package/dist/tools/sms-send.d.ts +62 -0
- package/dist/tools/sms-send.d.ts.map +1 -0
- package/dist/tools/sms-send.js +121 -0
- package/dist/tools/sms-send.js.map +1 -0
- package/dist/tools/threads.d.ts +127 -0
- package/dist/tools/threads.d.ts.map +1 -0
- package/dist/tools/threads.js +202 -0
- package/dist/tools/threads.js.map +1 -0
- package/dist/tools/todos.d.ts +142 -0
- package/dist/tools/todos.d.ts.map +1 -0
- package/dist/tools/todos.js +308 -0
- package/dist/tools/todos.js.map +1 -0
- package/dist/types/openclaw-api.d.ts +215 -0
- package/dist/types/openclaw-api.d.ts.map +1 -0
- package/dist/types/openclaw-api.js +10 -0
- package/dist/types/openclaw-api.js.map +1 -0
- package/dist/utils/zod-to-json-schema.d.ts +19 -0
- package/dist/utils/zod-to-json-schema.d.ts.map +1 -0
- package/dist/utils/zod-to-json-schema.js +132 -0
- package/dist/utils/zod-to-json-schema.js.map +1 -0
- package/openclaw.plugin.json +229 -0
- package/package.json +69 -0
- package/skills/contact-lookup/SKILL.md +30 -0
- package/skills/daily-summary/SKILL.md +23 -0
- package/skills/project-status/SKILL.md +33 -0
- package/skills/send-reminder/SKILL.md +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Troy Kelly
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
# @troykelly/openclaw-projects
|
|
2
|
+
|
|
3
|
+
An [OpenClaw](https://docs.openclaw.ai/) plugin that connects agents to the openclaw-projects backend for project management, memory, todos, and contacts.
|
|
4
|
+
|
|
5
|
+
> **Note:** This is a third-party plugin — not part of OpenClaw itself. It provides OpenClaw agents with tools to interact with the [openclaw-projects](https://github.com/troykelly/openclaw-projects) backend service.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Memory Management**: Store, recall, and forget memories with semantic search
|
|
10
|
+
- **Project Management**: List, get, and create projects
|
|
11
|
+
- **Todo Management**: Manage todos with completion tracking
|
|
12
|
+
- **Contact Management**: Search, get, and create contacts
|
|
13
|
+
- **Auto-Recall**: Automatically inject relevant context into conversations
|
|
14
|
+
- **Auto-Capture**: Capture important information from completed conversations
|
|
15
|
+
- **CLI Commands**: Debug and manage the plugin from the command line
|
|
16
|
+
- **Multi-User Support**: Flexible user scoping (agent, session, identity)
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pnpm add @troykelly/openclaw-projects
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { register } from '@troykelly/openclaw-projects'
|
|
28
|
+
|
|
29
|
+
const plugin = register({
|
|
30
|
+
config: {
|
|
31
|
+
apiUrl: 'https://your-backend.example.com',
|
|
32
|
+
apiKey: process.env.OPENCLAW_API_KEY,
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
// Use tools
|
|
37
|
+
const result = await plugin.tools.memoryRecall.execute({
|
|
38
|
+
query: 'user preferences',
|
|
39
|
+
})
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Configuration
|
|
43
|
+
|
|
44
|
+
| Option | Type | Default | Description |
|
|
45
|
+
|--------|------|---------|-------------|
|
|
46
|
+
| `apiUrl` | string | **required** | Backend API URL |
|
|
47
|
+
| `apiKey` | string | **required** | API authentication key |
|
|
48
|
+
| `autoRecall` | boolean | `true` | Enable auto-recall hook |
|
|
49
|
+
| `autoCapture` | boolean | `true` | Enable auto-capture hook |
|
|
50
|
+
| `userScoping` | string | `'agent'` | User scoping mode |
|
|
51
|
+
| `maxRecallMemories` | number | `5` | Max memories to return |
|
|
52
|
+
| `minRecallScore` | number | `0.7` | Minimum similarity score |
|
|
53
|
+
| `timeout` | number | `30000` | API timeout (ms) |
|
|
54
|
+
| `maxRetries` | number | `3` | Max retry attempts |
|
|
55
|
+
| `debug` | boolean | `false` | Enable debug logging |
|
|
56
|
+
|
|
57
|
+
### User Scoping Modes
|
|
58
|
+
|
|
59
|
+
| Mode | Description | Use Case |
|
|
60
|
+
|------|-------------|----------|
|
|
61
|
+
| `agent` | Scope by agent ID | Single user per agent |
|
|
62
|
+
| `session` | Scope by session key | Maximum isolation |
|
|
63
|
+
| `identity` | Scope by canonical identity | Shared identity across agents |
|
|
64
|
+
|
|
65
|
+
### Environment Variables
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
OPENCLAW_API_URL=https://your-backend.example.com
|
|
69
|
+
OPENCLAW_API_KEY=your-api-key
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Tools
|
|
73
|
+
|
|
74
|
+
### Memory Tools
|
|
75
|
+
|
|
76
|
+
#### `memory_recall`
|
|
77
|
+
Search memories semantically.
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
const result = await plugin.tools.memoryRecall.execute({
|
|
81
|
+
query: 'user preferences for notifications',
|
|
82
|
+
limit: 10, // optional, default: 5
|
|
83
|
+
category: 'preference', // optional filter
|
|
84
|
+
})
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### `memory_store`
|
|
88
|
+
Save information to long-term memory.
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
const result = await plugin.tools.memoryStore.execute({
|
|
92
|
+
text: 'User prefers dark mode',
|
|
93
|
+
category: 'preference', // preference, fact, decision, context, other
|
|
94
|
+
importance: 0.8, // optional, 0-1
|
|
95
|
+
})
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### `memory_forget`
|
|
99
|
+
Delete memories (GDPR data portability).
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// By ID
|
|
103
|
+
const result = await plugin.tools.memoryForget.execute({
|
|
104
|
+
memoryId: '123e4567-e89b-12d3-a456-426614174000',
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
// By query (bulk delete)
|
|
108
|
+
const result = await plugin.tools.memoryForget.execute({
|
|
109
|
+
query: 'outdated preferences',
|
|
110
|
+
confirm: true, // required for bulk delete
|
|
111
|
+
})
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Project Tools
|
|
115
|
+
|
|
116
|
+
#### `project_list`
|
|
117
|
+
List projects with optional filtering.
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
const result = await plugin.tools.projectList.execute({
|
|
121
|
+
status: 'active', // optional: active, completed, archived, on_hold
|
|
122
|
+
limit: 20, // optional
|
|
123
|
+
})
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
#### `project_get`
|
|
127
|
+
Get a specific project by ID.
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
const result = await plugin.tools.projectGet.execute({
|
|
131
|
+
id: '123e4567-e89b-12d3-a456-426614174000',
|
|
132
|
+
})
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
#### `project_create`
|
|
136
|
+
Create a new project.
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
const result = await plugin.tools.projectCreate.execute({
|
|
140
|
+
name: 'Home Renovation',
|
|
141
|
+
description: 'Kitchen remodel project', // optional
|
|
142
|
+
status: 'active', // optional
|
|
143
|
+
})
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Todo Tools
|
|
147
|
+
|
|
148
|
+
#### `todo_list`
|
|
149
|
+
List todos with optional filtering.
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
const result = await plugin.tools.todoList.execute({
|
|
153
|
+
projectId: '123e4567-e89b-12d3-a456-426614174000', // optional
|
|
154
|
+
completed: false, // optional
|
|
155
|
+
limit: 50, // optional
|
|
156
|
+
})
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
#### `todo_create`
|
|
160
|
+
Create a new todo.
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
const result = await plugin.tools.todoCreate.execute({
|
|
164
|
+
title: 'Buy groceries',
|
|
165
|
+
projectId: '123e4567-e89b-12d3-a456-426614174000', // optional
|
|
166
|
+
dueDate: '2024-01-15', // optional, ISO 8601
|
|
167
|
+
})
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
#### `todo_complete`
|
|
171
|
+
Mark a todo as complete.
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
const result = await plugin.tools.todoComplete.execute({
|
|
175
|
+
id: '123e4567-e89b-12d3-a456-426614174000',
|
|
176
|
+
})
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Contact Tools
|
|
180
|
+
|
|
181
|
+
#### `contact_search`
|
|
182
|
+
Search contacts.
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
const result = await plugin.tools.contactSearch.execute({
|
|
186
|
+
query: 'Alice',
|
|
187
|
+
limit: 10, // optional
|
|
188
|
+
})
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
#### `contact_get`
|
|
192
|
+
Get a specific contact by ID.
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
const result = await plugin.tools.contactGet.execute({
|
|
196
|
+
id: '123e4567-e89b-12d3-a456-426614174000',
|
|
197
|
+
})
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
#### `contact_create`
|
|
201
|
+
Create a new contact.
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
const result = await plugin.tools.contactCreate.execute({
|
|
205
|
+
name: 'Alice Smith',
|
|
206
|
+
email: 'alice@example.com', // optional
|
|
207
|
+
phone: '+1-555-123-4567', // optional
|
|
208
|
+
})
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Lifecycle Hooks
|
|
212
|
+
|
|
213
|
+
### `beforeAgentStart` (Auto-Recall)
|
|
214
|
+
|
|
215
|
+
Automatically fetches relevant context before the agent processes a prompt.
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
const context = await plugin.hooks.beforeAgentStart({
|
|
219
|
+
prompt: 'What are my notification preferences?',
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
if (context) {
|
|
223
|
+
// Prepend context.prependContext to the conversation
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### `agentEnd` (Auto-Capture)
|
|
228
|
+
|
|
229
|
+
Automatically captures important information after a conversation ends.
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
await plugin.hooks.agentEnd({
|
|
233
|
+
messages: [
|
|
234
|
+
{ role: 'user', content: 'Remember I prefer email notifications' },
|
|
235
|
+
{ role: 'assistant', content: 'Noted! I will remember your preference.' },
|
|
236
|
+
],
|
|
237
|
+
})
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## CLI Commands
|
|
241
|
+
|
|
242
|
+
The plugin provides CLI command handlers that can be registered with OpenClaw:
|
|
243
|
+
|
|
244
|
+
### `status`
|
|
245
|
+
Check API connectivity.
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
const result = await plugin.cli.status()
|
|
249
|
+
// { success: true, message: 'API is healthy (latency: 50ms)', data: { ... } }
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### `users`
|
|
253
|
+
Show user scoping configuration.
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
const result = await plugin.cli.users()
|
|
257
|
+
// { success: true, data: { scopingMode: 'agent', description: '...', currentUserId: '...' } }
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### `recall`
|
|
261
|
+
Search memories from CLI.
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
const result = await plugin.cli.recall({ query: 'preferences', limit: 10 })
|
|
265
|
+
// { success: true, data: { memories: [...], query: '...', limit: 10 } }
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### `stats`
|
|
269
|
+
Show memory statistics.
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
const result = await plugin.cli.stats()
|
|
273
|
+
// { success: true, data: { totalMemories: 42, byCategory: { ... } } }
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### `export`
|
|
277
|
+
Export all memories (GDPR data portability).
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
const result = await plugin.cli.export({ output: '/path/to/export.json' })
|
|
281
|
+
// { success: true, data: { memories: [...], exportedAt: '...', userId: '...' } }
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Health Check
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
const health = await plugin.healthCheck()
|
|
288
|
+
if (!health.healthy) {
|
|
289
|
+
console.error('Plugin unhealthy:', health.error)
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## Security
|
|
294
|
+
|
|
295
|
+
### API Key Management
|
|
296
|
+
|
|
297
|
+
- Store API keys in environment variables, never in code
|
|
298
|
+
- Use secrets management in production (Vault, AWS Secrets Manager, etc.)
|
|
299
|
+
- Rotate keys regularly
|
|
300
|
+
|
|
301
|
+
### Data Isolation
|
|
302
|
+
|
|
303
|
+
- All data is scoped to the configured user scope
|
|
304
|
+
- Cross-user access is prevented at the API level
|
|
305
|
+
- Audit logs track all data access
|
|
306
|
+
|
|
307
|
+
### Sensitive Content
|
|
308
|
+
|
|
309
|
+
- The plugin filters sensitive content (API keys, passwords, credit cards)
|
|
310
|
+
- PII is not logged at info level
|
|
311
|
+
- Error messages are sanitized to prevent information leakage
|
|
312
|
+
|
|
313
|
+
### HTTPS
|
|
314
|
+
|
|
315
|
+
- Use HTTPS in production
|
|
316
|
+
- HTTP is only recommended for local development
|
|
317
|
+
|
|
318
|
+
## Error Handling
|
|
319
|
+
|
|
320
|
+
All tool executions return a result object:
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
interface ToolResult {
|
|
324
|
+
success: boolean
|
|
325
|
+
content?: string // Human-readable response
|
|
326
|
+
data?: unknown // Structured data
|
|
327
|
+
error?: string // Error message if success is false
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## Troubleshooting
|
|
332
|
+
|
|
333
|
+
### Connection Issues
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
// Check health
|
|
337
|
+
const health = await plugin.healthCheck()
|
|
338
|
+
console.log('Healthy:', health.healthy, 'Error:', health.error)
|
|
339
|
+
|
|
340
|
+
// Check status via CLI
|
|
341
|
+
const status = await plugin.cli.status()
|
|
342
|
+
console.log('Status:', status.message, 'Latency:', status.data?.latencyMs)
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### No Memories Found
|
|
346
|
+
|
|
347
|
+
- Verify the user scoping mode matches your setup
|
|
348
|
+
- Check that memories were stored for the same user scope
|
|
349
|
+
- Try broadening your search query
|
|
350
|
+
|
|
351
|
+
### API Errors
|
|
352
|
+
|
|
353
|
+
- Verify API URL is correct and accessible
|
|
354
|
+
- Check API key is valid
|
|
355
|
+
- Review network connectivity
|
|
356
|
+
|
|
357
|
+
## API Reference
|
|
358
|
+
|
|
359
|
+
Full TypeScript types are exported:
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
import type {
|
|
363
|
+
PluginConfig,
|
|
364
|
+
PluginInstance,
|
|
365
|
+
MemoryRecallParams,
|
|
366
|
+
MemoryStoreParams,
|
|
367
|
+
ProjectListParams,
|
|
368
|
+
TodoCreateParams,
|
|
369
|
+
ContactSearchParams,
|
|
370
|
+
// ... and more
|
|
371
|
+
} from '@troykelly/openclaw-projects'
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
## Contributing
|
|
375
|
+
|
|
376
|
+
1. Fork the repository
|
|
377
|
+
2. Create a feature branch
|
|
378
|
+
3. Write tests for new functionality
|
|
379
|
+
4. Submit a pull request
|
|
380
|
+
|
|
381
|
+
## License
|
|
382
|
+
|
|
383
|
+
MIT
|
|
384
|
+
|
|
385
|
+
## Links
|
|
386
|
+
|
|
387
|
+
- [OpenClaw Documentation](https://docs.openclaw.ai/)
|
|
388
|
+
- [openclaw-projects Backend](https://github.com/troykelly/openclaw-projects)
|
|
389
|
+
- [Report Issues](https://github.com/troykelly/openclaw-projects/issues)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP API client for openclaw-projects backend.
|
|
3
|
+
* Handles authentication, request/response formatting, error handling,
|
|
4
|
+
* retry logic with exponential backoff, and timeout handling.
|
|
5
|
+
*/
|
|
6
|
+
import type { PluginConfig } from './config.js';
|
|
7
|
+
import { type Logger } from './logger.js';
|
|
8
|
+
/** API error response */
|
|
9
|
+
export interface ApiError {
|
|
10
|
+
status: number;
|
|
11
|
+
message: string;
|
|
12
|
+
code?: string;
|
|
13
|
+
details?: Record<string, unknown>;
|
|
14
|
+
/** Retry-After value in seconds (for 429 responses) */
|
|
15
|
+
retryAfter?: number;
|
|
16
|
+
}
|
|
17
|
+
/** API response wrapper */
|
|
18
|
+
export type ApiResponse<T> = {
|
|
19
|
+
success: true;
|
|
20
|
+
data: T;
|
|
21
|
+
} | {
|
|
22
|
+
success: false;
|
|
23
|
+
error: ApiError;
|
|
24
|
+
};
|
|
25
|
+
/** Request options */
|
|
26
|
+
export interface RequestOptions {
|
|
27
|
+
/** User ID for scoping */
|
|
28
|
+
userId?: string;
|
|
29
|
+
/** Custom timeout (overrides config) */
|
|
30
|
+
timeout?: number;
|
|
31
|
+
/** Mark request as coming from an agent (adds X-OpenClaw-Agent header) */
|
|
32
|
+
isAgent?: boolean;
|
|
33
|
+
}
|
|
34
|
+
/** API client options */
|
|
35
|
+
export interface ApiClientOptions {
|
|
36
|
+
config: PluginConfig;
|
|
37
|
+
logger?: Logger;
|
|
38
|
+
}
|
|
39
|
+
/** Health check result */
|
|
40
|
+
export interface HealthCheckResult {
|
|
41
|
+
healthy: boolean;
|
|
42
|
+
latencyMs: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* HTTP API client for the openclaw-projects backend.
|
|
46
|
+
*/
|
|
47
|
+
export declare class ApiClient {
|
|
48
|
+
private readonly baseUrl;
|
|
49
|
+
private readonly apiKey;
|
|
50
|
+
private readonly logger;
|
|
51
|
+
private readonly timeout;
|
|
52
|
+
private readonly maxRetries;
|
|
53
|
+
constructor(options: ApiClientOptions);
|
|
54
|
+
/**
|
|
55
|
+
* Makes an authenticated request to the API with retry logic.
|
|
56
|
+
*/
|
|
57
|
+
private request;
|
|
58
|
+
/**
|
|
59
|
+
* Execute a single request with timeout handling.
|
|
60
|
+
*/
|
|
61
|
+
private executeRequest;
|
|
62
|
+
/** GET request */
|
|
63
|
+
get<T>(path: string, options?: RequestOptions): Promise<ApiResponse<T>>;
|
|
64
|
+
/** POST request */
|
|
65
|
+
post<T>(path: string, body?: unknown, options?: RequestOptions): Promise<ApiResponse<T>>;
|
|
66
|
+
/** PUT request */
|
|
67
|
+
put<T>(path: string, body?: unknown, options?: RequestOptions): Promise<ApiResponse<T>>;
|
|
68
|
+
/** PATCH request */
|
|
69
|
+
patch<T>(path: string, body?: unknown, options?: RequestOptions): Promise<ApiResponse<T>>;
|
|
70
|
+
/** DELETE request */
|
|
71
|
+
delete<T>(path: string, options?: RequestOptions): Promise<ApiResponse<T>>;
|
|
72
|
+
/**
|
|
73
|
+
* Health check endpoint.
|
|
74
|
+
*/
|
|
75
|
+
healthCheck(): Promise<HealthCheckResult>;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Creates a new API client instance.
|
|
79
|
+
*/
|
|
80
|
+
export declare function createApiClient(options: ApiClientOptions): ApiClient;
|
|
81
|
+
//# sourceMappingURL=api-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,aAAa,CAAA;AAEvD,yBAAyB;AACzB,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACjC,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,2BAA2B;AAC3B,MAAM,MAAM,WAAW,CAAC,CAAC,IACrB;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GAC1B;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,QAAQ,CAAA;CAAE,CAAA;AAEvC,sBAAsB;AACtB,MAAM,WAAW,cAAc;IAC7B,0BAA0B;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,wCAAwC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,yBAAyB;AACzB,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,YAAY,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,0BAA0B;AAC1B,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;CAClB;AA+CD;;GAEG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;gBAEvB,OAAO,EAAE,gBAAgB;IASrC;;OAEG;YACW,OAAO;IAmErB;;OAEG;YACW,cAAc;IAiE5B,kBAAkB;IACZ,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAI7E,mBAAmB;IACb,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAI9F,kBAAkB;IACZ,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAI7F,oBAAoB;IACd,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAI/F,qBAAqB;IACf,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAIhF;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,iBAAiB,CAAC;CAYhD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,CAEpE"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP API client for openclaw-projects backend.
|
|
3
|
+
* Handles authentication, request/response formatting, error handling,
|
|
4
|
+
* retry logic with exponential backoff, and timeout handling.
|
|
5
|
+
*/
|
|
6
|
+
import { createLogger } from './logger.js';
|
|
7
|
+
/**
|
|
8
|
+
* Sleep for a specified duration.
|
|
9
|
+
*/
|
|
10
|
+
function sleep(ms) {
|
|
11
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Generate a unique request ID for tracing.
|
|
15
|
+
*/
|
|
16
|
+
function generateRequestId() {
|
|
17
|
+
return crypto.randomUUID();
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Calculate retry delay with exponential backoff and jitter.
|
|
21
|
+
*/
|
|
22
|
+
function calculateRetryDelay(attempt, baseDelay = 1000, maxDelay = 10000) {
|
|
23
|
+
// Exponential backoff: 1s, 2s, 4s, 8s...
|
|
24
|
+
const exponentialDelay = baseDelay * Math.pow(2, attempt);
|
|
25
|
+
// Add jitter (±25%) to prevent thundering herd
|
|
26
|
+
const jitter = exponentialDelay * (0.75 + Math.random() * 0.5);
|
|
27
|
+
return Math.min(jitter, maxDelay);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Check if an error is retryable.
|
|
31
|
+
*/
|
|
32
|
+
function isRetryableStatus(status) {
|
|
33
|
+
// Retry on 5xx server errors and network errors (status 0)
|
|
34
|
+
return status === 0 || (status >= 500 && status < 600);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Map HTTP status to error code.
|
|
38
|
+
*/
|
|
39
|
+
function getErrorCode(status) {
|
|
40
|
+
if (status === 401 || status === 403)
|
|
41
|
+
return 'AUTH_ERROR';
|
|
42
|
+
if (status === 404)
|
|
43
|
+
return 'NOT_FOUND';
|
|
44
|
+
if (status === 429)
|
|
45
|
+
return 'RATE_LIMITED';
|
|
46
|
+
if (status >= 500)
|
|
47
|
+
return 'SERVER_ERROR';
|
|
48
|
+
if (status === 0)
|
|
49
|
+
return 'NETWORK_ERROR';
|
|
50
|
+
return 'CLIENT_ERROR';
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* HTTP API client for the openclaw-projects backend.
|
|
54
|
+
*/
|
|
55
|
+
export class ApiClient {
|
|
56
|
+
baseUrl;
|
|
57
|
+
apiKey;
|
|
58
|
+
logger;
|
|
59
|
+
timeout;
|
|
60
|
+
maxRetries;
|
|
61
|
+
constructor(options) {
|
|
62
|
+
// Ensure URL doesn't have trailing slash
|
|
63
|
+
this.baseUrl = options.config.apiUrl.replace(/\/$/, '');
|
|
64
|
+
this.apiKey = options.config.apiKey;
|
|
65
|
+
this.logger = options.logger ?? createLogger('api-client');
|
|
66
|
+
this.timeout = options.config.timeout;
|
|
67
|
+
this.maxRetries = options.config.maxRetries;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Makes an authenticated request to the API with retry logic.
|
|
71
|
+
*/
|
|
72
|
+
async request(method, path, body, options) {
|
|
73
|
+
const url = `${this.baseUrl}${path}`;
|
|
74
|
+
const requestId = generateRequestId();
|
|
75
|
+
const timeout = options?.timeout ?? this.timeout;
|
|
76
|
+
let lastError = null;
|
|
77
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
78
|
+
if (attempt > 0) {
|
|
79
|
+
const delay = calculateRetryDelay(attempt - 1);
|
|
80
|
+
this.logger.debug(`Retrying request (attempt ${attempt + 1}/${this.maxRetries + 1})`, {
|
|
81
|
+
path,
|
|
82
|
+
delay,
|
|
83
|
+
});
|
|
84
|
+
await sleep(delay);
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const result = await this.executeRequest(method, url, body, requestId, options, timeout);
|
|
88
|
+
if (result.success) {
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
lastError = result.error;
|
|
92
|
+
// Don't retry 4xx errors (except we could retry 429, but typically you'd wait for Retry-After)
|
|
93
|
+
if (!isRetryableStatus(result.error.status)) {
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
// Handle timeout or network errors
|
|
99
|
+
lastError = {
|
|
100
|
+
status: 0,
|
|
101
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
102
|
+
code: error instanceof Error && error.name === 'AbortError' ? 'TIMEOUT' : 'NETWORK_ERROR',
|
|
103
|
+
};
|
|
104
|
+
this.logger.error('API request failed', {
|
|
105
|
+
method,
|
|
106
|
+
path,
|
|
107
|
+
requestId,
|
|
108
|
+
error: lastError.message,
|
|
109
|
+
});
|
|
110
|
+
// Network errors are retryable
|
|
111
|
+
if (attempt < this.maxRetries) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
success: false,
|
|
118
|
+
error: lastError ?? {
|
|
119
|
+
status: 0,
|
|
120
|
+
message: 'Request failed after retries',
|
|
121
|
+
code: 'NETWORK_ERROR',
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Execute a single request with timeout handling.
|
|
127
|
+
*/
|
|
128
|
+
async executeRequest(method, url, body, requestId, options, timeout) {
|
|
129
|
+
const controller = new AbortController();
|
|
130
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
131
|
+
try {
|
|
132
|
+
const headers = {
|
|
133
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
134
|
+
'Content-Type': 'application/json',
|
|
135
|
+
'X-Request-Id': requestId,
|
|
136
|
+
};
|
|
137
|
+
if (options?.userId) {
|
|
138
|
+
headers['X-Agent-Id'] = options.userId;
|
|
139
|
+
}
|
|
140
|
+
// Mark request as coming from an agent for privacy filtering
|
|
141
|
+
if (options?.isAgent) {
|
|
142
|
+
headers['X-OpenClaw-Agent'] = options.userId || 'plugin-agent';
|
|
143
|
+
}
|
|
144
|
+
const response = await fetch(url, {
|
|
145
|
+
method,
|
|
146
|
+
headers,
|
|
147
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
148
|
+
signal: controller.signal,
|
|
149
|
+
});
|
|
150
|
+
clearTimeout(timeoutId);
|
|
151
|
+
if (!response.ok) {
|
|
152
|
+
const errorBody = await response.json().catch(() => ({}));
|
|
153
|
+
const retryAfter = response.headers.get('Retry-After');
|
|
154
|
+
return {
|
|
155
|
+
success: false,
|
|
156
|
+
error: {
|
|
157
|
+
status: response.status,
|
|
158
|
+
message: errorBody.message || response.statusText,
|
|
159
|
+
code: getErrorCode(response.status),
|
|
160
|
+
details: errorBody.details,
|
|
161
|
+
retryAfter: retryAfter ? parseInt(retryAfter, 10) : undefined,
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
// Handle no content
|
|
166
|
+
if (response.status === 204) {
|
|
167
|
+
return { success: true, data: undefined };
|
|
168
|
+
}
|
|
169
|
+
const data = (await response.json());
|
|
170
|
+
return { success: true, data };
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
clearTimeout(timeoutId);
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/** GET request */
|
|
178
|
+
async get(path, options) {
|
|
179
|
+
return this.request('GET', path, undefined, options);
|
|
180
|
+
}
|
|
181
|
+
/** POST request */
|
|
182
|
+
async post(path, body, options) {
|
|
183
|
+
return this.request('POST', path, body, options);
|
|
184
|
+
}
|
|
185
|
+
/** PUT request */
|
|
186
|
+
async put(path, body, options) {
|
|
187
|
+
return this.request('PUT', path, body, options);
|
|
188
|
+
}
|
|
189
|
+
/** PATCH request */
|
|
190
|
+
async patch(path, body, options) {
|
|
191
|
+
return this.request('PATCH', path, body, options);
|
|
192
|
+
}
|
|
193
|
+
/** DELETE request */
|
|
194
|
+
async delete(path, options) {
|
|
195
|
+
return this.request('DELETE', path, undefined, options);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Health check endpoint.
|
|
199
|
+
*/
|
|
200
|
+
async healthCheck() {
|
|
201
|
+
const start = Date.now();
|
|
202
|
+
const result = await this.get('/api/health');
|
|
203
|
+
const latencyMs = Date.now() - start;
|
|
204
|
+
return {
|
|
205
|
+
healthy: result.success,
|
|
206
|
+
latencyMs,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Creates a new API client instance.
|
|
212
|
+
*/
|
|
213
|
+
export function createApiClient(options) {
|
|
214
|
+
return new ApiClient(options);
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=api-client.js.map
|