@konstantdotcloud/boombox 0.1.2 → 0.2.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
@@ -21,7 +21,7 @@ boombox admin # opens the VM admin UI through a short-lived cloud grant
21
21
  boombox run bd_pre_meeting_brief --input meeting_uid=sample --input organizer=user@example.com --input attendees='["user@example.com"]' --input start_time=2026-06-02T15:00:00Z --input summary=Sample
22
22
  ```
23
23
 
24
- For MCP clients, add `boombox serve --mcp` after `boombox login` succeeds.
24
+ For MCP clients, add `boombox serve --mcp` after `boombox login` succeeds. The MCP server defaults to the `advanced` profile (cassette authoring + rack verbs included); set `GARY_MCP_PROFILE=andrew` or pass `--profile` to narrow or change it.
25
25
 
26
26
  ## Commands
27
27
 
@@ -33,6 +33,7 @@ For MCP clients, add `boombox serve --mcp` after `boombox login` succeeds.
33
33
  | `boombox run <cassette_name> [--input key=value ...] [--no-tail] [--json]` | Manually triggers a cassette through `/gateway/rack/run`, prompts for required missing inputs, then polls to a terminal state unless `--no-tail` is set. |
34
34
  | `boombox cassettes list` | Lists active cassettes available to the tenant. |
35
35
  | `boombox cassettes show <id> [--draft]` | Prints a cassette YAML projection. |
36
+ | `boombox cassettes publish <draft_id> --name <name> [--version <n>] [--json]` | Publishes a dry-run-approved cassette draft to the tenant tier via `POST /gateway/cassette/publish`; renders gate rejections (e.g. `dry-run-not-green`) with fix guidance. |
36
37
  | `boombox runs list [--cassette=X] [--since=24h] [--actor=Y] [--status=completed] [--limit=20]` | Lists recent runs with run id, cassette, actor, status, duration, and start time. |
37
38
  | `boombox runs show <run_id>` | Shows one run: spec hash, inputs, step trace, delivery receipts, error summary, and replay ref. |
38
39
  | `boombox logs tail [--cassette=X]` | Tails run/step events from the VM SSE stream. |
package/dist/boombox.js CHANGED
@@ -37341,12 +37341,6 @@ function registerCassetteAuthoringVerbs(server, gateway) {
37341
37341
  publishSchema,
37342
37342
  async (input) => renderGatewayResult("cassette_publish", await gateway.call("/gateway/cassette/publish", input))
37343
37343
  );
37344
- server.tool(
37345
- "cassette_request_publish",
37346
- "Request tenant_admin approval to publish a cassette draft when the caller lacks cassette.publish.",
37347
- requestPublishSchema,
37348
- async (input) => renderGatewayResult("cassette_request_publish", await gateway.call("/gateway/cassette/request-publish", input))
37349
- );
37350
37344
  server.tool(
37351
37345
  "cassette_get",
37352
37346
  "Get a cassette draft or published cassette as YAML plus describe payload.",
@@ -37399,7 +37393,7 @@ ${JSON.stringify(record2, null, 2)}`;
37399
37393
  function isRecord(value) {
37400
37394
  return value !== null && typeof value === "object" && !Array.isArray(value);
37401
37395
  }
