@amalgm/automations 0.2.6 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/AXIOMS.md +54 -9
  2. package/PURPOSE.md +47 -11
  3. package/README.md +119 -12
  4. package/dist/src/cli/arguments.d.ts +20 -0
  5. package/dist/src/cli/arguments.js +49 -0
  6. package/dist/src/cli/input.d.ts +6 -0
  7. package/dist/src/cli/input.js +48 -0
  8. package/dist/src/cli/main.d.ts +7 -0
  9. package/dist/src/cli/main.js +39 -0
  10. package/dist/src/cli/run.d.ts +13 -0
  11. package/dist/src/cli/run.js +51 -0
  12. package/dist/src/cli/shell-runtime.d.ts +9 -0
  13. package/dist/src/cli/shell-runtime.js +131 -0
  14. package/dist/src/cli-main.js +2 -20
  15. package/dist/src/cli.d.ts +3 -10
  16. package/dist/src/cli.js +3 -186
  17. package/dist/src/command-result.d.ts +8 -0
  18. package/dist/src/command-result.js +13 -0
  19. package/dist/src/command-surface.d.ts +25 -0
  20. package/dist/src/command-surface.js +125 -0
  21. package/dist/src/contract.d.ts +1 -1
  22. package/dist/src/execution-contract.d.ts +28 -0
  23. package/dist/src/execution-contract.js +1 -0
  24. package/dist/src/execution-errors.d.ts +6 -0
  25. package/dist/src/execution-errors.js +48 -0
  26. package/dist/src/execution-limits.d.ts +6 -0
  27. package/dist/src/execution-limits.js +6 -0
  28. package/dist/src/executor.d.ts +3 -16
  29. package/dist/src/executor.js +31 -36
  30. package/dist/src/index.d.ts +8 -3
  31. package/dist/src/index.js +7 -2
  32. package/dist/src/input-template.d.ts +2 -2
  33. package/dist/src/input-template.js +4 -4
  34. package/dist/src/mcp-main.js +0 -0
  35. package/dist/src/mcp.js +23 -80
  36. package/dist/src/node-process-host.d.ts +13 -0
  37. package/dist/src/node-process-host.js +116 -0
  38. package/dist/src/node-process-runner.d.ts +23 -0
  39. package/dist/src/node-process-runner.js +140 -0
  40. package/dist/src/plan.js +125 -9
  41. package/dist/src/run-contract.d.ts +41 -1
  42. package/dist/src/run-journal.d.ts +2 -2
  43. package/dist/src/run-journal.js +56 -1
  44. package/dist/src/schema.d.ts +4 -4
  45. package/package.json +3 -3
  46. package/skills/automations/SKILL.md +69 -6
  47. package/supabase/migrations/20260904010000_typed_workflow_admission.sql +272 -0
package/AXIOMS.md CHANGED
@@ -41,7 +41,9 @@
41
41
  state; it never owns a second automation-definition API.
42
42
  15. Admitting a run atomically verifies the current enabled configuration,
43
43
  stores a secret-free snapshot, and, for a schedule, advances exactly the
44
- firing instant that was claimed.
44
+ firing instant that was claimed. Manual, schedule, and webhook admission
45
+ derive executability from the same declared set of supported workflow
46
+ versions.
45
47
  16. Legacy local automation storage is not a compatibility authority. Engine
46
48
  is deprecated read-only evidence; active callers use this SDK and its
47
49
  standalone service, and no old store may run beside them.
@@ -55,9 +57,11 @@
55
57
  DPoP request URL behind the Fly proxy.
56
58
  21. Machine execution consumes the immutable workflow snapshot stored on the
57
59
  run. It never rediscovers or silently updates the automation definition.
58
- 22. A compiled workflow is a small declarative sequence of one or more tool actions. The
59
- executor receives tool calling as a host capability and never embeds a
60
- Channels, Shell, CLI, or provider special case.
60
+ 22. Workflow version 1 remains action-only. Version 2 is a small declarative
61
+ sequence of one or more typed steps: `action` invokes an Amalgm action,
62
+ `command` starts an executable directly, and `script` evaluates explicit
63
+ shell, Node.js, or Python source. These are separate execution lanes, never
64
+ aliases for one another.
61
65
  23. Automations is a standalone hosted service. Gateway owns none of its API,
