@cruxgarden/cli 0.0.9 → 0.0.11

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 CHANGED
@@ -2,6 +2,27 @@
2
2
  <img src=".github/banner.jpg" alt="Crux Garden - Where Ideas Grow" width="100%">
3
3
  </div>
4
4
 
5
+ ## What is Crux Garden?
6
+
7
+ Crux Garden is a model of how ideas manifest and develop over time. The heart of the model is the **Crux**, an atomic representation of an idea. In our implementation, a Crux can be text, media, code, or any digital content worth preserving. But Cruxes, just like ideas, don't exist in isolation. Ideas have origins. They lead to new ideas. They evolve. And often, they randomly connect. So it is with Cruxes. In Crux Garden, there are four types of relationships Cruxes can have with each other. These are called **Dimensions**:
8
+
9
+ - **GATES** — Cruxes which influenced or inspired a Crux; its origins and sources.
10
+ - **GARDENS** — Cruxes which emerged or grew from a Crux, its creations and consequences.
11
+ - **GROWTH** — How a Crux developed over time, its transformation and refinement.
12
+ - **GRAFTS** — Cruxes which connect to a Crux laterally, its associations and resonances.
13
+
14
+ These four Dimensions capture the fundamental ways that Cruxes, or ideas, relate to one another.
15
+
16
+ The power of a system that models ideas at such a primitive scale is that literally any idea or framework of ideas can be realized inside Crux Garden: interactive fiction with evolving storylines, personal knowledge bases connecting insights across domains, product development roadmaps linking requirements to releases, research systems tracking citations and discoveries, or anything else where ideas have origins, consequences, transformation, and connection.
17
+
18
+ Along with Cruxes and Dimensions, there are several other types including Tags, Themes, and Paths. See the database schema in `db/schema.sql` or the API documentation at `/docs` for details.
19
+
20
+ For further reading on the goals and ambitions of Crux Garden, explore the history of the [Digital Garden](https://maggieappleton.com/garden-history) movement and the 1945 essay [As We May Think](https://en.wikipedia.org/wiki/As_We_May_Think) by Vannevar Bush.
21
+
22
+ Will Stepp, October 2025
23
+
24
+ ## CLI
25
+
5
26
  The Crux Garden CLI tool helps manage the Crux Garden Nursery environment with Docker.
6
27
 
7
28
  The **Nursery** is a production-like demo environment with sample data, perfect for trials, demos, and showcasing features.
@@ -57,7 +78,7 @@ Start the Nursery environment:
57
78
  crux nursery start
58
79
  ```
59
80
 
60
- The API will be available at `http://localhost:3000` with demo data loaded.
81
+ The App will be available at `http://localhost:8080` and the API at `http://localhost:3000` with demo data loaded.
61
82
 
62
83
  View logs:
63
84
 
@@ -77,15 +98,26 @@ All commands are scoped under `crux nursery`:
77
98
 
78
99
  ### `crux nursery start`
79
100
 
80
- Start the Nursery environment (PostgreSQL, Redis, Migrations, and API with demo data).
101
+ Start the Nursery environment (App, API, PostgreSQL, Redis with demo data).
81
102
 
82
103
  ```bash
83
104
  crux nursery start
105
+
106
+ # With inline environment variables
107
+ crux nursery start API_PORT=3001 APP_PORT=8081
108
+
109
+ # With options and environment variables
110
+ crux nursery start --api-only JWT_SECRET=my-secret
84
111
  ```
85
112
 
86
113
  **Options:**
87
114
 
88
115
  - `--db-only` - Start only database services (PostgreSQL and Redis)
116
+ - `--api-only` - Start only API services (API, PostgreSQL, Redis) without the App
117
+
118
+ **Environment Variables:**
119
+
120
+ You can pass environment variables directly on the command line in `KEY=VALUE` format after the command and options. These override values from your `.env` file.
89
121
 
90
122
  ### `crux nursery stop`
91
123
 
@@ -103,6 +135,11 @@ Restart the Nursery environment.
103
135
  crux nursery restart
104
136
  ```
105
137
 
138
+ **Options:**
139
+
140
+ - `--db-only` - Restart only database services (PostgreSQL and Redis)
141
+ - `--api-only` - Restart only API services (API, PostgreSQL, Redis) without the App
142
+
106
143
  ### `crux nursery status`
107
144
 
108
145
  Show the status of all Nursery services.
@@ -198,6 +235,14 @@ Connect to Nursery Redis with `redis-cli`.
198
235
  crux nursery redis connect
199
236
  ```
200
237
 
238
+ ### `crux nursery api start`
239
+
240
+ Start only Nursery API services (API, PostgreSQL, Redis) without the App.
241
+
242
+ ```bash
243
+ crux nursery api start
244
+ ```
245
+
201
246
  ### `crux nursery api connect`
202
247
 
203
248
  Open a shell in the Nursery API container.
@@ -217,31 +262,81 @@ The Nursery is a production-like demo environment that:
217
262
 
218
263
  **Services:**
219
264
 
265
+ - **App**: `http://localhost:8080` - Crux Garden Web Application
220
266
  - **API**: `http://localhost:3000` - Crux Garden API (published image with demo data)
221
267
  - **PostgreSQL**: `localhost:5432` - Database
222
268
  - **Redis**: `localhost:6379` - Cache
223
269
 
224
270
  ## Environment Variables
225
271
 
226
- The Nursery environment has defaults for all environment variables, so a `.env` file is optional. However, you can override any variable by creating a `.env` file in your working directory:
272
+ The Nursery environment has defaults for all environment variables, so configuration is optional. You can override variables in two ways:
273
+
274
+ ### 1. Using a `.env` file
275
+
276
+ Create a `.env` file in your working directory:
227
277
 
228
278
  ```bash
229
- # JWT (has dev default, but you should use a different one)
279
+ # Port Mappings
280
+ API_PORT=3000 # External API port
281
+ APP_PORT=8080 # External App port
282
+ POSTGRES_PORT=5432 # External PostgreSQL port
283
+ REDIS_PORT=6379 # External Redis port
284
+
285
+ # PostgreSQL Configuration
286
+ POSTGRES_DB=cruxgarden
287
+ POSTGRES_USER=cruxgarden
288
+ POSTGRES_PASSWORD=cruxgarden_nursery_password
289
+
290
+ # Database & Cache URLs (override to use external services)
291
+ DATABASE_URL=postgresql://cruxgarden:cruxgarden_nursery_password@postgres:5432/cruxgarden
292
+ REDIS_URL=redis://redis:6379
293
+
294
+ # Security (has dev default, but you should use a different one)
230
295
  JWT_SECRET=your-super-secret-jwt-key-min-32-chars
231
296
 
232
- # AWS (defaults to "dummy" values)
297
+ # AWS Configuration (defaults to "dummy" values)
233
298
  AWS_ACCESS_KEY_ID=your-key
234
299
  AWS_SECRET_ACCESS_KEY=your-secret
235
300
  AWS_REGION=us-east-1
236
301
  AWS_SES_FROM_EMAIL=demo@example.com
237
302
  AWS_S3_ATTACHMENTS_BUCKET=crux-garden-attachments
238
303
 
239
- # Optional overrides
304
+ # Optional Configuration
305
+ NODE_ENV=production
240
306
  CORS_ORIGIN=*
241
307
  LOG_LEVEL=info
242
- PORT=3000
308
+ HOSTNAME=0.0.0.0
309
+ DB_POOL_MIN=2
310
+ DB_POOL_MAX=10
311
+ DB_POOL_IDLE_TIMEOUT=30000
312
+ DB_POOL_ACQUIRE_TIMEOUT=60000
313
+ RATE_LIMIT_TTL=60000
314
+ RATE_LIMIT_MAX=100
315
+ ```
316
+
317
+ ### 2. Using inline environment variables
318
+
319
+ Pass environment variables directly on the command line:
320
+
321
+ ```bash
322
+ # Override ports
323
+ crux nursery start API_PORT=3001 APP_PORT=8081
324
+
325
+ # Override database connection
326
+ crux nursery start DATABASE_URL=postgresql://user:pass@external-host:5432/db
327
+
328
+ # Multiple variables
329
+ crux nursery start API_PORT=3001 JWT_SECRET=my-secret AWS_REGION=us-west-2
330
+
331
+ # Works with all start commands
332
+ crux nursery api start API_PORT=4000
333
+ crux nursery db start POSTGRES_PORT=5433
334
+ crux nursery restart API_PORT=3001
335
+ crux nursery reset JWT_SECRET=new-secret
243
336
  ```
244
337
 
338
+ **Note:** Inline environment variables override values from your `.env` file. You can override `DATABASE_URL` and `REDIS_URL` to connect the API to external database/cache services instead of the bundled ones.
339
+
245
340
  ## Common Workflows
246
341
 
247
342
  ### Demo/Trial Setup
@@ -249,10 +344,11 @@ PORT=3000
249
344
  Use the Nursery environment for demos, trials, or showcasing features:
250
345
 
251
346
  ```bash
252
- # First time setup - pulls image and starts with demo data
347
+ # First time setup - pulls images and starts with demo data
253
348
  crux nursery start
254
349
 
255
- # View the demo at http://localhost:3000
350
+ # View the app at http://localhost:8080
351
+ # Or access the API directly at http://localhost:3000
256
352
 
257
353
  # Stop (keeps data for next demo)
258
354
  crux nursery stop
@@ -304,6 +400,7 @@ If you get an error about ports being in use, stop any existing services:
304
400
 
305
401
  ```bash
306
402
  # Check what's using the ports
403
+ lsof -i :8080 # Nursery App
307
404
  lsof -i :3000 # Nursery API
308
405
  lsof -i :5432 # Nursery PostgreSQL
309
406
  lsof -i :6379 # Nursery Redis
package/bin/crux.js CHANGED
@@ -37,13 +37,14 @@ const nursery = program
37
37
  .description("Manage the Nursery environment (demo/trial environment)");
38
38
 
39
39
  nursery
40
- .command("start")
41
- .description("Start the Nursery environment (api, postgres, redis)")
40
+ .command("start [env...]")
41
+ .description("Start the Nursery environment (app, api, postgres, redis)")
42
42
  .option("--db-only", "Start only database services (postgres, redis)")
43
+ .option("--api-only", "Start only API services (api, postgres, redis) without app")
43
44
  .option("--no-banner", "Hide the startup banner")
44
- .action((options) => {
45
+ .action((envVars, options) => {
45
46
  if (!options.noBanner) showBanner();
46
- startNursery(options);
47
+ startNursery(options, envVars);
47
48
  });
48
49
 
49
50
  nursery
@@ -52,9 +53,11 @@ nursery
52
53
  .action(stopNursery);
53
54
 
54
55
  nursery
55
- .command("restart")
56
+ .command("restart [env...]")
56
57
  .description("Restart the Nursery environment")
57
- .action(restartNursery);
58
+ .option("--db-only", "Restart only database services (postgres, redis)")
59
+ .option("--api-only", "Restart only API services (api, postgres, redis) without app")
60
+ .action((envVars, options) => restartNursery(options, envVars));
58
61
 
59
62
  nursery
60
63
  .command("status")
@@ -85,7 +88,7 @@ nursery
85
88
  .action(updateNursery);
86
89
 
87
90
  nursery
88
- .command("reset")
91
+ .command("reset [env...]")
89
92
  .description("Complete fresh reset (stop, clean, pull latest image, restart)")
90
93
  .action(resetNursery);
91
94
 
@@ -95,9 +98,9 @@ const nurseryDb = nursery
95
98
  .description("Manage Nursery database services");
96
99
 
97
100
  nurseryDb
98
- .command("start")
101
+ .command("start [env...]")
99
102
  .description("Start only Nursery database services (postgres, redis)")
100
- .action(() => startNursery({ dbOnly: true }));
103
+ .action((envVars) => startNursery({ dbOnly: true }, envVars));
101
104
 
102
105
  nurseryDb
103
106
  .command("stop")
@@ -122,6 +125,11 @@ nurseryRedis
122
125
  // Nursery API commands
123
126
  const nurseryApi = nursery.command("api").description("Manage Nursery API");
124
127
 
128
+ nurseryApi
129
+ .command("start [env...]")
130
+ .description("Start only Nursery API services (api, postgres, redis) without app")
131
+ .action((envVars) => startNursery({ apiOnly: true }, envVars));
132
+
125
133
  nurseryApi
126
134
  .command("connect")
127
135
  .description("Open a shell in the Nursery API container")
@@ -12,15 +12,15 @@ services:
12
12
  image: postgres:16-alpine
13
13
  container_name: cruxgarden-nursery-postgres
14
14
  environment:
15
- POSTGRES_DB: cruxgarden
16
- POSTGRES_USER: cruxgarden
17
- POSTGRES_PASSWORD: cruxgarden_nursery_password
15
+ POSTGRES_DB: ${POSTGRES_DB:-cruxgarden}
16
+ POSTGRES_USER: ${POSTGRES_USER:-cruxgarden}
17
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-cruxgarden_nursery_password}
18
18
  ports:
19
- - "5432:5432"
19
+ - "${POSTGRES_PORT:-5432}:5432"
20
20
  volumes:
21
21
  - postgres_data_nursery:/var/lib/postgresql/data
22
22
  healthcheck:
23
- test: ["CMD-SHELL", "pg_isready -U cruxgarden"]
23
+ test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-cruxgarden}"]
24
24
  interval: 5s
25
25
  timeout: 5s
26
26
  retries: 5
@@ -31,7 +31,7 @@ services:
31
31
  container_name: cruxgarden-nursery-redis
32
32
  command: redis-server --appendonly yes
33
33
  ports:
34
- - "6379:6379"
34
+ - "${REDIS_PORT:-6379}:6379"
35
35
  volumes:
36
36
  - redis_data_nursery:/data
37
37
  healthcheck:
@@ -48,8 +48,8 @@ services:
48
48
  postgres:
49
49
  condition: service_healthy
50
50
  environment:
51
- NODE_ENV: production
52
- DATABASE_URL: postgresql://cruxgarden:cruxgarden_nursery_password@postgres:5432/cruxgarden
51
+ NODE_ENV: ${NODE_ENV:-production}
52
+ DATABASE_URL: ${DATABASE_URL:-postgresql://cruxgarden:cruxgarden_nursery_password@postgres:5432/cruxgarden}
53
53
  command: ["npm", "run", "migrate:nursery"]
54
54
  restart: "no"
55
55
 
@@ -64,19 +64,20 @@ services:
64
64
  redis:
65
65
  condition: service_healthy
66
66
  ports:
67
- - "${PORT:-3000}:3000"
67
+ - "${API_PORT:-3000}:3000"
68
68
  environment:
69
69
  # Application
70
- NODE_ENV: production
70
+ NODE_ENV: ${NODE_ENV:-production}
71
71
  PORT: 3000
72
- HOSTNAME: 0.0.0.0
72
+ HOSTNAME: ${HOSTNAME:-0.0.0.0}
73
73
 
74
- # Bundled Services
75
- DATABASE_URL: postgresql://cruxgarden:cruxgarden_nursery_password@postgres:5432/cruxgarden
76
- REDIS_URL: redis://redis:6379
74
+ # Bundled Services (can override to use external services)
75
+ DATABASE_URL: ${DATABASE_URL:-postgresql://cruxgarden:cruxgarden_nursery_password@postgres:5432/cruxgarden}
76
+ REDIS_URL: ${REDIS_URL:-redis://redis:6379}
77
77
 
78
78
  # Security
79
79
  JWT_SECRET: ${JWT_SECRET:-your-super-secret-jwt-key-min-32-chars-for-development-only}
80
+ NURSERY_MODE: ${NURSERY_MODE:-true}
80
81
 
81
82
  # AWS SES (optional for nursery)
82
83
  AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-dummy}
@@ -107,6 +108,17 @@ services:
107
108
  retries: 3
108
109
  start_period: 40s
109
110
 
111
+ # Crux Garden App - Web Application
112
+ app:
113
+ image: ghcr.io/cruxgarden/app:latest
114
+ container_name: cruxgarden-nursery-app
115
+ restart: unless-stopped
116
+ depends_on:
117
+ api:
118
+ condition: service_healthy
119
+ ports:
120
+ - "${APP_PORT:-8080}:80"
121
+
110
122
  volumes:
111
123
  postgres_data_nursery:
112
124
  redis_data_nursery:
package/lib/commands.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { execSync, spawn } from "child_process";
2
2
  import { fileURLToPath } from "url";
3
3
  import { dirname, join } from "path";
4
- import { readFileSync } from "fs";
4
+ import { readFileSync, existsSync } from "fs";
5
5
  import readline from "readline";
6
6
  import chalk from "chalk";
7
7
  import ora from "ora";
@@ -166,35 +166,113 @@ function ensureDockerRunning() {
166
166
  }
167
167
  }
168
168
 
169
+ function getEnvFileFlag() {
170
+ const envFilePath = join(process.cwd(), ".env");
171
+ if (existsSync(envFilePath)) {
172
+ return `--env-file "${envFilePath}"`;
173
+ }
174
+ return "";
175
+ }
176
+
177
+ function parseAndSetEnvVars(envArgs = []) {
178
+ if (!envArgs || envArgs.length === 0) return;
179
+
180
+ for (const arg of envArgs) {
181
+ const match = arg.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
182
+ if (match) {
183
+ const [, key, value] = match;
184
+ process.env[key] = value;
185
+ console.log(chalk.gray(` Setting ${key}=${value}`));
186
+ } else {
187
+ console.log(chalk.yellow(` Warning: Ignoring invalid env var format: ${arg}`));
188
+ }
189
+ }
190
+ }
191
+
169
192
  // ============================================================================
170
193
  // Nursery Environment Commands
171
194
  // ============================================================================
172
195
 
173
- export async function startNursery(options) {
196
+ export async function startNursery(options, envVars) {
174
197
  ensureDockerRunning();
198
+
199
+ // Ensure NURSERY_MODE is always set to true for nursery environment
200
+ process.env.NURSERY_MODE = 'true';
201
+
202
+ // Parse and set environment variables from command line
203
+ if (envVars && envVars.length > 0) {
204
+ console.log(chalk.cyan("\nApplying environment variables:"));
205
+ parseAndSetEnvVars(envVars);
206
+ console.log();
207
+ }
208
+
175
209
  const spinner = ora("Starting Crux Garden Nursery environment...").start();
210
+ const envFileFlag = getEnvFileFlag();
211
+
212
+ // Get actual port values from environment
213
+ const appPort = process.env.APP_PORT || "8080";
214
+ const apiPort = process.env.API_PORT || "3000";
215
+ const postgresPort = process.env.POSTGRES_PORT || "5432";
216
+ const redisPort = process.env.REDIS_PORT || "6379";
176
217
 
177
218
  try {
178
219
  if (options.dbOnly) {
179
220
  spinner.text = "Starting nursery database services (postgres, redis)...";
180
221
  await runCommandAsync(
181
- "docker-compose -f docker-compose.nursery.yml up -d --remove-orphans postgres redis",
222
+ `docker-compose ${envFileFlag} -f docker-compose.nursery.yml up -d --remove-orphans postgres redis`,
182
223
  { silent: true },
183
224
  );
184
225
  spinner.succeed("Nursery database services started!");
185
226
  console.log(
186
227
  chalk.hex(SUCCESS_GREEN)("\n✓ PostgreSQL running on:"),
187
- "localhost:5432",
228
+ `localhost:${postgresPort}`,
229
+ );
230
+ console.log(
231
+ chalk.hex(SUCCESS_GREEN)("✓ Redis running on:"),
232
+ `localhost:${redisPort}`,
233
+ );
234
+ } else if (options.apiOnly) {
235
+ spinner.text =
236
+ "Starting nursery API services (api, postgres, redis)...";
237
+ await runCommandAsync(
238
+ `docker-compose ${envFileFlag} -f docker-compose.nursery.yml up -d --remove-orphans postgres redis migrations api`,
239
+ {
240
+ silent: true,
241
+ },
242
+ );
243
+
244
+ // Clean up the migrations container if it exists (cross-platform)
245
+ await runCommandAsync(
246
+ "docker rm cruxgarden-nursery-migrations",
247
+ {
248
+ silent: true,
249
+ ignoreError: true,
250
+ },
251
+ );
252
+
253
+ spinner.succeed("Crux Garden Nursery API services started!");
254
+ console.log(
255
+ chalk.hex(SUCCESS_GREEN)("\n✓ API running on:"),
256
+ `http://localhost:${apiPort}`,
257
+ );
258
+ console.log(
259
+ chalk.hex(SUCCESS_GREEN)("✓ PostgreSQL running on:"),
260
+ `localhost:${postgresPort}`,
188
261
  );
189
262
  console.log(
190
263
  chalk.hex(SUCCESS_GREEN)("✓ Redis running on:"),
191
- "localhost:6379",
264
+ `localhost:${redisPort}`,
265
+ );
266
+ console.log(
267
+ chalk.yellow(
268
+ "\nℹ Nursery includes demo data for trials and showcases",
269
+ ),
192
270
  );
193
271
  } else {
194
272
  spinner.text =
195
- "Starting nursery services (api, postgres, redis)...";
273
+ "Starting nursery services (app, api, postgres, redis)...";
196
274
  await runCommandAsync(
197
- "docker-compose -f docker-compose.nursery.yml up -d --remove-orphans",
275
+ `docker-compose ${envFileFlag} -f docker-compose.nursery.yml up -d --remove-orphans`,
198
276
  {
199
277
  silent: true,
200
278
  },
@@ -211,16 +289,20 @@ export async function startNursery(options) {
211
289
 
212
290
  spinner.succeed("Crux Garden Nursery environment started!");
213
291
  console.log(
214
- chalk.hex(SUCCESS_GREEN)("\n✓ API running on:"),
215
- "http://localhost:3000",
292
+ chalk.hex(SUCCESS_GREEN)("\n✓ App running on:"),
293
+ `http://localhost:${appPort}`,
294
+ );
295
+ console.log(
296
+ chalk.hex(SUCCESS_GREEN)("✓ API running on:"),
297
+ `http://localhost:${apiPort}`,
216
298
  );
217
299
  console.log(
218
300
  chalk.hex(SUCCESS_GREEN)("✓ PostgreSQL running on:"),
219
- "localhost:5432",
301
+ `localhost:${postgresPort}`,
220
302
  );
221
303
  console.log(
222
304
  chalk.hex(SUCCESS_GREEN)("✓ Redis running on:"),
223
- "localhost:6379",
305
+ `localhost:${redisPort}`,
224
306
  );
225
307
  console.log(
226
308
  chalk.yellow(
@@ -259,7 +341,8 @@ export async function startNursery(options) {
259
341
  export async function stopNursery() {
260
342
  ensureDockerRunning();
261
343
  const spinner = ora("Stopping Crux Garden Nursery environment...").start();
262
- await runCommandAsync("docker-compose -f docker-compose.nursery.yml down", {
344
+ const envFileFlag = getEnvFileFlag();
345
+ await runCommandAsync(`docker-compose ${envFileFlag} -f docker-compose.nursery.yml down`, {
263
346
  silent: true,
264
347
  });
265
348
  spinner.succeed("Crux Garden Nursery environment stopped!");
@@ -273,13 +356,16 @@ export async function stopNursery() {
273
356
 
274
357
  export async function logsNursery(options) {
275
358
  ensureDockerRunning();
359
+ const envFileFlag = getEnvFileFlag();
360
+ const envFileArgs = envFileFlag ? envFileFlag.split(" ") : [];
361
+
276
362
  if (options.follow) {
277
363
  console.log(
278
364
  chalk.gray("Following nursery logs (press Ctrl+C to exit)...\n"),
279
365
  );
280
366
  const child = spawn(
281
367
  "docker-compose",
282
- ["-f", "docker-compose.nursery.yml", "logs", "-f"],
368
+ [...envFileArgs, "-f", "docker-compose.nursery.yml", "logs", "-f"],
283
369
  {
284
370
  cwd: dockerDir,
285
371
  stdio: "inherit",
@@ -291,7 +377,7 @@ export async function logsNursery(options) {
291
377
  process.exit(0);
292
378
  });
293
379
  } else {
294
- runCommand("docker-compose -f docker-compose.nursery.yml logs --tail=100");
380
+ runCommand(`docker-compose ${envFileFlag} -f docker-compose.nursery.yml logs --tail=100`);
295
381
  console.log();
296
382
  }
297
383
  }
@@ -316,9 +402,10 @@ export async function cleanNursery() {
316
402
  const spinner = ora(
317
403
  "Cleaning up nursery (removing containers and volumes)...",
318
404
  ).start();
405
+ const envFileFlag = getEnvFileFlag();
319
406
 
320
407
  await runCommandAsync(
321
- "docker-compose -f docker-compose.nursery.yml down -v",
408
+ `docker-compose ${envFileFlag} -f docker-compose.nursery.yml down -v`,
322
409
  {
323
410
  silent: true,
324
411
  ignoreError: true,
@@ -359,9 +446,10 @@ export async function purgeNursery() {
359
446
  const spinner = ora(
360
447
  "Purging nursery (removing containers, volumes, and images)...",
361
448
  ).start();
449
+ const envFileFlag = getEnvFileFlag();
362
450
 
363
451
  await runCommandAsync(
364
- "docker-compose -f docker-compose.nursery.yml down -v --rmi all",
452
+ `docker-compose ${envFileFlag} -f docker-compose.nursery.yml down -v --rmi all`,
365
453
  {
366
454
  silent: true,
367
455
  ignoreError: true,
@@ -383,6 +471,7 @@ export async function updateNursery() {
383
471
  const spinner = ora(
384
472
  "Checking for latest Crux Garden API image from ghcr.io...",
385
473
  ).start();
474
+ const envFileFlag = getEnvFileFlag();
386
475
 
387
476
  try {
388
477
  // Get the current local image digest before pulling
@@ -394,7 +483,7 @@ export async function updateNursery() {
394
483
 
395
484
  // Pull latest images
396
485
  spinner.text = "Pulling latest images...";
397
- await runCommandAsync("docker-compose -f docker-compose.nursery.yml pull", {
486
+ await runCommandAsync(`docker-compose ${envFileFlag} -f docker-compose.nursery.yml pull`, {
398
487
  silent: true,
399
488
  });
400
489
 
@@ -433,7 +522,7 @@ export async function updateNursery() {
433
522
  console.log();
434
523
  }
435
524
 
436
- export async function resetNursery() {
525
+ export async function resetNursery(envVars) {
437
526
  ensureDockerRunning();
438
527
  console.log(
439
528
  chalk.yellow(
@@ -450,13 +539,30 @@ export async function resetNursery() {
450
539
  return;
451
540
  }
452
541
 
542
+ // Ensure NURSERY_MODE is always set to true for nursery environment
543
+ process.env.NURSERY_MODE = 'true';
544
+
545
+ // Parse and set environment variables from command line
546
+ if (envVars && envVars.length > 0) {
547
+ console.log(chalk.cyan("\nApplying environment variables:"));
548
+ parseAndSetEnvVars(envVars);
549
+ console.log();
550
+ }
551
+
453
552
  const spinner = ora("Resetting nursery environment...").start();
553
+ const envFileFlag = getEnvFileFlag();
554
+
555
+ // Get actual port values from environment
556
+ const appPort = process.env.APP_PORT || "8080";
557
+ const apiPort = process.env.API_PORT || "3000";
558
+ const postgresPort = process.env.POSTGRES_PORT || "5432";
559
+ const redisPort = process.env.REDIS_PORT || "6379";
454
560
 
455
561
  try {
456
562
  // Stop and remove volumes
457
563
  spinner.text = "Stopping and removing containers/volumes...";
458
564
  await runCommandAsync(
459
- "docker-compose -f docker-compose.nursery.yml down -v",
565
+ `docker-compose ${envFileFlag} -f docker-compose.nursery.yml down -v`,
460
566
  {
461
567
  silent: true,
462
568
  ignoreError: true,
@@ -465,14 +571,14 @@ export async function resetNursery() {
465
571
 
466
572
  // Pull latest image
467
573
  spinner.text = "Pulling latest image...";
468
- await runCommandAsync("docker-compose -f docker-compose.nursery.yml pull", {
574
+ await runCommandAsync(`docker-compose ${envFileFlag} -f docker-compose.nursery.yml pull`, {
469
575
  silent: true,
470
576
  });
471
577
 
472
578
  // Start fresh
473
579
  spinner.text = "Starting fresh nursery environment...";
474
580
  await runCommandAsync(
475
- "docker-compose -f docker-compose.nursery.yml up -d --remove-orphans",
581
+ `docker-compose ${envFileFlag} -f docker-compose.nursery.yml up -d --remove-orphans`,
476
582
  {
477
583
  silent: true,
478
584
  },
@@ -489,16 +595,20 @@ export async function resetNursery() {
489
595
 
490
596
  spinner.succeed("Nursery environment reset complete!");
491
597
  console.log(
492
- chalk.hex(SUCCESS_GREEN)("\n✓ API running on:"),
493
- "http://localhost:3000",
598
+ chalk.hex(SUCCESS_GREEN)("\n✓ App running on:"),
599
+ `http://localhost:${appPort}`,
600
+ );
601
+ console.log(
602
+ chalk.hex(SUCCESS_GREEN)("✓ API running on:"),
603
+ `http://localhost:${apiPort}`,
494
604
  );
495
605
  console.log(
496
606
  chalk.hex(SUCCESS_GREEN)("✓ PostgreSQL running on:"),
497
- "localhost:5432",
607
+ `localhost:${postgresPort}`,
498
608
  );
499
609
  console.log(
500
610
  chalk.hex(SUCCESS_GREEN)("✓ Redis running on:"),
501
- "localhost:6379",
611
+ `localhost:${redisPort}`,
502
612
  );
503
613
  console.log(
504
614
  chalk.yellow("\nℹ Fresh nursery with latest demo data loaded"),
@@ -510,15 +620,16 @@ export async function resetNursery() {
510
620
  }
511
621
  }
512
622
 
513
- export async function restartNursery() {
623
+ export async function restartNursery(options = {}, envVars) {
514
624
  await stopNursery();
515
- await startNursery({});
625
+ await startNursery(options, envVars);
516
626
  }
517
627
 
518
628
  export async function statusNursery() {
519
629
  ensureDockerRunning();
630
+ const envFileFlag = getEnvFileFlag();
520
631
  console.log(chalk.bold("\nCrux Garden Nursery Environment Status:\n"));
521
- runCommand("docker-compose -f docker-compose.nursery.yml ps");
632
+ runCommand(`docker-compose ${envFileFlag} -f docker-compose.nursery.yml ps`);
522
633
  console.log();
523
634
  }
524
635
 
@@ -545,8 +656,9 @@ export async function connectNurseryApi() {
545
656
  export async function stopNurseryDb() {
546
657
  ensureDockerRunning();
547
658
  const spinner = ora("Stopping nursery database services...").start();
659
+ const envFileFlag = getEnvFileFlag();
548
660
  await runCommandAsync(
549
- "docker-compose -f docker-compose.nursery.yml stop postgres redis",
661
+ `docker-compose ${envFileFlag} -f docker-compose.nursery.yml stop postgres redis`,
550
662
  { silent: true },
551
663
  );
552
664
  spinner.succeed("Nursery database services stopped!");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cruxgarden/cli",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "Crux Garden CLI",
5
5
  "type": "module",
6
6
  "bin": {