@stackbone/cli 0.1.0-alpha.1 → 0.1.0-alpha.2

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/CHANGELOG.md CHANGED
@@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.0-alpha.2] - 2026-05-12
11
+
12
+ ### Added
13
+
14
+ - Per-stage spinners during `stackbone dev` (docker, platform/creator/rag
15
+ migrations, agent, emulator, tunnel) and a redesigned rounded-box Studio
16
+ banner that surfaces the actionable deeplink first. New global flag
17
+ `--verbose` (also `STACKBONE_VERBOSE=1`) restores the raw firehose of
18
+ docker compose stdout and pino logs for debugging.
19
+
20
+ ### Changed
21
+
22
+ - `stackbone dev` Studio banner now prints
23
+ `app.stackbone.ai/app?stackbone-dev=<tunnel>` instead of the retired
24
+ `/studio?baseUrl=…` deeplink. The new URL is consumed in one shot by the
25
+ `stackboneDevBootstrapGuard` mounted on `/app`, which flips Studio to
26
+ local mode against the tunnel and opens the panel on Runs.
27
+
28
+ ### Fixed
29
+
30
+ - `stackbone dev` boot banner no longer emits a leading newline or
31
+ duplicate `│` rail rows around the inlined Studio banner when running
32
+ under clack's left rail in human mode.
33
+
10
34
  ## [0.1.0-alpha.1] - 2026-05-12
11
35
 
12
36
  ### Changed