62
66
  scheduling, claim, execution, or persistence path.
63
67
  24. The durable run ledger is the offline queue. The platform never keeps a
@@ -69,18 +73,59 @@
69
73
  lease. It renews that lease while the action is running and stops advancing
70
74
  when renewal fails.
71
75
  27. One run-step pair has one stable idempotency key. Step ids are unique in a
72
- plan, and every action host receives that key and a cancellation signal.
76
+ plan, and every execution host receives that key and a cancellation signal.
73
77
  28. Only transient transport failures retry. They release the same run with a
74
78
  bounded future retry time and retain completed step output; invalid plans,
75
79
  missing actions, authorization failures, and other deterministic errors
76
80
  are terminal.
77
- 29. The selected target executes every action effect. The hosted service owns
81
+ 29. The selected target executes every workflow effect. The hosted service owns
78
82
  only configuration, admission, leases, the step journal, and run history.
79
83
  30. Run Now is triggerless manual admission. It atomically snapshots the
80
84
  current enabled automation and compiled workflow into the pending ledger;
81
85
  it never executes inline or changes a trigger's schedule state.
82
- 31. A step input is an immutable JSON template. The exact singleton
83
- `{ "$runInput": true }` explicitly resolves to the run's immutable input;
84
- no trigger payload is ambient and no action rediscovers it.
86
+ 31. Action input and process stdin are immutable JSON templates. The exact
87
+ singleton `{ "$runInput": true }` explicitly resolves to the run's
88
+ immutable input; no trigger payload is ambient and no step rediscovers it.
85
89
  32. An adapter preserves the owning service's error code and status. Transport
86
90
  errors may add presentation, but never relabel authorization as validation.
91
+ 33. MCP and CLI project one task-level command catalog: create, list, get,
92
+ update, delete, and run-now. Command names, input schemas, and invocation
93
+ behavior are defined once and neither adapter invents resource-level
94
+ lifecycle operations.
95
+ 34. A CLI running for a signed-in user reaches Automations through Shell's
96
+ authenticated loopback MCP route. It may possess the local runtime admission
97
+ token, but never a durable machine credential, device key, cached access
98
+ token, or reusable DPoP proof.
99
+ 35. Every CLI command consumes one JSON object and emits one JSON envelope. A
100
+ success is `{ "result": ... }`; a failure is
101
+ `{ "error": { "code": ..., "message": ..., "status"?: ... } }` and a
102
+ nonzero exit, so agents never have to scrape prose for command state.
103
+ 36. CLI invocation is global configuration control. It never scopes an
104
+ automation to the caller's current directory, and a scheduled process never
105
+ inherits the directory from which its definition was created.
106
+ 37. Every process step may persist an explicit `cwd`; omission means the
107
+ selected machine host's documented default. Relative working directories
108
+ are invalid because their future meaning would depend on ambient state.
109
+ 38. A `command` step supplies an executable and argv directly and never passes
110
+ through an implicit shell. Shell parsing, pipelines, redirection, and
111
+ interpolation require either an explicit `script` step or a command whose
112
+ executable is itself a shell.
113
+ 39. Process environment values stored in a workflow are ordinary persisted
114
+ configuration, never an implicit secret store. A host may add its own
115
+ baseline process environment, but adapters never capture the creator
116
+ process's environment into the definition.
117
+ 40. Automations owns step types, validation, ordering, leases, retry, and
118
+ journals; the target machine host owns action calls and process spawning.
119
+ MCP, CLI, HTTP, and hosted control-plane code execute neither.
120
+ 41. Direct process effects are at-least-once across an uncertain machine
121
+ failure. The host exposes the stable step key as
122
+ `AMALGM_AUTOMATION_IDEMPOTENCY_KEY`; a program that requires exactly-once
123
+ behavior must durably deduplicate it.
124
+ 42. A child process never inherits Shell's runtime admission token, machine
125
+ credential, tunnel credential, or a standalone Automations authorization
126
+ value. Native tool authentication comes from that tool's own user-level
127
+ setup, not from Amalgm's private transport authority.
128
+ 43. The durable step journal has one SDK-owned serialized size bound. Oversized
129
+ action or process results become an explicit byte count plus bounded JSON
130
+ preview before transport, so output can never strand a run outside the
131
+ ledger it is meant to update.
package/PURPOSE.md CHANGED
@@ -22,20 +22,56 @@ The product has two composable halves over that one state:
22
22
  plane's persisted configuration; it does not expose another definition
