@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 +24 -0
- package/main.js +342 -71
- package/package.json +1 -1
- package/stackbone-cli-0.1.0-alpha.2.tgz +0 -0
- package/stackbone-cli-0.1.0-alpha.1.tgz +0 -0
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
|
|
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({
|
|
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.
|
|
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/
|
|
2745
|
-
var
|
|
2746
|
-
|
|
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
|
|
2749
|
-
|
|
2750
|
-
lines.push(" Stackbone Studio");
|
|
2751
|
-
lines.push("");
|
|
2769
|
+
const colors = input.colors ?? false;
|
|
2770
|
+
const rows = [];
|
|
2752
2771
|
if (input.tunnelUrl) {
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
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,
|
|
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)
|
|
3494
|
-
|
|
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
|
-
|
|
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)
|
|
7137
|
-
|
|
7138
|
-
|
|
7139
|
-
|
|
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
|
-
|
|
7156
|
-
|
|
7157
|
-
|
|
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
|
-
|
|
7265
|
-
|
|
7266
|
-
|
|
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)
|
|
7279
|
-
|
|
7280
|
-
|
|
7281
|
-
|
|
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
|
-
|
|
7358
|
-
|
|
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
|
-
|
|
7365
|
-
|
|
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
|
Binary file
|
|
Binary file
|