package/main.js CHANGED
@@ -1269,7 +1269,9 @@ var createHttpAdapter = (opts) => {
1269
1269
  //#endregion
1270
1270
  //#region src/infra/logger.ts
1271
1271
  var createLogger = (opts) => {
1272
- const level = opts.level ?? process.env["STACKBONE_LOG_LEVEL"] ?? "info";
1272
+ const envLevel = process.env["STACKBONE_LOG_LEVEL"];
1273
+ const defaultLevel = opts.json || opts.verbose ? "info" : "warn";
1274
+ const level = opts.level ?? envLevel ?? defaultLevel;
1273
1275
  if (opts.json) return pino({ level }, pino.destination(2));
1274
1276
  const stream = pretty({
1275
1277
  destination: 2,
@@ -1360,7 +1362,8 @@ var parseFlags = (argv, env) => ({
1360
1362
  json: detectJsonMode(argv, env),
1361
1363
  yes: flagPresent(argv, "-y") || flagPresent(argv, "--yes"),
1362
1364
  autoLogin: env["STACKBONE_AUTO_LOGIN"] !== "0" && !flagPresent(argv, "--no-auto-login"),
1363
- apiUrlOverride: flagValue(argv, "--api-url") ?? env["STACKBONE_API_URL"] ?? null
1365
+ apiUrlOverride: flagValue(argv, "--api-url") ?? env["STACKBONE_API_URL"] ?? null,
1366
+ verbose: flagPresent(argv, "--verbose") || env["STACKBONE_VERBOSE"] === "1"
1364
1367
  });
1365
1368
  /**
1366
1369
  * Builds the single `CliContext` value passed to every command.
@@ -1378,7 +1381,10 @@ var parseFlags = (argv, env) => ({
1378
1381
  */
1379
1382
  var createCliContext = async (opts) => {
1380
1383
  const flags = parseFlags(opts.argv, opts.env);
1381
- const logger = createLogger({ json: flags.json });
1384
+ const logger = createLogger({
1385
+ json: flags.json,
1386
+ verbose: flags.verbose
1387
+ });
1382
1388
  const projectConfig = createProjectConfigAdapter(opts.cwd);
1383
1389
  const globalConfig = createGlobalConfigAdapter();
1384
1390
  const tokenStore = createTokenStore();
@@ -1416,7 +1422,7 @@ var isVersionFlag = (rawArgs) => rawArgs.length === 1 && VERSION_FLAGS.has(rawAr
1416
1422
  var formatVersionOutput = (input) => `stackbone-cli ${input.cliVersion}\ncontract ${input.contractVersion}`;
1417
1423
  //#endregion
1418
1424
  //#region src/dev/contract.ts
1419
- var STACKBONE_CLI_BUILD_VERSION = "0.1.0-alpha.1";
1425
+ var STACKBONE_CLI_BUILD_VERSION = "0.1.0-alpha.2";
1420
1426
  /**
1421
1427
  * Capabilities the local emulator advertises on `GET /api/contract`. Typed as
1422
1428
  * `readonly Capability[]` so the compiler refuses any string outside the
@@ -2741,22 +2747,93 @@ var createDeployCommand = (ctx) => buildStubCommand(ctx, {
2741
2747
  });
2742
2748
  //#endregion
2743
2749
  //#region src/dev/boot-banner.ts
2744
- var STUDIO_CLOUD_BASE_URL = "https://app.stackbone.ai/studio";
2745
- var BORDER = "─".repeat(72);
2746
- var buildDeeplink = (tunnelUrl) => `${STUDIO_CLOUD_BASE_URL}?baseUrl=${encodeURIComponent(tunnelUrl)}`;
2750
+ var STUDIO_CLOUD_BASE_URL = "https://app.stackbone.ai/app";
2751
+ var ANSI = {
2752
+ reset: "\x1B[0m",
2753
+ bold: "\x1B[1m",
2754
+ dim: "\x1B[2m",
2755
+ cyan: "\x1B[36m",
2756
+ underline: "\x1B[4m"
2757
+ };
2758
+ var ANSI_RE = /\x1b\[[0-9;]*m/g;
2759
+ var visibleLength = (s) => s.replace(ANSI_RE, "").length;
2760
+ var buildDeeplink = (tunnelUrl) => `${STUDIO_CLOUD_BASE_URL}?stackbone-dev=${encodeURIComponent(tunnelUrl)}`;
2761
+ var LABEL_WIDTH = 11;
2762
+ var renderRow = (row, colors) => {
2763
+ const marker = colors && row.marker.trim() ? `${ANSI.cyan}${row.marker}${ANSI.reset}` : row.marker;
2764
+ const labelText = row.label.padEnd(LABEL_WIDTH, " ");
2765
+ return `${marker} ${colors ? `${ANSI.dim}${labelText}${ANSI.reset}` : labelText} ${colors && row.primary ? `${ANSI.bold}${ANSI.cyan}${row.value}${ANSI.reset}` : row.value}`;
2766
+ };
2767
+ var TITLE = "stackbone studio";
2747
2768
  var formatBootBanner = (input) => {
2748
- const lines = [];
2749
- lines.push(BORDER);
2750
- lines.push(" Stackbone Studio");
2751
- lines.push("");
2769
+ const colors = input.colors ?? false;
2770
+ const rows = [];
2752
2771
  if (input.tunnelUrl) {
2753
- lines.push(` Tunnel: ${input.tunnelUrl}`);
2754
- lines.push(` Studio: ${buildDeeplink(input.tunnelUrl)}`);
2755
- lines.push(` Local: ${input.localUrl} (sin tunnel)`);
2756
- } else lines.push(` Local: ${input.localUrl}`);
2757
- lines.push(` Contract version ${input.contractVersion} (capabilities: ${input.capabilityCount})`);
2758
- lines.push("");
2759
- lines.push(BORDER);
2772
+ rows.push({
2773
+ marker: "▸",
2774
+ label: "Open Studio",
2775
+ value: buildDeeplink(input.tunnelUrl),
2776
+ primary: true
2777
+ });
2778
+ rows.push({
2779
+ marker: " ",
2780
+ label: "",
2781
+ value: ""
2782
+ });
2783
+ rows.push({
2784
+ marker: " ",
2785
+ label: "Tunnel",
2786
+ value: input.tunnelUrl
2787
+ });
2788
+ rows.push({
2789
+ marker: " ",
2790
+ label: "Local",
2791
+ value: input.localUrl
2792
+ });
2793
+ } else rows.push({
2794
+ marker: "▸",
2795
+ label: "Local",
2796
+ value: input.localUrl,
2797
+ primary: true
2798
+ });
2799
+ rows.push({
2800
+ marker: " ",
2801
+ label: "",
2802
+ value: ""
2803
+ });
2804
+ rows.push({
2805
+ marker: " ",
2806
+ label: "Agent",
2807
+ value: input.agentName
2808
+ });
2809
+ rows.push({
2810
+ marker: " ",
2811
+ label: "Protocol",
2812
+ value: `v${input.contractVersion} · ${input.capabilityCount} capabilities`
2813
+ });
2814
+ const renderedRows = rows.map((row) => {
2815
+ if (row.label === "" && row.value === "") return "";
2816
+ return renderRow(row, colors);
2817
+ });
2818
+ const padding = 2;
2819
+ const longestRow = renderedRows.reduce((max, line) => Math.max(max, visibleLength(line)), 0);
2820
+ const innerWidth = Math.max(longestRow, 22);
2821
+ const totalInner = innerWidth + padding * 2;
2822
+ const titleSegment = ` ${TITLE} `;
2823
+ const titleSegmentColored = colors ? ` ${ANSI.bold}${TITLE}${ANSI.reset} ` : titleSegment;
2824
+ const dashesAfter = totalInner - 1 - titleSegment.length;
2825
+ const top = `╭─${titleSegmentColored}${"─".repeat(Math.max(dashesAfter, 1))}╮`;
2826
+ const bottom = `╰${"─".repeat(totalInner)}╯`;
2827
+ const padRow = (line) => {
2828
+ const trailing = innerWidth - visibleLength(line);
2829
+ return `│ ${line}${" ".repeat(Math.max(trailing, 0))} │`;
2830
+ };
2831
+ const lines = [];
2832
+ lines.push(top);
2833
+ lines.push(padRow(""));
2834
+ for (const line of renderedRows) lines.push(padRow(line));
2835
+ lines.push(padRow(""));
2836
+ lines.push(bottom);
2760
2837
  return lines.join("\n");
2761
2838
  };
2762
2839
  //#endregion
@@ -3477,21 +3554,34 @@ volumes:
3477
3554
  //#endregion
3478
3555
  //#region src/dev/compose.ts
3479
3556
  var DOCKER_BIN = process.env["STACKBONE_DOCKER_BIN"] ?? "docker";
3480
- var runDocker = async (args, cwd, signal) => {
3557
+ var runDocker = async (args, cwd, opts = {}) => {
3481
3558
  await new Promise((resolve, reject) => {
3559
+ const captured = [];
3482
3560
  const child = spawn(DOCKER_BIN, args, {
3483
3561
  cwd,
3484
- stdio: [
3562
+ stdio: opts.verbose ? [
3485
3563
  "ignore",
3486
3564
  "inherit",
3487
3565
  "inherit"
3566
+ ] : [
3567
+ "ignore",
3568
+ "pipe",
3569
+ "pipe"
3488
3570
  ],
3489
- signal
3571
+ signal: opts.signal
3490
3572
  });
3573
+ if (!opts.verbose) {
3574
+ child.stdout?.on("data", (chunk) => captured.push(chunk));
3575
+ child.stderr?.on("data", (chunk) => captured.push(chunk));
3576
+ }
3491
3577
  child.once("error", reject);
3492
3578
  child.once("close", (code) => {
3493
- if (code === 0) resolve();
3494
- else reject(new StackboneCliError({
3579
+ if (code === 0) {
3580
+ resolve();
3581
+ return;
3582
+ }
3583
+ if (!opts.verbose && captured.length > 0) process.stderr.write(Buffer.concat(captured));
3584
+ reject(new StackboneCliError({
3495
3585
  code: "generic",
3496
3586
  message: `\`docker ${args.join(" ")}\` exited with code ${code}`,
3497
3587
  suggestion: "Check Docker Desktop is running and that the embedded compose file in `.stackbone/dev/` is valid"
@@ -3519,12 +3609,13 @@ var writeComposeFiles = async (runtime) => {
3519
3609
  await writeFile(join(runtime.workdir, "Dockerfile.postgres"), POSTGRES_DOCKERFILE);
3520
3610
  };
3521
3611
  var composeUp = async (runtime) => {
3612
+ const verbose = runtime.verbose ?? false;
3522
3613
  await runDocker([
3523
3614
  "compose",
3524
3615
  "up",
3525
3616
  "-d",
3526
3617
  "--wait"
3527
- ], runtime.workdir);
3618
+ ], runtime.workdir, { verbose });
3528
3619
  await runDocker([
3529
3620
  "compose",
3530
3621
  "--profile",
@@ -3532,11 +3623,11 @@ var composeUp = async (runtime) => {
3532
3623
  "run",
3533
3624
  "--rm",
3534
3625
  "minio-bucket-init"
3535
- ], runtime.workdir);
3626
+ ], runtime.workdir, { verbose });
3536
3627
  };
3537
3628
  var composeDown = async (runtime) => {
3538
3629
  try {
3539
- await runDocker(["compose", "down"], runtime.workdir);
3630
+ await runDocker(["compose", "down"], runtime.workdir, { verbose: runtime.verbose ?? false });
3540
3631
  } catch {}
3541
3632
  };
3542
3633
  var buildComposeRuntime = (projectRoot, options) => {
@@ -7080,6 +7171,7 @@ var autoMigrationName = () => {
7080
7171
  return `auto_${(/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/T/, "_").slice(0, 15)}`;
7081
7172
  };
7082
7173
  var startDevSession = async (input) => {
7174
+ const onStage = input.onStage ?? (() => void 0);
7083
7175
  await ensureDockerAvailable();
7084
7176
  const bus = createEventBus();
7085
7177
  const agentPort = await pickFreePort();
@@ -7099,13 +7191,40 @@ var startDevSession = async (input) => {
7099
7191
  minioRootPassword: "stackbone-secret",
7100
7192
  minioDefaultBucket: "stackbone-dev"
7101
7193
  });
7194
+ if (input.verbose) compose.verbose = true;
7102
7195
  await writeComposeFiles(compose);
7103
- await composeUp(compose);
7196
+ onStage({
7197
+ stage: "docker",
7198
+ status: "start"
7199
+ });
7200
+ try {
7201
+ await composeUp(compose);
7202
+ onStage({
7203
+ stage: "docker",
7204
+ status: "success",
7205
+ detail: "postgres + minio ready"
7206
+ });
7207
+ } catch (err) {
7208
+ onStage({
7209
+ stage: "docker",
7210
+ status: "fail",
7211
+ detail: err instanceof Error ? err.message : String(err)
7212
+ });
7213
+ throw err;
7214
+ }
7104
7215
  const migrationsDir = resolve(dirname(fileURLToPath(import.meta.url)), "migrations");
7216
+ onStage({
7217
+ stage: "platform-migrations",
7218
+ status: "start"
7219
+ });
7220
+ let platformApplied = 0;
7221
+ let platformSkipped = 0;
7105
7222
  try {
7106
7223
  await applyMigrations(injection.services.postgresUrl, {
7107
7224
  migrationsDir,
7108
7225
  onMigration: (event) => {
7226
+ if (event.action === "applied") platformApplied += 1;
7227
+ else platformSkipped += 1;
7109
7228
  input.ctx.logger.info({
7110
7229
  migration: event.version,
7111
7230
  action: event.action,
@@ -7113,7 +7232,17 @@ var startDevSession = async (input) => {
7113
7232
  }, `stackbone_platform schema: ${event.action} ${String(event.version).padStart(4, "0")} in ${event.durationMs}ms`);
7114
7233
  }
7115
7234
  });
7235
+ onStage({
7236
+ stage: "platform-migrations",
7237
+ status: "success",
7238
+ detail: platformApplied > 0 ? `${platformApplied} applied, ${platformSkipped} skipped` : `${platformSkipped} already applied`
7239
+ });
7116
7240
  } catch (err) {
7241
+ onStage({
7242
+ stage: "platform-migrations",
7243
+ status: "fail",
7244
+ detail: err instanceof Error ? err.message : String(err)
7245
+ });
7117
7246
  await composeDown(compose);
7118
7247
  throw new StackboneCliError({
7119
7248
  code: "generic",
@@ -7122,39 +7251,67 @@ var startDevSession = async (input) => {
7122
7251
  cause: err
7123
7252
  });
7124
7253
  }
7254
+ onStage({
7255
+ stage: "creator-migrations",
7256
+ status: "start"
7257
+ });
7125
7258
  try {
7126
7259
  await runPreBootMigrate({
7127
7260
  agentRoot: input.projectRoot,
7128
7261
  connectionString: injection.services.postgresUrl,
7129
7262
  logger: input.ctx.logger
7130
7263
  });
7264
+ onStage({
7265
+ stage: "creator-migrations",
7266
+ status: "success"
7267
+ });
7131
7268
  } catch (err) {
7269
+ onStage({
7270
+ stage: "creator-migrations",
7271
+ status: "fail",
7272
+ detail: err instanceof Error ? err.message : String(err)
7273
+ });
7132
7274
  await composeDown(compose);
7133
7275
  throw err;
7134
7276
  }
7135
7277
  const preBootRagManifest = readWatcherManifest(input.projectRoot);
7136
- if (preBootRagManifest !== null) try {
7137
- await runPreBootRag({
7138
- agentRoot: input.projectRoot,
7139
- migrationsDir: preBootRagManifest.migrations,
7140
- autoMigrate: preBootRagManifest.ragAutoMigrate,
7141
- logger: input.ctx.logger,
7142
- addRag,
7143
- runMigrateUp: () => migrateUp({
7144
- connectionString: injection.services.postgresUrl,
7145
- migrationsDir: preBootRagManifest.migrations,
7146
- onMigration: (event) => {
7147
- input.ctx.logger.info({
7148
- migration: event.tag,
7149
- action: event.action,
7150
- durationMs: event.durationMs
7151
- }, `RAG schema: ${event.action} ${event.tag}`);
7152
- }
7153
- })
7278
+ if (preBootRagManifest !== null) {
7279
+ onStage({
7280
+ stage: "rag-migrations",
7281
+ status: "start"
7154
7282
  });
7155
- } catch (err) {
7156
- await composeDown(compose);
7157
- throw err;
7283
+ try {
7284
+ await runPreBootRag({
7285
+ agentRoot: input.projectRoot,
7286
+ migrationsDir: preBootRagManifest.migrations,
7287
+ autoMigrate: preBootRagManifest.ragAutoMigrate,
7288
+ logger: input.ctx.logger,
7289
+ addRag,
7290
+ runMigrateUp: () => migrateUp({
7291
+ connectionString: injection.services.postgresUrl,
7292
+ migrationsDir: preBootRagManifest.migrations,
7293
+ onMigration: (event) => {
7294
+ input.ctx.logger.info({
7295
+ migration: event.tag,
7296
+ action: event.action,
7297
+ durationMs: event.durationMs
7298
+ }, `RAG schema: ${event.action} ${event.tag}`);
7299
+ }
7300
+ })
7301
+ });
7302
+ onStage({
7303
+ stage: "rag-migrations",
7304
+ status: "success"
7305
+ });
7306
+ } catch (err) {
7307
+ onStage({
7308
+ stage: "rag-migrations",
7309
+ status: "fail",
7310
+ detail: err instanceof Error ? err.message : String(err)
7311
+ });
7312
+ await composeDown(compose);
7313
+ throw err;
7314
+ }
7158
7315
  }
7159
7316
  const invocations = createInvocationsStore({ connectionString: injection.services.postgresUrl });
7160
7317
  const approvals = createApprovalsStore({
@@ -7199,6 +7356,10 @@ var startDevSession = async (input) => {
7199
7356
  });
7200
7357
  };
7201
7358
  try {
7359
+ onStage({
7360
+ stage: "agent",
7361
+ status: "start"
7362
+ });
7202
7363
  agent = startAgentProcess({
7203
7364
  cwd: input.projectRoot,
7204
7365
  env: input.ctx.env,
@@ -7207,6 +7368,11 @@ var startDevSession = async (input) => {
7207
7368
  injection,
7208
7369
  agentConfig: agentConfigEnv
7209
7370
  });
7371
+ onStage({
7372
+ stage: "agent",
7373
+ status: "success",
7374
+ detail: `http://127.0.0.1:${agentPort}`
7375
+ });
7210
7376
  configWatcher = await startAgentConfigWatcher({
7211
7377
  connectionString: injection.services.postgresUrl,
7212
7378
  logger: input.ctx.logger,
@@ -7261,25 +7427,63 @@ var startDevSession = async (input) => {
7261
7427
  envValue: input.ctx.env["STACKBONE_CORS_ALLOW_ORIGINS"],
7262
7428
  fileValue: fileConfig?.studio?.corsOrigins
7263
7429
  });
7264
- emulator = await startEmulatorServer({
7265
- port: input.emulatorPort,
7266
- bus,
7267
- invocations,
7268
- approvals,
7269
- injection,
7270
- agent: input.agent,
7271
- cors: corsAllowlist,
7272
- listen: input.listen
7430
+ onStage({
7431
+ stage: "emulator",
7432
+ status: "start"
7273
7433
  });
7434
+ try {
7435
+ emulator = await startEmulatorServer({
7436
+ port: input.emulatorPort,
7437
+ bus,
7438
+ invocations,
7439
+ approvals,
7440
+ injection,
7441
+ agent: input.agent,
7442
+ cors: corsAllowlist,
7443
+ listen: input.listen
7444
+ });
7445
+ onStage({
7446
+ stage: "emulator",
7447
+ status: "success",
7448
+ detail: emulator.url
7449
+ });
7450
+ } catch (err) {
7451
+ onStage({
7452
+ stage: "emulator",
7453
+ status: "fail",
7454
+ detail: err instanceof Error ? err.message : String(err)
7455
+ });
7456
+ throw err;
7457
+ }
7274
7458
  if (input.listen) input.ctx.logger.warn({
7275
7459
  boundTo: "0.0.0.0",
7276
7460
  port: input.emulatorPort
7277
7461
  }, `WARNING: emulator bound to 0.0.0.0:${input.emulatorPort} — reachable from your LAN`);
7278
- if (input.tunnel) tunnel = await openDevTunnel({
7279
- targetUrl: emulator.url,
7280
- ctx: input.ctx,
7281
- projectRoot: input.projectRoot
7282
- });
7462
+ if (input.tunnel) {
7463
+ onStage({
7464
+ stage: "tunnel",
7465
+ status: "start"
7466
+ });
7467
+ try {
7468
+ tunnel = await openDevTunnel({
7469
+ targetUrl: emulator.url,
7470
+ ctx: input.ctx,
7471
+ projectRoot: input.projectRoot
7472
+ });
7473
+ onStage({
7474
+ stage: "tunnel",
7475
+ status: "success",
7476
+ detail: tunnel.publicUrl
7477
+ });
7478
+ } catch (err) {
7479
+ onStage({
7480
+ stage: "tunnel",
7481
+ status: "fail",
7482
+ detail: err instanceof Error ? err.message : String(err)
7483
+ });
7484
+ throw err;
7485
+ }
7486
+ }
7283
7487
  return {
7284
7488
  emulatorUrl: emulator.url,
7285
7489
  agentUrl: `http://127.0.0.1:${agentPort}`,
@@ -7317,6 +7521,60 @@ var printContract = (deps = {}) => {
7317
7521
  };
7318
7522
  //#endregion
7319
7523
  //#region src/commands/dev/dev.command.ts
7524
+ var STAGE_LABELS = {
7525
+ docker: {
7526
+ start: "Starting docker services",
7527
+ done: "Docker services ready"
7528
+ },
7529
+ "platform-migrations": {
7530
+ start: "Applying platform migrations",
7531
+ done: "Platform migrations applied"
7532
+ },
7533
+ "creator-migrations": {
7534
+ start: "Applying agent migrations",
7535
+ done: "Agent migrations applied"
7536
+ },
7537
+ "rag-migrations": {
7538
+ start: "Preparing RAG schema",
7539
+ done: "RAG schema ready"
7540
+ },
7541
+ agent: {
7542
+ start: "Starting agent process",
7543
+ done: "Agent process ready"
7544
+ },
7545
+ emulator: {
7546
+ start: "Starting emulator",
7547
+ done: "Emulator listening"
7548
+ },
7549
+ tunnel: {
7550
+ start: "Opening tunnel",
7551
+ done: "Tunnel ready"
7552
+ }
7553
+ };
7554
+ /**
7555
+ * Wires `onStage` events from the orchestrator to a single rolling clack
7556
+ * spinner. Only one stage is in flight at a time (the orchestrator is
7557
+ * sequential), so we keep one handle and recycle it across stages.
7558
+ */
7559
+ var createStageRenderer = () => {
7560
+ let active = null;
7561
+ let activeStage = null;
7562
+ return (event) => {
7563
+ const label = STAGE_LABELS[event.stage];
7564
+ if (event.status === "start") {
7565
+ active = spinner();
7566
+ activeStage = event.stage;
7567
+ active.start(label.start);
7568
+ return;
7569
+ }
7570
+ if (active === null || activeStage !== event.stage) return;
7571
+ const detail = event.detail ? ` (${event.detail})` : "";
7572
+ if (event.status === "success") active.stop(`${label.done}${detail}`);
7573
+ else active.error(`${label.start} failed${detail}`);
7574
+ active = null;
7575
+ activeStage = null;
7576
+ };
7577
+ };
7320
7578
  var buildHandler$2 = (ctx, flags) => protectedCommand(async (innerCtx) => {
7321
7579
  const config = await innerCtx.projectConfig.read();
7322
7580
  if (!config) throw new StackboneCliError({
@@ -7332,6 +7590,7 @@ var buildHandler$2 = (ctx, flags) => protectedCommand(async (innerCtx) => {
7332
7590
  message: "The agent linked to this directory no longer exists in the workspace",
7333
7591
  suggestion: "Run `stackbone link --force --agent <slug>` to re-link to a different agent"
7334
7592
  });
7593
+ const renderStage = innerCtx.flags.json ? null : createStageRenderer();
7335
7594
  const session = await startDevSession({
7336
7595
  ctx: innerCtx,
7337
7596
  projectRoot,
@@ -7341,9 +7600,11 @@ var buildHandler$2 = (ctx, flags) => protectedCommand(async (innerCtx) => {
7341
7600
  },
7342
7601
  emulatorPort: flags.port,
7343
7602
  tunnel: flags.tunnel,
7344
- listen: flags.listen
7603
+ listen: flags.listen,
7604
+ verbose: flags.verbose,
7605
+ ...renderStage ? { onStage: renderStage } : {}
7345
7606
  });
7346
- emit(innerCtx.flags, () => "", {
7607
+ if (innerCtx.flags.json) emit(innerCtx.flags, () => "", {
7347
7608
  emulator_url: session.emulatorUrl,
7348
7609
  agent_url: session.agentUrl,
7349
7610
  public_url: session.publicUrl,
@@ -7354,15 +7615,19 @@ var buildHandler$2 = (ctx, flags) => protectedCommand(async (innerCtx) => {
7354
7615
  s3_bucket: session.injection.services.s3Bucket
7355
7616
  }
7356
7617
  });
7357
- if (!innerCtx.flags.json) {
7358
- log.step(`Agent: ${session.agentUrl}`);
7359
- console.log(`\n${formatBootBanner({
7618
+ else {
7619
+ const inlineLines = formatBootBanner({
7360
7620
  tunnelUrl: session.publicUrl,
7361
7621
  localUrl: session.emulatorUrl,
7362
7622
  contractVersion: 10,
7363
- capabilityCount: EMULATOR_CAPABILITIES.length
7364
- })}\n`);
7365
- log.step("Press Ctrl-C to stop.");
7623
+ capabilityCount: EMULATOR_CAPABILITIES.length,
7624
+ agentName: agent.name,
7625
+ colors: process.stdout.isTTY === true
7626
+ }).split("\n").slice(1, -1).map((line) => line.replace(/^│ {2}/, "").replace(/ {2}│$/, "").trimEnd());
7627
+ while (inlineLines.length > 0 && inlineLines[0] === "") inlineLines.shift();
7628
+ while (inlineLines.length > 0 && inlineLines[inlineLines.length - 1] === "") inlineLines.pop();
7629
+ log.message(inlineLines.join("\n"));
7630
+ log.step("Press Ctrl-C to stop");
7366
7631
  }
7367
7632
  const sigintHandler = () => {
7368
7633
  session.stop();
@@ -7398,6 +7663,11 @@ var createDevCommand = (ctx) => defineCommand({
7398
7663
  type: "boolean",
7399
7664
  description: "Print the JSON contract this CLI advertises (the same payload the emulator serves at /api/contract) and exit without booting the dev session.",
7400
7665
  default: false
7666
+ },
7667
+ verbose: {
7668
+ type: "boolean",
7669
+ description: "Stream every log line and docker compose output. Default UI uses per-stage spinners; --verbose switches back to the raw firehose.",
7670
+ default: false
7401
7671
  }
7402
7672
  },
7403
7673
  run: ({ args }) => safeRun(ctx.flags, async (a) => {
@@ -7414,7 +7684,8 @@ var createDevCommand = (ctx) => defineCommand({
7414
7684
  await buildHandler$2(ctx, {
7415
7685
  port,
7416
7686
  tunnel: Boolean(a.tunnel),
7417
- listen: Boolean(a.listen)
7687
+ listen: Boolean(a.listen),
7688
+ verbose: Boolean(a.verbose)
7418
7689
  });
7419
7690
  })(args)
7420
7691
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackbone/cli",
3
- "version": "0.1.0-alpha.1",
3
+ "version": "0.1.0-alpha.2",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "bin": {
Binary file
Binary file