23
23
  write path. When a machine is offline, new runs remain pending; when it
24
24
  reconnects, Shell claims pending runs directly from the Automations service
25
- with its machine-bound DPoP identity and executes the persisted tool-action
26
- plan. Execution itself stays on the selected machine. The platform never
25
+ with its machine-bound DPoP identity and executes the persisted plan.
26
+ Execution itself stays on the selected machine. The platform never
27
27
  needs to know whether a machine is online: an unclaimed run is the complete
28
28
  offline queue.
29
29
 
30
- One run is one immutable plan, one immutable trigger input, plus one durable ordered step journal. The
31
- machine records a step as running before invoking its action and records its
32
- output before advancing. Reconnect resumes at the first step that is not
33
- already complete. A transient network failure releases the same run back to
34
- the queue with a bounded future retry time; a configuration, authorization, or
35
- action error fails it. Stable per-step idempotency keys make an uncertain
36
- network acknowledgement safe to repeat. A workflow can place that run input in
37
- an action payload only through the explicit `{ "$runInput": true }` template
38
- value, so static configuration and occurrence data never blur together.
30
+ The agent CLI is the command-line projection of the same task-level command
31
+ surface as MCP: create, list, get, update, delete, and run-now. Each command
32
+ accepts the same JSON object as its corresponding MCP tool and returns the same
33
+ structured success or error envelope. In a signed-in installation the CLI
34
+ reaches that surface through Shell's authenticated loopback MCP route. Shell
35
+ keeps the durable machine credential and device key, and continues to mint the
36
+ short-lived access token and fresh DPoP proof used by the hosted SDK client; the
37
+ CLI sees only the local runtime admission token. The standalone adapter may be
38
+ composed over an already-bound SDK for tests and other hosts, but it never owns
39
+ automation lifecycle or authorization rules.
40
+
41
+ The CLI is global configuration control, not directory-local state. The agent
42
+ or person creating an automation may invoke it from any directory; the selected
43
+ machine and the persisted workflow decide where effects occur later. A process
44
+ step may persist an explicit working directory, or deliberately use that
45
+ machine host's documented default, but it never inherits the creator CLI's
46
+ ambient working directory.
47
+
48
+ One run is one immutable plan, one immutable trigger input, plus one durable
49
+ ordered step journal. Version 1 retains its original action-only meaning;
50
+ version 2 explicitly opts into three execution lanes: an `action`
51
+ step invokes an Amalgm action such as a tool or `chat.chat_agent_run`; a
52
+ `command` step starts an installed executable directly with argv semantics,
53
+ which includes native agents such as `codex exec` or `claude -p`; and a
54
+ `script` step evaluates persisted shell, Node.js, or Python source. These lanes
55
+ share ordering, leases, retries, journaling, and idempotency, but never disguise
56
+ one execution model as another. Automations describes and validates the lanes;
57
+ the selected machine host supplies every effectful capability.
58
+
59
+ The machine records a step as running before invoking it and records its output
60
+ before advancing. Reconnect resumes at the first step that is not already
61
+ complete. One serialized journal bound applies to action and process output;
62
+ oversized results retain their byte count and a bounded preview, so evidence
63
+ cannot make its own durable update impossible. A transient network failure releases the same run back to the queue
64
+ with a bounded future retry time; a configuration, authorization, or execution
65
+ error fails it. Every execution capability receives a stable per-step
66
+ idempotency key. Amalgm actions can use it to make an uncertain acknowledgement
67
+ safe to repeat; process steps also receive it in
68
+ `AMALGM_AUTOMATION_IDEMPOTENCY_KEY`, but arbitrary programs remain at-least-once
69
+ effects across a machine crash unless the program deduplicates that key. A
70
+ workflow can place run input into an action payload or process stdin only
71
+ through the explicit `{ "$runInput": true }` template value, so static
72
+ configuration and occurrence data never blur together. Direct commands never
73
+ invoke a shell; shell expansion is available only by choosing the visibly
74
+ distinct `script` lane.
39
75
 
