@amalgm/automations 0.1.2 → 0.2.0

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 (65) hide show
  1. package/AXIOMS.md +11 -0
  2. package/PURPOSE.md +10 -3
  3. package/README.md +60 -113
  4. package/dist/host/auth.d.ts +11 -0
  5. package/dist/host/auth.js +89 -0
  6. package/dist/host/config.d.ts +8 -0
  7. package/dist/host/config.js +29 -0
  8. package/dist/host/index.d.ts +3 -0
  9. package/dist/host/index.js +3 -0
  10. package/dist/host/main.d.ts +1 -0
  11. package/dist/host/main.js +47 -0
  12. package/dist/host/server.d.ts +11 -0
  13. package/dist/host/server.js +53 -0
  14. package/dist/src/automations.js +4 -1
  15. package/dist/src/client.d.ts +3 -1
  16. package/dist/src/client.js +6 -4
  17. package/dist/src/contract.d.ts +17 -1
  18. package/dist/src/crud/automations.d.ts +3 -0
  19. package/dist/src/crud/automations.js +55 -0
  20. package/dist/src/crud/context.d.ts +13 -0
  21. package/dist/src/crud/context.js +34 -0
  22. package/dist/src/crud/repository.d.ts +35 -0
  23. package/dist/src/crud/repository.js +1 -0
  24. package/dist/src/crud/runs.d.ts +3 -0
  25. package/dist/src/crud/runs.js +19 -0
  26. package/dist/src/crud/triggers.d.ts +3 -0
  27. package/dist/src/crud/triggers.js +118 -0
  28. package/dist/src/crud/workflow.d.ts +3 -0
  29. package/dist/src/crud/workflow.js +42 -0
  30. package/dist/src/crud.d.ts +3 -33
  31. package/dist/src/crud.js +10 -237
  32. package/dist/src/executor.d.ts +16 -0
  33. package/dist/src/executor.js +76 -0
  34. package/dist/src/index.d.ts +8 -2
  35. package/dist/src/index.js +5 -0
  36. package/dist/src/machine-client.d.ts +7 -0
  37. package/dist/src/machine-client.js +30 -0
  38. package/dist/src/machine-http.d.ts +5 -0
  39. package/dist/src/machine-http.js +40 -0
  40. package/dist/src/machine.d.ts +41 -0
  41. package/dist/src/machine.js +43 -0
  42. package/dist/src/mcp.js +2 -2
  43. package/dist/src/schema.d.ts +8 -0
  44. package/dist/src/schema.js +2 -0
  45. package/dist/src/supabase-crud/automations.d.ts +5 -0
  46. package/dist/src/supabase-crud/automations.js +36 -0
  47. package/dist/src/supabase-crud/mappers.d.ts +8 -0
  48. package/dist/src/supabase-crud/mappers.js +81 -0
  49. package/dist/src/supabase-crud/rows.d.ts +56 -0
  50. package/dist/src/supabase-crud/rows.js +1 -0
  51. package/dist/src/supabase-crud/rpc.d.ts +10 -0
  52. package/dist/src/supabase-crud/rpc.js +19 -0
  53. package/dist/src/supabase-crud/triggers.d.ts +5 -0
  54. package/dist/src/supabase-crud/triggers.js +46 -0
  55. package/dist/src/supabase-crud/workflow-runs.d.ts +5 -0
  56. package/dist/src/supabase-crud/workflow-runs.js +41 -0
  57. package/dist/src/supabase-crud.d.ts +5 -41
  58. package/dist/src/supabase-crud.js +4 -267
  59. package/dist/src/supabase-machine.d.ts +16 -0
  60. package/dist/src/supabase-machine.js +63 -0
  61. package/dist/src/supabase-store.js +1 -0
  62. package/dist/src/types.d.ts +1 -0
  63. package/package.json +11 -2
  64. package/skills/automations/SKILL.md +52 -0
  65. package/supabase/migrations/20260829010000_bounded_schedules_and_machine_claims.sql +311 -0
