@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
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Thread tools implementation.
3
+ * Provides thread_list and thread_get tools for viewing message threads.
4
+ */
5
+ import { z } from 'zod';
6
+ /** Channel type enum */
7
+ const ChannelType = z.enum(['sms', 'email']);
8
+ /** Default and max limits */
9
+ const DEFAULT_LIST_LIMIT = 20;
10
+ const MAX_LIST_LIMIT = 100;
11
+ const DEFAULT_MESSAGE_LIMIT = 50;
12
+ const MAX_MESSAGE_LIMIT = 200;
13
+ // =====================================================
14
+ // Thread List Tool
15
+ // =====================================================
16
+ /** Parameters for thread_list tool */
17
+ export const ThreadListParamsSchema = z.object({
18
+ channel: ChannelType.optional(),
19
+ contactId: z.string().uuid().optional(),
20
+ limit: z
21
+ .number()
22
+ .int()
23
+ .min(1, 'Limit must be at least 1')
24
+ .max(MAX_LIST_LIMIT, `Limit must be ${MAX_LIST_LIMIT} or less`)
25
+ .default(DEFAULT_LIST_LIMIT),
26
+ });
27
+ /**
28
+ * Creates the thread_list tool.
29
+ */
30
+ export function createThreadListTool(options) {
31
+ const { client, logger, userId } = options;
32
+ return {
33
+ name: 'thread_list',
34
+ description: 'List message threads (conversations). Use to see recent conversations with contacts. ' +
35
+ 'Can filter by channel (SMS/email) or contact.',
36
+ parameters: ThreadListParamsSchema,
37
+ async execute(params) {
38
+ // Validate parameters
39
+ const parseResult = ThreadListParamsSchema.safeParse(params);
40
+ if (!parseResult.success) {
41
+ const errorMessage = parseResult.error.errors
42
+ .map((e) => `${e.path.join('.')}: ${e.message}`)
43
+ .join(', ');
44
+ return { success: false, error: errorMessage };
45
+ }
46
+ const { channel, contactId, limit } = parseResult.data;
47
+ logger.info('thread_list invoked', {
48
+ userId,
49
+ channel,
50
+ hasContactId: !!contactId,
51
+ limit,
52
+ });
53
+ try {
54
+ // Build query parameters
55
+ const queryParams = new URLSearchParams();
56
+ queryParams.set('limit', String(limit));
57
+ if (channel) {
58
+ queryParams.set('channel', channel);
59
+ }
60
+ if (contactId) {
61
+ queryParams.set('contactId', contactId);
62
+ }
63
+ const response = await client.get(`/api/threads?${queryParams}`, { userId });
64
+ if (!response.success) {
65
+ logger.error('thread_list API error', {
66
+ userId,
67
+ status: response.error.status,
68
+ code: response.error.code,
69
+ });
70
+ return {
71
+ success: false,
72
+ error: response.error.message || 'Failed to list threads',
73
+ };
74
+ }
75
+ const { threads, total } = response.data;
76
+ logger.debug('thread_list completed', {
77
+ userId,
78
+ threadCount: threads.length,
79
+ total,
80
+ });
81
+ // Format content for display
82
+ const content = threads.length > 0
83
+ ? threads.map((t) => {
84
+ const contact = t.contactName || t.endpointValue;
85
+ const msgCount = `${t.messageCount} message${t.messageCount !== 1 ? 's' : ''}`;
86
+ return `[${t.channel}] ${contact} - ${msgCount}`;
87
+ }).join('\n')
88
+ : 'No threads found.';
89
+ return {
90
+ success: true,
91
+ data: {
92
+ content,
93
+ details: { threads, total, userId },
94
+ },
95
+ };
96
+ }
97
+ catch (error) {
98
+ logger.error('thread_list failed', {
99
+ userId,
100
+ error: error instanceof Error ? error.message : String(error),
101
+ });
102
+ return {
103
+ success: false,
104
+ error: error instanceof Error ? error.message : 'An unexpected error occurred while listing threads.',
105
+ };
106
+ }
107
+ },
108
+ };
109
+ }
110
+ // =====================================================
111
+ // Thread Get Tool
112
+ // =====================================================
113
+ /** Parameters for thread_get tool */
114
+ export const ThreadGetParamsSchema = z.object({
115
+ threadId: z.string().min(1, 'Thread ID is required'),
116
+ messageLimit: z
117
+ .number()
118
+ .int()
119
+ .min(1, 'Message limit must be at least 1')
120
+ .max(MAX_MESSAGE_LIMIT, `Message limit must be ${MAX_MESSAGE_LIMIT} or less`)
121
+ .default(DEFAULT_MESSAGE_LIMIT),
122
+ });
123
+ /**
124
+ * Creates the thread_get tool.
125
+ */
126
+ export function createThreadGetTool(options) {
127
+ const { client, logger, userId } = options;
128
+ return {
129
+ name: 'thread_get',
130
+ description: 'Get a thread with its message history. Use to view the full conversation in a thread.',
131
+ parameters: ThreadGetParamsSchema,
132
+ async execute(params) {
133
+ // Validate parameters
134
+ const parseResult = ThreadGetParamsSchema.safeParse(params);
135
+ if (!parseResult.success) {
136
+ const errorMessage = parseResult.error.errors
137
+ .map((e) => `${e.path.join('.')}: ${e.message}`)
138
+ .join(', ');
139
+ return { success: false, error: errorMessage };
140
+ }
141
+ const { threadId, messageLimit } = parseResult.data;
142
+ logger.info('thread_get invoked', {
143
+ userId,
144
+ threadId,
145
+ messageLimit,
146
+ });
147
+ try {
148
+ const queryParams = new URLSearchParams();
149
+ queryParams.set('messageLimit', String(messageLimit));
150
+ const response = await client.get(`/api/threads/${threadId}?${queryParams}`, { userId });
151
+ if (!response.success) {
152
+ logger.error('thread_get API error', {
153
+ userId,
154
+ threadId,
155
+ status: response.error.status,
156
+ code: response.error.code,
157
+ });
158
+ return {
159
+ success: false,
160
+ error: response.error.message || 'Failed to get thread',
161
+ };
162
+ }
163
+ const { thread, messages } = response.data;
164
+ logger.debug('thread_get completed', {
165
+ userId,
166
+ threadId,
167
+ messageCount: messages.length,
168
+ });
169
+ // Format content for display
170
+ const contact = thread.contactName || thread.endpointValue || 'Unknown';
171
+ const header = `Thread with ${contact} [${thread.channel}]`;
172
+ const messageContent = messages.length > 0
173
+ ? messages.map((m) => {
174
+ const prefix = m.direction === 'inbound' ? '←' : '→';
175
+ const timestamp = new Date(m.createdAt).toLocaleString();
176
+ return `${prefix} [${timestamp}] ${m.body}`;
177
+ }).join('\n')
178
+ : 'No messages in this thread.';
179
+ const content = `${header}\n\n${messageContent}`;
180
+ return {
181
+ success: true,
182
+ data: {
183
+ content,
184
+ details: { thread, messages, userId },
185
+ },
186
+ };
187
+ }
188
+ catch (error) {
189
+ logger.error('thread_get failed', {
190
+ userId,
191
+ threadId,
192
+ error: error instanceof Error ? error.message : String(error),
193
+ });
194
+ return {
195
+ success: false,
196
+ error: error instanceof Error ? error.message : 'An unexpected error occurred while getting thread.',
197
+ };
198
+ }
199
+ },
200
+ };
201
+ }
202
+ //# sourceMappingURL=threads.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"threads.js","sourceRoot":"","sources":["../../src/tools/threads.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Todo management tools implementation.
3
+ * Provides todo_list, todo_create, and todo_complete tools.
4
+ */
5
+ import { z } from 'zod';
6
+ import type { ApiClient } from '../api-client.js';
7
+ import type { Logger } from '../logger.js';
8
+ import type { PluginConfig } from '../config.js';
9
+ /** Parameters for todo_list tool */
10
+ export declare const TodoListParamsSchema: z.ZodObject<{
11
+ projectId: z.ZodOptional<z.ZodString>;
12
+ completed: z.ZodOptional<z.ZodBoolean>;
13
+ limit: z.ZodOptional<z.ZodNumber>;
14
+ offset: z.ZodOptional<z.ZodNumber>;
15
+ }, "strip", z.ZodTypeAny, {
16
+ limit?: number | undefined;
17
+ completed?: boolean | undefined;
18
+ offset?: number | undefined;
19
+ projectId?: string | undefined;
20
+ }, {
21
+ limit?: number | undefined;
22
+ completed?: boolean | undefined;
23
+ offset?: number | undefined;
24
+ projectId?: string | undefined;
25
+ }>;
26
+ export type TodoListParams = z.infer<typeof TodoListParamsSchema>;
27
+ /** Todo item from API */
28
+ export interface Todo {
29
+ id: string;
30
+ title: string;
31
+ completed: boolean;
32
+ projectId?: string;
33
+ dueDate?: string;
34
+ createdAt?: string;
35
+ updatedAt?: string;
36
+ }
37
+ /** Successful list result */
38
+ export interface TodoListSuccess {
39
+ success: true;
40
+ data: {
41
+ content: string;
42
+ details: {
43
+ todos: Todo[];
44
+ total: number;
45
+ userId: string;
46
+ };
47
+ };
48
+ }
49
+ /** Failed result */
50
+ export interface TodoFailure {
51
+ success: false;
52
+ error: string;
53
+ }
54
+ export type TodoListResult = TodoListSuccess | TodoFailure;
55
+ /** Tool configuration */
56
+ export interface TodoToolOptions {
57
+ client: ApiClient;
58
+ logger: Logger;
59
+ config: PluginConfig;
60
+ userId: string;
61
+ }
62
+ /** Tool definition */
63
+ export interface TodoListTool {
64
+ name: string;
65
+ description: string;
66
+ parameters: typeof TodoListParamsSchema;
67
+ execute: (params: TodoListParams) => Promise<TodoListResult>;
68
+ }
69
+ /**
70
+ * Creates the todo_list tool.
71
+ */
72
+ export declare function createTodoListTool(options: TodoToolOptions): TodoListTool;
73
+ /** Parameters for todo_create tool */
74
+ export declare const TodoCreateParamsSchema: z.ZodObject<{
75
+ title: z.ZodString;
76
+ projectId: z.ZodOptional<z.ZodString>;
77
+ dueDate: z.ZodOptional<z.ZodString>;
78
+ }, "strip", z.ZodTypeAny, {
79
+ title: string;
80
+ projectId?: string | undefined;
81
+ dueDate?: string | undefined;
82
+ }, {
83
+ title: string;
84
+ projectId?: string | undefined;
85
+ dueDate?: string | undefined;
86
+ }>;
87
+ export type TodoCreateParams = z.infer<typeof TodoCreateParamsSchema>;
88
+ /** Successful create result */
89
+ export interface TodoCreateSuccess {
90
+ success: true;
91
+ data: {
92
+ content: string;
93
+ details: {
94
+ id: string;
95
+ title: string;
96
+ userId: string;
97
+ };
98
+ };
99
+ }
100
+ export type TodoCreateResult = TodoCreateSuccess | TodoFailure;
101
+ export interface TodoCreateTool {
102
+ name: string;
103
+ description: string;
104
+ parameters: typeof TodoCreateParamsSchema;
105
+ execute: (params: TodoCreateParams) => Promise<TodoCreateResult>;
106
+ }
107
+ /**
108
+ * Creates the todo_create tool.
109
+ */
110
+ export declare function createTodoCreateTool(options: TodoToolOptions): TodoCreateTool;
111
+ /** Parameters for todo_complete tool */
112
+ export declare const TodoCompleteParamsSchema: z.ZodObject<{
113
+ id: z.ZodString;
114
+ }, "strip", z.ZodTypeAny, {
115
+ id: string;
116
+ }, {
117
+ id: string;
118
+ }>;
119
+ export type TodoCompleteParams = z.infer<typeof TodoCompleteParamsSchema>;
120
+ /** Successful complete result */
121
+ export interface TodoCompleteSuccess {
122
+ success: true;
123
+ data: {
124
+ content: string;
125
+ details: {
126
+ id: string;
127
+ userId: string;
128
+ };
129
+ };
130
+ }
131
+ export type TodoCompleteResult = TodoCompleteSuccess | TodoFailure;
132
+ export interface TodoCompleteTool {
133
+ name: string;
134
+ description: string;
135
+ parameters: typeof TodoCompleteParamsSchema;
136
+ execute: (params: TodoCompleteParams) => Promise<TodoCompleteResult>;
137
+ }
138
+ /**
139
+ * Creates the todo_complete tool.
140
+ */
141
+ export declare function createTodoCompleteTool(options: TodoToolOptions): TodoCompleteTool;
142
+ //# sourceMappingURL=todos.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"todos.d.ts","sourceRoot":"","sources":["../../src/tools/todos.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,308 @@
1
+ /**
2
+ * Todo management tools implementation.
3
+ * Provides todo_list, todo_create, and todo_complete tools.
4
+ */
5
+ import { z } from 'zod';
6
+ /** UUID validation regex */
7
+ const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
8
+ /** ISO 8601 date format (YYYY-MM-DD) */
9
+ const ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/;
10
+ // ==================== todo_list ====================
11
+ /** Parameters for todo_list tool */
12
+ export const TodoListParamsSchema = z.object({
13
+ projectId: z.string().optional(),
14
+ completed: z.boolean().optional(),
15
+ limit: z.number().int().min(1).max(200).optional(),
16
+ offset: z.number().int().min(0).optional(),
17
+ });
18
+ /**
19
+ * Create a sanitized error message.
20
+ */
21
+ function sanitizeErrorMessage(error) {
22
+ if (error instanceof Error) {
23
+ const message = error.message
24
+ .replace(/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, '[host]')
25
+ .replace(/:\d{2,5}\b/g, '')
26
+ .replace(/\b(?:localhost|internal[-\w]*)\b/gi, '[internal]');
27
+ if (message.includes('[internal]') || message.includes('[host]')) {
28
+ return 'Operation failed. Please try again.';
29
+ }
30
+ return message;
31
+ }
32
+ return 'An unexpected error occurred.';
33
+ }
34
+ /**
35
+ * Strip HTML tags from a string.
36
+ * Also removes content inside script and style tags for security.
37
+ */
38
+ function stripHtml(text) {
39
+ return text
40
+ .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
41
+ .replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '')
42
+ .replace(/<[^>]*>/g, '')
43
+ .trim();
44
+ }
45
+ /**
46
+ * Validate UUID format.
47
+ */
48
+ function isValidUuid(id) {
49
+ return UUID_REGEX.test(id);
50
+ }
51
+ /**
52
+ * Validate ISO 8601 date format (YYYY-MM-DD).
53
+ */
54
+ function isValidIsoDate(date) {
55
+ if (!ISO_DATE_REGEX.test(date)) {
56
+ return false;
57
+ }
58
+ // Also validate it's a real date
59
+ const parsed = new Date(date);
60
+ return !isNaN(parsed.getTime());
61
+ }
62
+ /**
63
+ * Creates the todo_list tool.
64
+ */
65
+ export function createTodoListTool(options) {
66
+ const { client, logger, userId } = options;
67
+ return {
68
+ name: 'todo_list',
69
+ description: 'List todos. Optionally filter by project ID or completion status.',
70
+ parameters: TodoListParamsSchema,
71
+ async execute(params) {
72
+ const parseResult = TodoListParamsSchema.safeParse(params);
73
+ if (!parseResult.success) {
74
+ const errorMessage = parseResult.error.errors
75
+ .map((e) => `${e.path.join('.')}: ${e.message}`)
76
+ .join(', ');
77
+ return { success: false, error: errorMessage };
78
+ }
79
+ const { projectId, completed, limit = 50, offset = 0 } = parseResult.data;
80
+ // Validate projectId if provided
81
+ if (projectId && !isValidUuid(projectId)) {
82
+ return { success: false, error: 'Invalid projectId format. Expected UUID.' };
83
+ }
84
+ logger.info('todo_list invoked', { userId, projectId, completed, limit, offset });
85
+ try {
86
+ const queryParams = new URLSearchParams({
87
+ limit: String(limit),
88
+ offset: String(offset),
89
+ });
90
+ if (projectId) {
91
+ queryParams.set('projectId', projectId);
92
+ }
93
+ if (completed !== undefined) {
94
+ queryParams.set('completed', String(completed));
95
+ }
96
+ const response = await client.get(`/api/todos?${queryParams.toString()}`, { userId });
97
+ if (!response.success) {
98
+ logger.error('todo_list API error', {
99
+ userId,
100
+ status: response.error.status,
101
+ code: response.error.code,
102
+ });
103
+ return {
104
+ success: false,
105
+ error: response.error.message || 'Failed to list todos',
106
+ };
107
+ }
108
+ const todos = response.data.todos ?? response.data.items ?? [];
109
+ const total = response.data.total ?? todos.length;
110
+ if (todos.length === 0) {
111
+ return {
112
+ success: true,
113
+ data: {
114
+ content: 'No todos found.',
115
+ details: { todos: [], total: 0, userId },
116
+ },
117
+ };
118
+ }
119
+ const content = todos
120
+ .map((t) => {
121
+ const checkbox = t.completed ? '[x]' : '[ ]';
122
+ const dueStr = t.dueDate ? ` (due: ${t.dueDate})` : '';
123
+ return `- ${checkbox} ${t.title}${dueStr}`;
124
+ })
125
+ .join('\n');
126
+ logger.debug('todo_list completed', { userId, count: todos.length });
127
+ return {
128
+ success: true,
129
+ data: {
130
+ content,
131
+ details: { todos, total, userId },
132
+ },
133
+ };
134
+ }
135
+ catch (error) {
136
+ logger.error('todo_list failed', {
137
+ userId,
138
+ error: error instanceof Error ? error.message : String(error),
139
+ });
140
+ return { success: false, error: sanitizeErrorMessage(error) };
141
+ }
142
+ },
143
+ };
144
+ }
145
+ // ==================== todo_create ====================
146
+ /** Parameters for todo_create tool */
147
+ export const TodoCreateParamsSchema = z.object({
148
+ title: z
149
+ .string()
150
+ .min(1, 'Todo title is required')
151
+ .max(500, 'Todo title must be 500 characters or less'),
152
+ projectId: z.string().optional(),
153
+ dueDate: z.string().optional(),
154
+ });
155
+ /**
156
+ * Creates the todo_create tool.
157
+ */
158
+ export function createTodoCreateTool(options) {
159
+ const { client, logger, userId } = options;
160
+ return {
161
+ name: 'todo_create',
162
+ description: 'Create a new todo item. Optionally associate with a project and set a due date.',
163
+ parameters: TodoCreateParamsSchema,
164
+ async execute(params) {
165
+ const parseResult = TodoCreateParamsSchema.safeParse(params);
166
+ if (!parseResult.success) {
167
+ const errorMessage = parseResult.error.errors
168
+ .map((e) => `${e.path.join('.')}: ${e.message}`)
169
+ .join(', ');
170
+ return { success: false, error: errorMessage };
171
+ }
172
+ const { title, projectId, dueDate } = parseResult.data;
173
+ // Sanitize input
174
+ const sanitizedTitle = stripHtml(title);
175
+ if (sanitizedTitle.length === 0) {
176
+ return { success: false, error: 'Todo title cannot be empty after sanitization' };
177
+ }
178
+ // Validate projectId if provided
179
+ if (projectId && !isValidUuid(projectId)) {
180
+ return { success: false, error: 'Invalid projectId format. Expected UUID.' };
181
+ }
182
+ // Validate dueDate if provided
183
+ if (dueDate && !isValidIsoDate(dueDate)) {
184
+ return { success: false, error: 'Invalid date format. Expected ISO 8601 (YYYY-MM-DD).' };
185
+ }
186
+ logger.info('todo_create invoked', {
187
+ userId,
188
+ titleLength: sanitizedTitle.length,
189
+ hasProjectId: !!projectId,
190
+ hasDueDate: !!dueDate,
191
+ });
192
+ try {
193
+ const body = {
194
+ title: sanitizedTitle,
195
+ };
196
+ if (projectId) {
197
+ body.projectId = projectId;
198
+ }
199
+ if (dueDate) {
200
+ body.dueDate = dueDate;
201
+ }
202
+ const response = await client.post('/api/todos', body, { userId });
203
+ if (!response.success) {
204
+ logger.error('todo_create API error', {
205
+ userId,
206
+ status: response.error.status,
207
+ code: response.error.code,
208
+ });
209
+ return {
210
+ success: false,
211
+ error: response.error.message || 'Failed to create todo',
212
+ };
213
+ }
214
+ const newTodo = response.data;
215
+ logger.debug('todo_create completed', {
216
+ userId,
217
+ todoId: newTodo.id,
218
+ });
219
+ return {
220
+ success: true,
221
+ data: {
222
+ content: `Created todo "${sanitizedTitle}" (ID: ${newTodo.id})`,
223
+ details: {
224
+ id: newTodo.id,
225
+ title: sanitizedTitle,
226
+ userId,
227
+ },
228
+ },
229
+ };
230
+ }
231
+ catch (error) {
232
+ logger.error('todo_create failed', {
233
+ userId,
234
+ error: error instanceof Error ? error.message : String(error),
235
+ });
236
+ return { success: false, error: sanitizeErrorMessage(error) };
237
+ }
238
+ },
239
+ };
240
+ }
241
+ // ==================== todo_complete ====================
242
+ /** Parameters for todo_complete tool */
243
+ export const TodoCompleteParamsSchema = z.object({
244
+ id: z.string().min(1, 'Todo ID is required'),
245
+ });
246
+ /**
247
+ * Creates the todo_complete tool.
248
+ */
249
+ export function createTodoCompleteTool(options) {
250
+ const { client, logger, userId } = options;
251
+ return {
252
+ name: 'todo_complete',
253
+ description: 'Mark a todo as completed. This operation is idempotent.',
254
+ parameters: TodoCompleteParamsSchema,
255
+ async execute(params) {
256
+ const parseResult = TodoCompleteParamsSchema.safeParse(params);
257
+ if (!parseResult.success) {
258
+ const errorMessage = parseResult.error.errors
259
+ .map((e) => `${e.path.join('.')}: ${e.message}`)
260
+ .join(', ');
261
+ return { success: false, error: errorMessage };
262
+ }
263
+ const { id } = parseResult.data;
264
+ // Validate UUID format
265
+ if (!isValidUuid(id)) {
266
+ return { success: false, error: 'Invalid todo ID format. Expected UUID.' };
267
+ }
268
+ logger.info('todo_complete invoked', { userId, todoId: id });
269
+ try {
270
+ const response = await client.post(`/api/todos/${id}/complete`, {}, { userId });
271
+ if (!response.success) {
272
+ if (response.error.code === 'NOT_FOUND') {
273
+ return { success: false, error: 'Todo not found.' };
274
+ }
275
+ logger.error('todo_complete API error', {
276
+ userId,
277
+ todoId: id,
278
+ status: response.error.status,
279
+ });
280
+ return {
281
+ success: false,
282
+ error: response.error.message || 'Failed to complete todo',
283
+ };
284
+ }
285
+ logger.debug('todo_complete completed', { userId, todoId: id });
286
+ return {
287
+ success: true,
288
+ data: {
289
+ content: 'Todo marked as completed.',
290
+ details: {
291
+ id,
292
+ userId,
293
+ },
294
+ },
295
+ };
296
+ }
297
+ catch (error) {
298
+ logger.error('todo_complete failed', {
299
+ userId,
300
+ todoId: id,
301
+ error: error instanceof Error ? error.message : String(error),
302
+ });
303
+ return { success: false, error: sanitizeErrorMessage(error) };
304
+ }
305
+ },
306
+ };
307
+ }
308
+ //# sourceMappingURL=todos.js.map