40
76
  Automations is its own hosted service and Fly machine. Its API and scheduler
41
77
  share the same SDK and Supabase authority; neither is composed into, proxied by,
package/README.md CHANGED
@@ -34,7 +34,7 @@ const run = await sdk.runs.runNow(automation.id, {
34
34
 
35
35
  That schedule admits exactly ten durable runs, then disables itself. If the
36
36
  machine is offline, the runs remain pending. Shell later claims only work whose
37
- target matches its DPoP `computer_id`, executes the immutable tool-action plan,
37
+ target matches its DPoP `computer_id`, executes the immutable typed plan,
38
38
  and commits the result. Run Now returns the newly admitted `pending` run; it
39
39
  uses that same machine execution path and never executes workflow effects in
40
40
  the API request.
@@ -42,11 +42,66 @@ the API request.
42
42
  ## Surfaces
43
43
 
44
44
  - `@amalgm/automations`: SDK, API/client, scheduler, machine claim client, and
45
- generic tool-action executor.
45
+ generic typed-step executor plus its optional Node process host adapter.
46
46
  - `@amalgm/automations/mcp`: agent tools over the same SDK.
47
47
  - `@amalgm/automations/host`: standalone Fly service composition.
48
48
  - `amalgm-automations`: CLI adapter.
49
49
 
50
+ ## Agent CLI
51
+
52
+ The CLI has the same six task-level operations and JSON input objects as MCP:
53
+ `create`, `list`, `get`, `update`, `delete`, and `run-now`. Its Shell-facing
54
+ argument form is `amalgm automations`; the package also ships the directly
55
+ executable `amalgm-automations` adapter.
56
+
57
+ It is global configuration control. Invoke it from any directory; automation
58
+ state is owned by the service and execution is owned by the selected machine.
59
+ The directory in which an agent creates a definition is never captured. Put an
60
+ absolute `cwd` on a command or script when location matters. When `cwd` is
61
+ omitted, Amalgm Shell deliberately uses the user's OS home on that machine.
62
+
63
+ ```bash
64
+ amalgm automations create --stdin <<'JSON'
65
+ {
66
+ "name": "Call Mom",
67
+ "schedules": [{
68
+ "cron": "* * * * *",
69
+ "timezone": "America/Los_Angeles",
70
+ "maxOccurrences": 10
71
+ }],
72
+ "workflow": {
73
+ "script": "Notify me to call Mom.",
74
+ "compiled": {
75
+ "version": 1,
76
+ "steps": [{
77
+ "id": "notify",
78
+ "actionId": "channels.notify_user",
79
+ "input": { "title": "Reminder", "message": "Call Mom" }
80
+ }]
81
+ }
82
+ }
83
+ }
84
+ JSON
85
+
86
+ amalgm automations get \
87
+ --input '{"automation_id":"<id>","include_runs":true}'
88
+ ```
89
+
90
+ Every success is one JSON line shaped as `{ "result": ... }`. Every failure is
91
+ one JSON line on stderr shaped as
92
+ `{ "error": { "code": "...", "message": "..." } }`, with an optional HTTP
93
+ `status`, and exits nonzero. Use `--stdin` for inputs containing credentials;
94
+ `--input` and `--file` are also supported.
95
+
96
+ When Shell invokes the adapter, it supplies `AMALGM_MCP_URL` and
97
+ `AMALGM_RUNTIME_TOKEN` from its running user runtime. The CLI sends that
98
+ runtime token only to the loopback `/mcp/automations` route. Shell retains the
99
+ durable machine credential and device key and creates the short-lived access
100
+ token and fresh DPoP proof for the hosted request. For transition
101
+ compatibility, the standalone entry point still accepts the prior paired
102
+ `AMALGM_AUTOMATIONS_API_URL` and `AMALGM_AUTOMATIONS_AUTHORIZATION`
103
+ environment variables.
104
+
50
105
  The hosted service accepts verified Supabase user sessions for browser control
51
106
  and Core-issued `amalgm-automations` DPoP grants for Shell. It does not run in
52
107
  or depend on Amalgm Gateway.
@@ -62,18 +117,70 @@ signing secret is write-only.
62
117
  Executable workflows use one small, non-empty format:
63
118
 
64
119
  ```ts
65
- type AutomationPlan = {
66
- version: 1;
67
- steps: [
68
- { id: string; actionId: string; input: Json },
69
- ...Array<{ id: string; actionId: string; input: Json }>,
70
- ];
71
- };
120
+ type AutomationPlan =
121
+ | { version: 1; steps: [ToolActionStep, ...ToolActionStep[]] }
122
+ | { version: 2; steps: [AutomationStep, ...AutomationStep[]] };
123
+
124
+ type AutomationStep =
125
+ // Amalgm action/tool/agent path
126
+ | { id: string; actionId: string; input: Json }
127
+ // Native executable path: no shell parsing
128
+ | {
129
+ id: string; kind: 'command'; command: string; args?: string[];
130
+ cwd?: string; // absolute path
131
+ env?: Record<string, string>; stdin?: Json;
132
+ timeoutMs?: number; maxOutputBytes?: number;
133
+ }
134
+ // Explicit persisted code path
135
+ | {
136
+ id: string; kind: 'script'; runtime: 'shell' | 'node' | 'python';
137
+ source: string; args?: string[]; cwd?: string; // absolute path
138
+ env?: Record<string, string>; stdin?: Json;
139
+ timeoutMs?: number; maxOutputBytes?: number;
140
+ };
141
+ ```
142
+
143
+ Version 1 remains the original action-only contract. Version 2 opts into the
144
+ typed execution contract and is required for command or script steps. The
145
+ separation is intentional. An Amalgm agent is an action step using
146
+ `chat.chat_agent_run`. A user's native Codex, Claude Code, or OpenCode is a
147
+ command step using the installed `codex`, `claude`, or `opencode` executable:
148
+
149
+ ```json
150
+ {
151
+ "version": 2,
152
+ "steps": [{
153
+ "id": "native-review",
154
+ "kind": "command",
155
+ "command": "codex",
156
+ "args": ["exec", "Review this repository and write REVIEW.md"],
157
+ "cwd": "/Users/me/src/project",
158
+ "timeoutMs": 1800000
159
+ }]
160
+ }
72
161
  ```
73
162
 
74
- The executor does not know about Channels or any other product. Shell injects
75
- one action-calling capability, and each step receives the stable idempotency key
76
- `<run-id>:<step-id>`.
163
+ Command args are passed literally with no shell. Use a `script` step when the
164
+ user explicitly wants shell parsing, a pipeline, or inline Node/Python code.
165
+ String `stdin` is passed verbatim; other JSON is serialized with a trailing
166
+ newline. Either action `input` or process `stdin` may contain the exact template
167
+ `{ "$runInput": true }` to receive that run's durable trigger input.
168
+
169
+ Shell injects action and process capabilities into the shared executor. Process
170
+ results record exit code, signal, stdout, stderr, and truncation in run history;
171
+ timeouts, cancellation, and output limits are enforced on the selected
172
+ machine. The durable journal begins compacting after 1 MiB and stores oversized
173
+ results as an explicit original byte count plus a bounded JSON preview, keeping
174
+ every run update within the service transport bound. Children inherit the
175
+ user's ordinary machine environment so installed
176
+ CLI auth continues to work, but never inherit Shell's private runtime, tunnel,
177
+ or machine credentials. Values in workflow `env` are persisted non-secret
178
+ configuration.
179
+
180
+ Each step receives the stable key `<run-id>:<step-id>`. The Node process host
181
+ also exposes it as `AMALGM_AUTOMATION_IDEMPOTENCY_KEY`. Arbitrary processes are
182
+ at-least-once across a machine crash; programs with non-repeatable effects must
183
+ durably deduplicate that key.
77
184
 
78
185
  ## HTTP
79
186
 
@@ -0,0 +1,20 @@
1
+ import type { AutomationCommandName } from '../command-surface.js';
2
+ export type AutomationCliInput = {
3
+ kind: 'empty';
4
+ } | {
5
+ kind: 'inline';
6
+ value: string;
7
+ } | {
8
+ kind: 'file';
9
+ filename: string;
10
+ } | {
11
+ kind: 'stdin';
12
+ };
13
+ export type ParsedAutomationCli = {
14
+ kind: 'help';
15
+ } | {
16
+ kind: 'command';
17
+ command: AutomationCommandName;
18
+ input: AutomationCliInput;
19
+ };
20
+ export declare function parseAutomationCliArguments(argv: readonly string[]): ParsedAutomationCli;
@@ -0,0 +1,49 @@
1
+ import { ValidationError } from '../errors.js';
2
+ const commandNames = new Set([
3
+ 'create', 'list', 'get', 'update', 'delete', 'run-now',
4
+ ]);
5
+ export function parseAutomationCliArguments(argv) {
6
+ const args = [...argv];
7
+ if (args[0] === 'automations')
8
+ args.shift();
9
+ if (args.length === 0 || args[0] === 'help' || args[0] === '--help' || args[0] === '-h') {
10
+ if (args.length > 1)
11
+ throw new ValidationError('Help does not accept additional arguments');
12
+ return { kind: 'help' };
13
+ }
14
+ const command = args.shift();
15
+ if (!commandNames.has(command)) {
16
+ throw new ValidationError(`Unknown Automations command: ${command ?? ''}`);
17
+ }
18
+ let input = { kind: 'empty' };
19
+ while (args.length > 0) {
20
+ const argument = args.shift();
21
+ if (argument === '--stdin') {
22
+ input = oneInput(input, { kind: 'stdin' });
23
+ continue;
24
+ }
25
+ const [flag, inline] = splitOption(argument);
26
+ if (flag !== '--input' && flag !== '--file') {
27
+ throw new ValidationError(`Unknown Automations CLI option: ${flag}`);
28
+ }
29
+ const value = inline ?? args.shift();
30
+ if (value === undefined || value === '')
31
+ throw new ValidationError(`${flag} requires a value`);
32
+ input = oneInput(input, flag === '--input'
33
+ ? { kind: 'inline', value }
34
+ : { kind: 'file', filename: value });
35
+ }
36
+ return { kind: 'command', command: command, input };
37
+ }
38
+ function oneInput(current, next) {
39
+ if (current.kind !== 'empty') {
40
+ throw new ValidationError('Provide command input exactly once with --input, --file, or --stdin');
41
+ }
42
+ return next;
43
+ }
44
+ function splitOption(value) {
45
+ const separator = value.indexOf('=');
46
+ return separator === -1
47
+ ? [value, undefined]
48
+ : [value.slice(0, separator), value.slice(separator + 1)];
49
+ }
@@ -0,0 +1,6 @@
1
+ import type { AutomationCliInput } from './arguments.js';
2
+ export interface AutomationCliInputPorts {
3
+ readFile?(filename: string): Promise<string>;
4
+ readStdin?(): Promise<string>;
5
+ }
6
+ export declare function readAutomationCliInput(input: AutomationCliInput, ports: AutomationCliInputPorts): Promise<Record<string, unknown>>;
@@ -0,0 +1,48 @@
1
+ import fs from 'node:fs/promises';
2
+ import { ValidationError } from '../errors.js';
3
+ export async function readAutomationCliInput(input, ports) {
4
+ if (input.kind === 'empty')
5
+ return {};
6
+ let source;
7
+ if (input.kind === 'inline')
8
+ source = input.value;
9
+ else if (input.kind === 'file') {
10
+ try {
11
+ source = await (ports.readFile ?? readFile)(input.filename);
12
+ }
13
+ catch (error) {
14
+ throw new ValidationError(`Cannot read Automations input file: ${message(error)}`);
15
+ }
16
+ }
17
+ else {
18
+ try {
19
+ source = await (ports.readStdin ?? readStdin)();
20
+ }
21
+ catch (error) {
22
+ throw new ValidationError(`Cannot read Automations input from stdin: ${message(error)}`);
23
+ }
24
+ }
25
+ let value;
26
+ try {
27
+ value = JSON.parse(source);
28
+ }
29
+ catch {
30
+ throw new ValidationError('Automations command input must be valid JSON');
31
+ }
32
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
33
+ throw new ValidationError('Automations command input must be a JSON object');
34
+ }
35
+ return value;
36
+ }
37
+ function readFile(filename) {
38
+ return fs.readFile(filename, 'utf8');
39
+ }
40
+ async function readStdin() {
41
+ const chunks = [];
42
+ for await (const chunk of process.stdin)
43
+ chunks.push(Buffer.from(chunk));
44
+ return Buffer.concat(chunks).toString('utf8');
45
+ }
46
+ function message(error) {
47
+ return error instanceof Error ? error.message : String(error);
48
+ }
@@ -0,0 +1,7 @@
1
+ import { type AutomationCliIo } from './run.js';
2
+ export interface AutomationCliMainOptions extends AutomationCliIo {
3
+ argv?: string[];
4
+ env?: NodeJS.ProcessEnv;
5
+ fetch?: typeof globalThis.fetch;
6
+ }
7
+ export declare function runAutomationCliMain(options?: AutomationCliMainOptions): Promise<number>;
@@ -0,0 +1,39 @@
1
+ import { createAutomationClient } from '../client.js';
2
+ import { createAutomationCommands, } from '../command-surface.js';
3
+ import { AutomationError } from '../errors.js';
4
+ import { runAutomationCli } from './run.js';
5
+ import { createShellAutomationCommands } from './shell-runtime.js';
6
+ export async function runAutomationCliMain(options = {}) {
7
+ const argv = options.argv ?? process.argv.slice(2);
8
+ const env = options.env ?? process.env;
9
+ return runAutomationCli(argv, lazyCommands(() => commandsFromEnvironment(env, options.fetch)), options);
10
+ }
11
+ function commandsFromEnvironment(env, fetch) {
12
+ const runtimeUrl = env.AMALGM_AUTOMATIONS_RUNTIME_URL || env.AMALGM_MCP_URL;
13
+ const runtimeToken = env.AMALGM_RUNTIME_TOKEN;
14
+ if (runtimeUrl || runtimeToken) {
15
+ if (!runtimeUrl || !runtimeToken) {
16
+ throw new AutomationError('runtime_unavailable', 'AMALGM_MCP_URL and AMALGM_RUNTIME_TOKEN must both come from the running Amalgm Shell runtime');
17
+ }
18
+ return createShellAutomationCommands({ runtimeUrl, runtimeToken, ...(fetch ? { fetch } : {}) });
19
+ }
20
+ const baseUrl = env.AMALGM_AUTOMATIONS_API_URL;
21
+ const authorization = env.AMALGM_AUTOMATIONS_AUTHORIZATION;
22
+ if (baseUrl && authorization) {
23
+ return createAutomationCommands(createAutomationClient({
24
+ baseUrl,
25
+ authorization: () => authorization,
26
+ ...(fetch ? { fetch } : {}),
27
+ }));
28
+ }
29
+ throw new AutomationError('runtime_unavailable', 'A running Amalgm Shell runtime is required (AMALGM_MCP_URL and AMALGM_RUNTIME_TOKEN)');
30
+ }
31
+ function lazyCommands(open) {
32
+ let commands;
33
+ return {
34
+ execute(name, input) {
35
+ commands ??= open();
36
+ return commands.execute(name, input);
37
+ },
38
+ };
39
+ }
@@ -0,0 +1,13 @@
1
+ import { type AutomationCommands } from '../command-surface.js';
2
+ import type { AutomationCrud } from '../contract.js';
3
+ import { type AutomationCliInputPorts } from './input.js';
4
+ interface Output {
5
+ write(chunk: string): unknown;
6
+ }
7
+ export interface AutomationCliIo extends AutomationCliInputPorts {
8
+ stdout?: Output;
9
+ stderr?: Output;
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\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
+ export declare function runAutomationCli(argv: string[], backend: AutomationCrud | AutomationCommands, io?: AutomationCliIo): Promise<number>;
13
+ export {};
@@ -0,0 +1,51 @@
1
+ import { createAutomationCommands, } from '../command-surface.js';
2
+ import { automationFailure } from '../command-result.js';
3
+ import { parseAutomationCliArguments } from './arguments.js';
4
+ import { readAutomationCliInput, } from './input.js';
5
+ export const automationCliHelp = `amalgm automations — durable automation control for agents
6
+
7
+ Usage:
8
+ amalgm automations <command> [--input JSON | --file PATH | --stdin]
9
+ amalgm-automations <command> [--input JSON | --file PATH | --stdin]
10
+
11
+ Commands (identical to the Automations MCP task surface):
12
+ create Create one complete definition
13
+ list List the user's automations
14
+ get Get one complete definition and optional run history
15
+ update Apply grouped definition changes
16
+ delete Delete current configuration; run history remains
17
+ run-now Admit one durable manual run
18
+
19
+ Input is one JSON object with the corresponding MCP tool's fields. Commands
20
+ without options receive {}. Output is one JSON success or error envelope.
21
+ Use --stdin when input contains credentials such as a webhook signing secret.
22
+
23
+ Workflow step lanes:
24
+ version 1 action-only: {"id":"...","actionId":"product.action","input":{...}}
25
+ version 2 action, command, or script steps:
26
+ command {"id":"...","kind":"command","command":"codex","args":["exec","..."],"cwd":"/absolute/path"}
27
+ script {"id":"...","kind":"script","runtime":"shell|node|python","source":"...","cwd":"/absolute/path"}
28
+ `;
29
+ export async function runAutomationCli(argv, backend, io = {}) {
30
+ const stdout = io.stdout ?? process.stdout;
31
+ const stderr = io.stderr ?? process.stderr;
32
+ try {
33
+ const parsed = parseAutomationCliArguments(argv);
34
+ if (parsed.kind === 'help') {
35
+ stdout.write(automationCliHelp);
36
+ return 0;
37
+ }
38
+ const input = await readAutomationCliInput(parsed.input, io);
39
+ const commands = isCommands(backend) ? backend : createAutomationCommands(backend);
40
+ const result = await commands.execute(parsed.command, input);
41
+ stdout.write(`${JSON.stringify({ result })}\n`);
42
+ return 0;
43
+ }
44
+ catch (error) {
45
+ stderr.write(`${JSON.stringify(automationFailure(error))}\n`);
46
+ return 1;
47
+ }
48
+ }
49
+ function isCommands(value) {
50
+ return 'execute' in value && typeof value.execute === 'function';
51
+ }
@@ -0,0 +1,9 @@
1
+ import { type AutomationCommands } from '../command-surface.js';
2
+ export interface ShellAutomationCommandOptions {
3
+ runtimeUrl: string;
4
+ runtimeToken: string;
5
+ fetch?: typeof globalThis.fetch;
6
+ requestTimeoutMs?: number;
7
+ }
8
+ export declare function createShellAutomationCommands(options: ShellAutomationCommandOptions): AutomationCommands;
9
+ export declare function shellAutomationEndpoint(runtimeUrl: string): string;