@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.
Files changed (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +389 -0
  3. package/dist/api-client.d.ts +81 -0
  4. package/dist/api-client.d.ts.map +1 -0
  5. package/dist/api-client.js +216 -0
  6. package/dist/api-client.js.map +1 -0
  7. package/dist/cli.d.ts +112 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +233 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/config.d.ts +324 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +287 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/context.d.ts +87 -0
  16. package/dist/context.d.ts.map +1 -0
  17. package/dist/context.js +144 -0
  18. package/dist/context.js.map +1 -0
  19. package/dist/gateway/rpc-methods.d.ts +93 -0
  20. package/dist/gateway/rpc-methods.d.ts.map +1 -0
  21. package/dist/gateway/rpc-methods.js +145 -0
  22. package/dist/gateway/rpc-methods.js.map +1 -0
  23. package/dist/hooks.d.ts +86 -0
  24. package/dist/hooks.d.ts.map +1 -0
  25. package/dist/hooks.js +314 -0
  26. package/dist/hooks.js.map +1 -0
  27. package/dist/index.d.ts +106 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +221 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/logger.d.ts +22 -0
  32. package/dist/logger.d.ts.map +1 -0
  33. package/dist/logger.js +78 -0
  34. package/dist/logger.js.map +1 -0
  35. package/dist/register-openclaw.d.ts +43 -0
  36. package/dist/register-openclaw.d.ts.map +1 -0
  37. package/dist/register-openclaw.js +1838 -0
  38. package/dist/register-openclaw.js.map +1 -0
  39. package/dist/secrets.d.ts +56 -0
  40. package/dist/secrets.d.ts.map +1 -0
  41. package/dist/secrets.js +161 -0
  42. package/dist/secrets.js.map +1 -0
  43. package/dist/services/notification-service.d.ts +60 -0
  44. package/dist/services/notification-service.d.ts.map +1 -0
  45. package/dist/services/notification-service.js +145 -0
  46. package/dist/services/notification-service.js.map +1 -0
  47. package/dist/tools/contacts.d.ts +139 -0
  48. package/dist/tools/contacts.d.ts.map +1 -0
  49. package/dist/tools/contacts.js +333 -0
  50. package/dist/tools/contacts.js.map +1 -0
  51. package/dist/tools/email-send.d.ts +71 -0
  52. package/dist/tools/email-send.d.ts.map +1 -0
  53. package/dist/tools/email-send.js +132 -0
  54. package/dist/tools/email-send.js.map +1 -0
  55. package/dist/tools/file-share.d.ts +64 -0
  56. package/dist/tools/file-share.d.ts.map +1 -0
  57. package/dist/tools/file-share.js +133 -0
  58. package/dist/tools/file-share.js.map +1 -0
  59. package/dist/tools/index.d.ts +22 -0
  60. package/dist/tools/index.d.ts.map +1 -0
  61. package/dist/tools/index.js +33 -0
  62. package/dist/tools/index.js.map +1 -0
  63. package/dist/tools/memory-forget.d.ts +69 -0
  64. package/dist/tools/memory-forget.d.ts.map +1 -0
  65. package/dist/tools/memory-forget.js +224 -0
  66. package/dist/tools/memory-forget.js.map +1 -0
  67. package/dist/tools/memory-recall.d.ts +82 -0
  68. package/dist/tools/memory-recall.d.ts.map +1 -0
  69. package/dist/tools/memory-recall.js +161 -0
  70. package/dist/tools/memory-recall.js.map +1 -0
  71. package/dist/tools/memory-store.d.ts +80 -0
  72. package/dist/tools/memory-store.d.ts.map +1 -0
  73. package/dist/tools/memory-store.js +172 -0
  74. package/dist/tools/memory-store.js.map +1 -0
  75. package/dist/tools/message-search.d.ts +85 -0
  76. package/dist/tools/message-search.d.ts.map +1 -0
  77. package/dist/tools/message-search.js +137 -0
  78. package/dist/tools/message-search.js.map +1 -0
  79. package/dist/tools/notebooks.d.ts +155 -0
  80. package/dist/tools/notebooks.d.ts.map +1 -0
  81. package/dist/tools/notebooks.js +287 -0
  82. package/dist/tools/notebooks.js.map +1 -0
  83. package/dist/tools/notes.d.ts +272 -0
  84. package/dist/tools/notes.d.ts.map +1 -0
  85. package/dist/tools/notes.js +530 -0
  86. package/dist/tools/notes.js.map +1 -0
  87. package/dist/tools/projects.d.ts +139 -0
  88. package/dist/tools/projects.d.ts.map +1 -0
  89. package/dist/tools/projects.js +280 -0
  90. package/dist/tools/projects.js.map +1 -0
  91. package/dist/tools/relationships.d.ts +133 -0
  92. package/dist/tools/relationships.d.ts.map +1 -0
  93. package/dist/tools/relationships.js +281 -0
  94. package/dist/tools/relationships.js.map +1 -0
  95. package/dist/tools/sms-send.d.ts +62 -0
  96. package/dist/tools/sms-send.d.ts.map +1 -0
  97. package/dist/tools/sms-send.js +121 -0
  98. package/dist/tools/sms-send.js.map +1 -0
  99. package/dist/tools/threads.d.ts +127 -0
  100. package/dist/tools/threads.d.ts.map +1 -0
  101. package/dist/tools/threads.js +202 -0
  102. package/dist/tools/threads.js.map +1 -0
  103. package/dist/tools/todos.d.ts +142 -0
  104. package/dist/tools/todos.d.ts.map +1 -0
  105. package/dist/tools/todos.js +308 -0
  106. package/dist/tools/todos.js.map +1 -0
  107. package/dist/types/openclaw-api.d.ts +215 -0
  108. package/dist/types/openclaw-api.d.ts.map +1 -0
  109. package/dist/types/openclaw-api.js +10 -0
  110. package/dist/types/openclaw-api.js.map +1 -0
  111. package/dist/utils/zod-to-json-schema.d.ts +19 -0
  112. package/dist/utils/zod-to-json-schema.d.ts.map +1 -0
  113. package/dist/utils/zod-to-json-schema.js +132 -0
  114. package/dist/utils/zod-to-json-schema.js.map +1 -0
  115. package/openclaw.plugin.json +229 -0
  116. package/package.json +69 -0
  117. package/skills/contact-lookup/SKILL.md +30 -0
  118. package/skills/daily-summary/SKILL.md +23 -0
  119. package/skills/project-status/SKILL.md +33 -0
  120. 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