37402
- var recordSchema, draftSchema, refineSchema, dryRunSchema, publishSchema, requestPublishSchema, getSchema, listDraftsSchema, listCapabilitiesSchema;
37396
+ var recordSchema, draftSchema, refineSchema, dryRunSchema, publishSchema, getSchema, listDraftsSchema, listCapabilitiesSchema;
37403
37397
  var init_cassette_authoring = __esm({
37404
37398
  "../../lib/gary-mcp/verbs/cassette-authoring.ts"() {
37405
37399
  "use strict";
@@ -37427,10 +37421,6 @@ var init_cassette_authoring = __esm({
37427
37421
  name: external_exports2.string().min(1),
37428
37422
  version: external_exports2.number().int().min(1).optional()
37429
37423
  };
37430
- requestPublishSchema = {
37431
- draft_id: external_exports2.string().min(1),
37432
- reason: external_exports2.string().min(1).optional()
37433
- };
37434
37424
  getSchema = {
37435
37425
  id: external_exports2.string().min(1).optional(),
37436
37426
  draft_id: external_exports2.string().min(1).optional()
@@ -37609,7 +37599,7 @@ var {
37609
37599
  init_esm_shims();
37610
37600
 
37611
37601
  // package.json
37612
- var version = "0.1.2";
37602
+ var version = "0.2.0";
37613
37603
 
37614
37604
  // src/lib/version.ts
37615
37605
  var BOOMBOX_VERSION = version;
@@ -47050,12 +47040,20 @@ function relativeToCwd(absPath) {
47050
47040
 
47051
47041
  // src/server/mcp-stdio.ts
47052
47042
  init_esm_shims();
47043
+ var DEFAULT_MCP_PROFILE = "advanced";
47044
+ function resolveMcpProfileSelection(flag) {
47045
+ if (flag === void 0) return void 0;
47046
+ return flag === "default" ? DEFAULT_MCP_PROFILE : flag;
47047
+ }
47053
47048
  async function startStdioMcp(config2) {
47054
47049
  process.env.GARY_MCP_GATEWAY_URL = resolveGateway(config2);
47055
47050
  process.env.GARY_MCP_API_KEY = config2.konstant.api_key;
47056
47051
  if (!process.env.KONSTANT_API_KEY) {
47057
47052
  process.env.KONSTANT_API_KEY = config2.konstant.api_key;
47058
47053
  }
47054
+ if (!(process.env.GARY_MCP_PROFILE ?? "").trim()) {
47055
+ process.env.GARY_MCP_PROFILE = DEFAULT_MCP_PROFILE;
47056
+ }
47059
47057
  const mod = await Promise.resolve().then(() => (init_gary_mcp(), gary_mcp_exports));
47060
47058
  await mod.startGaryMcpServer();
47061
47059
  }
@@ -47130,8 +47128,10 @@ async function runServe(options = {}) {
47130
47128
  log("Note: mcp_enabled=false in config; enabling for this session.");
47131
47129
  }
47132
47130
  if (enableMcp) {
47133
- const requestedProfile = options.profile === "default" ? "andrew" : options.profile;
47134
- process.env.GARY_MCP_PROFILE = requestedProfile ?? process.env.GARY_MCP_PROFILE ?? "andrew";
47131
+ const selectedProfile = resolveMcpProfileSelection(options.profile);
47132
+ if (selectedProfile !== void 0) {
47133
+ process.env.GARY_MCP_PROFILE = selectedProfile;
47134
+ }
47135
47135
  process.env.GARY_MCP_TENANT_ID = config2.konstant.tenant_id;
47136
47136
  }
47137
47137
  const handle = await serve2({
@@ -49199,6 +49199,96 @@ async function runCassettesDryRun(options) {
49199
49199
  }
49200
49200
  printSimulation(body, ctx);
49201
49201
  }
49202
+ async function runCassettesPublish(options) {
49203
+ const ctx = createOpsContext(options);
49204
+ const payload = {
49205
+ draft_id: options.draftId,
49206
+ name: options.name
49207
+ };
49208
+ if (options.version !== void 0) payload.version = options.version;
49209
+ let body;
49210
+ try {
49211
+ body = (await fetchJson(ctx, "/gateway/cassette/publish", { method: "POST", body: payload })).body;
49212
+ } catch (error2) {
49213
+ if (error2 instanceof OpsHttpError) {
49214
+ renderPublishRejection(error2.body, ctx, options, `HTTP ${error2.status}`);
49215
+ return 1;
49216
+ }
49217
+ throw error2;
49218
+ }
49219
+ const record2 = asRecord(body);
49220
+ if (record2 && (record2.ok === false || typeof record2.code === "string" && record2.ok !== true)) {
49221
+ renderPublishRejection(body, ctx, options, "rejected");
49222
+ return 1;
49223
+ }
49224
+ if (options.json) {
49225
+ ctx.log(JSON.stringify(body ?? null, null, 2));
49226
+ return 0;
49227
+ }
49228
+ const published = recordField(record2, "workflow") ?? recordField(record2, "published") ?? recordField(record2, "cassette") ?? record2 ?? {};
49229
+ printKeyValues([
49230
+ ["draft", options.draftId],
49231
+ ["workflow", field2(published, ["workflow_id", "id", "cassette_id"])],
49232
+ ["version", field2(published, ["version", "workflow_version"])],
49233
+ ["tier", field2(published, ["tier"])],
49234
+ ["status", field2(published, ["status"])]
49235
+ ], ctx.log);
49236
+ ctx.log("Published to the tenant tier.");
49237
+ return 0;
49238
+ }
49239
+ function publishRejectionHint(code, draftId) {
49240
+ switch (code.toLowerCase().replace(/_/g, "-")) {
49241
+ case "publish-blocked-draft-required":
49242
+ return `Publish requires a saved draft, not raw YAML. Save the draft first (cassette_draft / cassette_refine via MCP), then publish by draft_id (\`${draftId}\`).`;
49243
+ case "publish-blocked-dry-run-missing":
49244
+ return `This draft has no dry-run yet. Run one (cassette_dry_run via MCP, or \`boombox cassettes dry-run ${draftId} --draft\`) and get a ready_to_publish verdict before publishing.`;
49245
+ case "publish-blocked-dry-run-not-ready":
49246
+ case "dry-run-not-green":
49247
+ return `The draft's latest dry-run is not green. Re-run it (cassette_dry_run via MCP, or \`boombox cassettes dry-run ${draftId} --draft\`) until the verdict is ready_to_publish, then publish again.`;
49248
+ case "publish-blocked-dry-run-stale":
49249
+ case "dry-run-stale":
49250
+ // legacy aliases
49251
+ case "stale-dry-run":
49252
+ case "spec-hash-mismatch":
49253
+ return "The draft changed after its last green dry-run. Re-run the dry-run against the current draft revision, then publish again.";
49254
+ case "forbidden-authority":
49255
+ case "missing-authority":
49256
+ return "This API key lacks the cassette.publish grant. Use cassette_request_publish (MCP) to ask a tenant admin to publish, or have an admin add cassette.publish to your key.";
49257
+ case "publish-blocked-not-owner":
49258
+ case "forbidden-not-owner":
49259
+ case "forbidden":
49260
+ // legacy aliases
49261
+ case "draft-not-owned":
49262
+ return "This draft is not owned by your API key actor. Publish from the key that created the draft, or ask a tenant admin to publish it.";
49263
+ case "not-found":
49264
+ return `No draft "${draftId}" is visible to this key. Check \`boombox cassettes list --status draft\` (or cassette_list_drafts via MCP).`;
49265
+ case "publish-blocked-master-shadow":
49266
+ case "master-shadowing":
49267
+ // legacy aliases
49268
+ case "master-collision":
49269
+ return "The cassette id collides with a master/builtin cassette, which tenant publishes are not allowed to shadow. Rename the cassette and publish again.";
49270
+ case "publish-blocked-event-trigger":
49271
+ return "Tenant cassettes cannot publish with event triggers (those are reserved for master-tier cassettes). Remove the event trigger, or request an admin publish.";
49272
+ case "cassette-yaml-invalid":
49273
+ return "The draft YAML failed validation. Fix the reported errors (cassette_refine via MCP) and re-run a dry-run before publishing.";
49274
+ case "validation-error":
49275
+ return "The publish payload was rejected. Check the draft_id and --name values, then re-validate the draft before publishing.";
49276
+ default:
49277
+ return void 0;
49278
+ }
49279
+ }
49280
+ function renderPublishRejection(body, ctx, options, fallbackLabel) {
49281
+ if (options.json) {
49282
+ ctx.errLog(JSON.stringify(body ?? { code: fallbackLabel }, null, 2));
49283
+ return;
49284
+ }
49285
+ const record2 = asRecord(body);
49286
+ const code = stringField4(record2, "code") ?? fallbackLabel;
49287
+ const message = stringField4(record2, "message") ?? stringField4(record2, "error") ?? "no detail from gateway";
49288
+ ctx.errLog(`publish rejected (${code}): ${message}`);
49289
+ const hint = publishRejectionHint(code, options.draftId);
49290
+ if (hint) ctx.errLog(` fix: ${hint}`);
49291
+ }
49202
49292
  function extractCassettes(body) {
49203
49293
  const record2 = asRecord(body);
49204
49294
  const list = arrayField2(record2, "cassettes") ?? arrayField2(record2, "items") ?? arrayField2(record2, "drafts") ?? (Array.isArray(body) ? body : []);
@@ -49604,6 +49694,16 @@ cassettes.command("validate <id>").description("Run cassette oracle readiness ch
49604
49694
  cassettes.command("dry-run <id>").description("Run cassette v2 preview simulation with no durable side effects.").option("--draft", "load from cassette_drafts instead of published records").option("--json", "print raw JSON report").option("--config <path>", "override config path").action(async (id, opts) => {
49605
49695
  await runCassettesDryRun({ id, draft: opts.draft, json: opts.json, configPath: opts.config });
49606
49696
  });
49697
+ cassettes.command("publish <draft_id>").description("Publish a dry-run-approved cassette draft to the tenant tier.").requiredOption("--name <name>", "name for the published cassette").option("--version <n>", "explicit version (server assigns the next one when omitted)", parsePositiveIntFlag).option("--json", "print the raw JSON response").option("--config <path>", "override config path").action(async (draftId, opts) => {
49698
+ const code = await runCassettesPublish({
49699
+ draftId,
49700
+ name: opts.name,
49701
+ version: opts.version,
49702
+ json: opts.json,
49703
+ configPath: opts.config
49704
+ });
49705
+ if (code !== 0) process.exit(code);
49706
+ });
49607
49707
  var artifacts = program2.command("artifacts").description("Operate on artifacts.");
49608
49708
  artifacts.command("open <artifact_id>").description("Open an artifact URL in the system browser.").option("--config <path>", "override config path").action(async (artifactId, opts) => {
49609
49709
  await runArtifactsOpen({ artifactId, configPath: opts.config });
package/dist/index.js CHANGED
@@ -29412,12 +29412,6 @@ function registerCassetteAuthoringVerbs(server, gateway) {
29412
29412
  publishSchema,
29413
29413
  async (input) => renderGatewayResult("cassette_publish", await gateway.call("/gateway/cassette/publish", input))
29414
29414
  );
29415
- server.tool(
29416
- "cassette_request_publish",
29417
- "Request tenant_admin approval to publish a cassette draft when the caller lacks cassette.publish.",
29418
- requestPublishSchema,
29419
- async (input) => renderGatewayResult("cassette_request_publish", await gateway.call("/gateway/cassette/request-publish", input))
29420
- );
29421
29415
  server.tool(
29422
29416
  "cassette_get",
29423
29417
  "Get a cassette draft or published cassette as YAML plus describe payload.",
@@ -29470,7 +29464,7 @@ ${JSON.stringify(record2, null, 2)}`;
29470
29464
  function isRecord(value) {
29471
29465
  return value !== null && typeof value === "object" && !Array.isArray(value);
29472
29466
  }
29473
- var recordSchema, draftSchema, refineSchema, dryRunSchema, publishSchema, requestPublishSchema, getSchema, listDraftsSchema, listCapabilitiesSchema;
29467
+ var recordSchema, draftSchema, refineSchema, dryRunSchema, publishSchema, getSchema, listDraftsSchema, listCapabilitiesSchema;
29474
29468
  var init_cassette_authoring = __esm({
29475
29469
  "../../lib/gary-mcp/verbs/cassette-authoring.ts"() {
29476
29470
  "use strict";
@@ -29498,10 +29492,6 @@ var init_cassette_authoring = __esm({
29498
29492
  name: external_exports2.string().min(1),
29499
29493
  version: external_exports2.number().int().min(1).optional()
29500
29494
  };
29501
- requestPublishSchema = {
29502
- draft_id: external_exports2.string().min(1),
29503
- reason: external_exports2.string().min(1).optional()
29504
- };
29505
29495
  getSchema = {
29506
29496
  id: external_exports2.string().min(1).optional(),
29507
29497
  draft_id: external_exports2.string().min(1).optional()
@@ -34584,7 +34574,7 @@ init_esm_shims();
34584
34574
  init_esm_shims();
34585
34575
 
34586
34576
  // package.json
34587
- var version = "0.1.2";
34577
+ var version = "0.2.0";
34588
34578
 
34589
34579
  // src/lib/version.ts
34590
34580
  var BOOMBOX_VERSION = version;
@@ -42894,12 +42884,20 @@ function relativeToCwd(absPath) {
42894
42884
 
42895
42885
  // src/server/mcp-stdio.ts
42896
42886
  init_esm_shims();
42887
+ var DEFAULT_MCP_PROFILE = "advanced";
42888
+ function resolveMcpProfileSelection(flag) {
42889
+ if (flag === void 0) return void 0;
42890
+ return flag === "default" ? DEFAULT_MCP_PROFILE : flag;
42891
+ }
42897
42892
  async function startStdioMcp(config2) {
42898
42893
  process.env.GARY_MCP_GATEWAY_URL = resolveGateway(config2);
42899
42894
  process.env.GARY_MCP_API_KEY = config2.konstant.api_key;
42900
42895
  if (!process.env.KONSTANT_API_KEY) {
42901
42896
  process.env.KONSTANT_API_KEY = config2.konstant.api_key;
42902
42897
  }
42898
+ if (!(process.env.GARY_MCP_PROFILE ?? "").trim()) {
42899
+ process.env.GARY_MCP_PROFILE = DEFAULT_MCP_PROFILE;
42900
+ }
42903
42901
  const mod = await Promise.resolve().then(() => (init_gary_mcp(), gary_mcp_exports));
42904
42902
  await mod.startGaryMcpServer();
42905
42903
  }
@@ -43353,8 +43351,10 @@ async function runServe(options = {}) {
43353
43351
  log("Note: mcp_enabled=false in config; enabling for this session.");
43354
43352
  }
43355
43353
  if (enableMcp) {
43356
- const requestedProfile = options.profile === "default" ? "andrew" : options.profile;
43357
- process.env.GARY_MCP_PROFILE = requestedProfile ?? process.env.GARY_MCP_PROFILE ?? "andrew";
43354
+ const selectedProfile = resolveMcpProfileSelection(options.profile);
43355
+ if (selectedProfile !== void 0) {
43356
+ process.env.GARY_MCP_PROFILE = selectedProfile;
43357
+ }
43358
43358
  process.env.GARY_MCP_TENANT_ID = config2.konstant.tenant_id;
43359
43359
  }
43360
43360
  const handle = await serve2({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@konstantdotcloud/boombox",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Local Boombox runtime for Konstant cassettes — CLI, stdio MCP server, and local Hono proxy.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",