@replayio/app-building 1.15.0 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -16
- package/dist/container.d.ts +2 -0
- package/dist/container.js +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
loadDotEnv,
|
|
16
16
|
FileContainerRegistry,
|
|
17
17
|
getInfisicalConfig,
|
|
18
|
-
resolveContainerSecrets,
|
|
19
18
|
createMachine,
|
|
20
19
|
destroyMachine,
|
|
21
20
|
type ContainerConfig,
|
|
@@ -25,14 +24,22 @@ import {
|
|
|
25
24
|
httpOptsFor,
|
|
26
25
|
} from "@replayio/app-building";
|
|
27
26
|
|
|
28
|
-
// Load orchestration vars from .env, then
|
|
27
|
+
// Load orchestration vars from .env, then get Infisical credentials
|
|
29
28
|
const orchestrationVars = loadDotEnv("/path/to/project");
|
|
30
|
-
const infisicalConfig = getInfisicalConfig(orchestrationVars);
|
|
31
|
-
|
|
29
|
+
const infisicalConfig = await getInfisicalConfig(orchestrationVars);
|
|
30
|
+
|
|
31
|
+
// Only pass Infisical credentials to the container — not actual secrets.
|
|
32
|
+
// The container fetches secrets from Infisical at startup and manages them
|
|
33
|
+
// via an internal secrets server. The agent never has direct access to secrets.
|
|
34
|
+
const containerEnvVars: Record<string, string> = {
|
|
35
|
+
INFISICAL_TOKEN: infisicalConfig.token,
|
|
36
|
+
INFISICAL_PROJECT_ID: infisicalConfig.projectId,
|
|
37
|
+
INFISICAL_ENVIRONMENT: infisicalConfig.environment,
|
|
38
|
+
};
|
|
32
39
|
|
|
33
40
|
const config: ContainerConfig = {
|
|
34
41
|
projectRoot: "/path/to/project", // optional — only needed for local Docker operations
|
|
35
|
-
envVars:
|
|
42
|
+
envVars: containerEnvVars,
|
|
36
43
|
registry: new FileContainerRegistry("/path/to/.container-registry.jsonl"),
|
|
37
44
|
flyToken: orchestrationVars.FLY_API_TOKEN,
|
|
38
45
|
flyApp: orchestrationVars.FLY_APP_NAME,
|
|
@@ -53,13 +60,32 @@ const alive = await config.registry.findAlive();
|
|
|
53
60
|
await destroyMachine(config.flyApp, config.flyToken, machineId, volumeId);
|
|
54
61
|
```
|
|
55
62
|
|
|
63
|
+
## Secrets architecture
|
|
64
|
+
|
|
65
|
+
Secrets are never passed directly to the container or agent. Instead:
|
|
66
|
+
|
|
67
|
+
1. The orchestration host passes **Infisical credentials** (token, project ID, environment) to the container.
|
|
68
|
+
2. At startup, the container fetches all secrets from Infisical and stores them in memory.
|
|
69
|
+
3. A **secrets server** (`127.0.0.1:9119`) runs inside the container, accessible only locally.
|
|
70
|
+
4. The agent process runs with a **restricted environment** — only `ANTHROPIC_API_KEY` (required for the Claude CLI) is present.
|
|
71
|
+
5. When the agent needs to run a command that requires secrets, it uses `exec-secrets`:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
exec-secrets NEON_API_KEY -- curl -s -H "Authorization: Bearer $NEON_API_KEY" https://...
|
|
75
|
+
exec-secrets NETLIFY_AUTH_TOKEN NETLIFY_ACCOUNT_SLUG -- netlify deploy --prod
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The secrets server spawns the command with the requested secrets in its environment and **redacts all secret values** from the output.
|
|
79
|
+
|
|
80
|
+
The agent can also run `list-secrets` to see which secrets are available.
|
|
81
|
+
|
|
56
82
|
## Exported API
|
|
57
83
|
|
|
58
84
|
### Domain objects
|
|
59
85
|
|
|
60
86
|
| Export | Description |
|
|
61
87
|
|---|---|
|
|
62
|
-
| `ContainerConfig` | Interface bundling all external state: optional `projectRoot` (only needed for local Docker operations), `envVars` (
|
|
88
|
+
| `ContainerConfig` | Interface bundling all external state: optional `projectRoot` (only needed for local Docker operations), `envVars` (Infisical credentials), `registry`, optional `flyToken`/`flyApp`/`imageRef`/`webhookUrl`/`webhookSecret`/`detached`/`initialPrompt`/`localPort`/`absorbTasks`. See [Webhooks](#webhooks) and [Container lifecycle](#container-lifecycle) below. |
|
|
63
89
|
| `RepoOptions` | Per-invocation git settings: `repoUrl`, `cloneBranch`, `pushBranch`. |
|
|
64
90
|
| `ContainerRegistry` | Interface for container registry storage. Methods: `log`, `markStopped`, `clearStopped`, `getRecent`, `find`, `findAlive`. |
|
|
65
91
|
| `FileContainerRegistry` | Built-in file-backed implementation of `ContainerRegistry`, backed by a `.jsonl` file. |
|
|
@@ -123,8 +149,7 @@ await destroyMachine(config.flyApp, config.flyToken, machineId, volumeId);
|
|
|
123
149
|
|
|
124
150
|
| Export | Description |
|
|
125
151
|
|---|---|
|
|
126
|
-
| `getInfisicalConfig(envVars)` | Extract `InfisicalConfig` from env vars
|
|
127
|
-
| `resolveContainerSecrets(config)` | Fetch global build secrets from Infisical and merge with Infisical config vars. Returns a `Record<string, string>` suitable for `ContainerConfig.envVars`. |
|
|
152
|
+
| `getInfisicalConfig(envVars)` | Extract `InfisicalConfig` from env vars and log in. Requires `INFISICAL_CLIENT_ID`, `INFISICAL_CLIENT_SECRET`, `INFISICAL_PROJECT_ID`, `INFISICAL_ENVIRONMENT`. |
|
|
128
153
|
| `fetchGlobalSecrets(config)` | Fetch secrets from the `/global/` path. |
|
|
129
154
|
| `fetchBranchSecrets(config, branch)` | Fetch secrets from `/branches/<branch>/`. |
|
|
130
155
|
| `fetchInfisicalSecrets(config, path)` | Raw fetch from any Infisical folder path. |
|
|
@@ -135,13 +160,15 @@ await destroyMachine(config.flyApp, config.flyToken, machineId, volumeId);
|
|
|
135
160
|
|
|
136
161
|
```ts
|
|
137
162
|
const orchestrationVars = loadDotEnv(projectRoot);
|
|
138
|
-
const infisicalConfig = getInfisicalConfig(orchestrationVars);
|
|
139
|
-
const containerSecrets = infisicalConfig
|
|
140
|
-
? await resolveContainerSecrets(infisicalConfig)
|
|
141
|
-
: orchestrationVars; // fallback for local dev without Infisical
|
|
163
|
+
const infisicalConfig = await getInfisicalConfig(orchestrationVars);
|
|
142
164
|
|
|
165
|
+
// Pass only Infisical credentials to the container
|
|
143
166
|
const config: ContainerConfig = {
|
|
144
|
-
envVars:
|
|
167
|
+
envVars: {
|
|
168
|
+
INFISICAL_TOKEN: infisicalConfig.token,
|
|
169
|
+
INFISICAL_PROJECT_ID: infisicalConfig.projectId,
|
|
170
|
+
INFISICAL_ENVIRONMENT: infisicalConfig.environment,
|
|
171
|
+
},
|
|
145
172
|
flyToken: orchestrationVars.FLY_API_TOKEN,
|
|
146
173
|
flyApp: orchestrationVars.FLY_APP_NAME,
|
|
147
174
|
...
|
|
@@ -186,6 +213,12 @@ A container stays running and accepts messages until it receives a **detach** or
|
|
|
186
213
|
Without either signal, the container waits indefinitely for new messages — this is intentional
|
|
187
214
|
so that interactive users can send follow-up messages at any time.
|
|
188
215
|
|
|
216
|
+
### Task absorption
|
|
217
|
+
|
|
218
|
+
Set `config.absorbTasks = true` to have the container absorb task files from other containers
|
|
219
|
+
at startup. This is off by default. When enabled, the container scans `tasks/` for task files
|
|
220
|
+
belonging to other containers, merges their tasks into its own queue, and deletes the foreign files.
|
|
221
|
+
|
|
189
222
|
## Webhooks
|
|
190
223
|
|
|
191
224
|
Set `webhookUrl` on `ContainerConfig` to receive real-time notifications of container activity. The container POSTs JSON to that URL on key events (no retries; failures are logged to stderr). Set `webhookSecret` to include a `Bearer` token in the `Authorization` header for authenticating webhook requests.
|
|
@@ -230,12 +263,15 @@ Every POST body has this shape:
|
|
|
230
263
|
|
|
231
264
|
```ts
|
|
232
265
|
const orchestrationVars = loadDotEnv("/path/to/project");
|
|
233
|
-
const infisicalConfig = getInfisicalConfig(orchestrationVars);
|
|
234
|
-
const containerSecrets = await resolveContainerSecrets(infisicalConfig);
|
|
266
|
+
const infisicalConfig = await getInfisicalConfig(orchestrationVars);
|
|
235
267
|
|
|
236
268
|
const config: ContainerConfig = {
|
|
237
269
|
projectRoot: "/path/to/project",
|
|
238
|
-
envVars:
|
|
270
|
+
envVars: {
|
|
271
|
+
INFISICAL_TOKEN: infisicalConfig.token,
|
|
272
|
+
INFISICAL_PROJECT_ID: infisicalConfig.projectId,
|
|
273
|
+
INFISICAL_ENVIRONMENT: infisicalConfig.environment,
|
|
274
|
+
},
|
|
239
275
|
registry: new FileContainerRegistry("/path/to/.container-registry.jsonl"),
|
|
240
276
|
webhookUrl: "https://example.com/hooks/container-events",
|
|
241
277
|
webhookSecret: "your-webhook-secret",
|
package/dist/container.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export interface ContainerConfig {
|
|
|
23
23
|
initialPrompt?: string;
|
|
24
24
|
/** Override the host port for local containers (default: auto-selected). */
|
|
25
25
|
localPort?: number;
|
|
26
|
+
/** Absorb task files from other containers at startup. Default: false. */
|
|
27
|
+
absorbTasks?: boolean;
|
|
26
28
|
}
|
|
27
29
|
export interface RepoOptions {
|
|
28
30
|
repoUrl: string;
|
package/dist/container.js
CHANGED
|
@@ -119,6 +119,8 @@ export async function startContainer(config, repo) {
|
|
|
119
119
|
extra.DETACHED = "1";
|
|
120
120
|
if (config.initialPrompt)
|
|
121
121
|
extra.INITIAL_PROMPT = config.initialPrompt;
|
|
122
|
+
if (config.absorbTasks)
|
|
123
|
+
extra.ABSORB_TASKS = "1";
|
|
122
124
|
const containerEnv = buildContainerEnv(repo, config.envVars, extra);
|
|
123
125
|
// Build docker run args
|
|
124
126
|
const args = ["run", "-d", "--rm", "--name", containerName];
|