@amalgm/automations 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AXIOMS.md +8 -1
- package/PURPOSE.md +20 -0
- package/README.md +20 -3
- package/dist/host/config.js +8 -12
- package/dist/host/main.js +2 -0
- package/dist/host/server.d.ts +1 -0
- package/dist/host/server.js +5 -3
- package/dist/host/skill.d.ts +2 -0
- package/dist/host/skill.js +33 -0
- package/dist/skills/amalgm-automations.f64db3414c22f330c2c83a5d1dc76949d7a95f3c9ba06a94c5fbd9c4024d4acd.tgz +0 -0
- package/dist/skills/index.json +1 -0
- package/dist/src/cli/run.d.ts +1 -1
- package/dist/src/cli/run.js +5 -0
- package/dist/src/crud/triggers.js +0 -1
- package/dist/src/mcp.d.ts +2 -1
- package/dist/src/mcp.js +4 -2
- package/dist/src/schema.js +9 -3
- package/dist/src/tool-surface.js +21 -2
- package/package.json +4 -4
- package/skills/amalgm-automations/SKILL.md +7 -4
- package/skills/amalgm-automations/references/command-contract.md +7 -0
- package/skills/amalgm-automations/references/setup-and-support.md +47 -6
package/AXIOMS.md
CHANGED
|
@@ -64,6 +64,9 @@
|
|
|
64
64
|
aliases for one another.
|
|
65
65
|
23. Automations is a standalone hosted service. Gateway owns none of its API,
|
|
66
66
|
scheduling, claim, execution, or persistence path.
|
|
67
|
+
Its host validates Core's complete environment binding before opening its
|
|
68
|
+
database, issuer, public API or webhook authority; local has no managed
|
|
69
|
+
service fallback.
|
|
67
70
|
24. The durable run ledger is the offline queue. The platform never keeps a
|
|
68
71
|
second online-machine delivery buffer and never drops an unclaimed run.
|
|
69
72
|
25. A run advances through its immutable plan in order. Every step transition
|
|
@@ -91,7 +94,8 @@
|
|
|
91
94
|
33. MCP and CLI project one task-level command catalog: create, list, get,
|
|
92
95
|
update, delete, and run-now. Command names, input schemas, and invocation
|
|
93
96
|
behavior are defined once and neither adapter invents resource-level
|
|
94
|
-
lifecycle operations.
|
|
97
|
+
lifecycle operations. Unknown configuration fields are rejected, never
|
|
98
|
+
discarded by an adapter; declared JSON payloads remain open data.
|
|
95
99
|
34. A CLI running for a signed-in user reaches Automations through Shell's
|
|
96
100
|
authenticated loopback MCP route. It may possess the local runtime admission
|
|
97
101
|
token, but never a durable machine credential, device key, cached access
|
|
@@ -140,3 +144,6 @@
|
|
|
140
144
|
for that user and machine; a notification is never proof of execution.
|
|
141
145
|
48. Losing upstream notification coverage closes downstream streams, and every
|
|
142
146
|
reconnect checks retained work before becoming idle again.
|
|
147
|
+
49. A complete definition validates all supplied configuration before its
|
|
148
|
+
first write; a later service failure preserves an identifiable disabled
|
|
149
|
+
draft and the original failure code.
|
package/PURPOSE.md
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
+
Managed releases bind to one exact published Core version and build from the
|
|
6
|
+
locked dependency graph on the supported Node toolchain.
|
|
7
|
+
|
|
5
8
|
Automations is the sole owner of Amalgm automation behavior and data. It lets an
|
|
6
9
|
authenticated user create, inspect, change, delete, and manually run automation
|
|
7
10
|
configuration — an automation belongs to one user and one target, may own any
|
|
@@ -10,6 +13,11 @@ have each trigger occurrence run on the selected existing Amalgm machine.
|
|
|
10
13
|
Supabase holds everything except execution: the automation, its triggers, the
|
|
11
14
|
complete workflow, and permanent run history.
|
|
12
15
|
|
|
16
|
+
Each hosted environment reaches only its declared UI issuer, database and
|
|
17
|
+
webhook authority. Main and preview initially share the database by owner
|
|
18
|
+
direction; their public API and issuer identities remain distinct. Durable
|
|
19
|
+
claims remain the single authority for work even when hosts share data.
|
|
20
|
+
|
|
13
21
|
The product has two composable halves over that one state:
|
|
14
22
|
|
|
15
23
|
- **The configuration control plane** — one ergonomic, auth-bound SDK contract
|
|
@@ -36,6 +44,12 @@ the run ledger. Reconnecting re-establishes notification coverage before checkin
|
|
|
36
44
|
the ledger. Connection health and active execution leases have bounded timers;
|
|
37
45
|
idle machines have no periodic work-claim timer.
|
|
38
46
|
|
|
47
|
+
Complete create and update requests validate their supplied schedules, workflow
|
|
48
|
+
plans, and field names before writing configuration. Unknown fields cannot
|
|
49
|
+
silently become a different request. If a service fails after staging begins,
|
|
50
|
+
the disabled draft remains inspectable and the error keeps that service's code,
|
|
51
|
+
so the agent can repair the same definition rather than repeat its creation.
|
|
52
|
+
|
|
39
53
|
The agent CLI is the command-line projection of the same task-level command
|
|
40
54
|
surface as MCP: create, list, get, update, delete, and run-now. Each command
|
|
41
55
|
accepts the same JSON object as its corresponding MCP tool and returns the same
|
|
@@ -56,6 +70,12 @@ the user reach aayush@amalgm.ai with bounded, redacted diagnostic evidence when
|
|
|
56
70
|
the failing boundary cannot be repaired. The packaged skill is the source for
|
|
57
71
|
installed copies and public setup/support guidance.
|
|
58
72
|
|
|
73
|
+
The standalone service publishes a public skill discovery index and an
|
|
74
|
+
integrity-checked archive built from that same packaged skill. Installation
|
|
75
|
+
needs no private repository access, and the archive includes its referenced
|
|
76
|
+
instructions. Public distribution owns no copy of authentication or workflow
|
|
77
|
+
behavior.
|
|
78
|
+
|
|
59
79
|
The CLI is global configuration control, not directory-local state. The agent
|
|
60
80
|
or person creating an automation may invoke it from any directory; the selected
|
|
61
81
|
machine and the persisted workflow decide where effects occur later. A process
|
package/README.md
CHANGED
|
@@ -98,15 +98,32 @@ one JSON line on stderr shaped as
|
|
|
98
98
|
`status`, and exits nonzero. Use `--stdin` for inputs containing credentials;
|
|
99
99
|
`--input` and `--file` are also supported.
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
Shell supplies the selected running user's loopback connection directly to
|
|
102
|
+
the SDK adapter. The CLI sends its runtime token only to the loopback
|
|
103
|
+
`/mcp/automations` route. Shell retains the
|
|
104
104
|
durable machine credential and device key and creates the short-lived access
|
|
105
105
|
token and fresh DPoP proof for the hosted request. For transition
|
|
106
106
|
compatibility, the standalone entry point still accepts the prior paired
|
|
107
107
|
`AMALGM_AUTOMATIONS_API_URL` and `AMALGM_AUTOMATIONS_AUTHORIZATION`
|
|
108
108
|
environment variables.
|
|
109
109
|
|
|
110
|
+
For public local MCP clients, Shell 0.1.176+ provides
|
|
111
|
+
`amalgm automations mcp --user person@example.com`. It exposes the same six
|
|
112
|
+
tools over stdio and discovers the current runtime connection per call.
|
|
113
|
+
`amalgm status --all` lists local account registrations. No pasted token or
|
|
114
|
+
private repository access is part of either flow; the standalone
|
|
115
|
+
`amalgm-automations-mcp` entry point remains for custom authenticated hosts.
|
|
116
|
+
|
|
117
|
+
Install the complete portable skill with Node.js 22.20+:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npx skills add https://automations.amalgm.ai --skill amalgm-automations
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The host serves standard discovery at `/.well-known/agent-skills/index.json`
|
|
124
|
+
and a checksum-addressed archive built from `skills/amalgm-automations`.
|
|
125
|
+
The archive includes its references and agent metadata and requires no login.
|
|
126
|
+
|
|
110
127
|
The hosted service accepts verified Supabase user sessions for browser control
|
|
111
128
|
and Core-issued `amalgm-automations` DPoP grants for Shell. It does not run in
|
|
112
129
|
or depend on Amalgm Gateway.
|
package/dist/host/config.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
import { isLoopbackEndpoint, resolveRuntimeEndpoint, validateRuntimeEndpointEnvironment } from '@amalgm/core/binding';
|
|
1
2
|
export function automationsHostConfig(env = process.env) {
|
|
3
|
+
const publicOrigin = required(env.AMALGAM_PUBLIC_ORIGIN, 'AMALGAM_PUBLIC_ORIGIN');
|
|
4
|
+
const isolatedOrigin = isLoopbackEndpoint(publicOrigin) ? publicOrigin : undefined;
|
|
5
|
+
const binding = validateRuntimeEndpointEnvironment(env, isolatedOrigin);
|
|
2
6
|
return Object.freeze({
|
|
3
7
|
port: integer(env.PORT, 8080, 1, 65_535),
|
|
4
|
-
publicOrigin:
|
|
5
|
-
webhookOrigin:
|
|
8
|
+
publicOrigin: resolveRuntimeEndpoint(binding, 'automations', publicOrigin, isolatedOrigin),
|
|
9
|
+
webhookOrigin: resolveRuntimeEndpoint(binding, 'automations', required(env.AUTOMATIONS_WEBHOOK_ORIGIN, 'AUTOMATIONS_WEBHOOK_ORIGIN'), isolatedOrigin),
|
|
6
10
|
webhookTokenKey: secret(env.AUTOMATIONS_WEBHOOK_TOKEN_KEY, 'AUTOMATIONS_WEBHOOK_TOKEN_KEY'),
|
|
7
|
-
supabaseUrl:
|
|
11
|
+
supabaseUrl: resolveRuntimeEndpoint(binding, 'supabase', required(env.SUPABASE_URL ?? env.NEXT_PUBLIC_SUPABASE_URL, 'SUPABASE_URL'), isolatedOrigin),
|
|
8
12
|
supabaseServiceRoleKey: required(env.SUPABASE_SERVICE_ROLE_KEY, 'SUPABASE_SERVICE_ROLE_KEY'),
|
|
9
|
-
authorizationIssuer:
|
|
13
|
+
authorizationIssuer: resolveRuntimeEndpoint(binding, 'app', required(env.AMALGM_AUTHORIZATION_ISSUER, 'AMALGM_AUTHORIZATION_ISSUER'), isolatedOrigin),
|
|
10
14
|
schedulerIntervalMs: integer(env.AUTOMATIONS_SCHEDULER_INTERVAL_MS, 1_000, 250, 60_000),
|
|
11
15
|
});
|
|
12
16
|
}
|
|
@@ -21,14 +25,6 @@ function secret(value, name) {
|
|
|
21
25
|
throw new Error(`${name} must contain at least 32 bytes`);
|
|
22
26
|
return result;
|
|
23
27
|
}
|
|
24
|
-
function url(value, name) {
|
|
25
|
-
const parsed = new URL(required(value, name));
|
|
26
|
-
const local = parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1';
|
|
27
|
-
if (parsed.protocol !== 'https:' && !(local && parsed.protocol === 'http:')) {
|
|
28
|
-
throw new Error(`${name} must use HTTPS`);
|
|
29
|
-
}
|
|
30
|
-
return parsed.toString().replace(/\/$/, '');
|
|
31
|
-
}
|
|
32
28
|
function integer(value, fallback, minimum, maximum) {
|
|
33
29
|
const parsed = value === undefined ? fallback : Number(value);
|
|
34
30
|
if (!Number.isInteger(parsed) || parsed < minimum || parsed > maximum) {
|
package/dist/host/main.js
CHANGED
|
@@ -14,6 +14,7 @@ import { automationsHostConfig } from './config.js';
|
|
|
14
14
|
import { createAutomationsHost } from './server.js';
|
|
15
15
|
import { createMachineRunNotifications } from '../src/machine-notifications.js';
|
|
16
16
|
import { subscribeToRunChanges } from './notifications.js';
|
|
17
|
+
import { createPublicSkillApi } from './skill.js';
|
|
17
18
|
const config = automationsHostConfig();
|
|
18
19
|
const supabase = createClient(config.supabaseUrl, config.supabaseServiceRoleKey, {
|
|
19
20
|
auth: { persistSession: false, autoRefreshToken: false },
|
|
@@ -48,6 +49,7 @@ const host = createAutomationsHost({
|
|
|
48
49
|
controlApi,
|
|
49
50
|
machineApi,
|
|
50
51
|
eventsApi,
|
|
52
|
+
publicSkillApi: createPublicSkillApi(),
|
|
51
53
|
fireSchedules: () => delivery.fireDueCrons(),
|
|
52
54
|
schedulerIntervalMs: config.schedulerIntervalMs,
|
|
53
55
|
log,
|
package/dist/host/server.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export declare function createAutomationsHost(options: {
|
|
|
4
4
|
readonly controlApi: (request: Request) => Promise<Response>;
|
|
5
5
|
readonly machineApi: (request: Request) => Promise<Response>;
|
|
6
6
|
readonly eventsApi?: (request: Request) => Promise<Response>;
|
|
7
|
+
readonly publicSkillApi?: (request: Request) => Promise<Response>;
|
|
7
8
|
readonly fireSchedules: () => Promise<unknown>;
|
|
8
9
|
readonly schedulerIntervalMs: number;
|
|
9
10
|
readonly maxRequestBodyBytes?: number;
|
package/dist/host/server.js
CHANGED
|
@@ -25,9 +25,11 @@ export function createAutomationsHost(options) {
|
|
|
25
25
|
return await send(outgoing, Response.json({ ok: true }), controller.signal);
|
|
26
26
|
const request = await webRequest(incoming, options.publicOrigin, options.maxRequestBodyBytes ?? 2 * 1024 * 1024, controller.signal);
|
|
27
27
|
const pathname = new URL(request.url).pathname;
|
|
28
|
-
const api = pathname.startsWith('/
|
|
29
|
-
? options.
|
|
30
|
-
: pathname.startsWith('/
|
|
28
|
+
const api = pathname.startsWith('/.well-known/agent-skills/') && options.publicSkillApi
|
|
29
|
+
? options.publicSkillApi
|
|
30
|
+
: pathname.startsWith('/e/') && options.eventsApi
|
|
31
|
+
? options.eventsApi
|
|
32
|
+
: pathname.startsWith('/v1/machine/') ? options.machineApi : options.controlApi;
|
|
31
33
|
await send(outgoing, await api(request), controller.signal);
|
|
32
34
|
}
|
|
33
35
|
catch (error) {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
const prefix = '/.well-known/agent-skills/';
|
|
3
|
+
/** Only the two packaged public artifacts are addressable; no arbitrary file reads. */
|
|
4
|
+
export function createPublicSkillApi(directory = new URL('../skills/', import.meta.url)) {
|
|
5
|
+
const index = readFileSync(new URL('index.json', directory));
|
|
6
|
+
const entry = JSON.parse(index.toString('utf8')).skills[0];
|
|
7
|
+
const filename = entry.url.replace(/^\.\//, '');
|
|
8
|
+
if (!/^amalgm-automations\.[a-f0-9]{64}\.tgz$/.test(filename))
|
|
9
|
+
throw new Error('Invalid skill artifact');
|
|
10
|
+
const files = new Map([
|
|
11
|
+
[`${prefix}index.json`, { bytes: index, type: 'application/json', cache: 'no-cache' }],
|
|
12
|
+
[`${prefix}${filename}`, {
|
|
13
|
+
bytes: readFileSync(new URL(filename, directory)), type: 'application/gzip',
|
|
14
|
+
cache: 'public, max-age=31536000, immutable',
|
|
15
|
+
}],
|
|
16
|
+
]);
|
|
17
|
+
return async (request) => {
|
|
18
|
+
const file = files.get(new URL(request.url).pathname);
|
|
19
|
+
if (!file)
|
|
20
|
+
return new Response('Not found', { status: 404 });
|
|
21
|
+
if (request.method !== 'GET' && request.method !== 'HEAD') {
|
|
22
|
+
return new Response('Method not allowed', { status: 405, headers: { allow: 'GET, HEAD' } });
|
|
23
|
+
}
|
|
24
|
+
return new Response(request.method === 'HEAD' ? null : new Uint8Array(file.bytes), {
|
|
25
|
+
headers: {
|
|
26
|
+
'content-type': file.type,
|
|
27
|
+
'content-length': String(file.bytes.byteLength),
|
|
28
|
+
'cache-control': file.cache,
|
|
29
|
+
'x-content-type-options': 'nosniff',
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"$schema":"https://schemas.agentskills.io/discovery/0.2.0/schema.json","skills":[{"name":"amalgm-automations","description":"Set up, operate, and troubleshoot Amalgm Automations through MCP or the amalgm automations CLI. Use for installation, Google sign-in, scheduled or webhook workflows, Run Now, run history, and Automations support.","type":"archive","url":"./amalgm-automations.f64db3414c22f330c2c83a5d1dc76949d7a95f3c9ba06a94c5fbd9c4024d4acd.tgz","digest":"sha256:f64db3414c22f330c2c83a5d1dc76949d7a95f3c9ba06a94c5fbd9c4024d4acd"}]}
|
package/dist/src/cli/run.d.ts
CHANGED
|
@@ -8,6 +8,6 @@ export interface AutomationCliIo extends AutomationCliInputPorts {
|
|
|
8
8
|
stdout?: Output;
|
|
9
9
|
stderr?: Output;
|
|
10
10
|
}
|
|
11
|
-
export declare const automationCliHelp = "amalgm automations \u2014 durable automation control for agents\n\nUsage:\n amalgm automations <command> [--input JSON | --file PATH | --stdin]\n amalgm-automations <command> [--input JSON | --file PATH | --stdin]\n\nCommands (identical to the Automations MCP task surface):\n create Create one complete definition\n list List the user's automations\n get Get one complete definition and optional run history\n update Apply grouped definition changes\n delete Delete current configuration; run history remains\n run-now Admit one durable manual run\n\nInput is one JSON object with the corresponding MCP tool's fields. Commands\nwithout options receive {}. Output is one JSON success or error envelope.\nUse --stdin when input contains credentials such as a webhook signing secret.\n\nSetup and support:\n Already connected? Reuse the running Amalgm Shell.\n First login: amalgm login --no-open\n Open the printed link, sign in with Google, and approve this computer.\n Keep the login process running; it hosts Shell after approval.\n Check: amalgm status --user EMAIL\n Resume a registered computer: amalgm run --user EMAIL\n Verify access: amalgm automations list --user EMAIL --input '{\"limit\":1}'\n No local installation? https://amalgm.ai/setup\n Support: aayush@amalgm.ai\n Never copy private connection credentials into a command or support email.\n\nWorkflow step lanes:\n version 1 action-only: {\"id\":\"...\",\"actionId\":\"product.action\",\"input\":{...}}\n version 2 action, command, or script steps:\n command {\"id\":\"...\",\"kind\":\"command\",\"command\":\"codex\",\"args\":[\"exec\",\"...\"],\"cwd\":\"/absolute/path\"}\n script {\"id\":\"...\",\"kind\":\"script\",\"runtime\":\"shell|node|python\",\"source\":\"...\",\"cwd\":\"/absolute/path\"}\n";
|
|
11
|
+
export declare const automationCliHelp = "amalgm automations \u2014 durable automation control for agents\n\nUsage:\n amalgm automations <command> [--input JSON | --file PATH | --stdin]\n amalgm-automations <command> [--input JSON | --file PATH | --stdin]\n\nCommands (identical to the Automations MCP task surface):\n create Create one complete definition\n list List the user's automations\n get Get one complete definition and optional run history\n update Apply grouped definition changes\n delete Delete current configuration; run history remains\n run-now Admit one durable manual run\n\nInput is one JSON object with the corresponding MCP tool's fields. Commands\nwithout options receive {}. Output is one JSON success or error envelope.\nUse --stdin when input contains credentials such as a webhook signing secret.\n\nSetup and support:\n Already connected? Reuse the running Amalgm Shell.\n First login: amalgm login --no-open\n Open the printed link, sign in with Google, and approve this computer.\n Keep the login process running; it hosts Shell after approval.\n Check: amalgm status --user EMAIL\n List local accounts: amalgm status --all\n Resume a registered computer: amalgm run --user EMAIL\n Verify access: amalgm automations list --user EMAIL --input '{\"limit\":1}'\n No local installation? https://amalgm.ai/setup\n Support: aayush@amalgm.ai\n Never copy private connection credentials into a command or support email.\n\nExternal agent MCP setup (after Shell login):\n amalgm automations mcp --user EMAIL\n Configure your client to launch this stdio command. No credential fields.\n\nWorkflow step lanes:\n version 1 action-only: {\"id\":\"...\",\"actionId\":\"product.action\",\"input\":{...}}\n version 2 action, command, or script steps:\n command {\"id\":\"...\",\"kind\":\"command\",\"command\":\"codex\",\"args\":[\"exec\",\"...\"],\"cwd\":\"/absolute/path\"}\n script {\"id\":\"...\",\"kind\":\"script\",\"runtime\":\"shell|node|python\",\"source\":\"...\",\"cwd\":\"/absolute/path\"}\n";
|
|
12
12
|
export declare function runAutomationCli(argv: string[], backend: AutomationCrud | AutomationCommands, io?: AutomationCliIo): Promise<number>;
|
|
13
13
|
export {};
|
package/dist/src/cli/run.js
CHANGED
|
@@ -26,12 +26,17 @@ Setup and support:
|
|
|
26
26
|
Open the printed link, sign in with Google, and approve this computer.
|
|
27
27
|
Keep the login process running; it hosts Shell after approval.
|
|
28
28
|
Check: amalgm status --user EMAIL
|
|
29
|
+
List local accounts: amalgm status --all
|
|
29
30
|
Resume a registered computer: amalgm run --user EMAIL
|
|
30
31
|
Verify access: amalgm automations list --user EMAIL --input '{"limit":1}'
|
|
31
32
|
No local installation? https://amalgm.ai/setup
|
|
32
33
|
Support: aayush@amalgm.ai
|
|
33
34
|
Never copy private connection credentials into a command or support email.
|
|
34
35
|
|
|
36
|
+
External agent MCP setup (after Shell login):
|
|
37
|
+
amalgm automations mcp --user EMAIL
|
|
38
|
+
Configure your client to launch this stdio command. No credential fields.
|
|
39
|
+
|
|
35
40
|
Workflow step lanes:
|
|
36
41
|
version 1 action-only: {"id":"...","actionId":"product.action","input":{...}}
|
|
37
42
|
version 2 action, command, or script steps:
|
|
@@ -22,7 +22,6 @@ export function triggerOperations(context) {
|
|
|
22
22
|
await context.exists(automationId);
|
|
23
23
|
const parsed = parseCreateScheduleTrigger(input);
|
|
24
24
|
const timezone = parsed.timezone || 'UTC';
|
|
25
|
-
schedule(parsed.cron, timezone);
|
|
26
25
|
if (parsed.id)
|
|
27
26
|
await unique(automationId, parsed.id);
|
|
28
27
|
return repository.createScheduleTrigger(principal.userId, automationId, {
|
package/dist/src/mcp.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { type AutomationCommands } from './command-surface.js';
|
|
2
3
|
import type { AutomationCrud } from './contract.js';
|
|
3
4
|
import { createAutomationToolSurface } from './tool-surface.js';
|
|
4
5
|
export { createAutomationToolSurface };
|
|
5
6
|
export type { AutomationToolSurface, AutomationView, CreateAutomationDefinition, UpdateAutomationDefinition, WorkflowChange, } from './tool-surface.js';
|
|
6
|
-
export declare function createAutomationMcpServer(sdk: AutomationCrud): McpServer;
|
|
7
|
+
export declare function createAutomationMcpServer(sdk: AutomationCrud | AutomationCommands): McpServer;
|
package/dist/src/mcp.js
CHANGED
|
@@ -14,7 +14,7 @@ const resultSchema = {
|
|
|
14
14
|
};
|
|
15
15
|
export function createAutomationMcpServer(sdk) {
|
|
16
16
|
const server = new McpServer({ name: 'amalgm-automations-mcp-server', version: '0.1.0' });
|
|
17
|
-
const commands = createAutomationCommands(sdk);
|
|
17
|
+
const commands = 'execute' in sdk ? sdk : createAutomationCommands(sdk);
|
|
18
18
|
for (const definition of automationCommandDefinitions)
|
|
19
19
|
register(server, commands, definition);
|
|
20
20
|
return server;
|
|
@@ -23,7 +23,9 @@ function register(server, commands, definition) {
|
|
|
23
23
|
server.registerTool(definition.mcpName, {
|
|
24
24
|
title: definition.title,
|
|
25
25
|
description: definition.description,
|
|
26
|
-
|
|
26
|
+
// Forward unknown top-level fields to the SDK's strict parser. The MCP
|
|
27
|
+
// SDK's default object parser strips them before our command sees them.
|
|
28
|
+
inputSchema: z.object(definition.inputShape).passthrough(),
|
|
27
29
|
outputSchema: resultSchema,
|
|
28
30
|
annotations: definition.annotations,
|
|
29
31
|
}, async (input) => {
|
package/dist/src/schema.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod/v3';
|
|
2
2
|
import { ValidationError } from './errors.js';
|
|
3
3
|
import { compiledAutomationPlan } from './plan.js';
|
|
4
|
-
import { IDENTIFIER } from './validation.js';
|
|
4
|
+
import { IDENTIFIER, schedule } from './validation.js';
|
|
5
5
|
const maxPageSize = 100;
|
|
6
6
|
const identifierMessage = 'Identifier is invalid';
|
|
7
7
|
const text = (maximum, label) => z.string()
|
|
@@ -96,10 +96,16 @@ export function parseListAutomations(value) {
|
|
|
96
96
|
return parse(listAutomationsSchema, value);
|
|
97
97
|
}
|
|
98
98
|
export function parseCreateScheduleTrigger(value) {
|
|
99
|
-
|
|
99
|
+
const trigger = parse(createScheduleTriggerSchema, value);
|
|
100
|
+
schedule(trigger.cron, trigger.timezone ?? 'UTC');
|
|
101
|
+
return trigger;
|
|
100
102
|
}
|
|
101
103
|
export function parseUpdateScheduleTrigger(value) {
|
|
102
|
-
|
|
104
|
+
const patch = parse(updateScheduleTriggerSchema, value);
|
|
105
|
+
if (patch.cron !== undefined || patch.timezone !== undefined) {
|
|
106
|
+
schedule(patch.cron ?? '* * * * *', patch.timezone ?? 'UTC');
|
|
107
|
+
}
|
|
108
|
+
return patch;
|
|
103
109
|
}
|
|
104
110
|
export function parseCreateWebhookTrigger(value) {
|
|
105
111
|
return parse(createWebhookTriggerSchema, value);
|
package/dist/src/tool-surface.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { NotFoundError, ValidationError } from './errors.js';
|
|
1
|
+
import { AutomationError, NotFoundError, ValidationError } from './errors.js';
|
|
2
|
+
import { parseCreateAutomation, parseCreateScheduleTrigger, parseCreateWebhookTrigger, parseCreateWorkflow, parseUpdateAutomation, parseUpdateScheduleTrigger, parseUpdateWebhookTrigger, parseUpdateWorkflow, } from './schema.js';
|
|
2
3
|
export function createAutomationToolSurface(sdk) {
|
|
3
4
|
const view = async (automationId, runs = false) => {
|
|
4
5
|
const automation = await sdk.automations.get(automationId);
|
|
@@ -14,6 +15,11 @@ export function createAutomationToolSurface(sdk) {
|
|
|
14
15
|
const surface = {
|
|
15
16
|
async create(input) {
|
|
16
17
|
const { schedules = [], webhooks = [], workflow, ...automationInput } = input;
|
|
18
|
+
parseCreateAutomation(automationInput);
|
|
19
|
+
schedules.forEach(parseCreateScheduleTrigger);
|
|
20
|
+
webhooks.forEach(parseCreateWebhookTrigger);
|
|
21
|
+
if (workflow)
|
|
22
|
+
parseCreateWorkflow(workflow);
|
|
17
23
|
validateCreateIds(schedules, webhooks);
|
|
18
24
|
const staged = schedules.length > 0 || webhooks.length > 0 || workflow !== undefined;
|
|
19
25
|
const requestedEnabled = automationInput.enabled !== false;
|
|
@@ -40,6 +46,7 @@ export function createAutomationToolSurface(sdk) {
|
|
|
40
46
|
get: view,
|
|
41
47
|
async update(automationId, input) {
|
|
42
48
|
requireChanges(input);
|
|
49
|
+
validateUpdate(input);
|
|
43
50
|
validateChanges(input.schedules, 'schedule');
|
|
44
51
|
validateChanges(input.webhooks, 'webhook');
|
|
45
52
|
const current = await sdk.automations.get(automationId);
|
|
@@ -98,6 +105,18 @@ function requireChanges(input) {
|
|
|
98
105
|
throw new ValidationError('Automation update must include a change');
|
|
99
106
|
}
|
|
100
107
|
}
|
|
108
|
+
function validateUpdate(input) {
|
|
109
|
+
if (input.patch)
|
|
110
|
+
parseUpdateAutomation(input.patch);
|
|
111
|
+
input.schedules?.create?.forEach(parseCreateScheduleTrigger);
|
|
112
|
+
input.schedules?.update?.forEach(({ patch }) => parseUpdateScheduleTrigger(patch));
|
|
113
|
+
input.webhooks?.create?.forEach(parseCreateWebhookTrigger);
|
|
114
|
+
input.webhooks?.update?.forEach(({ patch }) => parseUpdateWebhookTrigger(patch));
|
|
115
|
+
if (input.workflow && 'create' in input.workflow)
|
|
116
|
+
parseCreateWorkflow(input.workflow.create);
|
|
117
|
+
if (input.workflow && 'update' in input.workflow)
|
|
118
|
+
parseUpdateWorkflow(input.workflow.update);
|
|
119
|
+
}
|
|
101
120
|
function hasChanges(changes) {
|
|
102
121
|
return Boolean(changes && [changes.create, changes.update, changes.delete]
|
|
103
122
|
.some((items) => items && items.length > 0));
|
|
@@ -122,5 +141,5 @@ function validateCreateIds(schedules, webhooks) {
|
|
|
122
141
|
}
|
|
123
142
|
function disabledDraftError(automationId, error) {
|
|
124
143
|
const reason = error instanceof Error ? error.message : String(error);
|
|
125
|
-
return new
|
|
144
|
+
return new AutomationError(error instanceof AutomationError ? error.code : 'internal', `Automation ${automationId} remains disabled because configuration failed: ${reason}`, error instanceof AutomationError ? error.status : undefined);
|
|
126
145
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amalgm/automations",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Amalgm's automation SDK: durable trigger admission and target-machine execution.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"README.md"
|
|
46
46
|
],
|
|
47
47
|
"scripts": {
|
|
48
|
-
"build": "
|
|
48
|
+
"build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json && tsx scripts/mark-executables.ts && tsx scripts/build-public-skill.ts",
|
|
49
49
|
"check": "tsx scripts/check-tree.ts && tsc -p tsconfig.json --noEmit",
|
|
50
50
|
"test": "tsx --test test/*.test.ts",
|
|
51
51
|
"test:supabase": "tsx --test test/integration/postgres.test.ts",
|
|
@@ -56,10 +56,10 @@
|
|
|
56
56
|
"start": "node dist/host/main.js"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
|
-
"node": ">=
|
|
59
|
+
"node": ">=24"
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"@amalgm/core": "0.4.
|
|
62
|
+
"@amalgm/core": "0.4.7",
|
|
63
63
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
64
64
|
"@supabase/supabase-js": "2.57.4",
|
|
65
65
|
"cron-parser": "^5.4.0",
|
|
@@ -72,13 +72,16 @@ input.
|
|
|
72
72
|
complex source. Do not expose those values in command arguments or logs.
|
|
73
73
|
- Delete only after the exact id is established and deletion is within the
|
|
74
74
|
user's request. Deleting configuration intentionally retains run history.
|
|
75
|
+
Omit `id` when creating a new automation; explicitly reusing a deleted id
|
|
76
|
+
refers to the same logical identity and retains its history and retry keys.
|
|
75
77
|
|
|
76
78
|
## Build one complete definition
|
|
77
79
|
|
|
78
80
|
Create the automation, its schedule/webhook triggers, and its optional workflow
|
|
79
|
-
in one task-level call.
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
in one task-level call. Invalid supplied configuration fails before any write.
|
|
82
|
+
The service stages valid multi-resource changes and enables the definition
|
|
83
|
+
only after setup succeeds. If a later service failure returns a disabled draft
|
|
84
|
+
id, get and repair that draft with `update`; do not create a replacement.
|
|
82
85
|
|
|
83
86
|
Use a finite `maxOccurrences` when the user's request is bounded. Do not turn
|
|
84
87
|
"ten times" into an unbounded schedule plus a future cleanup promise.
|
|
@@ -102,7 +105,7 @@ executables through the selected machine; never guess either.
|
|
|
102
105
|
not evidence that work completed.
|
|
103
106
|
|
|
104
107
|
1. Supply a stable, caller-chosen `idempotency_key` whenever the request might
|
|
105
|
-
be retried.
|
|
108
|
+
be retried. Use a new key for each new logical run.
|
|
106
109
|
2. If admission is retried, send the same automation id, input, and key. The
|
|
107
110
|
returned run id must remain the same.
|
|
108
111
|
3. Poll `get` with `include_runs: true` or a `run_query` until the admitted run
|
|
@@ -15,6 +15,8 @@ Failures use stderr, a nonzero exit, and:
|
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
Do not scrape prose or infer success from exit code alone; parse the envelope.
|
|
18
|
+
Unknown configuration fields are rejected. Arbitrary JSON inside workflow
|
|
19
|
+
action input and Run Now input remains valid payload data.
|
|
18
20
|
|
|
19
21
|
## create
|
|
20
22
|
|
|
@@ -62,6 +64,11 @@ At least one executable step is required when `compiled` is supplied. Omit
|
|
|
62
64
|
webhook URL is the provider destination; a configured signing secret is
|
|
63
65
|
write-only on later reads.
|
|
64
66
|
|
|
67
|
+
Omit `id` for a new automation. An explicit id names a persistent logical
|
|
68
|
+
identity: deleting and recreating it retains that identity's run history and
|
|
69
|
+
Run Now idempotency keys. Use a new key for each new logical run and reuse it
|
|
70
|
+
only when retrying that run.
|
|
71
|
+
|
|
65
72
|
CLI example:
|
|
66
73
|
|
|
67
74
|
```bash
|
|
@@ -5,6 +5,19 @@ an error. Use Shell's commands for setup and Automations reads to verify access.
|
|
|
5
5
|
The agent handles the commands; the person chooses their Google account and
|
|
6
6
|
approves connecting their computer in the browser.
|
|
7
7
|
|
|
8
|
+
## Install this skill
|
|
9
|
+
|
|
10
|
+
The public download includes this skill and all its references; it needs no
|
|
11
|
+
GitHub account or access to Amalgm's source repositories. With Node.js 22.20+
|
|
12
|
+
(or Amalgm's supplied Node runtime), run:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx skills add https://automations.amalgm.ai --skill amalgm-automations
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Choose the intended agent in the installer's prompt. Installing the skill
|
|
19
|
+
supplies guidance; use the connection steps below to access Automations.
|
|
20
|
+
|
|
8
21
|
## Start with the connection you have
|
|
9
22
|
|
|
10
23
|
If Automations MCP already answers `list` with `{"limit":1}`, continue the task.
|
|
@@ -34,6 +47,28 @@ A successful Automations response is a JSON `result` envelope; an empty list
|
|
|
34
47
|
is valid. The six Automations commands use that envelope. Shell's `status`
|
|
35
48
|
returns a different JSON shape; `login` and `run` report progress as text.
|
|
36
49
|
|
|
50
|
+
For a local stdio MCP client, configure this server using the actual selected
|
|
51
|
+
account email (Shell 0.1.176 or newer):
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"mcpServers": {
|
|
56
|
+
"amalgm-automations": {
|
|
57
|
+
"command": "amalgm",
|
|
58
|
+
"args": ["automations", "mcp", "--user", "person@example.com"]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
This is the server entry for clients using `mcpServers` JSON; clients with a
|
|
65
|
+
different configuration format use the same command and arguments. Use the
|
|
66
|
+
exact installed launcher path if the client cannot find `amalgm` on PATH.
|
|
67
|
+
Shell supplies the current local connection on every tool call, including
|
|
68
|
+
after a runtime restart. Do not add credentials or environment variables.
|
|
69
|
+
Listing tools works before login; calling them requires a running signed-in
|
|
70
|
+
runtime. After configuring the client, verify its Automations `list` tool.
|
|
71
|
+
|
|
37
72
|
## Install only when needed
|
|
38
73
|
|
|
39
74
|
Check for an existing app-managed installation before adding another copy.
|
|
@@ -105,8 +140,11 @@ do not reopen approval until they choose to continue.
|
|
|
105
140
|
|
|
106
141
|
## Reuse or resume a registered computer
|
|
107
142
|
|
|
108
|
-
|
|
109
|
-
email
|
|
143
|
+
Discover registered accounts with `amalgm status --all` (Shell 0.1.176+).
|
|
144
|
+
It returns email and registration state only. A sole ready account is selected
|
|
145
|
+
implicitly; multiple ready accounts require `--user`. Once the account email
|
|
146
|
+
is known, inspect it explicitly. Replace the example email below with the
|
|
147
|
+
user's actual selected account:
|
|
110
148
|
|
|
111
149
|
```bash
|
|
112
150
|
amalgm status --user person@example.com
|
|
@@ -140,14 +178,15 @@ pending; it does not erase their history.
|
|
|
140
178
|
|
|
141
179
|
## Diagnose the failing boundary
|
|
142
180
|
|
|
143
|
-
Read the error's code, message, and status together
|
|
144
|
-
|
|
181
|
+
Read the error's code, message, and status together. Keep automation and run
|
|
182
|
+
ids when available. Try the
|
|
145
183
|
appropriate repair and verify again. If the same failure persists with no new
|
|
146
184
|
evidence, explain it and offer support rather than looping through login,
|
|
147
185
|
reinstallation, or repeated writes.
|
|
148
186
|
|
|
149
187
|
| Failure | What to do |
|
|
150
188
|
| --- | --- |
|
|
189
|
+
| `user_not_registered` or `user_selection_required` | Use `amalgm status --all` to discover local accounts, then select the intended email or complete login for it. |
|
|
151
190
|
| `runtime_unavailable`, connection refused, or Shell says it is not running | Inspect the selected account's status and use the resume/setup decision above. A standalone adapter asking for environment credentials should be replaced by the global `amalgm automations` path. |
|
|
152
191
|
| `runtime_unauthorized` | Retry the read once through the public Shell command, which obtains the current local connection. If it still fails, keep the error for support; never extract or replace tokens manually. |
|
|
153
192
|
| `runtime_transport` or `runtime_protocol` | Check local readiness, connectivity, and the installed version. A timeout or service outage is not evidence that the user needs a new account. |
|
|
@@ -159,8 +198,10 @@ reinstallation, or repeated writes.
|
|
|
159
198
|
|
|
160
199
|
For a failed mutation, inspect existing state before retrying. Reuse the same
|
|
161
200
|
Run Now input and idempotency key after an uncertain response. Preserve a
|
|
162
|
-
returned disabled draft id
|
|
163
|
-
|
|
201
|
+
returned disabled draft id: get that definition, then repair it with `update`
|
|
202
|
+
instead of retrying `create`. Invalid supplied configuration is rejected
|
|
203
|
+
before any write; a later service failure can still leave a disabled draft.
|
|
204
|
+
A retry must not turn one requested automation or run into several.
|
|
164
205
|
|
|
165
206
|
## Get support
|
|
166
207
|
|