@amalgm/automations 0.4.0 → 0.4.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.
package/AXIOMS.md CHANGED
@@ -91,7 +91,8 @@
91
91
  33. MCP and CLI project one task-level command catalog: create, list, get,
92
92
  update, delete, and run-now. Command names, input schemas, and invocation
93
93
  behavior are defined once and neither adapter invents resource-level
94
- lifecycle operations.
94
+ lifecycle operations. Unknown configuration fields are rejected, never
95
+ discarded by an adapter; declared JSON payloads remain open data.
95
96
  34. A CLI running for a signed-in user reaches Automations through Shell's
96
97
  authenticated loopback MCP route. It may possess the local runtime admission
97
98
  token, but never a durable machine credential, device key, cached access
@@ -140,3 +141,6 @@
140
141
  for that user and machine; a notification is never proof of execution.
141
142
  48. Losing upstream notification coverage closes downstream streams, and every
142
143
  reconnect checks retained work before becoming idle again.
144
+ 49. A complete definition validates all supplied configuration before its
145
+ first write; a later service failure preserves an identifiable disabled
146
+ draft and the original failure code.
package/PURPOSE.md CHANGED
@@ -36,6 +36,12 @@ the run ledger. Reconnecting re-establishes notification coverage before checkin
36
36
  the ledger. Connection health and active execution leases have bounded timers;
37
37
  idle machines have no periodic work-claim timer.
38
38
 
39
+ Complete create and update requests validate their supplied schedules, workflow
40
+ plans, and field names before writing configuration. Unknown fields cannot
41
+ silently become a different request. If a service fails after staging begins,
42
+ the disabled draft remains inspectable and the error keeps that service's code,
43
+ so the agent can repair the same definition rather than repeat its creation.
44
+
39
45
  The agent CLI is the command-line projection of the same task-level command
40
46
  surface as MCP: create, list, get, update, delete, and run-now. Each command
41
47
  accepts the same JSON object as its corresponding MCP tool and returns the same
@@ -56,6 +62,12 @@ the user reach aayush@amalgm.ai with bounded, redacted diagnostic evidence when
56
62
  the failing boundary cannot be repaired. The packaged skill is the source for
57
63
  installed copies and public setup/support guidance.
58
64
 
65
+ The standalone service publishes a public skill discovery index and an
66
+ integrity-checked archive built from that same packaged skill. Installation
67
+ needs no private repository access, and the archive includes its referenced
68
+ instructions. Public distribution owns no copy of authentication or workflow
69
+ behavior.
70
+
59
71
  The CLI is global configuration control, not directory-local state. The agent
60
72
  or person creating an automation may invoke it from any directory; the selected
61
73
  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
- When Shell invokes the adapter, it supplies `AMALGM_MCP_URL` and
102
- `AMALGM_RUNTIME_TOKEN` from its running user runtime. The CLI sends that
103
- runtime token only to the loopback `/mcp/automations` route. Shell retains the
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/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,
@@ -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;
@@ -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('/e/') && options.eventsApi
29
- ? options.eventsApi
30
- : pathname.startsWith('/v1/machine/') ? options.machineApi : options.controlApi;
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,2 @@
1
+ /** Only the two packaged public artifacts are addressable; no arbitrary file reads. */
2
+ export declare function createPublicSkillApi(directory?: URL): (request: Request) => Promise<Response>;
@@ -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
+ }
@@ -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.5e4e14f0ace632c8383cd014e1f0f34b24ec0d5c5600132a840abc4c313b31d4.tgz","digest":"sha256:5e4e14f0ace632c8383cd014e1f0f34b24ec0d5c5600132a840abc4c313b31d4"}]}
@@ -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 {};
@@ -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
- inputSchema: definition.inputShape,
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) => {
@@ -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
- return parse(createScheduleTriggerSchema, value);
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
- return parse(updateScheduleTriggerSchema, value);
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);
@@ -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 Error(`Automation ${automationId} remains disabled because configuration failed: ${reason}`);
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.0",
3
+ "version": "0.4.1",
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": "rm -rf dist && tsc -p tsconfig.build.json && tsx scripts/mark-executables.ts",
48
+ "build": "rm -rf dist && 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",
@@ -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. The service stages multi-resource changes safely and
80
- enables the definition only after setup succeeds. If setup fails, report the
81
- returned disabled draft id rather than silently creating a replacement.
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
- Once the account email is known, inspect it explicitly. Replace the example
109
- email below with the user's actual selected account:
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; some Shell failures are
144
- wrapped as `internal`. Keep automation and run ids when available. Try the
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. A retry must not turn one requested automation or
163
- run into several.
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