loopwork 0.3.0 → 0.3.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 (46) hide show
  1. package/bin/loopwork +0 -0
  2. package/package.json +48 -4
  3. package/src/backends/github.ts +6 -3
  4. package/src/backends/json.ts +28 -10
  5. package/src/commands/run.ts +2 -2
  6. package/src/contracts/config.ts +3 -75
  7. package/src/contracts/index.ts +0 -6
  8. package/src/core/cli.ts +25 -16
  9. package/src/core/state.ts +10 -4
  10. package/src/core/utils.ts +10 -4
  11. package/src/monitor/index.ts +56 -34
  12. package/src/plugins/index.ts +9 -131
  13. package/examples/README.md +0 -70
  14. package/examples/basic-json-backend/.specs/tasks/TASK-001.md +0 -22
  15. package/examples/basic-json-backend/.specs/tasks/TASK-002.md +0 -23
  16. package/examples/basic-json-backend/.specs/tasks/TASK-003.md +0 -37
  17. package/examples/basic-json-backend/.specs/tasks/tasks.json +0 -19
  18. package/examples/basic-json-backend/README.md +0 -32
  19. package/examples/basic-json-backend/TESTING.md +0 -184
  20. package/examples/basic-json-backend/hello.test.ts +0 -9
  21. package/examples/basic-json-backend/hello.ts +0 -3
  22. package/examples/basic-json-backend/loopwork.config.js +0 -35
  23. package/examples/basic-json-backend/math.test.ts +0 -29
  24. package/examples/basic-json-backend/math.ts +0 -3
  25. package/examples/basic-json-backend/package.json +0 -15
  26. package/examples/basic-json-backend/quick-start.sh +0 -80
  27. package/loopwork.config.ts +0 -164
  28. package/src/plugins/asana.ts +0 -192
  29. package/src/plugins/cost-tracking.ts +0 -402
  30. package/src/plugins/discord.ts +0 -269
  31. package/src/plugins/everhour.ts +0 -335
  32. package/src/plugins/telegram/bot.ts +0 -517
  33. package/src/plugins/telegram/index.ts +0 -6
  34. package/src/plugins/telegram/notifications.ts +0 -198
  35. package/src/plugins/todoist.ts +0 -261
  36. package/test/backends.test.ts +0 -929
  37. package/test/cli.test.ts +0 -145
  38. package/test/config.test.ts +0 -90
  39. package/test/e2e.test.ts +0 -458
  40. package/test/github-tasks.test.ts +0 -191
  41. package/test/loopwork-config-types.test.ts +0 -288
  42. package/test/monitor.test.ts +0 -123
  43. package/test/plugins.test.ts +0 -1175
  44. package/test/state.test.ts +0 -295
  45. package/test/utils.test.ts +0 -60
  46. package/tsconfig.json +0 -20
