@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.
- package/AXIOMS.md +54 -9
- package/PURPOSE.md +47 -11
- package/README.md +119 -12
- package/dist/src/cli/arguments.d.ts +20 -0
- package/dist/src/cli/arguments.js +49 -0
- package/dist/src/cli/input.d.ts +6 -0
- package/dist/src/cli/input.js +48 -0
- package/dist/src/cli/main.d.ts +7 -0
- package/dist/src/cli/main.js +39 -0
- package/dist/src/cli/run.d.ts +13 -0
- package/dist/src/cli/run.js +51 -0
- package/dist/src/cli/shell-runtime.d.ts +9 -0
- package/dist/src/cli/shell-runtime.js +131 -0
- package/dist/src/cli-main.js +2 -20
- package/dist/src/cli.d.ts +3 -10
- package/dist/src/cli.js +3 -186
- package/dist/src/command-result.d.ts +8 -0
- package/dist/src/command-result.js +13 -0
- package/dist/src/command-surface.d.ts +25 -0
- package/dist/src/command-surface.js +125 -0
- package/dist/src/contract.d.ts +1 -1
- package/dist/src/execution-contract.d.ts +28 -0
- package/dist/src/execution-contract.js +1 -0
- package/dist/src/execution-errors.d.ts +6 -0
- package/dist/src/execution-errors.js +48 -0
- package/dist/src/execution-limits.d.ts +6 -0
- package/dist/src/execution-limits.js +6 -0
- package/dist/src/executor.d.ts +3 -16
- package/dist/src/executor.js +31 -36
- package/dist/src/index.d.ts +8 -3
- package/dist/src/index.js +7 -2
- package/dist/src/input-template.d.ts +2 -2
- package/dist/src/input-template.js +4 -4
- package/dist/src/mcp-main.js +0 -0
- package/dist/src/mcp.js +23 -80
- package/dist/src/node-process-host.d.ts +13 -0
- package/dist/src/node-process-host.js +116 -0
- package/dist/src/node-process-runner.d.ts +23 -0
- package/dist/src/node-process-runner.js +140 -0
- package/dist/src/plan.js +125 -9
- package/dist/src/run-contract.d.ts +41 -1
- package/dist/src/run-journal.d.ts +2 -2
- package/dist/src/run-journal.js +56 -1
- package/dist/src/schema.d.ts +4 -4
- package/package.json +3 -3
- package/skills/automations/SKILL.md +69 -6
- 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.
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
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
|
|
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.
|
|
83
|
-
`{ "$runInput": true }` explicitly resolves to the run's
|
|
84
|
-
no trigger payload is ambient and no
|
|
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
|
|
26
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
69
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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;
|