@bagdock/cli 0.4.0 → 0.5.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 CHANGED
@@ -1,3 +1,17 @@
1
+ ```
2
+ ----++ ----++ ---+++
3
+ ---+++ ---++ ---++
4
+ ----+--- ----- --------- --------++ ------ ----- ----++-----
5
+ ---------+ --------++----------++--------+++--------+ --------++---++---++++
6
+ ---+++---++ ++++---++---+++---++---+++---++---+++---++---++---++------++++
7
+ ----++ ---++--------++---++----++---++ ---++---++ ---+---++ -------++
8
+ ----++----+---+++---++---++----++---++----++---++---+++--++ --------+---++
9
+ ---------++--------+++--------+++--------++ -------+++ -------++---++----++
10
+ +++++++++ +++++++++- +++---++ ++++++++ ++++++ ++++++ ++++ ++++
11
+ --------+++
12
+ +++++++
13
+ ```
14
+
1
15
  # @bagdock/cli
2
16
 
3
17
  The official CLI for Bagdock. Built for humans, AI agents, and CI/CD pipelines.
@@ -160,6 +174,42 @@ The CLI resolves your API key using the following priority chain:
160
174
 
161
175
  If no key is found from any source, the CLI errors with code `auth_error`.
162
176
 
177
+ ## Environment context
178
+
179
+ The CLI supports Stripe-style live/test mode switching. Login is universal — you authenticate once, then select which operator and environment to target.
180
+
181
+ ### Operator + environment resolution
182
+
183
+ | Priority | Source | How to set |
184
+ |----------|--------|-----------|
185
+ | 1 (highest) | `--env` global flag | `bagdock --env test deploy` |
186
+ | 2 | `.bagdock/link.json` | `bagdock link --env test` |
187
+ | 3 | Profile stored value | `bagdock switch` |
188
+ | 4 (lowest) | Default | `live` |
189
+
190
+ For operator slug:
191
+
192
+ | Priority | Source | How to set |
193
+ |----------|--------|-----------|
194
+ | 1 (highest) | `BAGDOCK_OPERATOR` env var | `export BAGDOCK_OPERATOR=wisestorage` |
195
+ | 2 (lowest) | Profile stored value | `bagdock switch` or `bagdock login` |
196
+
197
+ ### Typical workflow
198
+
199
+ ```bash
200
+ # Login (universal identity)
201
+ bagdock login
202
+
203
+ # Select operator and environment
204
+ bagdock switch
205
+
206
+ # Or override per-command
207
+ bagdock --env test deploy --target staging
208
+ bagdock --env live apps list
209
+ ```
210
+
211
+ All API requests include `X-Environment` and `X-Operator-Slug` headers, ensuring the backend resolves the correct tenant database.
212
+
163
213
  ## Commands
164
214
 
165
215
  ### `bagdock login`
@@ -246,6 +296,7 @@ bagdock doctor
246
296
  |-------|------|------|------|
247
297
  | CLI Version | Running latest | Update available or registry unreachable | — |
248
298
  | API Key | Key found (shows masked key + source) | — | No key found |
299
+ | Operator Context | Operator + environment set | No operator selected | — |
249
300
  | Project Config | Valid `bagdock.json` found | No config or incomplete | — |
250
301
  | AI Agents | Lists detected agents (or none) | — | — |
251
302
 
@@ -300,6 +351,46 @@ Exits `0` when all checks pass or warn. Exits `1` if any check fails.
300
351
 
301
352
  ---
302
353
 
354
+ ### `bagdock switch`
355
+
356
+ Switch operator and environment context. After login, use this to select which operator (live or sandbox) to target.
357
+
358
+ ```bash
359
+ bagdock switch
360
+ ```
361
+
362
+ #### Interactive mode
363
+
364
+ ```
365
+ ? Select operator:
366
+ 1. WiseStorage (wisestorage)
367
+ 2. Ardran REIT (ardran-reit)
368
+ > 1
369
+
370
+ ? Select environment:
371
+ 1. Live
372
+ 2. Sandbox: crm-integration (default)
373
+ 3. Sandbox: access-testing
374
+ > 2
375
+
376
+ Switched to wisestorage [test] (sandbox: crm-integration)
377
+ ```
378
+
379
+ #### Non-interactive mode (CI/CD)
380
+
381
+ ```bash
382
+ bagdock switch --operator wisestorage --env test
383
+ ```
384
+
385
+ #### JSON output
386
+
387
+ ```bash
388
+ bagdock switch --operator wisestorage --env live --json
389
+ # => {"operator":{"id":"opreg_xxx","slug":"wisestorage","name":"WiseStorage"},"environment":"live"}
390
+ ```
391
+
392
+ ---
393
+
303
394
  ### Switch between profiles
304
395
 
305
396
  If you work across multiple Bagdock operators, the CLI supports named profiles.
@@ -700,6 +791,7 @@ bagdock [global options] <command> [command options]
700
791
  |------|-------------|
701
792
  | `--api-key <key>` | Override API key for this invocation (takes highest priority) |
702
793
  | `-p, --profile <name>` | Profile to use (overrides `BAGDOCK_PROFILE` env var) |
794
+ | `--env <live\|test>` | Override environment for this invocation |
703
795
  | `--json` | Force JSON output even in interactive terminals |
704
796
  | `-q, --quiet` | Suppress spinners and status output (implies `--json`) |
705
797
  | `--version` | Print version and exit |
package/dist/bagdock.js CHANGED
@@ -2088,6 +2088,12 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
2088
2088
  function setProfileOverride(name) {
2089
2089
  profileOverride = name;
2090
2090
  }
2091
+ function setEnvironmentOverride(env) {
2092
+ envOverride = env;
2093
+ }
2094
+ function getEnvironmentOverride() {
2095
+ return envOverride;
2096
+ }
2091
2097
  function ensureConfigDir() {
2092
2098
  if (!existsSync(CONFIG_DIR)) {
2093
2099
  mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
@@ -2153,6 +2159,8 @@ function listProfiles() {
2153
2159
  name,
2154
2160
  email: creds.email,
2155
2161
  operatorId: creds.operatorId,
2162
+ operatorSlug: creds.operatorSlug,
2163
+ environment: creds.environment,
2156
2164
  active: name === store.activeProfile
2157
2165
  }));
2158
2166
  }
