@bagdock/cli 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,6 +16,12 @@ curl -fsSL https://bdok.dev/install.sh | bash
16
16
  irm https://bdok.dev/install.ps1 | iex
17
17
  ```
18
18
 
19
+ ### Homebrew (macOS / Linux)
20
+
21
+ ```bash
22
+ brew install bagdock/cli/bagdock
23
+ ```
24
+
19
25
  ### Node.js
20
26
 
21
27
  ```bash
@@ -42,12 +48,16 @@ pnpm dlx @bagdock/cli --help
42
48
 
43
49
  ### Agent skills
44
50
 
45
- This CLI ships with an agent skill that teaches AI coding agents (Cursor, Claude Code, Windsurf, etc.) how to use the Bagdock CLI effectively — including non-interactive flags, output formats, and common pitfalls.
51
+ This CLI ships with an agent skill that teaches AI coding agents (Cursor, Claude Code, Codex, Conductor, etc.) how to use the Bagdock CLI effectively — including non-interactive flags, output formats, and common pitfalls.
52
+
53
+ To install skills for Bagdock's full platform (API, CLI, adapters) from the central skills repository:
46
54
 
47
55
  ```bash
48
56
  npx skills add bagdock/bagdock-skills
49
57
  ```
50
58
 
59
+ See [bagdock/bagdock-skills](https://github.com/bagdock/bagdock-skills) for all available skills and plugin manifests.
60
+
51
61
  ## Local development
52
62
 
53
63
  Use this when you want to change the CLI and run your build locally.
@@ -383,6 +393,129 @@ bagdock submit
383
393
 
384
394
  ---
385
395
 
396
+ ### `bagdock validate`
397
+
398
+ Run local pre-submission checks on `bagdock.json` and your bundle before submitting.
399
+
400
+ ```bash
401
+ bagdock validate
402
+ ```
403
+
404
+ Checks performed:
405
+
406
+ | Check | Pass | Warn | Fail |
407
+ |-------|------|------|------|
408
+ | bagdock.json | Found and parsed | — | Missing or invalid JSON |
409
+ | Required fields | All present | — | Missing name, slug, version, type, category, or main |
410
+ | Type | Valid type | — | Invalid type value |
411
+ | Kind | — | Unknown kind | — |
412
+ | Entry point | File exists (shows size) | — | File not found |
413
+ | Bundle size | Under 10 MB | Approaching limit (>80%) | Over 10 MB |
414
+ | Project link | — | Slug mismatch with linked project | — |
415
+
416
+ ```bash
417
+ # JSON output
418
+ bagdock validate --json
419
+ # => {"ok":true,"checks":[...]}
420
+ ```
421
+
422
+ Exit code `0` if all checks pass or warn. Exit code `1` if any check fails.
423
+
424
+ ---
425
+
426
+ ### `bagdock submission list`
427
+
428
+ List submission history for the current app.
429
+
430
+ ```bash
431
+ bagdock submission list
432
+ bagdock submission list --app my-adapter --json
433
+ ```
434
+
435
+ | Flag | Description |
436
+ |------|-------------|
437
+ | `--app <slug>` | App slug (defaults to `bagdock.json` or linked project) |
438
+
439
+ ### `bagdock submission status <id>`
440
+
441
+ Fetch detailed review state for a specific submission.
442
+
443
+ ```bash
444
+ bagdock submission status iadpv_abc123
445
+ bagdock submission status iadpv_abc123 --json
446
+ ```
447
+
448
+ | Flag | Description |
449
+ |------|-------------|
450
+ | `--app <slug>` | App slug |
451
+
452
+ ### `bagdock submission withdraw <id>`
453
+
454
+ Cancel a pending submission before approval. Only works when `review_status` is `submitted`.
455
+
456
+ ```bash
457
+ bagdock submission withdraw iadpv_abc123
458
+ ```
459
+
460
+ | Flag | Description |
461
+ |------|-------------|
462
+ | `--app <slug>` | App slug |
463
+
464
+ #### Error codes
465
+
466
+ | Code | Cause |
467
+ |------|-------|
468
+ | `not_found` | Submission or app not found |
469
+ | `invalid_status` | App is not in `submitted` state |
470
+
471
+ ---
472
+
473
+ ### `bagdock open [slug]`
474
+
475
+ Open the current project in the Bagdock dashboard.
476
+
477
+ ```bash
478
+ bagdock open
479
+ bagdock open my-adapter
480
+ ```
481
+
482
+ Reads the slug from `bagdock.json`, linked project, or the argument.
483
+
484
+ ---
485
+
486
+ ### `bagdock inspect [slug]`
487
+
488
+ Show deployment details and status for an app.
489
+
490
+ ```bash
491
+ bagdock inspect
492
+ bagdock inspect my-adapter --json
493
+ ```
494
+
495
+ Displays: name, slug, type, version, review status, worker URL, namespace, timestamps.
496
+
497
+ ---
498
+
499
+ ### `bagdock link`
500
+
501
+ Link the current directory to a Bagdock app or edge. Other commands use the linked slug as a fallback.
502
+
503
+ ```bash
504
+ # Interactive: select from your apps
505
+ bagdock link
506
+
507
+ # Non-interactive
508
+ bagdock link --slug my-adapter
509
+ ```
510
+
511
+ | Flag | Description |
512
+ |------|-------------|
513
+ | `--slug <slug>` | Project slug (required in non-interactive mode) |
514
+
515
+ Stores the link in `.bagdock/link.json` in the current directory.
516
+
517
+ ---
518
+
386
519
  ### `bagdock env list`
387
520
 
388
521
  List environment variables for the current app.
@@ -408,6 +541,17 @@ Remove an environment variable.
408
541
  bagdock env remove VENDOR_API_KEY
409
542
  ```
410
543
 
544
+ ### `bagdock env pull [file]`
545
+
546
+ Pull remote env var keys to a local `.env` file for development.
547
+
548
+ ```bash
549
+ bagdock env pull
550
+ bagdock env pull .env.development
551
+ ```
552
+
553
+ The API does not expose secret values. The file is created with keys and empty values — fill them in for local dev.
554
+
411
555
  ---
412
556
 
413
557
  ### `bagdock keys create`
package/dist/bagdock.js CHANGED
@@ -3894,14 +3894,430 @@ var init_submit = __esm(() => {
3894
3894
  init_auth();
3895
3895
  });
3896
3896
 
3897
+ // src/link.ts
3898
+ var exports_link = {};
3899
+ __export(exports_link, {
3900
+ resolveSlug: () => resolveSlug,
3901
+ requireSlug: () => requireSlug,
3902
+ link: () => link
3903
+ });
3904
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
3905
+ import { join as join6 } from "path";
3906
+ function resolveSlug() {
3907
+ const config = loadBagdockJson(process.cwd());
3908
+ if (config?.slug)
3909
+ return config.slug;
3910
+ const linkPath = join6(process.cwd(), LINK_DIR, LINK_FILE);
3911
+ if (existsSync6(linkPath)) {
3912
+ try {
3913
+ const data = JSON.parse(readFileSync4(linkPath, "utf-8"));
3914
+ return data.slug ?? null;
3915
+ } catch {
3916
+ return null;
3917
+ }
3918
+ }
3919
+ return null;
3920
+ }
3921
+ function requireSlug(slugArg) {
3922
+ const slug = slugArg ?? resolveSlug();
3923
+ if (!slug) {
3924
+ if (isJsonMode()) {
3925
+ outputError("no_project", "No project found. Pass --slug, add bagdock.json, or run bagdock link.");
3926
+ }
3927
+ console.error(source_default.red("No project found."), "Pass a slug, create bagdock.json, or run", source_default.cyan("bagdock link"));
3928
+ process.exit(1);
3929
+ }
3930
+ return slug;
3931
+ }
3932
+ async function link(opts) {
3933
+ let slug = opts.slug;
3934
+ if (!slug) {
3935
+ const config = loadBagdockJson(process.cwd());
3936
+ if (config?.slug) {
3937
+ slug = config.slug;
3938
+ status(`Found bagdock.json — linking to ${slug}`);
3939
+ }
3940
+ }
3941
+ if (!slug && process.stdout.isTTY && !isJsonMode()) {
3942
+ const token = getAuthToken();
3943
+ if (!token) {
3944
+ console.error(source_default.red("Not authenticated."), "Run", source_default.cyan("bagdock login"), "first.");
3945
+ process.exit(1);
3946
+ }
3947
+ status("Fetching your apps...");
3948
+ try {
3949
+ const res = await fetch(`${API_BASE}/v1/developer/apps`, {
3950
+ headers: { Authorization: `Bearer ${token}` }
3951
+ });
3952
+ if (!res.ok)
3953
+ throw new Error(`API returned ${res.status}`);
3954
+ const { data } = await res.json();
3955
+ if (!data?.length) {
3956
+ console.error(source_default.yellow("No apps found."), "Create one with", source_default.cyan("bagdock init"));
3957
+ process.exit(1);
3958
+ }
3959
+ console.log(source_default.bold(`
3960
+ Your apps:
3961
+ `));
3962
+ data.forEach((app, i) => console.log(` ${source_default.cyan(i + 1)} ${app.name} ${source_default.dim(`(${app.slug})`)}`));
3963
+ console.log();
3964
+ const readline = await import("readline");
3965
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
3966
+ const answer = await new Promise((resolve) => rl.question("Select app number: ", resolve));
3967
+ rl.close();
3968
+ const idx = parseInt(answer, 10) - 1;
3969
+ if (isNaN(idx) || idx < 0 || idx >= data.length) {
3970
+ console.error(source_default.red("Invalid selection"));
3971
+ process.exit(1);
3972
+ }
3973
+ slug = data[idx].slug;
3974
+ } catch (err) {
3975
+ console.error(source_default.red("Failed to fetch apps:"), err.message);
3976
+ process.exit(1);
3977
+ }
3978
+ }
3979
+ if (!slug) {
3980
+ outputError("missing_slug", "Slug required. Pass --slug in non-interactive mode.");
3981
+ process.exit(1);
3982
+ }
3983
+ const dir = join6(process.cwd(), LINK_DIR);
3984
+ if (!existsSync6(dir))
3985
+ mkdirSync3(dir, { recursive: true });
3986
+ const linkData = { slug, linkedAt: new Date().toISOString() };
3987
+ writeFileSync4(join6(dir, LINK_FILE), JSON.stringify(linkData, null, 2));
3988
+ if (isJsonMode()) {
3989
+ outputSuccess({ slug, path: join6(dir, LINK_FILE) });
3990
+ } else {
3991
+ console.log(source_default.green(`Linked to ${source_default.bold(slug)}`));
3992
+ console.log(source_default.dim(` Stored in ${LINK_DIR}/${LINK_FILE}`));
3993
+ }
3994
+ }
3995
+ var LINK_DIR = ".bagdock", LINK_FILE = "link.json";
3996
+ var init_link = __esm(() => {
3997
+ init_source();
3998
+ init_config();
3999
+ init_auth();
4000
+ init_output();
4001
+ });
4002
+
4003
+ // src/validate.ts
4004
+ var exports_validate = {};
4005
+ __export(exports_validate, {
4006
+ validate: () => validate
4007
+ });
4008
+ import { existsSync as existsSync7, statSync } from "fs";
4009
+ import { join as join7 } from "path";
4010
+ async function validate() {
4011
+ const checks = [];
4012
+ const dir = process.cwd();
4013
+ const config = loadBagdockJson(dir);
4014
+ if (!config) {
4015
+ checks.push({ name: "bagdock.json", status: "fail", message: "Not found or invalid JSON" });
4016
+ return finish(checks);
4017
+ }
4018
+ checks.push({ name: "bagdock.json", status: "pass", message: "Found and parsed" });
4019
+ const required = ["name", "slug", "version", "type", "category", "main"];
4020
+ const missing = required.filter((f) => !config[f]);
4021
+ if (missing.length) {
4022
+ checks.push({ name: "Required fields", status: "fail", message: `Missing: ${missing.join(", ")}` });
4023
+ } else {
4024
+ checks.push({ name: "Required fields", status: "pass", message: "All present" });
4025
+ }
4026
+ if (!VALID_TYPES.includes(config.type)) {
4027
+ checks.push({ name: "Type", status: "fail", message: `Invalid type "${config.type}". Must be: ${VALID_TYPES.join(", ")}` });
4028
+ } else {
4029
+ checks.push({ name: "Type", status: "pass", message: config.type });
4030
+ }
4031
+ if (config.kind && !VALID_KINDS.includes(config.kind)) {
4032
+ checks.push({ name: "Kind", status: "warn", message: `Unknown kind "${config.kind}". Expected: ${VALID_KINDS.join(", ")}` });
4033
+ }
4034
+ const entryPath = join7(dir, config.main);
4035
+ if (!existsSync7(entryPath)) {
4036
+ checks.push({ name: "Entry point", status: "fail", message: `File not found: ${config.main}` });
4037
+ } else {
4038
+ const size = statSync(entryPath).size;
4039
+ checks.push({ name: "Entry point", status: "pass", message: `${config.main} (${(size / 1024).toFixed(1)} KB)` });
4040
+ if (size > MAX_BUNDLE_BYTES) {
4041
+ checks.push({ name: "Bundle size", status: "fail", message: `${(size / 1024 / 1024).toFixed(1)} MB exceeds ${MAX_BUNDLE_BYTES / 1024 / 1024} MB limit` });
4042
+ } else if (size > MAX_BUNDLE_BYTES * 0.8) {
4043
+ checks.push({ name: "Bundle size", status: "warn", message: `${(size / 1024 / 1024).toFixed(1)} MB — approaching limit` });
4044
+ } else {
4045
+ checks.push({ name: "Bundle size", status: "pass", message: `${(size / 1024).toFixed(1)} KB` });
4046
+ }
4047
+ }
4048
+ const linked = resolveSlug();
4049
+ if (linked && linked !== config.slug) {
4050
+ checks.push({ name: "Project link", status: "warn", message: `bagdock.json slug "${config.slug}" differs from linked project "${linked}"` });
4051
+ }
4052
+ return finish(checks);
4053
+ }
4054
+ function finish(checks) {
4055
+ const hasFail = checks.some((c) => c.status === "fail");
4056
+ const hasWarn = checks.some((c) => c.status === "warn");
4057
+ if (isJsonMode()) {
4058
+ outputSuccess({ ok: !hasFail, checks });
4059
+ if (hasFail)
4060
+ process.exit(1);
4061
+ return;
4062
+ }
4063
+ console.log(source_default.bold(`
4064
+ Bagdock Validate
4065
+ `));
4066
+ for (const c of checks) {
4067
+ const icon = c.status === "pass" ? source_default.green("✔") : c.status === "warn" ? source_default.yellow("⚠") : source_default.red("✖");
4068
+ console.log(` ${icon} ${c.name}: ${c.message}`);
4069
+ }
4070
+ console.log();
4071
+ if (hasFail) {
4072
+ console.log(source_default.red(` Validation failed. Fix errors before submitting.
4073
+ `));
4074
+ process.exit(1);
4075
+ } else if (hasWarn) {
4076
+ console.log(source_default.yellow(` Passed with warnings.
4077
+ `));
4078
+ } else {
4079
+ console.log(source_default.green(` All checks passed. Ready to submit.
4080
+ `));
4081
+ }
4082
+ }
4083
+ var VALID_TYPES, VALID_KINDS, MAX_BUNDLE_BYTES;
4084
+ var init_validate = __esm(() => {
4085
+ init_source();
4086
+ init_config();
4087
+ init_output();
4088
+ init_link();
4089
+ VALID_TYPES = ["edge", "app"];
4090
+ VALID_KINDS = ["adapter", "comms", "webhook", "ui-extension", "microfrontend"];
4091
+ MAX_BUNDLE_BYTES = 10 * 1024 * 1024;
4092
+ });
4093
+
4094
+ // src/submission.ts
4095
+ var exports_submission = {};
4096
+ __export(exports_submission, {
4097
+ submissionWithdraw: () => submissionWithdraw,
4098
+ submissionStatus: () => submissionStatus,
4099
+ submissionList: () => submissionList
4100
+ });
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
+ async function submissionList(opts) {
4110
+ const token = requireAuth();
4111
+ const slug = requireSlug(opts.app);
4112
+ status(`Fetching submissions for ${slug}...`);
4113
+ try {
4114
+ const res = await fetch(`${API_BASE}/v1/developer/apps/${slug}/submissions`, {
4115
+ headers: { Authorization: `Bearer ${token}` }
4116
+ });
4117
+ if (res.status === 404) {
4118
+ outputError("not_found", `App "${slug}" not found or no submissions exist.`);
4119
+ }
4120
+ if (!res.ok) {
4121
+ outputError("api_error", `API returned ${res.status}`);
4122
+ }
4123
+ const { data } = await res.json();
4124
+ if (isJsonMode()) {
4125
+ outputList("submission", data, false);
4126
+ return;
4127
+ }
4128
+ if (!data?.length) {
4129
+ console.log(source_default.yellow("No submissions found for this app."));
4130
+ console.log("Submit with", source_default.cyan("bagdock submit"));
4131
+ return;
4132
+ }
4133
+ console.log(source_default.bold(`
4134
+ Submissions for ${slug}:
4135
+ `));
4136
+ console.log(` ${"ID".padEnd(22)} ${"Version".padEnd(10)} ${"Reason".padEnd(30)} ${"Date"}`);
4137
+ console.log(source_default.dim(" " + "─".repeat(80)));
4138
+ for (const s of data) {
4139
+ console.log(` ${source_default.cyan(s.id.padEnd(22))} ${(s.version ?? "").padEnd(10)} ${(s.change_reason ?? "").slice(0, 30).padEnd(30)} ${source_default.dim(new Date(s.created_at).toLocaleDateString())}`);
4140
+ }
4141
+ console.log();
4142
+ } catch (err) {
4143
+ outputError("network_error", err.message);
4144
+ }
4145
+ }
4146
+ async function submissionStatus(id, opts) {
4147
+ const token = requireAuth();
4148
+ const slug = requireSlug(opts.app);
4149
+ status(`Fetching submission ${id}...`);
4150
+ try {
4151
+ const res = await fetch(`${API_BASE}/v1/developer/apps/${slug}/submissions/${id}`, {
4152
+ headers: { Authorization: `Bearer ${token}` }
4153
+ });
4154
+ if (res.status === 404) {
4155
+ outputError("not_found", `Submission "${id}" not found.`);
4156
+ }
4157
+ if (!res.ok) {
4158
+ outputError("api_error", `API returned ${res.status}`);
4159
+ }
4160
+ const { data } = await res.json();
4161
+ if (isJsonMode()) {
4162
+ outputSuccess(data);
4163
+ return;
4164
+ }
4165
+ console.log(source_default.bold(`
4166
+ Submission ${source_default.cyan(data.id)}
4167
+ `));
4168
+ const fields = [
4169
+ ["App", `${data.name} (${data.slug})`],
4170
+ ["Version", data.version],
4171
+ ["Review Status", data.review_status],
4172
+ ["Type", data.type],
4173
+ ["Visibility", data.visibility],
4174
+ ["Reason", data.change_reason],
4175
+ ["Submitted by", data.changed_by],
4176
+ ["Date", new Date(data.created_at).toLocaleString()]
4177
+ ];
4178
+ for (const [label, value] of fields) {
4179
+ console.log(` ${source_default.dim(label.padEnd(16))} ${value ?? source_default.dim("—")}`);
4180
+ }
4181
+ console.log();
4182
+ } catch (err) {
4183
+ outputError("network_error", err.message);
4184
+ }
4185
+ }
4186
+ async function submissionWithdraw(id, opts) {
4187
+ const token = requireAuth();
4188
+ const slug = requireSlug(opts.app);
4189
+ status(`Withdrawing submission ${id}...`);
4190
+ try {
4191
+ const res = await fetch(`${API_BASE}/v1/developer/apps/${slug}/submissions/${id}/withdraw`, {
4192
+ method: "POST",
4193
+ headers: { Authorization: `Bearer ${token}` }
4194
+ });
4195
+ if (res.status === 404) {
4196
+ outputError("not_found", `App "${slug}" not found.`);
4197
+ }
4198
+ if (res.status === 409) {
4199
+ const body = await res.json();
4200
+ outputError(body.code ?? "invalid_status", body.message);
4201
+ }
4202
+ if (!res.ok) {
4203
+ outputError("api_error", `API returned ${res.status}`);
4204
+ }
4205
+ const { data } = await res.json();
4206
+ if (isJsonMode()) {
4207
+ outputSuccess(data);
4208
+ return;
4209
+ }
4210
+ console.log(source_default.green(`Submission withdrawn.`), source_default.dim(`Status is now: ${data.review_status}`));
4211
+ console.log("You can re-submit with", source_default.cyan("bagdock submit"));
4212
+ } catch (err) {
4213
+ outputError("network_error", err.message);
4214
+ }
4215
+ }
4216
+ var init_submission = __esm(() => {
4217
+ init_source();
4218
+ init_config();
4219
+ init_auth();
4220
+ init_output();
4221
+ init_link();
4222
+ });
4223
+
4224
+ // src/open.ts
4225
+ var exports_open2 = {};
4226
+ __export(exports_open2, {
4227
+ open: () => open2
4228
+ });
4229
+ async function open2(slugArg) {
4230
+ const slug = requireSlug(slugArg);
4231
+ const url = `${DASHBOARD_BASE}/developer/apps/${slug}`;
4232
+ if (isJsonMode()) {
4233
+ outputSuccess({ url, slug });
4234
+ return;
4235
+ }
4236
+ status(`Opening ${url}`);
4237
+ await open_default(url);
4238
+ console.log(source_default.green("Opened"), source_default.cyan(url));
4239
+ }
4240
+ var init_open2 = __esm(() => {
4241
+ init_source();
4242
+ init_open();
4243
+ init_config();
4244
+ init_output();
4245
+ init_link();
4246
+ });
4247
+
4248
+ // src/inspect.ts
4249
+ var exports_inspect = {};
4250
+ __export(exports_inspect, {
4251
+ inspect: () => inspect
4252
+ });
4253
+ 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
+ const slug = requireSlug(slugArg);
4260
+ status(`Inspecting ${slug}...`);
4261
+ try {
4262
+ const res = await fetch(`${API_BASE}/v1/developer/apps/${slug}`, {
4263
+ headers: { Authorization: `Bearer ${token}` }
4264
+ });
4265
+ if (res.status === 404)
4266
+ outputError("not_found", `App "${slug}" not found.`);
4267
+ if (!res.ok)
4268
+ outputError("api_error", `API returned ${res.status}`);
4269
+ const { data } = await res.json();
4270
+ if (isJsonMode()) {
4271
+ outputSuccess(data);
4272
+ return;
4273
+ }
4274
+ console.log(source_default.bold(`
4275
+ ${data.name} ${source_default.dim(`(${data.slug})`)}
4276
+ `));
4277
+ const fields = [
4278
+ ["ID", data.id],
4279
+ ["Type", data.type],
4280
+ ["Category", data.category],
4281
+ ["Version", data.version],
4282
+ ["Maintainer", data.maintainer],
4283
+ ["Visibility", data.visibility],
4284
+ ["Review Status", data.review_status],
4285
+ ["Active", data.is_active ? "yes" : "no"],
4286
+ ["Worker URL", data.worker_url],
4287
+ ["Namespace", data.worker_namespace],
4288
+ ["Created", data.created_at ? new Date(data.created_at).toLocaleString() : undefined],
4289
+ ["Updated", data.updated_at ? new Date(data.updated_at).toLocaleString() : undefined],
4290
+ ["Published", data.published_at ? new Date(data.published_at).toLocaleString() : undefined]
4291
+ ];
4292
+ for (const [label, value] of fields) {
4293
+ if (value !== undefined && value !== null) {
4294
+ console.log(` ${source_default.dim(label.padEnd(16))} ${value}`);
4295
+ }
4296
+ }
4297
+ console.log();
4298
+ } catch (err) {
4299
+ outputError("network_error", err.message);
4300
+ }
4301
+ }
4302
+ var init_inspect = __esm(() => {
4303
+ init_source();
4304
+ init_config();
4305
+ init_auth();
4306
+ init_output();
4307
+ init_link();
4308
+ });
4309
+
3897
4310
  // src/env-cmd.ts
3898
4311
  var exports_env_cmd = {};
3899
4312
  __export(exports_env_cmd, {
3900
4313
  envSet: () => envSet,
3901
4314
  envRemove: () => envRemove,
4315
+ envPull: () => envPull,
3902
4316
  envList: () => envList
3903
4317
  });
3904
- function requireAuth() {
4318
+ import { writeFileSync as writeFileSync5 } from "fs";
4319
+ import { resolve } from "path";
4320
+ function requireAuth2() {
3905
4321
  const token = getAuthToken();
3906
4322
  if (!token) {
3907
4323
  console.error(source_default.red("Not authenticated. Run"), source_default.cyan("bagdock login"), source_default.red("or set BAGDOCK_API_KEY."));
@@ -3918,7 +4334,7 @@ function requireConfig() {
3918
4334
  return config;
3919
4335
  }
3920
4336
  async function envList() {
3921
- const token = requireAuth();
4337
+ const token = requireAuth2();
3922
4338
  const config = requireConfig();
3923
4339
  try {
3924
4340
  const res = await fetch(`${API_BASE}/v1/developer/apps/${config.slug}/env`, {
@@ -3947,7 +4363,7 @@ Environment variables for ${config.slug}:
3947
4363
  }
3948
4364
  }
3949
4365
  async function envSet(key, value) {
3950
- const token = requireAuth();
4366
+ const token = requireAuth2();
3951
4367
  const config = requireConfig();
3952
4368
  try {
3953
4369
  const res = await fetch(`${API_BASE}/v1/developer/apps/${config.slug}/env`, {
@@ -3970,7 +4386,7 @@ async function envSet(key, value) {
3970
4386
  }
3971
4387
  }
3972
4388
  async function envRemove(key) {
3973
- const token = requireAuth();
4389
+ const token = requireAuth2();
3974
4390
  const config = requireConfig();
3975
4391
  try {
3976
4392
  const res = await fetch(`${API_BASE}/v1/developer/apps/${config.slug}/env/${key}`, {
@@ -3987,10 +4403,52 @@ async function envRemove(key) {
3987
4403
  process.exit(1);
3988
4404
  }
3989
4405
  }
4406
+ async function envPull(file) {
4407
+ const token = requireAuth2();
4408
+ const slug = requireSlug();
4409
+ const target = resolve(file ?? ".env.local");
4410
+ status(`Pulling env vars for ${slug}...`);
4411
+ try {
4412
+ const res = await fetch(`${API_BASE}/v1/developer/apps/${slug}/env`, {
4413
+ headers: { Authorization: `Bearer ${token}` }
4414
+ });
4415
+ if (!res.ok) {
4416
+ outputError("api_error", `Failed to pull env vars (${res.status})`);
4417
+ process.exit(1);
4418
+ }
4419
+ const { data } = await res.json();
4420
+ if (isJsonMode()) {
4421
+ outputSuccess({ file: target, keys: data.map((v) => v.key) });
4422
+ return;
4423
+ }
4424
+ if (!data?.length) {
4425
+ console.log(source_default.yellow("No environment variables set."));
4426
+ return;
4427
+ }
4428
+ const lines = [
4429
+ `# Pulled from Bagdock — ${slug}`,
4430
+ `# ${new Date().toISOString()}`,
4431
+ `# Values are placeholders — the API does not expose secrets.`,
4432
+ `# Fill in real values for local development.`,
4433
+ "",
4434
+ ...data.map((v) => `${v.key}=`),
4435
+ ""
4436
+ ];
4437
+ writeFileSync5(target, lines.join(`
4438
+ `));
4439
+ console.log(source_default.green(`Wrote ${data.length} keys to ${target}`));
4440
+ console.log(source_default.yellow("Note:"), "Values are empty — fill them in for local dev.");
4441
+ } catch (err) {
4442
+ console.error(source_default.red("Failed:"), err.message);
4443
+ process.exit(1);
4444
+ }
4445
+ }
3990
4446
  var init_env_cmd = __esm(() => {
3991
4447
  init_source();
3992
4448
  init_config();
3993
4449
  init_auth();
4450
+ init_output();
4451
+ init_link();
3994
4452
  });
