@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.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +364 -2
  3. package/assets/icon.svg +5 -0
  4. package/bin/agent-env.mjs +214 -0
  5. package/docs/agent-swarm.md +146 -0
  6. package/docs/releases.md +210 -0
  7. package/docs/screenshots/dashboard-desktop.png +0 -0
  8. package/docs/screenshots/dashboard-mobile.png +0 -0
  9. package/docs/snapshots-and-worktrees.md +221 -0
  10. package/examples/agent-swarm/README.md +5 -0
  11. package/examples/agent-swarm/agent-env.json +11 -0
  12. package/examples/agent-swarm/compose.agent-env.yaml +101 -0
  13. package/examples/agent-swarm/verify.mjs +55 -0
  14. package/examples/compose/.dockerignore +3 -0
  15. package/examples/compose/Dockerfile +4 -0
  16. package/examples/compose/README.md +109 -0
  17. package/examples/compose/compose.yaml +36 -0
  18. package/examples/compose/server.mjs +6 -0
  19. package/examples/hello/agent-env.json +20 -0
  20. package/examples/hello/index.html +12 -0
  21. package/package.json +50 -4
  22. package/skills/agent-env/SKILL.md +156 -0
  23. package/skills/agent-env/references/remote-and-data.md +140 -0
  24. package/src/cloud.mjs +314 -0
  25. package/src/config.mjs +133 -0
  26. package/src/daemon.mjs +484 -0
  27. package/src/environments.mjs +207 -0
  28. package/src/goodies.mjs +71 -0
  29. package/src/init.mjs +68 -0
  30. package/src/locks.mjs +56 -0
  31. package/src/maintenance.mjs +74 -0
  32. package/src/paths.mjs +5 -0
  33. package/src/providers/CLOUD_SANDBOX.md +240 -0
  34. package/src/providers/README.md +62 -0
  35. package/src/providers/compose.mjs +460 -0
  36. package/src/providers/docker.mjs +207 -0
  37. package/src/providers/index.mjs +13 -0
  38. package/src/providers/sandbox.mjs +170 -0
  39. package/src/proxy.mjs +149 -0
  40. package/src/runtime.mjs +83 -0
  41. package/src/skill.mjs +51 -0
  42. package/src/snapshots.mjs +394 -0
  43. package/src/source-paths.mjs +15 -0
  44. package/src/system.mjs +39 -0
  45. package/src/tls.mjs +57 -0
  46. package/src/ui-client.js +51 -0
  47. package/src/ui.html +12 -0
  48. package/src/ui.mjs +32 -0
  49. package/src/watch.mjs +20 -0
  50. 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,3 @@
1
+ .env
2
+ .env.*
3
+ agent-env.json
@@ -0,0 +1,4 @@
1
+ FROM node:22-bookworm-slim
2
+ WORKDIR /app
3
+ COPY server.mjs .
4
+ CMD ["node", "server.mjs"]
@@ -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,6 @@
1
+ import http from 'node:http';
2
+
3
+ http.createServer((request, response) => {
4
+ response.setHeader('content-type', 'text/plain');
5
+ response.end('Compose web is ready. Postgres is internal.\n');
6
+ }).listen(Number(process.env.PORT ?? 3000), '0.0.0.0');
@@ -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.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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.