@@ -0,0 +1,46 @@
1
+ import { paged, schedule, webhook } from './mappers.js';
2
+ import { call } from './rpc.js';
3
+ export function triggerRepository(client) {
4
+ const list = async (functionName, userId, automationId, query, mapper) => paged(await call(client, functionName, {
5
+ p_user_id: userId,
6
+ p_automation_id: automationId,
7
+ p_limit: query.limit,
8
+ p_offset: query.offset,
9
+ }), mapper, query);
10
+ return {
11
+ createScheduleTrigger: async (userId, automationId, input) => schedule(await call(client, 'create_amalgm_schedule_trigger', { p_user_id: userId, p_automation_id: automationId, p_trigger: input })),
12
+ listScheduleTriggers: (userId, automationId, query) => list('list_amalgm_schedule_triggers', userId, automationId, query, schedule),
13
+ getScheduleTrigger: async (userId, automationId, triggerId) => {
14
+ const row = await call(client, 'get_amalgm_schedule_trigger', {
15
+ p_user_id: userId, p_automation_id: automationId, p_trigger_id: triggerId,
16
+ });
17
+ return row ? schedule(row) : null;
18
+ },
19
+ updateScheduleTrigger: async (userId, automationId, triggerId, patch) => {
20
+ const row = await call(client, 'update_amalgm_schedule_trigger', {
21
+ p_user_id: userId, p_automation_id: automationId, p_trigger_id: triggerId, p_patch: patch,
22
+ });
23
+ return row ? schedule(row) : null;
24
+ },
25
+ deleteScheduleTrigger: (userId, automationId, triggerId) => call(client, 'delete_amalgm_schedule_trigger', { p_user_id: userId, p_automation_id: automationId, p_trigger_id: triggerId }),
26
+ createWebhookTrigger: async (userId, automationId, input) => webhook(await call(client, 'create_amalgm_webhook_trigger', { p_user_id: userId, p_automation_id: automationId, p_trigger: input })),
27
+ listWebhookTriggers: (userId, automationId, query) => list('list_amalgm_webhook_triggers', userId, automationId, query, webhook),
28
+ getWebhookTrigger: async (userId, automationId, triggerId) => {
29
+ const row = await call(client, 'get_amalgm_webhook_trigger', {
30
+ p_user_id: userId, p_automation_id: automationId, p_trigger_id: triggerId,
31
+ });
32
+ return row ? webhook(row) : null;
33
+ },
34
+ updateWebhookTrigger: async (userId, automationId, triggerId, patch) => {
35
+ const row = await call(client, 'update_amalgm_webhook_trigger', {
36
+ p_user_id: userId, p_automation_id: automationId, p_trigger_id: triggerId, p_patch: patch,
37
+ });
38
+ return row ? webhook(row) : null;
39
+ },
40
+ deleteWebhookTrigger: (userId, automationId, triggerId) => call(client, 'delete_amalgm_webhook_trigger', { p_user_id: userId, p_automation_id: automationId, p_trigger_id: triggerId }),
41
+ listTriggers: async (userId, automationId) => (await call(client, 'list_amalgm_triggers', {
42
+ p_user_id: userId,
43
+ p_automation_id: automationId,
44
+ })).map((row) => row.kind === 'schedule' ? schedule(row) : webhook(row)),
45
+ };
46
+ }
@@ -0,0 +1,5 @@
1
+ import type { AutomationCrudRepository } from '../crud.js';
2
+ import { type SupabaseRpcClient } from './rpc.js';
3
+ type Operations = Pick<AutomationCrudRepository, 'createWorkflow' | 'getWorkflow' | 'updateWorkflow' | 'deleteWorkflow' | 'listRuns' | 'getRun'>;
4
+ export declare function workflowRunRepository(client: SupabaseRpcClient): Operations;
5
+ export {};
@@ -0,0 +1,41 @@
1
+ import { paged, run, workflow } from './mappers.js';
2
+ import { call } from './rpc.js';
3
+ export function workflowRunRepository(client) {
4
+ return {
5
+ createWorkflow: async (userId, automationId, input) => workflow(await call(client, 'create_amalgm_workflow', { p_user_id: userId, p_automation_id: automationId, p_workflow: input })),
6
+ getWorkflow: async (userId, automationId) => {
7
+ const row = await call(client, 'get_amalgm_workflow', {
8
+ p_user_id: userId,
9
+ p_automation_id: automationId,
10
+ });
11
+ return row ? workflow(row) : null;
12
+ },
13
+ updateWorkflow: async (userId, automationId, patch) => {
14
+ const row = await call(client, 'update_amalgm_workflow', {
15
+ p_user_id: userId,
16
+ p_automation_id: automationId,
17
+ p_patch: patch,
18
+ });
19
+ return row ? workflow(row) : null;
20
+ },
21
+ deleteWorkflow: (userId, automationId) => call(client, 'delete_amalgm_workflow', {
22
+ p_user_id: userId,
23
+ p_automation_id: automationId,
24
+ }),
25
+ listRuns: async (userId, automationId, query) => paged(await call(client, 'list_amalgm_runs', {
26
+ p_user_id: userId,
27
+ p_automation_id: automationId,
28
+ p_status: query.status || null,
29
+ p_limit: query.limit,
30
+ p_offset: query.offset,
31
+ }), run, query),
32
+ getRun: async (userId, automationId, runId) => {
33
+ const row = await call(client, 'get_amalgm_run', {
34
+ p_user_id: userId,
35
+ p_automation_id: automationId,
36
+ p_run_id: runId,
37
+ });
38
+ return row ? run(row) : null;
39
+ },
40
+ };
41
+ }
@@ -1,44 +1,8 @@
1
- import type { Automation, AutomationRun, CreateAutomation, CreateWebhookTrigger, CreateWorkflow, Page, ScheduleTrigger, Trigger, UpdateAutomation, UpdateWebhookTrigger, UpdateWorkflow, WebhookTrigger, Workflow } from './contract.js';
2
- import type { AutomationCrudRepository, NormalizedListAutomations, NormalizedListRuns, StoredScheduleCreate, StoredScheduleUpdate } from './crud.js';
3
- export interface SupabaseRpcClient {
4
- rpc(functionName: string, arguments_: Record<string, unknown>): PromiseLike<{
5
- data: unknown;
6
- error: {
7
- code?: string;
8
- message?: string;
9
- } | null;
10
- }>;
1
+ import type { AutomationCrudRepository } from './crud.js';
2
+ import type { SupabaseRpcClient } from './supabase-crud/rpc.js';
3
+ export type { SupabaseRpcClient } from './supabase-crud/rpc.js';
4
+ export interface SupabaseAutomationCrudRepository extends AutomationCrudRepository {
11
5
  }
12
- export declare class SupabaseAutomationCrudRepository implements AutomationCrudRepository {
13
- #private;
14
- private readonly client;
6
+ export declare class SupabaseAutomationCrudRepository {
15
7
  constructor(client: SupabaseRpcClient);
16
- createAutomation(userId: string, input: Required<CreateAutomation>): Promise<Automation>;
17
- listAutomations(userId: string, query: NormalizedListAutomations): Promise<Page<Automation>>;
18
- getAutomation(userId: string, automationId: string): Promise<Automation | null>;
19
- updateAutomation(userId: string, automationId: string, patch: UpdateAutomation): Promise<Automation | null>;
20
- deleteAutomation(userId: string, automationId: string): Promise<boolean>;
21
- createScheduleTrigger(userId: string, automationId: string, input: StoredScheduleCreate): Promise<ScheduleTrigger>;
22
- listScheduleTriggers(userId: string, automationId: string, query: Required<{
23
- limit?: number;
24
- offset?: number;
25
- }>): Promise<Page<ScheduleTrigger>>;
26
- getScheduleTrigger(userId: string, automationId: string, triggerId: string): Promise<ScheduleTrigger | null>;
27
- updateScheduleTrigger(userId: string, automationId: string, triggerId: string, patch: StoredScheduleUpdate): Promise<ScheduleTrigger | null>;
28
- deleteScheduleTrigger(userId: string, automationId: string, triggerId: string): Promise<boolean>;
29
- createWebhookTrigger(userId: string, automationId: string, input: Required<CreateWebhookTrigger>): Promise<WebhookTrigger>;
30
- listWebhookTriggers(userId: string, automationId: string, query: Required<{
31
- limit?: number;
32
- offset?: number;
33
- }>): Promise<Page<WebhookTrigger>>;
34
- getWebhookTrigger(userId: string, automationId: string, triggerId: string): Promise<WebhookTrigger | null>;
35
- updateWebhookTrigger(userId: string, automationId: string, triggerId: string, patch: UpdateWebhookTrigger): Promise<WebhookTrigger | null>;
36
- deleteWebhookTrigger(userId: string, automationId: string, triggerId: string): Promise<boolean>;
37
- listTriggers(userId: string, automationId: string): Promise<Trigger[]>;
38
- createWorkflow(userId: string, automationId: string, input: Required<CreateWorkflow>): Promise<Workflow>;
39
- getWorkflow(userId: string, automationId: string): Promise<Workflow | null>;
40
- updateWorkflow(userId: string, automationId: string, patch: UpdateWorkflow): Promise<Workflow | null>;
41
- deleteWorkflow(userId: string, automationId: string): Promise<boolean>;
42
- listRuns(userId: string, automationId: string, query: NormalizedListRuns): Promise<Page<AutomationRun>>;
43
- getRun(userId: string, automationId: string, runId: string): Promise<AutomationRun | null>;
44
8
  }
@@ -1,271 +1,8 @@
1
- import { ConflictError } from './errors.js';
1
+ import { automationRepository } from './supabase-crud/automations.js';
2
+ import { triggerRepository } from './supabase-crud/triggers.js';
3
+ import { workflowRunRepository } from './supabase-crud/workflow-runs.js';
2
4
  export class SupabaseAutomationCrudRepository {
3
- client;
4
5
  constructor(client) {
5
- this.client = client;
6
+ Object.assign(this, automationRepository(client), triggerRepository(client), workflowRunRepository(client));
6
7
  }
7
- async createAutomation(userId, input) {
8
- return automation(await this.#call('create_amalgm_automation', {
9
- p_user_id: userId,
10
- p_automation: input,
11
- }));
12
- }
13
- async listAutomations(userId, query) {
14
- const result = await this.#call('list_amalgm_automations', {
15
- p_user_id: userId,
16
- p_target_id: query.targetId || null,
17
- p_enabled: query.enabled ?? null,
18
- p_limit: query.limit,
19
- p_offset: query.offset,
20
- });
21
- return paged(result, automation, query);
22
- }
23
- async getAutomation(userId, automationId) {
24
- const row = await this.#call('get_amalgm_automation', {
25
- p_user_id: userId,
26
- p_automation_id: automationId,
27
- });
28
- return row ? automation(row) : null;
29
- }
30
- async updateAutomation(userId, automationId, patch) {
31
- const row = await this.#call('update_amalgm_automation', {
32
- p_user_id: userId,
33
- p_automation_id: automationId,
34
- p_patch: patch,
35
- });
36
- return row ? automation(row) : null;
37
- }
38
- deleteAutomation(userId, automationId) {
39
- return this.#call('delete_amalgm_automation', {
40
- p_user_id: userId,
41
- p_automation_id: automationId,
42
- });
43
- }
44
- async createScheduleTrigger(userId, automationId, input) {
45
- return schedule(await this.#call('create_amalgm_schedule_trigger', {
46
- p_user_id: userId,
47
- p_automation_id: automationId,
48
- p_trigger: input,
49
- }));
50
- }
51
- async listScheduleTriggers(userId, automationId, query) {
52
- return this.#listTriggers('list_amalgm_schedule_triggers', userId, automationId, query, schedule);
53
- }
54
- async getScheduleTrigger(userId, automationId, triggerId) {
55
- const row = await this.#call('get_amalgm_schedule_trigger', {
56
- p_user_id: userId,
57
- p_automation_id: automationId,
58
- p_trigger_id: triggerId,
59
- });
60
- return row ? schedule(row) : null;
61
- }
62
- async updateScheduleTrigger(userId, automationId, triggerId, patch) {
63
- const row = await this.#call('update_amalgm_schedule_trigger', {
64
- p_user_id: userId,
65
- p_automation_id: automationId,
66
- p_trigger_id: triggerId,
67
- p_patch: patch,
68
- });
69
- return row ? schedule(row) : null;
70
- }
71
- deleteScheduleTrigger(userId, automationId, triggerId) {
72
- return this.#call('delete_amalgm_schedule_trigger', {
73
- p_user_id: userId,
74
- p_automation_id: automationId,
75
- p_trigger_id: triggerId,
76
- });
77
- }
78
- async createWebhookTrigger(userId, automationId, input) {
79
- return webhook(await this.#call('create_amalgm_webhook_trigger', {
80
- p_user_id: userId,
81
- p_automation_id: automationId,
82
- p_trigger: input,
83
- }));
84
- }
85
- async listWebhookTriggers(userId, automationId, query) {
86
- return this.#listTriggers('list_amalgm_webhook_triggers', userId, automationId, query, webhook);
87
- }
88
- async getWebhookTrigger(userId, automationId, triggerId) {
89
- const row = await this.#call('get_amalgm_webhook_trigger', {
90
- p_user_id: userId,
91
- p_automation_id: automationId,
92
- p_trigger_id: triggerId,
93
- });
94
- return row ? webhook(row) : null;
95
- }
96
- async updateWebhookTrigger(userId, automationId, triggerId, patch) {
97
- const row = await this.#call('update_amalgm_webhook_trigger', {
98
- p_user_id: userId,
99
- p_automation_id: automationId,
100
- p_trigger_id: triggerId,
101
- p_patch: patch,
102
- });
103
- return row ? webhook(row) : null;
104
- }
105
- deleteWebhookTrigger(userId, automationId, triggerId) {
106
- return this.#call('delete_amalgm_webhook_trigger', {
107
- p_user_id: userId,
108
- p_automation_id: automationId,
109
- p_trigger_id: triggerId,
110
- });
111
- }
112
- async listTriggers(userId, automationId) {
113
- const rows = await this.#call('list_amalgm_triggers', {
114
- p_user_id: userId,
115
- p_automation_id: automationId,
116
- });
117
- return rows.map((row) => row.kind === 'schedule' ? schedule(row) : webhook(row));
118
- }
119
- async createWorkflow(userId, automationId, input) {
120
- return workflow(await this.#call('create_amalgm_workflow', {
121
- p_user_id: userId,
122
- p_automation_id: automationId,
123
- p_workflow: input,
124
- }));
125
- }
126
- async getWorkflow(userId, automationId) {
127
- const row = await this.#call('get_amalgm_workflow', {
128
- p_user_id: userId,
129
- p_automation_id: automationId,
130
- });
131
- return row ? workflow(row) : null;
132
- }
133
- async updateWorkflow(userId, automationId, patch) {
134
- const row = await this.#call('update_amalgm_workflow', {
135
- p_user_id: userId,
136
- p_automation_id: automationId,
137
- p_patch: patch,
138
- });
139
- return row ? workflow(row) : null;
140
- }
141
- deleteWorkflow(userId, automationId) {
142
- return this.#call('delete_amalgm_workflow', {
143
- p_user_id: userId,
144
- p_automation_id: automationId,
145
- });
146
- }
147
- async listRuns(userId, automationId, query) {
148
- const result = await this.#call('list_amalgm_runs', {
149
- p_user_id: userId,
150
- p_automation_id: automationId,
151
- p_status: query.status || null,
152
- p_limit: query.limit,
153
- p_offset: query.offset,
154
- });
155
- return paged(result, run, query);
156
- }
157
- async getRun(userId, automationId, runId) {
158
- const row = await this.#call('get_amalgm_run', {
159
- p_user_id: userId,
160
- p_automation_id: automationId,
161
- p_run_id: runId,
162
- });
163
- return row ? run(row) : null;
164
- }
165
- async #listTriggers(functionName, userId, automationId, query, mapper) {
166
- const result = await this.#call(functionName, {
167
- p_user_id: userId,
168
- p_automation_id: automationId,
169
- p_limit: query.limit,
170
- p_offset: query.offset,
171
- });
172
- return paged(result, mapper, query);
173
- }
174
- async #call(functionName, arguments_) {
175
- const { data, error } = await this.client.rpc(functionName, arguments_);
176
- if (error) {
177
- if (error.code === '23505' || /duplicate key|unique constraint/i.test(error.message || '')) {
178
- throw new ConflictError(resource(functionName));
179
- }
180
- throw new Error(error.message || `Supabase function ${functionName} failed`);
181
- }
182
- return data;
183
- }
184
- }
185
- function resource(functionName) {
186
- if (functionName.includes('workflow'))
187
- return 'Workflow';
188
- if (functionName.includes('trigger'))
189
- return 'Trigger';
190
- if (functionName.includes('automation'))
191
- return 'Automation';
192
- return 'Automation resource';
193
- }
194
- function automation(row) {
195
- return {
196
- id: row.id,
197
- targetId: row.target_id,
198
- ...(row.name ? { name: row.name } : {}),
199
- ...(row.description ? { description: row.description } : {}),
200
- enabled: row.enabled,
201
- createdAt: timestamp(row.created_at),
202
- updatedAt: timestamp(row.updated_at),
203
- };
204
- }
205
- function schedule(row) {
206
- return {
207
- id: row.id,
208
- automationId: row.automation_id,
209
- kind: 'schedule',
210
- enabled: row.enabled,
211
- cron: row.cron,
212
- timezone: row.timezone,
213
- createdAt: timestamp(row.created_at),
214
- updatedAt: timestamp(row.updated_at),
215
- };
216
- }
217
- function webhook(row) {
218
- return {
219
- id: row.id,
220
- automationId: row.automation_id,
221
- kind: 'webhook',
222
- enabled: row.enabled,
223
- source: row.source,
224
- event: row.event,
225
- secretConfigured: row.secret_configured,
226
- createdAt: timestamp(row.created_at),
227
- updatedAt: timestamp(row.updated_at),
228
- };
229
- }
230
- function workflow(row) {
231
- return {
232
- id: row.id,
233
- automationId: row.automation_id,
234
- ...(row.name ? { name: row.name } : {}),
235
- script: row.script,
236
- ...(row.compiled !== null ? { compiled: row.compiled } : {}),
237
- ...(row.allowlist !== null ? { allowlist: row.allowlist } : {}),
238
- ...(row.limits !== null ? { limits: row.limits } : {}),
239
- createdAt: timestamp(row.created_at),
240
- updatedAt: timestamp(row.updated_at),
241
- };
242
- }
243
- function run(row) {
244
- return {
245
- id: row.id,
246
- automationId: row.automation_id,
247
- triggerId: row.trigger_id,
248
- workflowId: row.workflow_id,
249
- targetId: row.target_id,
250
- status: row.status,
251
- input: row.input,
252
- createdAt: timestamp(row.created_at),
253
- ...(row.sent_at ? { sentAt: timestamp(row.sent_at) } : {}),
254
- ...(row.started_at ? { startedAt: timestamp(row.started_at) } : {}),
255
- ...(row.finished_at ? { finishedAt: timestamp(row.finished_at) } : {}),
256
- ...(row.output !== null ? { output: row.output } : {}),
257
- ...(row.error !== null ? { error: row.error } : {}),
258
- };
259
- }
260
- function paged(result, mapper, query) {
261
- return {
262
- items: result.items.map(mapper),
263
- total: result.total,
264
- limit: query.limit,
265
- offset: query.offset,
266
- hasMore: query.offset + result.items.length < result.total,
267
- };
268
- }
269
- function timestamp(value) {
270
- return new Date(value).toISOString();
271
8
  }