3995
4453
 
3996
4454
  // src/keys.ts
@@ -4209,7 +4667,7 @@ async function apiRequest3(method, path2) {
4209
4667
  headers: { Authorization: `Bearer ${token}` }
4210
4668
  });
4211
4669
  }
4212
- function resolveSlug(slug) {
4670
+ function resolveSlug2(slug) {
4213
4671
  if (slug)
4214
4672
  return slug;
4215
4673
  const config = loadBagdockJson(process.cwd());
@@ -4219,7 +4677,7 @@ function resolveSlug(slug) {
4219
4677
  return "";
4220
4678
  }
4221
4679
  async function logsList(opts) {
4222
- const slug = resolveSlug(opts.app);
4680
+ const slug = resolveSlug2(opts.app);
4223
4681
  const limit = opts.limit || "50";
4224
4682
  status(`Fetching logs for ${slug}...`);
4225
4683
  const res = await apiRequest3("GET", `/api/v1/developer/apps/${slug}/logs?limit=${limit}`);
@@ -4250,7 +4708,7 @@ async function logsList(opts) {
4250
4708
  }
4251
4709
  }
4252
4710
  async function logsGet(id, opts) {
4253
- const slug = resolveSlug(opts.app);
4711
+ const slug = resolveSlug2(opts.app);
4254
4712
  status(`Fetching log entry ${id}...`);
4255
4713
  const res = await apiRequest3("GET", `/api/v1/developer/apps/${slug}/logs/${id}`);
4256
4714
  if (!res.ok) {
@@ -4265,7 +4723,7 @@ async function logsGet(id, opts) {
4265
4723
  }
4266
4724
  }
4267
4725
  async function logsTail(opts) {
4268
- const slug = resolveSlug(opts.app);
4726
+ const slug = resolveSlug2(opts.app);
4269
4727
  if (isJsonMode()) {
4270
4728
  outputError("UNSUPPORTED", "Log tailing is not supported in JSON mode. Use `logs list` instead.");
4271
4729
  }
@@ -4575,7 +5033,7 @@ function toPascalCase(s) {
4575
5033
  init_output();
4576
5034
  init_config();
4577
5035
  var program2 = new Command;
4578
- program2.name("bagdock").description("Bagdock developer CLI — built for humans, AI agents, and CI/CD pipelines").version("0.3.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) => {
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) => {
4579
5037
  const opts = program2.opts();
4580
5038
  setOutputMode({ json: opts.json, quiet: opts.quiet });
4581
5039
  if (opts.apiKey)
@@ -4606,6 +5064,35 @@ program2.command("submit").description("Submit app for Bagdock marketplace revie
4606
5064
  const { submit: submit2 } = await Promise.resolve().then(() => (init_submit(), exports_submit));
4607
5065
  await submit2();
4608
5066
  });
5067
+ program2.command("validate").description("Run local pre-submission checks on bagdock.json and bundle").action(async () => {
5068
+ const { validate: validate2 } = await Promise.resolve().then(() => (init_validate(), exports_validate));
5069
+ await validate2();
5070
+ });
5071
+ var subCmd = program2.command("submission").description("Track marketplace submission status");
5072
+ subCmd.command("list").description("List submission history for the current app").option("--app <slug>", "App slug (defaults to bagdock.json or linked project)").action(async (opts) => {
5073
+ const { submissionList: submissionList2 } = await Promise.resolve().then(() => (init_submission(), exports_submission));
5074
+ await submissionList2(opts);
5075
+ });
5076
+ subCmd.command("status <id>").description("Fetch detailed review state for a submission").option("--app <slug>", "App slug").action(async (id, opts) => {
5077
+ const { submissionStatus: submissionStatus2 } = await Promise.resolve().then(() => (init_submission(), exports_submission));
5078
+ await submissionStatus2(id, opts);
5079
+ });
5080
+ subCmd.command("withdraw <id>").description("Cancel a pending submission before approval").option("--app <slug>", "App slug").action(async (id, opts) => {
5081
+ const { submissionWithdraw: submissionWithdraw2 } = await Promise.resolve().then(() => (init_submission(), exports_submission));
5082
+ await submissionWithdraw2(id, opts);
5083
+ });
5084
+ program2.command("open [slug]").description("Open project in the Bagdock dashboard").action(async (slug) => {
5085
+ const { open: open3 } = await Promise.resolve().then(() => (init_open2(), exports_open2));
5086
+ await open3(slug);
5087
+ });
5088
+ program2.command("inspect [slug]").description("Show deployment details and status for an app").action(async (slug) => {
5089
+ const { inspect: inspect2 } = await Promise.resolve().then(() => (init_inspect(), exports_inspect));
5090
+ await inspect2(slug);
5091
+ });
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) => {
5093
+ const { link: link2 } = await Promise.resolve().then(() => (init_link(), exports_link));
5094
+ await link2(opts);
5095
+ });
4609
5096
  var envCmd = program2.command("env").description("Manage app environment variables");
4610
5097
  envCmd.command("list").description("List environment variables for this app").action(async () => {
4611
5098
  const { envList: envList2 } = await Promise.resolve().then(() => (init_env_cmd(), exports_env_cmd));
@@ -4619,6 +5106,10 @@ envCmd.command("remove <key>").description("Remove an environment variable").act
4619
5106
  const { envRemove: envRemove2 } = await Promise.resolve().then(() => (init_env_cmd(), exports_env_cmd));
4620
5107
  await envRemove2(key);
4621
5108
  });
5109
+ envCmd.command("pull [file]").description("Pull remote env var keys to a local .env file").action(async (file) => {
5110
+ const { envPull: envPull2 } = await Promise.resolve().then(() => (init_env_cmd(), exports_env_cmd));
5111
+ await envPull2(file);
5112
+ });
4622
5113
  var keysCmd = program2.command("keys").description("Manage operator API keys");
4623
5114
  keysCmd.command("create").description("Create a new API key (raw key shown once)").requiredOption("--name <name>", "Key name").option("--type <type>", "Key type (secret, publishable)", "secret").option("--category <category>", "Key category (standard, restricted, personal)", "standard").option("--environment <env>", "Environment (live, test)", "live").option("--scopes <scopes...>", "Permission scopes").action(async (opts) => {
4624
5115
  const { keysCreate: keysCreate2 } = await Promise.resolve().then(() => (init_keys(), exports_keys));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bagdock/cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.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.3.0",
3
+ "version": "0.4.0",
4
4
  "evals": [
5
5
  {
6
6
  "id": "login-flow",
@@ -147,6 +147,62 @@
147
147
  "input": "What apps do I have deployed?",
148
148
  "expected_commands": ["bagdock apps list --json"],
149
149
  "expected_behavior": "Lists all deployed apps with slugs, types, and status"
150
+ },
151
+ {
152
+ "id": "validate-before-submit",
153
+ "description": "Agent should validate before submitting",
154
+ "input": "Check if my adapter is ready to submit to the marketplace",
155
+ "expected_commands": ["bagdock validate --json"],
156
+ "expected_behavior": "Runs local checks on bagdock.json and bundle, returns pass/warn/fail"
157
+ },
158
+ {
159
+ "id": "submission-list",
160
+ "description": "Agent should list submissions",
161
+ "input": "Show me all my marketplace submissions for this app",
162
+ "expected_commands": ["bagdock submission list --json"],
163
+ "expected_behavior": "Lists submission history with IDs, versions, and dates"
164
+ },
165
+ {
166
+ "id": "submission-status",
167
+ "description": "Agent should check submission status",
168
+ "input": "What's the review status of submission iadpv_abc123?",
169
+ "expected_commands": ["bagdock submission status iadpv_abc123 --json"],
170
+ "expected_behavior": "Returns detailed review state including status, reason, and timestamps"
171
+ },
172
+ {
173
+ "id": "submission-withdraw",
174
+ "description": "Agent should withdraw a pending submission",
175
+ "input": "Withdraw my pending submission iadpv_abc123, I found a bug",
176
+ "expected_commands": ["bagdock submission withdraw iadpv_abc123"],
177
+ "expected_behavior": "Cancels submission, sets review_status back to draft"
178
+ },
179
+ {
180
+ "id": "open-dashboard",
181
+ "description": "Agent should open project in dashboard",
182
+ "input": "Open my adapter in the Bagdock dashboard",
183
+ "expected_commands": ["bagdock open"],
184
+ "expected_behavior": "Opens browser to dashboard URL for the current project"
185
+ },
186
+ {
187
+ "id": "inspect-app",
188
+ "description": "Agent should inspect app deployment details",
189
+ "input": "Show me the deployment details for smart-entry",
190
+ "expected_commands": ["bagdock inspect smart-entry --json"],
191
+ "expected_behavior": "Returns app details including worker URL, version, review status"
192
+ },
193
+ {
194
+ "id": "env-pull",
195
+ "description": "Agent should pull env vars for local development",
196
+ "input": "Pull the env var keys from my deployed adapter to a local .env file",
197
+ "expected_commands": ["bagdock env pull .env.local"],
198
+ "expected_behavior": "Creates .env.local with keys and empty values, warns that values need filling"
199
+ },
200
+ {
201
+ "id": "link-project",
202
+ "description": "Agent should link directory to a project",
203
+ "input": "Link this directory to my smart-entry adapter",
204
+ "expected_commands": ["bagdock link --slug smart-entry"],
205
+ "expected_behavior": "Creates .bagdock/link.json with slug, other commands use it as fallback"
150
206
  }
151
207
  ]
152
208
  }
@@ -62,9 +62,17 @@ For CI/CD, set `BAGDOCK_API_KEY` in your environment. For interactive use, run `
62
62
  | `env list` | List app environment variables |
63
63
  | `env set <key> <value>` | Set an environment variable |
64
64
  | `env remove <key>` | Remove an environment variable |
65
+ | `env pull [file]` | Pull remote env var keys to local .env file |
65
66
  | `keys create` | Create a new API key (raw key shown once) |
66
67
  | `keys list` | List API keys |
67
68
  | `keys delete <id>` | Revoke an API key |
69
+ | `validate` | Run local pre-submission checks on bagdock.json and bundle |
70
+ | `submission list` | List marketplace submission history |
71
+ | `submission status <id>` | Fetch detailed review state for a submission |
72
+ | `submission withdraw <id>` | Cancel a pending submission |
73
+ | `open [slug]` | Open project in Bagdock dashboard |
74
+ | `inspect [slug]` | Show deployment details and status |
75
+ | `link` | Link directory to a Bagdock app or edge |
68
76
  | `doctor` | Run environment diagnostics (version, auth, config, agents) |
69
77
  | `auth list` | List stored profiles |
70
78
  | `auth switch [name]` | Switch active profile |
@@ -112,6 +120,34 @@ bagdock keys list --json
112
120
  3. **Missing `bagdock.json`** — `deploy`, `submit`, and `env` commands require a `bagdock.json` in the current directory. Run `bagdock init` first.
113
121
  4. **Expired session** — If `whoami` fails, run `bagdock login` again. Sessions expire after 8 hours.
114
122
 
123
+ ### Validate before submitting
124
+
125
+ ```bash
126
+ bagdock validate
127
+ bagdock submit
128
+ ```
129
+
130
+ ### Check submission status
131
+
132
+ ```bash
133
+ bagdock submission list --json
134
+ bagdock submission status iadpv_xxx
135
+ ```
136
+
137
+ ### Link a directory and inspect
138
+
139
+ ```bash
140
+ bagdock link --slug my-adapter
141
+ bagdock inspect
142
+ bagdock open
143
+ ```
144
+
145
+ ### Pull env vars for local dev
146
+
147
+ ```bash
148
+ bagdock env pull .env.local
149
+ ```
150
+
115
151
  ## When to Load References
116
152
 
117
153
  Load specific reference files when the task involves:
@@ -121,3 +157,5 @@ Load specific reference files when the task involves:
121
157
  - **Environment variables** → `references/env.md`
122
158
  - **Local development** → `references/dev.md`
123
159
  - **Error codes or troubleshooting** → `references/error-codes.md`
160
+ - **Marketplace submission lifecycle** → `references/marketplace.md`
161
+ - **App management (open, inspect, link)** → `references/app-management.md`
@@ -0,0 +1,63 @@
1
+ # App Management Commands
2
+
3
+ Commands for managing the relationship between your local directory and a Bagdock app or edge.
4
+
5
+ ## `bagdock link`
6
+
7
+ Links the current directory to a Bagdock app or edge. Once linked, other commands (deploy, env, open, inspect, submission) use the linked slug as a fallback — no `bagdock.json` required.
8
+
9
+ ```bash
10
+ # Interactive: select from your apps
11
+ bagdock link
12
+
13
+ # Non-interactive (CI/agents)
14
+ bagdock link --slug my-adapter
15
+ ```
16
+
17
+ Stores the link in `.bagdock/link.json`:
18
+
19
+ ```json
20
+ {
21
+ "slug": "my-adapter",
22
+ "linkedAt": "2026-04-05T00:00:00.000Z"
23
+ }
24
+ ```
25
+
26
+ ### Slug Resolution Order
27
+
28
+ 1. Explicit `--slug` or `--app` argument
29
+ 2. `bagdock.json` in current directory
30
+ 3. `.bagdock/link.json` (linked app)
31
+
32
+ ## `bagdock open [slug]`
33
+
34
+ Opens the project in the Bagdock dashboard. Uses the slug resolution order above.
35
+
36
+ ```bash
37
+ bagdock open
38
+ bagdock open my-adapter
39
+ ```
40
+
41
+ In JSON mode, returns the URL instead of opening the browser:
42
+
43
+ ```bash
44
+ bagdock open --json
45
+ # => {"url":"https://dashboard.bagdock.com/developer/apps/my-adapter","slug":"my-adapter"}
46
+ ```
47
+
48
+ ## `bagdock inspect [slug]`
49
+
50
+ Shows deployment details for an app.
51
+
52
+ ```bash
53
+ bagdock inspect
54
+ bagdock inspect my-adapter --json
55
+ ```
56
+
57
+ Displays:
58
+ - Name, slug, ID
59
+ - Type, category
60
+ - Version, maintainer, visibility
61
+ - Review status
62
+ - Worker URL, namespace
63
+ - Created, updated, published timestamps
@@ -0,0 +1,69 @@
1
+ # Marketplace Submission Lifecycle
2
+
3
+ The Bagdock marketplace uses a reviewed submission model. Developers submit, Bagdock reviews, then approves or rejects. `bagdock publish` is intentionally not a CLI command — final publication is handled by Bagdock staff.
4
+
5
+ ## Workflow
6
+
7
+ 1. `bagdock validate` — Run local checks before uploading
8
+ 2. `bagdock submit` — Upload bundle and request review (draft -> submitted)
9
+ 3. `bagdock submission list` — View all submissions
10
+ 4. `bagdock submission status <id>` — Check review progress
11
+ 5. `bagdock submission withdraw <id>` — Cancel before approval (submitted -> draft)
12
+
13
+ ## Commands
14
+
15
+ ### `bagdock validate`
16
+
17
+ Local-only checks — no API call needed. Validates:
18
+
19
+ - `bagdock.json` exists and parses
20
+ - Required fields present (name, slug, version, type, category, main)
21
+ - Type is `edge` or `app`
22
+ - Entry point file exists
23
+ - Bundle size under 10 MB
24
+
25
+ ```bash
26
+ bagdock validate
27
+ bagdock validate --json
28
+ ```
29
+
30
+ Exit code 0 = pass/warn, 1 = fail.
31
+
32
+ ### `bagdock submission list`
33
+
34
+ ```bash
35
+ bagdock submission list --app my-adapter
36
+ bagdock submission list --json
37
+ ```
38
+
39
+ ### `bagdock submission status <id>`
40
+
41
+ ```bash
42
+ bagdock submission status iadpv_abc123 --json
43
+ ```
44
+
45
+ Returns: app name, version, review_status, type, visibility, change_reason, submitted_by, date.
46
+
47
+ ### `bagdock submission withdraw <id>`
48
+
49
+ ```bash
50
+ bagdock submission withdraw iadpv_abc123
51
+ ```
52
+
53
+ Only works when `review_status` is `submitted`. Returns the app to `draft` status so you can make changes and re-submit.
54
+
55
+ ## Status Values
56
+
57
+ | Status | Meaning |
58
+ |--------|---------|
59
+ | `draft` | Not yet submitted |
60
+ | `submitted` | Under review |
61
+ | `approved` | Cleared for production |
62
+ | `rejected` | Changes requested |
63
+
64
+ ## API Endpoints
65
+
66
+ - `POST /v1/developer/apps/:slug/submit` — Submit
67
+ - `GET /v1/developer/apps/:slug/submissions` — List
68
+ - `GET /v1/developer/apps/:slug/submissions/:id` — Detail
69
+ - `POST /v1/developer/apps/:slug/submissions/:id/withdraw` — Withdraw