@@ -2167,6 +2175,46 @@ function switchProfile(name) {
2167
2175
  function getActiveProfileName() {
2168
2176
  return resolveProfile();
2169
2177
  }
2178
+ function resolveLinkEnvironment() {
2179
+ try {
2180
+ const p = join(process.cwd(), ".bagdock", "link.json");
2181
+ if (!existsSync(p))
2182
+ return;
2183
+ const data = JSON.parse(readFileSync(p, "utf-8"));
2184
+ if (data.environment === "live" || data.environment === "test")
2185
+ return data.environment;
2186
+ return;
2187
+ } catch {
2188
+ return;
2189
+ }
2190
+ }
2191
+ function resolveEnvironment() {
2192
+ if (envOverride)
2193
+ return envOverride;
2194
+ const envVar = process.env.BAGDOCK_ENV;
2195
+ if (envVar === "test" || envVar === "live")
2196
+ return envVar;
2197
+ const creds = loadCredentials();
2198
+ return creds?.environment ?? "live";
2199
+ }
2200
+ function resolveOperatorSlug() {
2201
+ const envVar = process.env.BAGDOCK_OPERATOR;
2202
+ if (envVar)
2203
+ return envVar;
2204
+ const creds = loadCredentials();
2205
+ return creds?.operatorSlug;
2206
+ }
2207
+ function updateProfileContext(operatorId, operatorSlug, environment) {
2208
+ const creds = loadCredentials();
2209
+ if (!creds)
2210
+ return;
2211
+ saveCredentials({
2212
+ ...creds,
2213
+ operatorId,
2214
+ operatorSlug,
2215
+ environment
2216
+ });
2217
+ }
2170
2218
  function loadBagdockJson(dir) {
2171
2219
  const file = join(dir, "bagdock.json");
2172
2220
  if (!existsSync(file))
@@ -2177,7 +2225,7 @@ function loadBagdockJson(dir) {
2177
2225
  return null;
2178
2226
  }
2179
2227
  }
2180
- var CONFIG_DIR, CREDENTIALS_FILE, API_BASE, DASHBOARD_BASE, profileOverride;
2228
+ var CONFIG_DIR, CREDENTIALS_FILE, API_BASE, DASHBOARD_BASE, profileOverride, envOverride;
2181
2229
  var init_config = __esm(() => {
2182
2230
  CONFIG_DIR = join(homedir(), ".bagdock");
2183
2231
  CREDENTIALS_FILE = join(CONFIG_DIR, "credentials.json");
@@ -3341,15 +3389,27 @@ Requesting device authorization...
3341
3389
  refreshToken: tokens.refresh_token,
3342
3390
  expiresAt: tokens.expires_in ? Date.now() + tokens.expires_in * 1000 : undefined,
3343
3391
  email: tokens.email,
3344
- operatorId: tokens.operator_id
3392
+ operatorId: tokens.operator_id,
3393
+ operatorSlug: tokens.operator_slug,
3394
+ environment: "live"
3345
3395
  });
3346
3396
  console.log(source_default.green(`
3347
3397
  Logged in successfully!`));
3348
3398
  if (tokens.email)
3349
3399
  console.log(" Email:", source_default.bold(tokens.email));
3350
- if (tokens.operator_id)
3351
- console.log(" Operator:", source_default.bold(tokens.operator_id));
3400
+ if (tokens.operator_slug) {
3401
+ console.log(" Operator:", source_default.bold(tokens.operator_slug));
3402
+ } else if (tokens.operator_id) {
3403
+ console.log(" Operator ID:", source_default.bold(tokens.operator_id));
3404
+ }
3405
+ console.log(" Environment:", source_default.bold("live"));
3352
3406
  console.log(" Profile:", source_default.bold(getActiveProfileName()));
3407
+ if (!tokens.operator_slug) {
3408
+ await resolveOperatorAfterLogin(tokens.access_token);
3409
+ } else {
3410
+ console.log();
3411
+ console.log(source_default.dim(" Tip: run"), source_default.cyan("bagdock switch"), source_default.dim("to change operator or environment."));
3412
+ }
3353
3413
  return;
3354
3414
  }
3355
3415
  const error = await tokenRes.json().catch(() => ({ error: "unknown" }));
@@ -3396,12 +3456,22 @@ async function whoami() {
3396
3456
  process.exit(1);
3397
3457
  }
3398
3458
  const user = await res.json();
3459
+ const creds = loadCredentials();
3399
3460
  if (isJsonMode()) {
3400
- outputSuccess({ ...user, profile: getActiveProfileName() });
3461
+ outputSuccess({
3462
+ ...user,
3463
+ profile: getActiveProfileName(),
3464
+ operator_slug: creds?.operatorSlug,
3465
+ environment: creds?.environment ?? "live"
3466
+ });
3401
3467
  } else {
3402
3468
  console.log(source_default.green("Logged in as"), source_default.bold(user.email));
3403
- if (user.operator_id)
3404
- console.log("Operator:", source_default.bold(user.operator_id));
3469
+ if (creds?.operatorSlug) {
3470
+ console.log("Operator:", source_default.bold(creds.operatorSlug));
3471
+ } else if (user.operator_id) {
3472
+ console.log("Operator ID:", source_default.bold(user.operator_id));
3473
+ }
3474
+ console.log("Environment:", source_default.bold(creds?.environment ?? "live"));
3405
3475
  if (user.name)
3406
3476
  console.log("Name:", user.name);
3407
3477
  console.log("Profile:", source_default.bold(getActiveProfileName()));
@@ -3428,8 +3498,9 @@ async function authList() {
3428
3498
  const marker = p.active ? source_default.green("* ") : " ";
3429
3499
  const label = p.active ? source_default.bold(p.name) : p.name;
3430
3500
  const email = p.email ? source_default.dim(` (${p.email})`) : "";
3431
- const op = p.operatorId ? source_default.dim(` [${p.operatorId}]`) : "";
3432
- console.log(` ${marker}${label}${email}${op}`);
3501
+ const operator = p.operatorSlug ? source_default.dim(` ${p.operatorSlug}`) : p.operatorId ? source_default.dim(` ${p.operatorId}`) : "";
3502
+ const env2 = p.environment ? source_default.dim(` [${p.environment}]`) : "";
3503
+ console.log(` ${marker}${label}${email}${operator}${env2}`);
3433
3504
  }
3434
3505
  console.log();
3435
3506
  }
@@ -3482,6 +3553,28 @@ Available profiles:
3482
3553
  process.exit(1);
3483
3554
  }
3484
3555
  }
3556
+ async function resolveOperatorAfterLogin(accessToken) {
3557
+ try {
3558
+ const res = await fetch(`${API_BASE}/api/v1/me/operators`, {
3559
+ headers: { Authorization: `Bearer ${accessToken}` }
3560
+ });
3561
+ if (!res.ok)
3562
+ return;
3563
+ const body = await res.json();
3564
+ const operators = body.data ?? [];
3565
+ if (operators.length === 1) {
3566
+ const op = operators[0];
3567
+ updateProfileContext(op.id, op.slug, "live");
3568
+ console.log(" Operator:", source_default.bold(`${op.name} (${op.slug})`));
3569
+ console.log();
3570
+ console.log(source_default.dim(" Tip: run"), source_default.cyan("bagdock switch"), source_default.dim("to change environment to test/sandbox."));
3571
+ } else if (operators.length > 1) {
3572
+ console.log();
3573
+ console.log(source_default.dim(` You have access to ${operators.length} operators.`));
3574
+ console.log(source_default.dim(" Run"), source_default.cyan("bagdock switch"), source_default.dim("to select one."));
3575
+ }
3576
+ } catch {}
3577
+ }
3485
3578
  function sleep(ms) {
3486
3579
  return new Promise((resolve) => setTimeout(resolve, ms));
3487
3580
  }