@@ -0,0 +1,16 @@
1
+ import type { ClaimedAutomationRun, MachineRunRepository } from './machine.js';
2
+ export interface MachineRpcClient {
3
+ rpc(name: string, arguments_: Record<string, unknown>): PromiseLike<{
4
+ data: unknown;
5
+ error: {
6
+ message?: string;
7
+ } | null;
8
+ }>;
9
+ }
10
+ export declare class SupabaseMachineRunRepository implements MachineRunRepository {
11
+ #private;
12
+ private readonly client;
13
+ constructor(client: MachineRpcClient);
14
+ claim(input: Parameters<MachineRunRepository['claim']>[0]): Promise<ClaimedAutomationRun[]>;
15
+ update(input: Parameters<MachineRunRepository['update']>[0]): Promise<ClaimedAutomationRun | null>;
16
+ }
@@ -0,0 +1,63 @@
1
+ import { AutomationError } from './errors.js';
2
+ export class SupabaseMachineRunRepository {
3
+ client;
4
+ constructor(client) {
5
+ this.client = client;
6
+ }
7
+ async claim(input) {
8
+ const rows = await this.#call('claim_amalgm_machine_runs', {
9
+ p_user_id: input.userId,
10
+ p_target_id: input.targetId,
11
+ p_limit: input.limit,
12
+ p_lease_seconds: input.leaseSeconds,
13
+ });
14
+ return rows.map(run);
15
+ }
16
+ async update(input) {
17
+ const rows = await this.#call('update_amalgm_claimed_run', {
18
+ p_user_id: input.userId,
19
+ p_target_id: input.targetId,
20
+ p_run_id: input.runId,
21
+ p_lease_token: input.update.leaseToken,
22
+ p_update: update(input.update),
23
+ p_now: input.now.toISOString(),
24
+ });
25
+ return rows[0] ? run(rows[0]) : null;
26
+ }
27
+ async #call(name, arguments_) {
28
+ const { data, error } = await this.client.rpc(name, arguments_);
29
+ if (error)
30
+ throw new AutomationError('internal', error.message ?? `${name} failed`);
31
+ return data;
32
+ }
33
+ }
34
+ function run(row) {
35
+ return {
36
+ id: row.id,
37
+ userId: row.user_id,
38
+ targetId: row.target_id,
39
+ automationId: row.automation_id,
40
+ triggerId: row.trigger_id,
41
+ workflowId: row.workflow_id,
42
+ automation: row.automation_payload,
43
+ input: row.input,
44
+ status: row.status,
45
+ attempts: row.attempts,
46
+ leaseToken: row.lease_token,
47
+ leaseExpiresAt: timestamp(row.lease_expires_at),
48
+ createdAt: timestamp(row.created_at),
49
+ ...(row.sent_at ? { sentAt: timestamp(row.sent_at) } : {}),
50
+ ...(row.started_at ? { startedAt: timestamp(row.started_at) } : {}),
51
+ ...(row.finished_at ? { finishedAt: timestamp(row.finished_at) } : {}),
52
+ ...(row.output === null ? {} : { output: row.output }),
53
+ ...(row.error === null ? {} : { error: row.error }),
54
+ };
55
+ }
56
+ function update(value) {
57
+ return {
58
+ status: value.status,
59
+ ...(value.output === undefined ? {} : { output: value.output }),
60
+ ...(value.error === undefined ? {} : { error: value.error }),
61
+ };
62
+ }
63
+ const timestamp = (value) => new Date(value).toISOString();
@@ -49,6 +49,7 @@ export class SupabaseStore {
49
49
  cron: row.cron,
50
50
  timezone: row.timezone,
51
51
  nextRunAt: new Date(row.next_run_at).toISOString(),
52
+ ...(row.remaining_occurrences === null ? {} : { remainingOccurrences: row.remaining_occurrences }),
52
53
  }));
