@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 +92 -0
- package/dist/bagdock.js +348 -132
- package/package.json +1 -1
- package/skill-evals/bagdock-cli/evals.json +43 -1
- package/skills/bagdock-cli/SKILL.md +12 -1
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.
|
|
3351
|
-
console.log(" Operator:", source_default.bold(tokens.
|
|
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({
|
|
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 (
|
|
3404
|
-
console.log("Operator:", source_default.bold(
|
|
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
|
|
3432
|
-
|
|
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(
|
|
4112
|
+
function confirm(prompt2) {
|
|
3829
4113
|
return new Promise((resolve) => {
|
|
3830
4114
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
3831
|
-
rl.question(
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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("--
|
|
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>", "
|
|
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
|
"skill": "bagdock-cli",
|
|
3
|
-
"version": "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 |
|