@@ -3492,6 +3585,175 @@ var init_auth = __esm(() => {
3492
3585
  init_output();
3493
3586
  });
3494
3587
 
3588
+ // src/api.ts
3589
+ function resolveFullEnvironment() {
3590
+ const flagOverride = getEnvironmentOverride();
3591
+ if (flagOverride)
3592
+ return flagOverride;
3593
+ const linkEnv = resolveLinkEnvironment();
3594
+ if (linkEnv)
3595
+ return linkEnv;
3596
+ return resolveEnvironment();
3597
+ }
3598
+ async function apiFetch(path2, init2) {
3599
+ const token = getAuthToken();
3600
+ if (!token) {
3601
+ outputError("auth_error", "Not authenticated. Run bagdock login or set BAGDOCK_API_KEY.");
3602
+ process.exit(1);
3603
+ }
3604
+ const env2 = resolveFullEnvironment();
3605
+ const opSlug = resolveOperatorSlug();
3606
+ const headers = new Headers(init2?.headers);
3607
+ headers.set("Authorization", `Bearer ${token}`);
3608
+ headers.set("X-Environment", env2);
3609
+ if (opSlug)
3610
+ headers.set("X-Operator-Slug", opSlug);
3611
+ return fetch(`${API_BASE}${path2}`, { ...init2, headers });
3612
+ }
3613
+ async function apiFetchJson(path2, method, body) {
3614
+ return apiFetch(path2, {
3615
+ method,
3616
+ headers: { "Content-Type": "application/json" },
3617
+ body: JSON.stringify(body)
3618
+ });
3619
+ }
3620
+ var init_api = __esm(() => {
3621
+ init_config();
3622
+ init_auth();
3623
+ init_output();
3624
+ });
3625
+
3626
+ // src/switch.ts
3627
+ var exports_switch = {};
3628
+ __export(exports_switch, {
3629
+ switchContext: () => switchContext
3630
+ });
3631
+ async function fetchOperators() {
3632
+ const res = await apiFetch("/api/v1/me/operators");
3633
+ if (!res.ok) {
3634
+ const err = await res.json().catch(() => ({}));
3635
+ throw new Error(err?.error?.message || `API returned ${res.status}`);
3636
+ }
3637
+ const body = await res.json();
3638
+ return body.data ?? [];
3639
+ }
3640
+ async function fetchSandboxes() {
3641
+ const res = await apiFetch("/api/v1/me/sandboxes");
3642
+ if (!res.ok)
3643
+ return [];
3644
+ const body = await res.json();
3645
+ return body.data ?? [];
3646
+ }
3647
+ function prompt(question) {
3648
+ const readline = __require("readline");
3649
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
3650
+ return new Promise((resolve) => rl.question(question, (answer) => {
3651
+ rl.close();
3652
+ resolve(answer.trim());
3653
+ }));
3654
+ }
3655
+ async function switchContext(opts) {
3656
+ status("Fetching your operators...");
3657
+ let operators;
3658
+ try {
3659
+ operators = await fetchOperators();
3660
+ } catch (err) {
3661
+ outputError("api_error", `Failed to list operators: ${err.message}`);
3662
+ return;
3663
+ }
3664
+ if (!operators.length) {
3665
+ outputError("no_operators", "No operators found for your account.");
3666
+ return;
3667
+ }
3668
+ let selected;
3669
+ if (opts.operator) {
3670
+ const match = operators.find((o) => o.slug === opts.operator || o.id === opts.operator);
3671
+ if (!match) {
3672
+ outputError("not_found", `Operator "${opts.operator}" not found. Available: ${operators.map((o) => o.slug).join(", ")}`);
3673
+ return;
3674
+ }
3675
+ selected = match;
3676
+ } else if (operators.length === 1) {
3677
+ selected = operators[0];
3678
+ if (!isJsonMode()) {
3679
+ console.log(source_default.dim(` Auto-selected: ${selected.name} (${selected.slug})`));
3680
+ }
3681
+ } else if (!process.stdout.isTTY || isJsonMode()) {
3682
+ outputError("operator_required", `Multiple operators available. Pass --operator <slug>. Available: ${operators.map((o) => o.slug).join(", ")}`);
3683
+ return;
3684
+ } else {
3685
+ console.log(source_default.bold(`
3686
+ Select operator:
3687
+ `));
3688
+ operators.forEach((op, i) => {
3689
+ console.log(` ${source_default.cyan(String(i + 1))} ${op.name} ${source_default.dim(`(${op.slug})`)}`);
3690
+ });
3691
+ console.log();
3692
+ const answer = await prompt(" > ");
3693
+ const idx = parseInt(answer, 10) - 1;
3694
+ if (isNaN(idx) || idx < 0 || idx >= operators.length) {
3695
+ outputError("invalid_selection", "Invalid selection.");
3696
+ return;
3697
+ }
3698
+ selected = operators[idx];
3699
+ }
3700
+ let environment = "live";
3701
+ if (opts.env) {
3702
+ if (opts.env !== "live" && opts.env !== "test") {
3703
+ outputError("invalid_env", 'Environment must be "live" or "test".');
3704
+ return;
3705
+ }
3706
+ environment = opts.env;
3707
+ } else if (process.stdout.isTTY && !isJsonMode()) {
3708
+ status("Fetching sandboxes...");
3709
+ updateProfileContext(selected.id, selected.slug, "live");
3710
+ const sandboxes = await fetchSandboxes();
3711
+ console.log(source_default.bold(`
3712
+ Select environment:
3713
+ `));
3714
+ console.log(` ${source_default.cyan("1")} Live`);
3715
+ if (sandboxes.length > 0) {
3716
+ sandboxes.forEach((sb, i) => {
3717
+ const tag = sb.is_default ? source_default.dim(" (default)") : "";
3718
+ console.log(` ${source_default.cyan(String(i + 2))} Sandbox: ${sb.name}${tag}`);
3719
+ });
3720
+ } else {
3721
+ console.log(` ${source_default.cyan("2")} Test ${source_default.dim("(default sandbox)")}`);
3722
+ }
3723
+ console.log();
3724
+ const envAnswer = await prompt(" > ");
3725
+ const envIdx = parseInt(envAnswer, 10);
3726
+ if (envIdx === 1) {
3727
+ environment = "live";
3728
+ } else if (sandboxes.length > 0 && envIdx >= 2 && envIdx <= sandboxes.length + 1) {
3729
+ environment = "test";
3730
+ } else if (envIdx === 2 && sandboxes.length === 0) {
3731
+ environment = "test";
3732
+ } else {
3733
+ outputError("invalid_selection", "Invalid selection.");
3734
+ return;
3735
+ }
3736
+ }
3737
+ updateProfileContext(selected.id, selected.slug, environment);
3738
+ if (isJsonMode()) {
3739
+ outputSuccess({
3740
+ operator: { id: selected.id, slug: selected.slug, name: selected.name },
3741
+ environment
3742
+ });
3743
+ } else {
3744
+ const envLabel = environment === "test" ? source_default.yellow("test") : source_default.green("live");
3745
+ console.log();
3746
+ console.log(source_default.green(" Switched to"), source_default.bold(selected.name), source_default.dim(`(${selected.slug})`), `[${envLabel}]`);
3747
+ console.log();
3748
+ }
3749
+ }
3750
+ var init_switch = __esm(() => {
3751
+ init_source();
3752
+ init_config();
3753
+ init_api();
3754
+ init_output();
3755
+ });
3756
+
3495
3757
  // src/doctor.ts