53
54
  }
54
55
  enqueueEvent(trigger, input, now) {
@@ -22,6 +22,7 @@ export interface DueCronTrigger extends StoredTriggerBase {
22
22
  cron: string;
23
23
  timezone: string;
24
24
  nextRunAt: string;
25
+ remainingOccurrences?: number;
25
26
  }
26
27
  export type RunInput = {
27
28
  kind: 'event';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amalgm/automations",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Amalgm's cloud automation SDK: Supabase-backed configuration, trigger admission, and run delivery.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -26,6 +26,10 @@
26
26
  "./mcp": {
27
27
  "types": "./dist/src/mcp.d.ts",
28
28
  "import": "./dist/src/mcp.js"
29
+ },
30
+ "./host": {
31
+ "types": "./dist/host/index.d.ts",
32
+ "import": "./dist/host/index.js"
29
33
  }
30
34
  },
31
35
  "bin": {
@@ -34,6 +38,7 @@
34
38
  },
35
39
  "files": [
36
40
  "dist",
41
+ "skills",
37
42
  "supabase",
38
43
  "AXIOMS.md",
39
44
  "PURPOSE.md",
@@ -46,14 +51,18 @@
46
51
  "test:supabase": "tsx --test test/integration/postgres.test.ts",
47
52
  "verify": "npm run check && npm test && npm run build",
48
53
  "release:check": "npm run verify && npm run test:supabase",
49
- "prepack": "npm run build"
54
+ "prepack": "npm run build",
55
+ "start": "node dist/host/main.js"
50
56
  },
51
57
  "engines": {
52
58
  "node": ">=20"
53
59
  },
54
60
  "dependencies": {
61
+ "@amalgm/core": "0.4.2",
55
62
  "@modelcontextprotocol/sdk": "^1.30.0",
63
+ "@supabase/supabase-js": "2.57.4",
56
64
  "cron-parser": "^5.4.0",
65
+ "jose": "^6.1.0",
57
66
  "zod": "^4.4.3"
58
67
  },
59
68
  "devDependencies": {
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: automations
3
+ description: Create, inspect, change, or delete Amalgm automations, schedules, workflows, and run history through the Automations MCP tools.
4
+ ---
5
+
6
+ # Amalgm Automations
7
+
8
+ Use the Automations tools for scheduled or webhook-driven work. The tools are
9
+ the agent adapter over the same hosted SDK used by the UI.
10
+
11
+ ## Create a scheduled notification
12
+
13
+ For a request such as “remind me to call my mom every minute for the next ten
14
+ minutes,” create three resources for the current machine:
15
+
16
+ 1. `amalgm_automations_create` with a clear name and the current machine target.
17
+ 2. `amalgm_workflow_create` with a readable script summary and this compiled
18
+ plan:
19
+
20
+ ```json
21
+ {
22
+ "version": 1,
23
+ "steps": [
24
+ {
25
+ "id": "notify",
26
+ "actionId": "channels.notify_user",
27
+ "input": { "title": "Reminder", "message": "Call Mom" }
28
+ }
29
+ ]
30
+ }
31
+ ```
32
+
33
+ 3. `amalgm_schedule_triggers_create` with cron `* * * * *`, the user's
34
+ timezone, and `maxOccurrences: 10`.
35
+
36
+ Create the automation disabled, attach its workflow and trigger, then enable it
37
+ only after both exist. If setup fails, leave it disabled and explain which
38
+ resource failed. Never replace a bounded occurrence count with an unbounded
39
+ schedule plus a promise to clean it up later.
40
+
41
+ ## Read and change
42
+
43
+ Use list/get before changing an existing automation. Schedule and webhook
44
+ triggers are separate resources. A workflow is zero-or-one per automation. Run
45
+ history is read-only and remains after configuration deletion.
46
+
47
+ `pending` means a run is durable and waiting for its selected machine. `sent`
48
+ or `running` means that machine holds a lease. `completed` and `failed` are
49
+ terminal. Do not infer delivery from the schedule alone; inspect runs when the
50
+ user asks whether it actually happened.
51
+
52
+ Webhook secrets are write-only. Never echo, log, or place them in a workflow.