@desplega.ai/agent-env 0.0.0-stage → 0.4.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/LICENSE +21 -0
- package/README.md +364 -2
- package/assets/icon.svg +5 -0
- package/bin/agent-env.mjs +214 -0
- package/docs/agent-swarm.md +146 -0
- package/docs/releases.md +210 -0
- package/docs/screenshots/dashboard-desktop.png +0 -0
- package/docs/screenshots/dashboard-mobile.png +0 -0
- package/docs/snapshots-and-worktrees.md +221 -0
- package/examples/agent-swarm/README.md +5 -0
- package/examples/agent-swarm/agent-env.json +11 -0
- package/examples/agent-swarm/compose.agent-env.yaml +101 -0
- package/examples/agent-swarm/verify.mjs +55 -0
- package/examples/compose/.dockerignore +3 -0
- package/examples/compose/Dockerfile +4 -0
- package/examples/compose/README.md +109 -0
- package/examples/compose/compose.yaml +36 -0
- package/examples/compose/server.mjs +6 -0
- package/examples/hello/agent-env.json +20 -0
- package/examples/hello/index.html +12 -0
- package/package.json +50 -4
- package/skills/agent-env/SKILL.md +156 -0
- package/skills/agent-env/references/remote-and-data.md +140 -0
- package/src/cloud.mjs +314 -0
- package/src/config.mjs +133 -0
- package/src/daemon.mjs +484 -0
- package/src/environments.mjs +207 -0
- package/src/goodies.mjs +71 -0
- package/src/init.mjs +68 -0
- package/src/locks.mjs +56 -0
- package/src/maintenance.mjs +74 -0
- package/src/paths.mjs +5 -0
- package/src/providers/CLOUD_SANDBOX.md +240 -0
- package/src/providers/README.md +62 -0
- package/src/providers/compose.mjs +460 -0
- package/src/providers/docker.mjs +207 -0
- package/src/providers/index.mjs +13 -0
- package/src/providers/sandbox.mjs +170 -0
- package/src/proxy.mjs +149 -0
- package/src/runtime.mjs +83 -0
- package/src/skill.mjs +51 -0
- package/src/snapshots.mjs +394 -0
- package/src/source-paths.mjs +15 -0
- package/src/system.mjs +39 -0
- package/src/tls.mjs +57 -0
- package/src/ui-client.js +51 -0
- package/src/ui.html +12 -0
- package/src/ui.mjs +32 -0
- package/src/watch.mjs +20 -0
- package/src/worktrees.mjs +154 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
x-workspace: &workspace
|
|
2
|
+
- .:/workspace
|
|
3
|
+
- dependencies:/workspace/node_modules
|
|
4
|
+
- ui-dependencies:/workspace/apps/ui/node_modules
|
|
5
|
+
- templates-dependencies:/workspace/apps/templates-ui/node_modules
|
|
6
|
+
- evals-dependencies:/workspace/apps/evals/node_modules
|
|
7
|
+
- catalog-dependencies:/workspace/packages/model-catalog/node_modules
|
|
8
|
+
- routing-dependencies:/workspace/packages/model-routing/node_modules
|
|
9
|
+
- e2e-dependencies:/workspace/packages/ui-e2e/node_modules
|
|
10
|
+
- bun-cache:/root/.bun/install/cache
|
|
11
|
+
|
|
12
|
+
services:
|
|
13
|
+
setup:
|
|
14
|
+
image: oven/bun:1.4.4
|
|
15
|
+
working_dir: /workspace
|
|
16
|
+
command: ["bun", "--no-env-file", "install", "--frozen-lockfile", "--ignore-scripts"]
|
|
17
|
+
volumes: *workspace
|
|
18
|
+
api:
|
|
19
|
+
image: oven/bun:1.4.4
|
|
20
|
+
working_dir: /workspace
|
|
21
|
+
command: ["bun", "--no-env-file", "--expose-gc", "src/http.ts"]
|
|
22
|
+
depends_on:
|
|
23
|
+
setup:
|
|
24
|
+
condition: service_completed_successfully
|
|
25
|
+
environment:
|
|
26
|
+
AGENT_SWARM_API_KEY: ${AGENT_SWARM_API_KEY:?Provide a test-only API key through a private env file}
|
|
27
|
+
DATABASE_PATH: /data/agent-swarm.sqlite
|
|
28
|
+
AGENT_FS_LOCAL_DIR: /data/files
|
|
29
|
+
NODE_ENV: development
|
|
30
|
+
PORT: "3013"
|
|
31
|
+
MCP_BASE_URL: http://api:3013
|
|
32
|
+
APP_URL: http://localhost:4574
|
|
33
|
+
CORS_ALLOWED_ORIGINS: http://localhost:4574,http://${AGENT_SWARM_INSTANCE:-swarm-example}.localhost:4574
|
|
34
|
+
DESPLEGA_TELEMETRY_ENV: test
|
|
35
|
+
ANONYMIZED_TELEMETRY: "false"
|
|
36
|
+
CAPABILITIES: core,task,kv,skills,repo
|
|
37
|
+
HEARTBEAT_DISABLE: "true"
|
|
38
|
+
HEARTBEAT_CHECKLIST_DISABLE: "true"
|
|
39
|
+
TASK_AUTO_TITLE_ENABLED: "false"
|
|
40
|
+
RUNTIME_POOLS_ENABLED: "false"
|
|
41
|
+
CHAT_SDK_ENABLED: "false"
|
|
42
|
+
SLACK_DISABLE: "true"
|
|
43
|
+
GITHUB_DISABLE: "true"
|
|
44
|
+
GITLAB_DISABLE: "true"
|
|
45
|
+
AZURE_DEVOPS_DISABLE: "true"
|
|
46
|
+
BITBUCKET_DISABLE: "true"
|
|
47
|
+
AGENTMAIL_DISABLE: "true"
|
|
48
|
+
LINEAR_DISABLE: "true"
|
|
49
|
+
JIRA_DISABLE: "true"
|
|
50
|
+
OAUTH_KEEPALIVE_DISABLE: "true"
|
|
51
|
+
OAUTH_REFRESH_SWEEP_DISABLE: "true"
|
|
52
|
+
volumes:
|
|
53
|
+
- .:/workspace:ro
|
|
54
|
+
- dependencies:/workspace/node_modules
|
|
55
|
+
- ui-dependencies:/workspace/apps/ui/node_modules
|
|
56
|
+
- templates-dependencies:/workspace/apps/templates-ui/node_modules
|
|
57
|
+
- evals-dependencies:/workspace/apps/evals/node_modules
|
|
58
|
+
- catalog-dependencies:/workspace/packages/model-catalog/node_modules
|
|
59
|
+
- routing-dependencies:/workspace/packages/model-routing/node_modules
|
|
60
|
+
- e2e-dependencies:/workspace/packages/ui-e2e/node_modules
|
|
61
|
+
- data:/data
|
|
62
|
+
ports: ["127.0.0.1:4313:3013"]
|
|
63
|
+
healthcheck:
|
|
64
|
+
test: ["CMD", "bun", "--no-env-file", "-e", "fetch('http://127.0.0.1:3013/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"]
|
|
65
|
+
interval: 2s
|
|
66
|
+
timeout: 3s
|
|
67
|
+
retries: 25
|
|
68
|
+
ui:
|
|
69
|
+
image: oven/bun:1.4.4
|
|
70
|
+
working_dir: /workspace/apps/ui
|
|
71
|
+
command:
|
|
72
|
+
- bun
|
|
73
|
+
- --no-env-file
|
|
74
|
+
- -e
|
|
75
|
+
- "import {createServer} from 'vite'; const server = await createServer({envDir: false, server: {host: '0.0.0.0', port: 5274, strictPort: true}}); await server.listen(); server.printUrls();"
|
|
76
|
+
depends_on:
|
|
77
|
+
setup:
|
|
78
|
+
condition: service_completed_successfully
|
|
79
|
+
api:
|
|
80
|
+
condition: service_healthy
|
|
81
|
+
environment:
|
|
82
|
+
VITE_PROXY_TARGET: http://api:3013
|
|
83
|
+
VITE_ALLOWED_HOSTS: .localhost
|
|
84
|
+
VITE_PLAUSIBLE_ANALYTICS: "false"
|
|
85
|
+
volumes: *workspace
|
|
86
|
+
ports: ["127.0.0.1:4574:5274"]
|
|
87
|
+
healthcheck:
|
|
88
|
+
test: ["CMD", "bun", "--no-env-file", "-e", "fetch('http://127.0.0.1:5274').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"]
|
|
89
|
+
interval: 2s
|
|
90
|
+
timeout: 3s
|
|
91
|
+
retries: 25
|
|
92
|
+
volumes:
|
|
93
|
+
data: {}
|
|
94
|
+
dependencies: {}
|
|
95
|
+
ui-dependencies: {}
|
|
96
|
+
templates-dependencies: {}
|
|
97
|
+
evals-dependencies: {}
|
|
98
|
+
catalog-dependencies: {}
|
|
99
|
+
routing-dependencies: {}
|
|
100
|
+
e2e-dependencies: {}
|
|
101
|
+
bun-cache: {}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import http from 'node:http';
|
|
3
|
+
|
|
4
|
+
const instance = process.env.AGENT_SWARM_INSTANCE ?? 'swarm-example';
|
|
5
|
+
const apiKey = process.env.AGENT_SWARM_API_KEY;
|
|
6
|
+
assert.ok(apiKey, 'Provide a test-only AGENT_SWARM_API_KEY.');
|
|
7
|
+
const readOnly = process.argv.includes('--read-only');
|
|
8
|
+
const path = '/api/kv/_/agent-env-example/persistence';
|
|
9
|
+
const value = 'agent-env-local-persistence-check';
|
|
10
|
+
|
|
11
|
+
function request(port, path, { method = 'GET', authenticated = false, body, named = true } = {}) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
const req = http.request({
|
|
14
|
+
hostname: '127.0.0.1', port, path, method,
|
|
15
|
+
headers: {
|
|
16
|
+
host: `${named ? `${instance}.localhost` : 'localhost'}:${port}`,
|
|
17
|
+
...(authenticated ? { authorization: `Bearer ${apiKey}` } : {}),
|
|
18
|
+
...(body ? { 'content-type': 'application/json' } : {}),
|
|
19
|
+
},
|
|
20
|
+
}, response => {
|
|
21
|
+
let text = '';
|
|
22
|
+
response.setEncoding('utf8');
|
|
23
|
+
response.on('data', chunk => { text += chunk; });
|
|
24
|
+
response.on('end', () => resolve({ status: response.statusCode, text, headers: response.headers }));
|
|
25
|
+
});
|
|
26
|
+
req.setTimeout(15000, () => req.destroy(new Error('The local HTTP request timed out.')));
|
|
27
|
+
req.on('error', reject);
|
|
28
|
+
req.end(body ? JSON.stringify(body) : undefined);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
for (const named of [true, false]) {
|
|
33
|
+
const health = await request(4313, '/health', { named });
|
|
34
|
+
assert.equal(health.status, 200, 'The API health request must succeed.');
|
|
35
|
+
assert.equal(JSON.parse(health.text).status, 'ok');
|
|
36
|
+
}
|
|
37
|
+
assert.equal((await request(4313, path)).status, 401, 'The API must require authentication.');
|
|
38
|
+
if (!readOnly) {
|
|
39
|
+
const write = await request(4313, path, { method: 'PUT', authenticated: true, body: { value } });
|
|
40
|
+
assert.equal(write.status, 200, 'The API write must succeed.');
|
|
41
|
+
assert.equal(JSON.parse(write.text).value, value);
|
|
42
|
+
}
|
|
43
|
+
for (const port of [4313, 4574]) {
|
|
44
|
+
const read = await request(port, path, { authenticated: true });
|
|
45
|
+
assert.equal(read.status, 200, 'The direct or proxied API read must succeed.');
|
|
46
|
+
assert.equal(JSON.parse(read.text).value, value);
|
|
47
|
+
}
|
|
48
|
+
const ui = await request(4574, '/');
|
|
49
|
+
assert.equal(ui.status, 200, 'The dashboard document must load.');
|
|
50
|
+
assert.match(ui.text, /\/src\/main\.tsx/);
|
|
51
|
+
const module = await request(4574, '/src/main.tsx');
|
|
52
|
+
assert.equal(module.status, 200, 'Vite must transform the real application entry.');
|
|
53
|
+
assert.match(module.headers['content-type'], /javascript/);
|
|
54
|
+
assert.equal((await request(4574, '/health')).status, 200, 'The dashboard proxy must reach the API.');
|
|
55
|
+
console.log(`PASS: ${readOnly ? 'retained KV value' : 'KV write/read'}, authentication, named and selected localhost API routes, dashboard HTML, transformed entry, and dashboard API proxy.`);
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Compose example
|
|
2
|
+
|
|
3
|
+
This project builds a Node web service and starts Postgres after its health check succeeds.
|
|
4
|
+
The `tools` service requires the `tools` profile.
|
|
5
|
+
|
|
6
|
+
Use these commands from this directory:
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
agent-env init
|
|
10
|
+
agent-env env set POSTGRES_PASSWORD < /path/to/private/password-file
|
|
11
|
+
agent-env up --name compose-one
|
|
12
|
+
agent-env up --name compose-two --profile tools
|
|
13
|
+
agent-env exec compose-one --service db -- psql -U postgres -d app -c 'select 1'
|
|
14
|
+
agent-env rm compose-one --purge
|
|
15
|
+
agent-env rm compose-two --purge
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The source Compose file stays unchanged. Each instance has its own containers, network, build image, and data volume.
|
|
19
|
+
The web service retains public port 3000 in its metadata. Docker assigns a private loopback port.
|
|
20
|
+
Postgres has no published port and receives no HTTP route.
|
|
21
|
+
|
|
22
|
+
`loadSpec` accepts one environment name through `env`, as a string or a one-element array.
|
|
23
|
+
The `profile`, `compose-file`, and `env-file` options accept a string or an array of strings.
|
|
24
|
+
Arrays preserve order. Later Compose files and environment files override earlier files.
|
|
25
|
+
|
|
26
|
+
Named environments use this public configuration:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"provider": "compose",
|
|
31
|
+
"compose": { "files": ["compose.yaml"] },
|
|
32
|
+
"environments": {
|
|
33
|
+
"review": {
|
|
34
|
+
"compose": { "profiles": ["tools"] },
|
|
35
|
+
"envFiles": [".env.review"],
|
|
36
|
+
"env": { "POSTGRES_PASSWORD": { "secret": "POSTGRES_PASSWORD" } }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
An environment value can be a literal string, `{ "secret": "PRIVATE_KEY" }`, or `{ "env": "PROCESS_VARIABLE" }`.
|
|
43
|
+
Store secret values through stdin or a process variable reference. The private store uses mode 0600 outside the checkout.
|
|
44
|
+
Environment files support `export`, comments, quoted values, and multiline quoted values. Their values remain literal, without shell execution or variable expansion.
|
|
45
|
+
Global environment values apply to Docker services and Compose interpolation. Service `environment` entries control which variables enter Compose containers.
|
|
46
|
+
|
|
47
|
+
For multiple published ports, define one alias for each port:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"compose": {
|
|
52
|
+
"files": ["compose.yaml"],
|
|
53
|
+
"routes": {
|
|
54
|
+
"web": { "service": "web", "target": 3000, "port": 3000, "protocol": "http" },
|
|
55
|
+
"admin": { "service": "web", "target": 9090, "port": 9090, "protocol": "http" }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Services without published ports remain internal, including services with `expose` entries.
|
|
62
|
+
Use explicit protocols for ports that do not match common HTTP ports.
|
|
63
|
+
UDP, replicas, host namespaces, static addresses, custom network allocation, and volume driver options require separate support.
|
|
64
|
+
The provider rejects these features. External networks and volumes remain external and survive purge.
|
|
65
|
+
Remote bind and build paths must resolve inside the checkout. Remote binds cannot use files excluded from synchronization.
|
|
66
|
+
The daemon implements Compose volume forks through verified snapshots.
|
|
67
|
+
|
|
68
|
+
## Integration contract
|
|
69
|
+
|
|
70
|
+
Import `ComposeProvider` from `src/providers/compose.mjs` and register it under `compose`.
|
|
71
|
+
Docker Compose must support `config --format json`, `config --variables`, and `up --wait`.
|
|
72
|
+
The class implements the Docker provider interface. It uses the Docker helpers for commands, synchronization, SSH, and tunnels.
|
|
73
|
+
`create`, `start`, and `wait` retain desired public ports and discover private backend ports.
|
|
74
|
+
The daemon coordinates snapshot-based forks. Direct provider `fork` calls remain unsupported.
|
|
75
|
+
|
|
76
|
+
Each `spec.services` and `handle.services` entry has `composeService`.
|
|
77
|
+
Internal entries have `internal: true` and no `port`, `protocol`, or HTTP URL.
|
|
78
|
+
The parent must exclude internal entries when it reserves listeners, changes route focus, and formats URLs.
|
|
79
|
+
Published entries have `port`, `containerPort`, `protocol`, and `transport`.
|
|
80
|
+
Handle entries also have `container` and `hostPort`. Several aliases can refer to one container.
|
|
81
|
+
|
|
82
|
+
The initialization exports are `createInitialConfig(options)` and `initialize(options)` from `src/init.mjs`.
|
|
83
|
+
Both accept `cwd`, `command`, `port`, and `image`.
|
|
84
|
+
The first returns public configuration. The second writes a new file and returns `{ filename, config }`.
|
|
85
|
+
`initialize` also accepts `config` for the output filename. It never replaces an existing file.
|
|
86
|
+
|
|
87
|
+
The environment command exports come from `src/environments.mjs`:
|
|
88
|
+
|
|
89
|
+
- `setEnvironmentSecret({ cwd, environment, key, stdin })` reads a readable stream.
|
|
90
|
+
- `setEnvironmentSecret({ cwd, environment, key, fromEnv })` reads a process variable reference.
|
|
91
|
+
- `listEnvironmentSecrets({ cwd, environment })` returns sorted variable names.
|
|
92
|
+
- `unsetEnvironmentSecret({ cwd, environment, key })` returns whether the variable existed.
|
|
93
|
+
|
|
94
|
+
These helpers also accept `home` to select the private state directory.
|
|
95
|
+
`setEnvironmentSecret` accepts `env` for an explicit source environment. It rejects a literal `value` argument.
|
|
96
|
+
`readEnvironmentSecrets` and `loadEnvironment` return private values for runtime integration only.
|
|
97
|
+
`parseEnvFile`, `resolveEnvironment`, and `stringList` provide parsing and configuration helpers.
|
|
98
|
+
|
|
99
|
+
`redactEnvironment(output, values)` accepts an array or an object with private values.
|
|
100
|
+
Compose logs and execution output already use this helper.
|
|
101
|
+
The daemon applies it to Docker logs, execution output, and errors.
|
|
102
|
+
Keep specification values in private daemon state. Never print the full specification or write it into generated public configuration.
|
|
103
|
+
`spec.compose.model` captures the resolved source configuration. It can contain private values.
|
|
104
|
+
This snapshot preserves shell interpolation when the daemon has a different environment.
|
|
105
|
+
It also lets the daemon detect changes to commands, builds, health checks, and other Compose settings.
|
|
106
|
+
`spec.compose.environment` contains private values needed for runtime secrets and redaction.
|
|
107
|
+
|
|
108
|
+
Run `node test/compose.e2e.mjs` for the provider E2E. It creates and removes only its own resources.
|
|
109
|
+
Run `npm run test:features` for the integrated Compose CLI, snapshot, and worktree flow.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
services:
|
|
2
|
+
web:
|
|
3
|
+
build: .
|
|
4
|
+
container_name: existing-web-name
|
|
5
|
+
ports:
|
|
6
|
+
- "3000:3000"
|
|
7
|
+
environment:
|
|
8
|
+
PORT: "3000"
|
|
9
|
+
depends_on:
|
|
10
|
+
db:
|
|
11
|
+
condition: service_healthy
|
|
12
|
+
healthcheck:
|
|
13
|
+
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3000').then(r => { if (!r.ok) process.exit(1) }).catch(() => process.exit(1))"]
|
|
14
|
+
interval: 2s
|
|
15
|
+
timeout: 2s
|
|
16
|
+
retries: 30
|
|
17
|
+
db:
|
|
18
|
+
image: postgres:16-alpine
|
|
19
|
+
environment:
|
|
20
|
+
POSTGRES_USER: postgres
|
|
21
|
+
POSTGRES_DB: app
|
|
22
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD through agent-env env or an explicit env file}
|
|
23
|
+
volumes:
|
|
24
|
+
- data:/var/lib/postgresql/data
|
|
25
|
+
healthcheck:
|
|
26
|
+
test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U postgres -d app"]
|
|
27
|
+
interval: 2s
|
|
28
|
+
timeout: 2s
|
|
29
|
+
retries: 30
|
|
30
|
+
tools:
|
|
31
|
+
image: busybox:1.37
|
|
32
|
+
profiles: [tools]
|
|
33
|
+
command: ["sh", "-c", "while :; do sleep 3600; done"]
|
|
34
|
+
volumes:
|
|
35
|
+
data:
|
|
36
|
+
name: existing-shared-data-name
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"services": {
|
|
3
|
+
"web": {
|
|
4
|
+
"image": "busybox:1.37",
|
|
5
|
+
"port": 3300,
|
|
6
|
+
"command": "httpd -f -p 3000 -h /workspace",
|
|
7
|
+
"containerPort": 3000,
|
|
8
|
+
"volumes": { "data": "/data" }
|
|
9
|
+
},
|
|
10
|
+
"db": {
|
|
11
|
+
"image": "postgres:16-alpine",
|
|
12
|
+
"port": 55432,
|
|
13
|
+
"containerPort": 5432,
|
|
14
|
+
"protocol": "tcp",
|
|
15
|
+
"health": "pg_isready -h 127.0.0.1 -U postgres",
|
|
16
|
+
"env": { "POSTGRES_PASSWORD": "dev-only", "POSTGRES_DB": "app" },
|
|
17
|
+
"volumes": { "data": "/var/lib/postgresql/data" }
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<meta charset="utf-8">
|
|
4
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
5
|
+
<title>agent-env</title>
|
|
6
|
+
<style>body{font:18px system-ui;background:#f7f7f2;color:#242923;max-width:640px;margin:15vh auto;padding:24px}code{background:#e8ebe2;padding:4px 8px;border-radius:5px}h1{font-size:48px;letter-spacing:-2px}</style>
|
|
7
|
+
<h1>Local address. Any machine.</h1>
|
|
8
|
+
<p>This page runs in an isolated Docker container.</p>
|
|
9
|
+
<p>Your address: <code id="address"></code></p>
|
|
10
|
+
<p>Edit this file, then reload. For SSH instances, run <code>agent-env sync NAME</code> first.</p>
|
|
11
|
+
<script>document.getElementById('address').textContent=location.host</script>
|
|
12
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,52 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@desplega.ai/agent-env",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Run development services behind stable localhost addresses.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"agent-env": "./bin/agent-env.mjs"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node test/run.mjs",
|
|
11
|
+
"check": "node scripts/check.mjs",
|
|
12
|
+
"test:e2e": "node test/run.mjs",
|
|
13
|
+
"test:core": "node test/run.mjs core",
|
|
14
|
+
"test:docker": "node test/run.mjs docker",
|
|
15
|
+
"test:runtime": "node test/runtime-e2e.mjs",
|
|
16
|
+
"test:features": "node test/run.mjs features",
|
|
17
|
+
"test:compose": "node test/run.mjs compose",
|
|
18
|
+
"test:ssh": "node test/run.mjs ssh",
|
|
19
|
+
"test:s3": "node test/run.mjs s3",
|
|
20
|
+
"test:cloud": "node test/run.mjs cloud"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=22"
|
|
24
|
+
},
|
|
25
|
+
"os": [
|
|
26
|
+
"darwin",
|
|
27
|
+
"linux"
|
|
28
|
+
],
|
|
29
|
+
"files": [
|
|
30
|
+
"bin",
|
|
31
|
+
"src",
|
|
32
|
+
"skills",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE",
|
|
35
|
+
"examples",
|
|
36
|
+
"docs",
|
|
37
|
+
"assets"
|
|
38
|
+
],
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"author": "Desplega Labs",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/desplega-ai/agent-env.git"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://github.com/desplega-ai/agent-env#readme",
|
|
46
|
+
"bugs": {
|
|
47
|
+
"url": "https://github.com/desplega-ai/agent-env/issues"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-env
|
|
3
|
+
description: Run and troubleshoot development environments with agent-env, including Docker, Compose, dotenv, SSH, local routing, and persistent data.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# agent-env
|
|
8
|
+
|
|
9
|
+
Use the `agent-env` CLI to run development services behind stable localhost addresses.
|
|
10
|
+
These instructions describe CLI version 0.4.0.
|
|
11
|
+
Check `agent-env --help` when the installed version differs from these instructions.
|
|
12
|
+
Run `agent-env skill` to read this guide and its reference offline.
|
|
13
|
+
Run `agent-env skill install` to install the bundled skill for your coding agent.
|
|
14
|
+
|
|
15
|
+
## Prepare the project
|
|
16
|
+
|
|
17
|
+
1. Inspect `agent-env.json`, Compose files, and package scripts before changing configuration.
|
|
18
|
+
2. Check `agent-env --version`. If absent, install the CLI with `npm install -g @desplega.ai/agent-env`.
|
|
19
|
+
3. Check `docker info`. Compose projects also require `docker compose version`.
|
|
20
|
+
4. Inspect `agent-env --json list` before choosing an instance name or reusing ports.
|
|
21
|
+
|
|
22
|
+
The CLI requires macOS or Linux and Node.js 22 or later.
|
|
23
|
+
Installing this skill does not install the CLI or Docker.
|
|
24
|
+
Preserve existing project configuration and the user's selected compute target.
|
|
25
|
+
Use local Docker when no remote target is requested.
|
|
26
|
+
|
|
27
|
+
## Start and verify
|
|
28
|
+
|
|
29
|
+
Existing Compose files work directly with `up`.
|
|
30
|
+
For another project without `agent-env.json`, run `agent-env init` to detect its development command.
|
|
31
|
+
Do not overwrite an existing configuration.
|
|
32
|
+
If detection fails, supply the actual project command, image, and port. For example:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
agent-env init --image node:22-bookworm-slim --port 3000 -- node server.js
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Replace `node server.js` with the project's command.
|
|
39
|
+
Native services run in `/workspace`, which contains the checkout.
|
|
40
|
+
Include dependency installation in the command when needed.
|
|
41
|
+
The server must listen on `0.0.0.0` inside its container.
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
agent-env up --name app --local
|
|
45
|
+
agent-env --json list
|
|
46
|
+
agent-env logs app --service web
|
|
47
|
+
agent-env exec app --service web -- node --version
|
|
48
|
+
agent-env ui
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Replace `app` and `web` with the chosen instance and service names.
|
|
52
|
+
Choose an exec command supported by the service's image.
|
|
53
|
+
Use returned addresses instead of assuming port 3000.
|
|
54
|
+
Verify a real application response through the returned HTTP URL.
|
|
55
|
+
For PostgreSQL or another TCP service, verify a connection with its native client.
|
|
56
|
+
Report the instance, target, service URLs, verification result, and any remaining failure.
|
|
57
|
+
|
|
58
|
+
`app.localhost:PORT` addresses one HTTP instance.
|
|
59
|
+
`agent-env use app` selects the instance behind bare `localhost:PORT` and raw TCP ports.
|
|
60
|
+
Changing selection closes existing connections through bare localhost.
|
|
61
|
+
The local daemon must remain running for routing and SSH tunnels.
|
|
62
|
+
|
|
63
|
+
## Compose and configuration
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
agent-env up --name app --local --compose-file compose.yaml --profile tools
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Use only files and profiles that the project defines.
|
|
70
|
+
Repeat `--compose-file` or `--profile` for multiple entries.
|
|
71
|
+
Compose retains builds, health checks, dependencies, named volumes, and internal services.
|
|
72
|
+
Keep secrets in declared `environment`, `env_file`, or Compose secret sources.
|
|
73
|
+
Use `compose.routes` when one service needs aliases for multiple published ports.
|
|
74
|
+
See the [Compose configuration examples](https://github.com/desplega-ai/agent-env/blob/master/examples/compose/README.md).
|
|
75
|
+
|
|
76
|
+
For a Vite project using native Docker, define `services` in `agent-env.json`:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"services": {
|
|
81
|
+
"web": {
|
|
82
|
+
"image": "node:22-bookworm-slim",
|
|
83
|
+
"port": 3000,
|
|
84
|
+
"containerPort": 3000,
|
|
85
|
+
"command": "npm ci && npm run dev -- --host 0.0.0.0 --port 3000",
|
|
86
|
+
"volumes": { "dependencies": "/workspace/node_modules" }
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Adapt the command to the project. Named volumes retain dependencies and application data.
|
|
93
|
+
Use `agent-env add postgres` or `agent-env add redis` to generate a persistent service configuration.
|
|
94
|
+
For a named environment, include `--env review` when adding its service.
|
|
95
|
+
|
|
96
|
+
## Dotenv and private values
|
|
97
|
+
|
|
98
|
+
Automatic file order is `.env`, `.env.local`, `.env.<name>`, then `.env.<name>.local`.
|
|
99
|
+
The last two files apply when `--env NAME` selects a named environment.
|
|
100
|
+
Example files do not load automatically.
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
agent-env up --name review --local --env review
|
|
104
|
+
agent-env up --name app --local --env-file .env.development
|
|
105
|
+
agent-env up --name app --local --no-env-file
|
|
106
|
+
agent-env env set API_TOKEN --from-env MY_API_TOKEN
|
|
107
|
+
agent-env env list
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Choose one dotenv selection method. `--env-file` replaces automatic selection and conflicts with `--no-env-file`.
|
|
111
|
+
Do not print dotenv contents or secret values.
|
|
112
|
+
Private values can also enter through stdin: `agent-env env set API_TOKEN < /path/to/private/token-file`.
|
|
113
|
+
Use `--env NAME` consistently for the secret store and startup.
|
|
114
|
+
Stores belong to the current project and selected environment.
|
|
115
|
+
|
|
116
|
+
Native Docker treats dotenv values literally. It does not expand shell expressions.
|
|
117
|
+
The parent shell enters native containers only through explicit `{ "env": "NAME" }` references.
|
|
118
|
+
Compose retains shell interpolation precedence. Its services must declare container variables through `environment` or `env_file`.
|
|
119
|
+
`--no-env-file` does not disable Compose service `env_file` declarations.
|
|
120
|
+
See [environment precedence and secret references](https://github.com/desplega-ai/agent-env/blob/master/README.md#environments-and-secrets) for detailed configuration.
|
|
121
|
+
|
|
122
|
+
Saved instances retain resolved values:
|
|
123
|
+
|
|
124
|
+
- `agent-env up app` resumes saved configuration from any directory.
|
|
125
|
+
- `agent-env up --name app` reads the current project's configuration and reloads edited dotenv files.
|
|
126
|
+
- Environment-only changes recreate Docker or Compose containers and preserve named volumes.
|
|
127
|
+
- Structural changes, such as ports or commands, require `rm app`, then `up --name app` with the intended target.
|
|
128
|
+
|
|
129
|
+
Do not use `rm --purge` to resolve a configuration mismatch. It deletes managed data.
|
|
130
|
+
|
|
131
|
+
## Stop and troubleshoot
|
|
132
|
+
|
|
133
|
+
Use `agent-env down app` to stop services and retain data.
|
|
134
|
+
`agent-env rm app` removes containers but retains volumes and built images.
|
|
135
|
+
`agent-env rm app --purge` also removes managed data and owned Compose image tags.
|
|
136
|
+
Delete data only when that deletion is part of the user's request.
|
|
137
|
+
`agent-env daemon stop` stops routing and synchronization, but leaves containers running.
|
|
138
|
+
Avoid `--all` when working on one project.
|
|
139
|
+
|
|
140
|
+
| Symptom | Next action |
|
|
141
|
+
| --- | --- |
|
|
142
|
+
| Startup timeout | Inspect `logs`. Check the bind address, port, and health check. Increase `--timeout` for slow initialization. |
|
|
143
|
+
| Old environment values | Run `up --name NAME` from the correct checkout with the intended environment and dotenv options. |
|
|
144
|
+
| Configuration changed | Inspect the change. Use `rm NAME`, then recreate without `--purge` to retain data. |
|
|
145
|
+
| Port conflict | Inspect `list` and the port owner. Change configuration instead of terminating unrelated processes. |
|
|
146
|
+
| Stale remote files | Run `sync NAME`. Check exclusions before expecting credentials or generated directories to transfer. |
|
|
147
|
+
| CLI request timed out | Check `list` and `logs` before retrying. The daemon may still be starting services. |
|
|
148
|
+
|
|
149
|
+
For a slow saved instance, use `agent-env up app --timeout 900`.
|
|
150
|
+
Startup timeouts accept 1 to 3600 seconds. Compose image builds precede the readiness timeout.
|
|
151
|
+
|
|
152
|
+
## Additional workflows
|
|
153
|
+
|
|
154
|
+
Read [remote services and data operations](references/remote-and-data.md) for SSH, Hetzner, HTTPS, snapshots, and worktree forks.
|
|
155
|
+
For Agent Swarm itself, follow the [tested example](https://github.com/desplega-ai/agent-env/blob/master/docs/agent-swarm.md).
|
|
156
|
+
That example uses an explicit private env file, isolated Linux dependencies, and a 900-second startup timeout.
|