3496
3758
  var exports_doctor = {};
3497
3759
  __export(exports_doctor, {
@@ -3579,11 +3841,30 @@ function checkAgents() {
3579
3841
  }
3580
3842
  return { name: "AI Agents", status: "pass", message: `Detected: ${detected.join(", ")}` };
3581
3843
  }
3844
+ function checkOperatorContext() {
3845
+ const creds = loadCredentials();
3846
+ const slug = resolveOperatorSlug();
3847
+ const env2 = resolveEnvironment();
3848
+ const profile = getActiveProfileName();
3849
+ if (!slug) {
3850
+ return {
3851
+ name: "Operator Context",
3852
+ status: "warn",
3853
+ message: `No operator selected (profile: ${profile}). Run \`bagdock switch\` to set one.`
3854
+ };
3855
+ }
3856
+ return {
3857
+ name: "Operator Context",
3858
+ status: "pass",
3859
+ message: `${slug} [${env2}] (profile: ${profile})`
3860
+ };
3861
+ }
3582
3862
  async function doctor() {
3583
3863
  const checks = [];
3584
3864
  if (isJsonMode()) {
3585
3865
  checks.push(await checkVersion());
3586
3866
  checks.push(checkAuth());
3867
+ checks.push(checkOperatorContext());
3587
3868
  checks.push(checkProjectConfig());
3588
3869
  checks.push(checkAgents());
3589
3870
  const ok = checks.every((c) => c.status !== "fail");
@@ -3601,6 +3882,9 @@ async function doctor() {
3601
3882
  const authCheck = checkAuth();
3602
3883
  checks.push(authCheck);
3603
3884
  console.log(` ${ICON[authCheck.status]} ${authCheck.name}: ${authCheck.message}`);
3885
+ const contextCheck = checkOperatorContext();
3886
+ checks.push(contextCheck);
3887
+ console.log(` ${ICON[contextCheck.status]} ${contextCheck.name}: ${contextCheck.message}`);
3604
3888
  const configCheck = checkProjectConfig();
3605
3889
  checks.push(configCheck);
3606
3890
  console.log(` ${ICON[configCheck.status]} ${configCheck.name}: ${configCheck.message}`);
@@ -3825,10 +4109,10 @@ Deploying ${config.slug}@${config.version} → ${envLabel}
3825
4109
  process.exit(1);
3826
4110
  }
3827
4111
  }
3828
- function confirm(prompt) {
4112
+ function confirm(prompt2) {
3829
4113
  return new Promise((resolve) => {
3830
4114
  const rl = createInterface({ input: process.stdin, output: process.stdout });
3831
- rl.question(prompt, (answer) => {
4115
+ rl.question(prompt2, (answer) => {
3832
4116
  rl.close();
3833
4117
  resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
3834
4118
  });
@@ -3931,6 +4215,7 @@ function requireSlug(slugArg) {
3931
4215
  }
3932
4216
  async function link(opts) {
3933
4217
  let slug = opts.slug;
4218
+ const linkEnv = opts.env === "test" || opts.env === "live" ? opts.env : undefined;
3934
4219
  if (!slug) {
3935
4220
  const config = loadBagdockJson(process.cwd());
3936
4221
  if (config?.slug) {
@@ -3946,9 +4231,7 @@ async function link(opts) {
3946
4231
  }
3947
4232
  status("Fetching your apps...");
3948
4233
  try {
3949
- const res = await fetch(`${API_BASE}/v1/developer/apps`, {
3950
- headers: { Authorization: `Bearer ${token}` }
3951
- });
4234
+ const res = await apiFetch("/api/v1/developer/apps");
3952
4235
  if (!res.ok)
3953
4236
  throw new Error(`API returned ${res.status}`);
3954
4237
  const { data } = await res.json();
@@ -3983,12 +4266,13 @@ Your apps:
3983
4266
  const dir = join6(process.cwd(), LINK_DIR);
3984
4267
  if (!existsSync6(dir))
3985
4268
  mkdirSync3(dir, { recursive: true });
3986
- const linkData = { slug, linkedAt: new Date().toISOString() };
4269
+ const linkData = { slug, environment: linkEnv, linkedAt: new Date().toISOString() };
3987
4270
  writeFileSync4(join6(dir, LINK_FILE), JSON.stringify(linkData, null, 2));
3988
4271
  if (isJsonMode()) {
3989
- outputSuccess({ slug, path: join6(dir, LINK_FILE) });
4272
+ outputSuccess({ slug, environment: linkEnv, path: join6(dir, LINK_FILE) });
3990
4273
  } else {
3991
- console.log(source_default.green(`Linked to ${source_default.bold(slug)}`));
4274
+ const envLabel = linkEnv ? ` [${linkEnv}]` : "";
4275
+ console.log(source_default.green(`Linked to ${source_default.bold(slug)}${envLabel}`));
3992
4276
  console.log(source_default.dim(` Stored in ${LINK_DIR}/${LINK_FILE}`));
3993
4277
  }
3994
4278
  }
@@ -3997,6 +4281,7 @@ var init_link = __esm(() => {
3997
4281
  init_source();
3998
4282
  init_config();
3999
4283
  init_auth();
4284
+ init_api();
4000
4285
  init_output();
4001
4286
  });
4002
4287
 
@@ -4098,22 +4383,11 @@ __export(exports_submission, {
4098
4383
  submissionStatus: () => submissionStatus,
4099
4384
  submissionList: () => submissionList
4100
4385
  });
4101
- function requireAuth() {
4102
- const token = getAuthToken();
4103
- if (!token) {
4104
- outputError("auth_error", "Not authenticated. Run bagdock login or set BAGDOCK_API_KEY.");
4105
- process.exit(1);
4106
- }
4107
- return token;
4108
- }
4109
4386
  async function submissionList(opts) {
4110
- const token = requireAuth();
4111
4387
  const slug = requireSlug(opts.app);
4112
4388
  status(`Fetching submissions for ${slug}...`);
4113
4389
  try {
4114
- const res = await fetch(`${API_BASE}/v1/developer/apps/${slug}/submissions`, {
4115
- headers: { Authorization: `Bearer ${token}` }
4116
- });
4390
+ const res = await apiFetch(`/api/v1/developer/apps/${slug}/submissions`);
4117
4391
  if (res.status === 404) {
4118
4392
  outputError("not_found", `App "${slug}" not found or no submissions exist.`);
4119
4393
  }
@@ -4144,13 +4418,10 @@ Submissions for ${slug}:
4144
4418
  }
4145
4419
  }
4146
4420
  async function submissionStatus(id, opts) {
4147
- const token = requireAuth();
4148
4421
  const slug = requireSlug(opts.app);
4149
4422
  status(`Fetching submission ${id}...`);
4150
4423
  try {
4151
- const res = await fetch(`${API_BASE}/v1/developer/apps/${slug}/submissions/${id}`, {
4152
- headers: { Authorization: `Bearer ${token}` }
4153
- });
4424
+ const res = await apiFetch(`/api/v1/developer/apps/${slug}/submissions/${id}`);
4154
4425
  if (res.status === 404) {
4155
4426
  outputError("not_found", `Submission "${id}" not found.`);
4156
4427
  }
@@ -4184,13 +4455,11 @@ async function submissionStatus(id, opts) {
4184
4455
  }
4185
4456
  }
4186
4457
  async function submissionWithdraw(id, opts) {
4187
- const token = requireAuth();
4188
4458
  const slug = requireSlug(opts.app);
4189
4459
  status(`Withdrawing submission ${id}...`);
4190
4460
  try {
4191
- const res = await fetch(`${API_BASE}/v1/developer/apps/${slug}/submissions/${id}/withdraw`, {
4192
- method: "POST",
4193
- headers: { Authorization: `Bearer ${token}` }
4461
+ const res = await apiFetch(`/api/v1/developer/apps/${slug}/submissions/${id}/withdraw`, {
4462
+ method: "POST"
4194
4463
  });
4195
4464
  if (res.status === 404) {
4196
4465
  outputError("not_found", `App "${slug}" not found.`);
@@ -4215,8 +4484,7 @@ async function submissionWithdraw(id, opts) {
4215
4484
  }
4216
4485
  var init_submission = __esm(() => {
4217
4486
  init_source();
4218
- init_config();
4219
- init_auth();
4487
+ init_api();
4220
4488
  init_output();
4221
4489
  init_link();
4222
4490
  });
@@ -4228,9 +4496,16 @@ __export(exports_open2, {
4228
4496
  });
4229
4497
  async function open2(slugArg) {
4230
4498
  const slug = requireSlug(slugArg);
4231
- const url = `${DASHBOARD_BASE}/developer/apps/${slug}`;
4499
+ const operatorSlug = resolveOperatorSlug();
4500
+ const env2 = resolveEnvironment();
4501
+ if (!operatorSlug) {
4502
+ outputError("no_operator", "No operator context. Run bagdock switch or bagdock login to set one.");
4503
+ return;
4504
+ }
4505
+ const envSegment = env2 === "test" ? "/test" : "";
4506
+ const url = `${DASHBOARD_BASE}/${operatorSlug}${envSegment}/developer/apps/${slug}`;
4232
4507
  if (isJsonMode()) {
4233
- outputSuccess({ url, slug });
4508
+ outputSuccess({ url, slug, operator: operatorSlug, environment: env2 });
4234
4509
  return;
4235
4510
  }
4236
4511
  status(`Opening ${url}`);
@@ -4251,17 +4526,10 @@ __export(exports_inspect, {
4251
4526
  inspect: () => inspect
4252
4527
  });
4253
4528
  async function inspect(slugArg) {
4254
- const token = getAuthToken();
4255
- if (!token) {
4256
- outputError("auth_error", "Not authenticated. Run bagdock login or set BAGDOCK_API_KEY.");
4257
- process.exit(1);
4258
- }
4259
4529
  const slug = requireSlug(slugArg);
4260
4530
  status(`Inspecting ${slug}...`);
4261
4531
  try {
4262
- const res = await fetch(`${API_BASE}/v1/developer/apps/${slug}`, {
4263
- headers: { Authorization: `Bearer ${token}` }
4264
- });
4532
+ const res = await apiFetch(`/api/v1/developer/apps/${slug}`);
4265
4533
  if (res.status === 404)
4266
4534
  outputError("not_found", `App "${slug}" not found.`);
4267
4535
  if (!res.ok)
@@ -4301,8 +4569,7 @@ async function inspect(slugArg) {
4301
4569
  }
4302
4570
  var init_inspect = __esm(() => {
4303
4571
  init_source();
4304
- init_config();
4305
- init_auth();
4572
+ init_api();
4306
4573
  init_output();
4307
4574
  init_link();
4308
4575
  });
@@ -4317,14 +4584,6 @@ __export(exports_env_cmd, {
4317
4584
  });
4318
4585
  import { writeFileSync as writeFileSync5 } from "fs";
4319
4586
  import { resolve } from "path";
4320
- function requireAuth2() {
4321
- const token = getAuthToken();
4322
- if (!token) {
4323
- console.error(source_default.red("Not authenticated. Run"), source_default.cyan("bagdock login"), source_default.red("or set BAGDOCK_API_KEY."));
4324
- process.exit(1);
4325
- }
4326
- return token;
4327
- }
4328
4587
  function requireConfig() {
4329
4588
  const config = loadBagdockJson(process.cwd());
4330
4589
  if (!config) {
@@ -4334,12 +4593,9 @@ function requireConfig() {
4334
4593
  return config;
4335
4594
  }
4336
4595
  async function envList() {
4337
- const token = requireAuth2();
4338
4596
  const config = requireConfig();
4339
4597
  try {
4340
- const res = await fetch(`${API_BASE}/v1/developer/apps/${config.slug}/env`, {
4341
- headers: { Authorization: `Bearer ${token}` }
4342
- });
4598
+ const res = await apiFetch(`/api/v1/developer/apps/${config.slug}/env`);
4343
4599
  if (!res.ok) {
4344
4600
  console.error(source_default.red(`Failed to list env vars (${res.status})`));
4345
4601
  process.exit(1);
@@ -4363,17 +4619,9 @@ Environment variables for ${config.slug}:
4363
4619
  }
4364
4620
  }
4365
4621
  async function envSet(key, value) {
4366
- const token = requireAuth2();
4367
4622
  const config = requireConfig();
4368
4623
  try {
4369
- const res = await fetch(`${API_BASE}/v1/developer/apps/${config.slug}/env`, {
4370
- method: "PUT",
4371
- headers: {
4372
- "Content-Type": "application/json",
4373
- Authorization: `Bearer ${token}`
4374
- },
4375
- body: JSON.stringify({ key, value })
4376
- });
4624
+ const res = await apiFetchJson(`/api/v1/developer/apps/${config.slug}/env`, "PUT", { key, value });
4377
4625
  if (!res.ok) {
4378
4626
  const body = await res.text();
4379
4627
  console.error(source_default.red(`Failed to set ${key} (${res.status}):`), body.slice(0, 200));
@@ -4386,13 +4634,9 @@ async function envSet(key, value) {
4386
4634
  }
4387
4635
  }
4388
4636
  async function envRemove(key) {
4389
- const token = requireAuth2();
4390
4637
  const config = requireConfig();
4391
4638
  try {
4392
- const res = await fetch(`${API_BASE}/v1/developer/apps/${config.slug}/env/${key}`, {
4393
- method: "DELETE",
4394
- headers: { Authorization: `Bearer ${token}` }
4395
- });
4639
+ const res = await apiFetch(`/api/v1/developer/apps/${config.slug}/env/${key}`, { method: "DELETE" });
4396
4640
  if (!res.ok) {
4397
4641
  console.error(source_default.red(`Failed to remove ${key} (${res.status})`));
4398
4642
  process.exit(1);
@@ -4404,14 +4648,11 @@ async function envRemove(key) {
4404
4648
  }
4405
4649
  }
4406
4650
  async function envPull(file) {
4407
- const token = requireAuth2();
4408
4651
  const slug = requireSlug();
4409
4652
  const target = resolve(file ?? ".env.local");
4410
4653
  status(`Pulling env vars for ${slug}...`);
4411
4654
  try {
4412
- const res = await fetch(`${API_BASE}/v1/developer/apps/${slug}/env`, {
4413
- headers: { Authorization: `Bearer ${token}` }
4414
- });
4655
+ const res = await apiFetch(`/api/v1/developer/apps/${slug}/env`);
4415
4656
  if (!res.ok) {
4416
4657
  outputError("api_error", `Failed to pull env vars (${res.status})`);
4417
4658
  process.exit(1);
@@ -4446,7 +4687,7 @@ async function envPull(file) {
4446
4687
  var init_env_cmd = __esm(() => {
4447
4688
  init_source();
4448
4689
  init_config();
4449
- init_auth();
4690
+ init_api();
4450
4691
  init_output();
4451
4692
  init_link();
4452
4693
  });
@@ -4458,23 +4699,9 @@ __export(exports_keys, {
4458
4699
  keysDelete: () => keysDelete,
4459
4700
  keysCreate: () => keysCreate
4460
4701
  });
4461
- async function apiRequest(method, path2, body) {
4462
- const token = getAuthToken();
4463
- if (!token) {
4464
- outputError("UNAUTHENTICATED", "Not logged in. Run `bagdock login` or set BAGDOCK_API_KEY.");
4465
- }
4466
- const headers = { Authorization: `Bearer ${token}` };
4467
- const init2 = { method, headers };
4468
- if (body) {
4469
- headers["Content-Type"] = "application/json";
4470
- init2.body = JSON.stringify(body);
4471
- }
4472
- const res = await fetch(`${API_BASE}${path2}`, init2);
4473
- return res;
4474
- }
4475
4702
  async function keysCreate(opts) {
4476
4703
  status("Creating API key...");
4477
- const res = await apiRequest("POST", "/api/v1/operator/api-keys", {
4704
+ const res = await apiFetchJson("/api/v1/operator/api-keys", "POST", {
4478
4705
  name: opts.name,
4479
4706
  key_type: opts.type || "secret",
4480
4707
  key_category: opts.category || "standard",
@@ -4511,7 +4738,7 @@ async function keysList(opts) {
4511
4738
  let path2 = "/api/v1/operator/api-keys";
4512
4739
  if (opts.environment)
4513
4740
  path2 += `?environment=${opts.environment}`;
4514
- const res = await apiRequest("GET", path2);
4741
+ const res = await apiFetch(path2);
4515
4742
  if (!res.ok) {
4516
4743
  const err = await res.json().catch(() => ({ error: { message: res.statusText } }));
4517
4744
  outputError(err.error?.code || "API_ERROR", err.error?.message || `HTTP ${res.status}`);
@@ -4541,7 +4768,7 @@ async function keysDelete(id, opts) {
4541
4768
  outputError("CONFIRMATION_REQUIRED", "Pass --yes to confirm deletion in non-interactive mode.");
4542
4769
  }
4543
4770
  status(`Revoking API key ${id}...`);
4544
- const res = await apiRequest("DELETE", `/api/v1/operator/api-keys/${id}`, {
4771
+ const res = await apiFetchJson(`/api/v1/operator/api-keys/${id}`, "DELETE", {
4545
4772
  reason: opts.reason
4546
4773
  });
4547
4774
  if (!res.ok) {
@@ -4558,8 +4785,7 @@ async function keysDelete(id, opts) {
4558
4785
  }
4559
4786
  var init_keys = __esm(() => {
4560
4787
  init_source();
4561
- init_config();
4562
- init_auth();
4788
+ init_api();
4563
4789
  init_output();
4564
4790
  });
4565
4791
 
@@ -4569,19 +4795,9 @@ __export(exports_apps, {
4569
4795
  appsList: () => appsList,
4570
4796
  appsGet: () => appsGet
4571
4797
  });
4572
- async function apiRequest2(method, path2) {
4573
- const token = getAuthToken();
4574
- if (!token) {
4575
- outputError("UNAUTHENTICATED", "Not logged in. Run `bagdock login` or set BAGDOCK_API_KEY.");
4576
- }
4577
- return fetch(`${API_BASE}${path2}`, {
4578
- method,
4579
- headers: { Authorization: `Bearer ${token}` }
4580
- });
4581
- }
4582
4798
  async function appsList() {
4583
4799
  status("Fetching apps...");
4584
- const res = await apiRequest2("GET", "/api/v1/developer/apps");
4800
+ const res = await apiFetch("/api/v1/developer/apps");
4585
4801
  if (!res.ok) {
4586
4802
  const err = await res.json().catch(() => ({ error: { message: res.statusText } }));
4587
4803
  outputError(err.error?.code || "API_ERROR", err.error?.message || `HTTP ${res.status}`);
@@ -4611,7 +4827,7 @@ async function appsList() {
4611
4827
  }
4612
4828
  async function appsGet(slug) {
4613
4829
  status(`Fetching app ${slug}...`);
4614
- const res = await apiRequest2("GET", `/api/v1/developer/apps/${slug}`);
4830
+ const res = await apiFetch(`/api/v1/developer/apps/${slug}`);
4615
4831
  if (!res.ok) {
4616
4832
  if (res.status === 404) {
4617
4833
  outputError("NOT_FOUND", `App "${slug}" not found`);
@@ -4645,8 +4861,7 @@ async function appsGet(slug) {
4645
4861
  }
4646
4862
  var init_apps = __esm(() => {
4647
4863
  init_source();
4648
- init_config();
4649
- init_auth();
4864
+ init_api();
4650
4865
  init_output();
4651
4866
  });
4652
4867
 
@@ -4657,16 +4872,6 @@ __export(exports_logs, {
4657
4872
  logsList: () => logsList,
4658
4873
  logsGet: () => logsGet
4659
4874
  });
4660
- async function apiRequest3(method, path2) {
4661
- const token = getAuthToken();
4662
- if (!token) {
4663
- outputError("UNAUTHENTICATED", "Not logged in. Run `bagdock login` or set BAGDOCK_API_KEY.");
4664
- }
4665
- return fetch(`${API_BASE}${path2}`, {
4666
- method,
4667
- headers: { Authorization: `Bearer ${token}` }
4668
- });
4669
- }
4670
4875
  function resolveSlug2(slug) {
4671
4876
  if (slug)
4672
4877
  return slug;
@@ -4680,7 +4885,7 @@ async function logsList(opts) {
4680
4885
  const slug = resolveSlug2(opts.app);
4681
4886
  const limit = opts.limit || "50";
4682
4887
  status(`Fetching logs for ${slug}...`);
4683
- const res = await apiRequest3("GET", `/api/v1/developer/apps/${slug}/logs?limit=${limit}`);
4888
+ const res = await apiFetch(`/api/v1/developer/apps/${slug}/logs?limit=${limit}`);
4684
4889
  if (!res.ok) {
4685
4890
  if (res.status === 404) {
4686
4891
  outputError("NOT_FOUND", `App "${slug}" not found or no logs available`);
@@ -4710,7 +4915,7 @@ async function logsList(opts) {
4710
4915
  async function logsGet(id, opts) {
4711
4916
  const slug = resolveSlug2(opts.app);
4712
4917
  status(`Fetching log entry ${id}...`);
4713
- const res = await apiRequest3("GET", `/api/v1/developer/apps/${slug}/logs/${id}`);
4918
+ const res = await apiFetch(`/api/v1/developer/apps/${slug}/logs/${id}`);
4714
4919
  if (!res.ok) {
4715
4920
  const err = await res.json().catch(() => ({ error: { message: res.statusText } }));
4716
4921
  outputError(err.error?.code || "API_ERROR", err.error?.message || `HTTP ${res.status}`);
@@ -4733,7 +4938,7 @@ async function logsTail(opts) {
4733
4938
  let lastTimestamp = new Date().toISOString();
4734
4939
  const poll = async () => {
4735
4940
  try {
4736
- const res = await apiRequest3("GET", `/api/v1/developer/apps/${slug}/logs?since=${encodeURIComponent(lastTimestamp)}&limit=100`);
4941
+ const res = await apiFetch(`/api/v1/developer/apps/${slug}/logs?since=${encodeURIComponent(lastTimestamp)}&limit=100`);
4737
4942
  if (res.ok) {
4738
4943
  const result = await res.json();
4739
4944
  for (const entry of result.data || []) {
@@ -4759,7 +4964,7 @@ async function logsTail(opts) {
4759
4964
  var init_logs = __esm(() => {
4760
4965
  init_source();
4761
4966
  init_config();
4762
- init_auth();
4967
+ init_api();
4763
4968
  init_output();
4764
4969
  });
4765
4970
 
@@ -5033,13 +5238,20 @@ function toPascalCase(s) {
5033
5238
  init_output();
5034
5239
  init_config();
5035
5240
  var program2 = new Command;
5036
- program2.name("bagdock").description("Bagdock developer CLI — built for humans, AI agents, and CI/CD pipelines").version("0.4.0").option("--json", "Force JSON output (auto-enabled in non-TTY)").option("-q, --quiet", "Suppress status messages (implies --json)").option("--api-key <key>", "API key to use for this invocation").option("-p, --profile <name>", "Profile to use (overrides BAGDOCK_PROFILE)").hook("preAction", (_thisCommand, actionCommand) => {
5241
+ program2.name("bagdock").description("Bagdock developer CLI — built for humans, AI agents, and CI/CD pipelines").version("0.5.0").option("--json", "Force JSON output (auto-enabled in non-TTY)").option("-q, --quiet", "Suppress status messages (implies --json)").option("--api-key <key>", "API key to use for this invocation").option("-p, --profile <name>", "Profile to use (overrides BAGDOCK_PROFILE)").option("--env <environment>", "Override environment for this invocation (live, test)").hook("preAction", (_thisCommand, actionCommand) => {
5037
5242
  const opts = program2.opts();
5038
5243
  setOutputMode({ json: opts.json, quiet: opts.quiet });
5039
5244
  if (opts.apiKey)
5040
5245
  setApiKeyOverride(opts.apiKey);
5041
5246
  if (opts.profile)
5042
5247
  setProfileOverride(opts.profile);
5248
+ if (opts.env) {
5249
+ if (opts.env !== "live" && opts.env !== "test") {
5250
+ console.error('Error: --env must be "live" or "test"');
5251
+ process.exit(1);
5252
+ }
5253
+ setEnvironmentOverride(opts.env);
5254
+ }
5043
5255
  });
5044
5256
  program2.command("login").description("Authenticate with Bagdock (opens browser)").action(login);
5045
5257
  program2.command("logout").description("Clear stored credentials").action(logout);
@@ -5047,6 +5259,10 @@ program2.command("whoami").description("Show current authenticated user").action
5047
5259
  var authCmd = program2.command("auth").description("Manage authentication profiles");
5048
5260
  authCmd.command("list").description("List all stored profiles").action(authList);
5049
5261
  authCmd.command("switch [name]").description("Switch active profile").action(async (name) => authSwitch(name));
5262
+ program2.command("switch").description("Switch operator and environment context (live/test)").option("--operator <slug>", "Operator slug (required in non-interactive mode)").option("--env <environment>", "Environment: live or test").action(async (opts) => {
5263
+ const { switchContext: switchContext2 } = await Promise.resolve().then(() => (init_switch(), exports_switch));
5264
+ await switchContext2(opts);
5265
+ });
5050
5266
  program2.command("doctor").description("Run environment diagnostics").action(async () => {
5051
5267
  const { doctor: doctor2 } = await Promise.resolve().then(() => (init_doctor(), exports_doctor));
5052
5268
  await doctor2();
@@ -5056,7 +5272,7 @@ program2.command("dev").description("Start local dev server").option("-p, --port
5056
5272
  const { dev: dev2 } = await Promise.resolve().then(() => (init_dev(), exports_dev));
5057
5273
  await dev2(opts);
5058
5274
  });
5059
- program2.command("deploy").description("Build locally and deploy via Bagdock API").option("--env <environment>", "Target environment (preview, staging, production)", "staging").option("--preview", "Deploy an ephemeral preview ({slug}-{hash}.pre.bdok.dev)").option("--production", "Deploy to production ({slug}.bdok.dev)").option("-y, --yes", "Skip confirmation prompts").action(async (opts) => {
5275
+ program2.command("deploy").description("Build locally and deploy via Bagdock API").option("--target <target>", "Deploy target (preview, staging, production)", "staging").option("--preview", "Deploy an ephemeral preview ({slug}-{hash}.pre.bdok.dev)").option("--production", "Deploy to production ({slug}.bdok.dev)").option("-y, --yes", "Skip confirmation prompts").action(async (opts) => {
5060
5276
  const { deploy: deploy2 } = await Promise.resolve().then(() => (init_deploy(), exports_deploy));
5061
5277
  await deploy2(opts);
5062
5278
  });
@@ -5089,7 +5305,7 @@ program2.command("inspect [slug]").description("Show deployment details and stat
5089
5305
  const { inspect: inspect2 } = await Promise.resolve().then(() => (init_inspect(), exports_inspect));
5090
5306
  await inspect2(slug);
5091
5307
  });
5092
- program2.command("link").description("Link current directory to a Bagdock app or edge").option("--slug <slug>", "Project slug (required in non-interactive mode)").action(async (opts) => {
5308
+ program2.command("link").description("Link current directory to a Bagdock app or edge").option("--slug <slug>", "App slug (required in non-interactive mode)").option("--env <environment>", "Default environment for this link (live, test)").action(async (opts) => {
5093
5309
  const { link: link2 } = await Promise.resolve().then(() => (init_link(), exports_link));
5094
5310
  await link2(opts);
5095
5311
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bagdock/cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Bagdock developer CLI — build, test, and deploy apps and edges on the Bagdock platform",
5
5
  "keywords": [
6
6
  "bagdock",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "skill": "bagdock-cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "evals": [
5
5
  {
6
6
  "id": "login-flow",
@@ -203,6 +203,48 @@
203
203
  "input": "Link this directory to my smart-entry adapter",
204
204
  "expected_commands": ["bagdock link --slug smart-entry"],
205
205
  "expected_behavior": "Creates .bagdock/link.json with slug, other commands use it as fallback"
206
+ },
207
+ {
208
+ "id": "switch-operator-sandbox",
209
+ "description": "Agent should switch to sandbox environment for testing",
210
+ "input": "Switch to test mode for the WiseStorage operator",
211
+ "expected_commands": ["bagdock switch --operator wisestorage --env test"],
212
+ "expected_behavior": "Updates active profile with operatorSlug=wisestorage and environment=test"
213
+ },
214
+ {
215
+ "id": "switch-interactive",
216
+ "description": "Agent should use interactive switch when operator unknown",
217
+ "input": "I want to switch to a different operator",
218
+ "expected_commands": ["bagdock switch"],
219
+ "expected_behavior": "Lists available operators, prompts selection, then prompts environment choice"
220
+ },
221
+ {
222
+ "id": "deploy-to-test",
223
+ "description": "Agent should deploy to sandbox environment",
224
+ "input": "Deploy my adapter to the sandbox for testing",
225
+ "expected_commands": ["bagdock --env test deploy --target staging --yes"],
226
+ "expected_behavior": "Deploys to staging target within the test/sandbox environment"
227
+ },
228
+ {
229
+ "id": "inspect-test-env",
230
+ "description": "Agent should inspect an app in test environment",
231
+ "input": "Show me the deployment details of my app in the sandbox",
232
+ "expected_commands": ["bagdock --env test inspect --json"],
233
+ "expected_behavior": "Returns app details from the sandbox tenant database"
234
+ },
235
+ {
236
+ "id": "link-with-env",
237
+ "description": "Agent should link directory with specific environment",
238
+ "input": "Link this directory to smart-entry in test mode",
239
+ "expected_commands": ["bagdock link --slug smart-entry --env test"],
240
+ "expected_behavior": "Creates .bagdock/link.json with slug and environment=test"
241
+ },
242
+ {
243
+ "id": "env-override-per-command",
244
+ "description": "Agent should use --env flag to override environment for a single command",
245
+ "input": "List apps on the live environment even though I'm currently in test mode",
246
+ "expected_commands": ["bagdock --env live apps list --json"],
247
+ "expected_behavior": "Lists apps from the live tenant database, does not change profile"
206
248
  }
207
249
  ]
208
250
  }
@@ -45,9 +45,19 @@ For CI/CD, set `BAGDOCK_API_KEY` in your environment. For interactive use, run `
45
45
  | `-q, --quiet` | Suppress status messages (implies `--json`) |
46
46
  | `--api-key <key>` | Override auth for this invocation |
47
47
  | `-p, --profile <name>` | Use a named profile (overrides `BAGDOCK_PROFILE`) |
48
+ | `--env <live\|test>` | Override environment for this invocation |
48
49
  | `-V, --version` | Print version |
49
50
  | `-h, --help` | Print help |
50
51
 
52
+ ## Environment Context
53
+
54
+ All API calls include `X-Environment` and `X-Operator-Slug` headers. Resolution:
55
+
56
+ - **Environment**: `--env` flag > `.bagdock/link.json` > profile > `BAGDOCK_ENV` > `live`
57
+ - **Operator**: `BAGDOCK_OPERATOR` env var > profile stored value
58
+
59
+ Use `bagdock switch` to select operator and environment interactively, or pass `--env test` to any command.
60
+
51
61
  ## Available Commands
52
62
 
53
63
  | Command | Description |
@@ -73,8 +83,9 @@ For CI/CD, set `BAGDOCK_API_KEY` in your environment. For interactive use, run `
73
83
  | `open [slug]` | Open project in Bagdock dashboard |
74
84
  | `inspect [slug]` | Show deployment details and status |
75
85
  | `link` | Link directory to a Bagdock app or edge |
86
+ | `switch` | Switch operator and environment context (live/test) |
76
87
  | `doctor` | Run environment diagnostics (version, auth, config, agents) |
77
- | `auth list` | List stored profiles |
88
+ | `auth list` | List stored profiles (with operator + env) |
78
89
  | `auth switch [name]` | Switch active profile |
79
90
  | `apps list` | List deployed applications |
80
91
  | `apps get <slug>` | Show details for an application |