@@ -1,261 +0,0 @@
1
- /**
2
- * Todoist Plugin for Loopwork
3
- *
4
- * Syncs task status with Todoist projects.
5
- * Tasks should have metadata.todoistId set to the Todoist task ID.
6
- *
7
- * Setup:
8
- * 1. Get API token from Todoist Settings > Integrations > Developer
9
- * 2. Set TODOIST_API_TOKEN env var
10
- * 3. Add todoistId to task metadata in your tasks file
11
- */
12
-
13
- import type { LoopworkPlugin, PluginTask } from '../contracts'
14
-
15
- export interface TodoistConfig {
16
- apiToken?: string
17
- projectId?: string
18
- /** Sync status changes to Todoist (complete tasks) */
19
- syncStatus?: boolean
20
- /** Add comments to tasks on events */
21
- addComments?: boolean
22
- }
23
-
24
- interface TodoistTask {
25
- id: string
26
- content: string
27
- description: string
28
- is_completed: boolean
29
- project_id: string
30
- priority: number
31
- labels: string[]
32
- }
33
-
34
- interface TodoistComment {
35
- id: string
36
- task_id: string
37
- content: string
38
- posted_at: string
39
- }
40
-
41
- export class TodoistClient {
42
- private baseUrl = 'https://api.todoist.com/rest/v2'
43
- private apiToken: string
44
-
45
- constructor(apiToken: string) {
46
- this.apiToken = apiToken
47
- }
48
-
49
- private async request<T>(
50
- method: string,
51
- endpoint: string,
52
- body?: Record<string, unknown>
53
- ): Promise<T> {
54
- const url = `${this.baseUrl}${endpoint}`
55
- const response = await fetch(url, {
56
- method,
57
- headers: {
58
- 'Authorization': `Bearer ${this.apiToken}`,
59
- 'Content-Type': 'application/json',
60
- },
61
- body: body ? JSON.stringify(body) : undefined,
62
- })
63
-
64
- if (!response.ok) {
65
- const error = await response.text()
66
- throw new Error(`Todoist API error: ${response.status} - ${error}`)
67
- }
68
-
69
- // Some endpoints return empty response (204)
70
- if (response.status === 204) {
71
- return {} as T
72
- }
73
-
74
- return response.json() as Promise<T>
75
- }
76
-
77
- /**
78
- * Get a task by ID
79
- */
80
- async getTask(taskId: string): Promise<TodoistTask> {
81
- return this.request('GET', `/tasks/${taskId}`)
82
- }
83
-
84
- /**
85
- * Create a new task
86
- */
87
- async createTask(content: string, options?: {
88
- description?: string
89
- projectId?: string
90
- priority?: 1 | 2 | 3 | 4
91
- labels?: string[]
92
- }): Promise<TodoistTask> {
93
- return this.request('POST', '/tasks', {
94
- content,
95
- description: options?.description,
96
- project_id: options?.projectId,
97
- priority: options?.priority,
98
- labels: options?.labels,
99
- })
100
- }
101
-
102
- /**
103
- * Update a task
104
- */
105
- async updateTask(taskId: string, updates: {
106
- content?: string
107
- description?: string
108
- priority?: 1 | 2 | 3 | 4
109
- labels?: string[]
110
- }): Promise<TodoistTask> {
111
- return this.request('POST', `/tasks/${taskId}`, updates)
112
- }
113
-
114
- /**
115
- * Complete (close) a task
116
- */
117
- async completeTask(taskId: string): Promise<void> {
118
- await this.request('POST', `/tasks/${taskId}/close`)
119
- }
120
-
121
- /**
122
- * Reopen a completed task
123
- */
124
- async reopenTask(taskId: string): Promise<void> {
125
- await this.request('POST', `/tasks/${taskId}/reopen`)
126
- }
127
-
128
- /**
129
- * Delete a task
130
- */
131
- async deleteTask(taskId: string): Promise<void> {
132
- await this.request('DELETE', `/tasks/${taskId}`)
133
- }
134
-
135
- /**
136
- * Get all tasks in a project
137
- */
138
- async getProjectTasks(projectId: string): Promise<TodoistTask[]> {
139
- return this.request('GET', `/tasks?project_id=${projectId}`)
140
- }
141
-
142
- /**
143
- * Add a comment to a task
144
- */
145
- async addComment(taskId: string, content: string): Promise<TodoistComment> {
146
- return this.request('POST', '/comments', {
147
- task_id: taskId,
148
- content,
149
- })
150
- }
151
-
152
- /**
153
- * Get comments for a task
154
- */
155
- async getComments(taskId: string): Promise<TodoistComment[]> {
156
- return this.request('GET', `/comments?task_id=${taskId}`)
157
- }
158
-
159
- /**
160
- * Get all projects
161
- */
162
- async getProjects(): Promise<Array<{ id: string; name: string }>> {
163
- return this.request('GET', '/projects')
164
- }
165
- }
166
-
167
- /**
168
- * Create Todoist plugin wrapper
169
- */
170
- export function withTodoist(config: TodoistConfig = {}) {
171
- const apiToken = config.apiToken || process.env.TODOIST_API_TOKEN
172
-
173
- return (baseConfig: any) => ({
174
- ...baseConfig,
175
- todoist: {
176
- apiToken,
177
- projectId: config.projectId || process.env.TODOIST_PROJECT_ID,
178
- syncStatus: config.syncStatus ?? true,
179
- addComments: config.addComments ?? true,
180
- },
181
- })
182
- }
183
-
184
- /** Helper to get Todoist ID from task metadata */
185
- function getTodoistId(task: PluginTask): string | undefined {
186
- return task.metadata?.todoistId as string | undefined
187
- }
188
-
189
- /**
190
- * Create Todoist hook plugin
191
- *
192
- * Tasks should have metadata.todoistId set for Todoist integration.
193
- */
194
- export function createTodoistPlugin(config: TodoistConfig = {}): LoopworkPlugin {
195
- const apiToken = config.apiToken || process.env.TODOIST_API_TOKEN || ''
196
- const addComments = config.addComments ?? true
197
-
198
- if (!apiToken) {
199
- return {
200
- name: 'todoist',
201
- onConfigLoad: (cfg) => {
202
- console.warn('Todoist plugin: Missing TODOIST_API_TOKEN')
203
- return cfg
204
- },
205
- }
206
- }
207
-
208
- const client = new TodoistClient(apiToken)
209
-
210
- return {
211
- name: 'todoist',
212
-
213
- async onTaskStart(task) {
214
- const todoistId = getTodoistId(task)
215
- if (!todoistId || !addComments) return
216
-
217
- try {
218
- await client.addComment(todoistId, `🔄 Loopwork started working on this task`)
219
- } catch (e: any) {
220
- console.warn(`Todoist: Failed to add comment: ${e.message}`)
221
- }
222
- },
223
-
224
- async onTaskComplete(task, result) {
225
- const todoistId = getTodoistId(task)
226
- if (!todoistId) return
227
-
228
- try {
229
- if (config.syncStatus !== false) {
230
- await client.completeTask(todoistId)
231
- }
232
- if (addComments) {
233
- await client.addComment(
234
- todoistId,
235
- `✅ Completed by Loopwork in ${Math.round(result.duration)}s`
236
- )
237
- }
238
- } catch (e: any) {
239
- console.warn(`Todoist: Failed to update task: ${e.message}`)
240
- }
241
- },
242
-
243
- async onTaskFailed(task, error) {
244
- const todoistId = getTodoistId(task)
245
- if (!todoistId || !addComments) return
246
-
247
- try {
248
- await client.addComment(
249
- todoistId,
250
- `❌ Loopwork failed: ${error.slice(0, 200)}`
251
- )
252
- } catch (e: any) {
253
- console.warn(`Todoist: Failed to add comment: ${e.message}`)
254
- }
255
- },
256
-
257
- async onLoopEnd(stats) {
258
- console.log(`📋 Todoist sync: ${stats.completed} tasks synced`)
259
- },
260
- }
261
- }