@stacksjs/buddy 0.70.162 → 0.70.163

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.
@@ -0,0 +1,13 @@
1
+ import type { CLI, CliOptions } from '@stacksjs/types';
2
+ export declare function generateAiContextOutput(options?: AiContextCommandOptions, projectRoot?: string): AiContextCommandResult;
3
+ export declare function aiContext(buddy: CLI): void;
4
+ export declare interface AiContextCommandOptions extends CliOptions {
5
+ json?: boolean
6
+ output?: string
7
+ maxChars?: string | number
8
+ model?: string
9
+ }
10
+ export declare interface AiContextCommandResult {
11
+ output: string
12
+ outputPath: string | null
13
+ }
@@ -0,0 +1,29 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+ import process from "node:process";
4
+ import { buildProjectContext } from "@stacksjs/ai";
5
+ import { onUnknownSubcommand } from "@stacksjs/cli";
6
+ export function generateAiContextOutput(options = {}, projectRoot = process.cwd()) {
7
+ const maxChars = options.maxChars === void 0 ? void 0 : Number(options.maxChars), result = buildProjectContext(projectRoot, { maxChars, model: options.model }), output = options.json ? `${JSON.stringify(result, null, 2)}
8
+ ` : `${result.text}
9
+
10
+ Context metrics:
11
+ ${JSON.stringify(result.metrics, null, 2)}
12
+ `;
13
+ if (!options.output)
14
+ return { output, outputPath: null };
15
+ const outputPath = resolve(projectRoot, String(options.output));
16
+ mkdirSync(dirname(outputPath), { recursive: !0 });
17
+ writeFileSync(outputPath, output);
18
+ return { output, outputPath };
19
+ }
20
+ export function aiContext(buddy) {
21
+ buddy.command("ai:context", "Generate compact deterministic project context for coding models").option("-J, --json", "Emit the versioned machine-readable context contract", { default: !1 }).option("-o, --output [path]", "Write output to a file instead of stdout").option("--max-chars [characters]", "Maximum characters in the prompt context payload", { default: 4000 }).option("--model [model]", "Model family used for the heuristic token estimate", { default: "gpt-4o" }).example("buddy ai:context").example("buddy ai:context --json").example("buddy ai:context --json --output .stacks/ai-context.json").action((options) => {
22
+ const generated = generateAiContextOutput(options);
23
+ if (generated.outputPath)
24
+ console.log(`Wrote AI project context to ${generated.outputPath}`);
25
+ else
26
+ process.stdout.write(generated.output);
27
+ });
28
+ onUnknownSubcommand(buddy, "ai:context");
29
+ }
@@ -395,7 +395,7 @@ async function pollUntil(opts) {
395
395
  let lastHeartbeat = 0;
396
396
  for (;; )
397
397
  try {
398
- opts.check();
398
+ await opts.check();
399
399
  return;
400
400
  } catch {
401
401
  const elapsedSecs = Math.floor((Date.now() - started) / 1000);
@@ -408,33 +408,18 @@ async function pollUntil(opts) {
408
408
  await new Promise((r) => setTimeout(r, opts.intervalMs ?? 5000));
409
409
  }
410
410
  }
411
- async function waitForRemoteReady(ip, verbose) {
412
- const { execSync } = await import("node:child_process"), sshArgs = [
413
- "-o",
414
- "StrictHostKeyChecking=no",
415
- "-o",
416
- "UserKnownHostsFile=/dev/null",
417
- "-o",
418
- "BatchMode=yes",
419
- "-o",
420
- "ConnectTimeout=10",
421
- `root@${ip}`
422
- ], run = (remote) => execSync(`ssh ${sshArgs.map((a) => `'${a}'`).join(" ")} '${remote.replace(/'/g, "'\\''")}'`, {
423
- encoding: "utf8",
424
- stdio: ["ignore", "pipe", verbose ? "inherit" : "pipe"]
425
- }), sshWaitSecs = readWaitSecs("TS_CLOUD_SSH_WAIT_SECS", 480);
411
+ async function waitForRemoteReady(ip) {
412
+ const { sshExecOrThrow } = await import("@stacksjs/ts-cloud"), run = (remote) => sshExecOrThrow(ip, remote, { user: "root", connectTimeoutSec: 10 }), sshWaitSecs = readWaitSecs("TS_CLOUD_SSH_WAIT_SECS", 480);
426
413
  await pollUntil({
427
414
  label: "Waiting for SSH to come up",
428
415
  timeoutSecs: sshWaitSecs,
429
- check: () => {
430
- run("true");
431
- },
416
+ check: () => run("true"),
432
417
  timeoutMessage: (elapsed) => `SSH did not become reachable on ${ip} within ${fmtDuration(sshWaitSecs)} (waited ${elapsed}s). ` + "The box may still be booting \u2014 raise TS_CLOUD_SSH_WAIT_SECS and retry."
433
418
  });
434
419
  log.success("SSH is up");
435
420
  log.info("Waiting for cloud-init (installing bun + caddy)...");
436
421
  try {
437
- run("cloud-init status --wait || true");
422
+ await run("cloud-init status --wait || true");
438
423
  } catch (err) {
439
424
  log.debug("cloud-init status --wait returned non-zero (continuing):", err);
440
425
  }
@@ -442,9 +427,7 @@ async function waitForRemoteReady(ip, verbose) {
442
427
  await pollUntil({
443
428
  label: "Waiting for the bun runtime",
444
429
  timeoutSecs: bootWaitSecs,
445
- check: () => {
446
- run("test -x /usr/local/bin/bun");
447
- },
430
+ check: () => run("test -x /usr/local/bin/bun"),
448
431
  timeoutMessage: (elapsed) => `bun runtime did not appear at /usr/local/bin/bun within ${fmtDuration(bootWaitSecs)} (waited ${elapsed}s). ` + "cloud-init may have failed \u2014 SSH in and check /var/log/cloud-init-output.log; " + "raise TS_CLOUD_BOOT_WAIT_SECS for slow regions."
449
432
  });
450
433
  log.success("Server is ready (bun installed)");
@@ -716,7 +699,7 @@ async function runHetznerDeploy(args) {
716
699
  log.error("Provisioned server has no public IP \u2014 cannot deploy over SSH.");
717
700
  process.exit(ExitCode.FatalError);
718
701
  }
719
- await waitForRemoteReady(ip, verbose);
702
+ await waitForRemoteReady(ip);
720
703
  const { execSync } = await import("node:child_process"), { tmpdir } = await import("node:os"), sites = applyEnvironmentToSites(tsCloudConfig.sites || {}, environment, tsCloudConfig), slug = tsCloudConfig.project?.slug || "app";
721
704
  let sha;
722
705
  try {
@@ -214,7 +214,7 @@ export function migrate(buddy) {
214
214
  log.info(` \u2022 ${describeOp(op)}${op.destructive ? " [destructive]" : ""}`);
215
215
  }
216
216
  } catch (error) {
217
- log.error("Failed to preview migrations:", error);
217
+ await log.error("Failed to preview migrations:", error);
218
218
  }
219
219
  await outro("Diff complete \u2014 no changes applied.", { startTime: perf, useSeconds: !0 });
220
220
  process.exit(ExitCode.Success);
@@ -224,6 +224,7 @@ export function migrate(buddy) {
224
224
  log.debug("[migrate] confirmMigrate guard skipped \u2014 non-interactive environment.");
225
225
  else {
226
226
  const APP_ENV = process.env.APP_ENV || "local";
227
+ await log.flush();
227
228
  if (!await confirm({
228
229
  message: `Run migrations against the ${APP_ENV} database "${currentDatabaseLabel()}"?`,
229
230
  initial: !0
@@ -234,7 +235,7 @@ export function migrate(buddy) {
234
235
  }
235
236
  const lock = acquireMigrationLock();
236
237
  if (!lock.acquired) {
237
- log.error("Another migration is already running (.stacks/migrations.lock exists). Wait for it to finish, or remove the lockfile if it is stale.");
238
+ log.syncError("Another migration is already running (.stacks/migrations.lock exists). Wait for it to finish, or remove the lockfile if it is stale.");
238
239
  process.exit(ExitCode.FatalError);
239
240
  }
240
241
  if (!await confirmDestructiveMigrations({ force: options.force, fromDb: options.fromDb, applyRenames })) {
@@ -303,7 +304,7 @@ export function migrate(buddy) {
303
304
  }
304
305
  const guards = await resolveMigrationGuards(), dbLabel = currentDatabaseLabel(), APP_ENV = process.env.APP_ENV || "local";
305
306
  if (guards.migrateFresh === "disabled") {
306
- log.error(`\`buddy migrate:fresh\` is disabled by your migration safety guards (it DROPS every table).
307
+ await log.error(`\`buddy migrate:fresh\` is disabled by your migration safety guards (it DROPS every table).
307
308
  Target: ${APP_ENV} database "${dbLabel}"
308
309
  To allow it, set database.safety.migrateFresh to 'allow' in config/database.ts,
309
310
  or run once with: DB_MIGRATE_FRESH=allow ./buddy migrate:fresh`);
@@ -313,11 +314,12 @@ export function migrate(buddy) {
313
314
  if (!(guards.migrateFresh === "allow" && options.force === !0)) {
314
315
  if (isCI || !hasTTY) {
315
316
  const hint = guards.migrateFresh === "confirm" ? 'Guard is "confirm": migrate:fresh must be run interactively.' : "Re-run with --force to drop the database non-interactively.";
316
- log.error(`Refusing to drop the ${APP_ENV} database "${dbLabel}" in a non-interactive environment. ${hint}`);
317
+ await log.error(`Refusing to drop the ${APP_ENV} database "${dbLabel}" in a non-interactive environment. ${hint}`);
317
318
  await outro("migrate:fresh cancelled.", { startTime: perf, useSeconds: !0 });
318
319
  process.exit(ExitCode.FatalError);
319
320
  }
320
321
  log.warn(`This will DROP ALL TABLES in the ${APP_ENV} database "${dbLabel}" and rebuild them from scratch. All data will be lost.`);
322
+ await log.flush();
321
323
  if ((await text({ message: `Type the database name "${dbLabel}" to confirm (blank to cancel):` })).trim() !== dbLabel) {
322
324
  await outro("migrate:fresh cancelled \u2014 confirmation did not match.", { startTime: perf, useSeconds: !0 });
323
325
  process.exit(ExitCode.Success);
@@ -1,6 +1,7 @@
1
1
  const commandRegistry = {
2
2
  about: { path: "./commands/about.js", exportName: "about" },
3
3
  add: { path: "./commands/add.js", exportName: "add" },
4
+ "ai:context": { path: "./commands/ai-context.js", exportName: "aiContext" },
4
5
  auth: { path: "./commands/auth.js", exportName: "auth" },
5
6
  build: { path: "./commands/build.js", exportName: "build" },
6
7
  cd: { path: "./commands/cd.js", exportName: "cd" },
@@ -12,6 +12,7 @@ const APP_KEY_OPTIONAL_COMMANDS = [
12
12
  "clean",
13
13
  "fresh",
14
14
  "about",
15
+ "ai:context",
15
16
  "doctor",
16
17
  "list",
17
18
  "setup",
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/buddy",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.162",
5
+ "version": "0.70.163",
6
6
  "description": "Meet Buddy. The Stacks runtime.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -64,7 +64,6 @@
64
64
  "types": "dist/index.d.ts",
65
65
  "bin": {
66
66
  "stacks": "dist/cli.js",
67
- "stx": "dist/cli.js",
68
67
  "buddy": "dist/cli.js",
69
68
  "bud": "dist/cli.js"
70
69
  },
@@ -92,51 +91,51 @@
92
91
  "prepublishOnly": "bun run build"
93
92
  },
94
93
  "dependencies": {
95
- "@stacksjs/actions": "^0.70.162",
96
- "@stacksjs/ai": "^0.70.162",
97
- "@stacksjs/alias": "^0.70.162",
98
- "@stacksjs/arrays": "^0.70.162",
99
- "@stacksjs/auth": "^0.70.162",
100
- "@stacksjs/build": "^0.70.162",
101
- "@stacksjs/cache": "^0.70.162",
102
- "@stacksjs/cli": "^0.70.162",
94
+ "@stacksjs/actions": "^0.70.163",
95
+ "@stacksjs/ai": "^0.70.163",
96
+ "@stacksjs/alias": "^0.70.163",
97
+ "@stacksjs/arrays": "^0.70.163",
98
+ "@stacksjs/auth": "^0.70.163",
99
+ "@stacksjs/build": "^0.70.163",
100
+ "@stacksjs/cache": "^0.70.163",
101
+ "@stacksjs/cli": "^0.70.163",
103
102
  "@stacksjs/clapp": "^0.2.12",
104
- "@stacksjs/cloud": "^0.70.162",
105
- "@stacksjs/collections": "^0.70.162",
106
- "@stacksjs/config": "^0.70.162",
107
- "@stacksjs/database": "^0.70.162",
108
- "@stacksjs/desktop": "^0.70.162",
109
- "@stacksjs/dns": "^0.70.162",
110
- "@stacksjs/email": "^0.70.162",
111
- "@stacksjs/enums": "^0.70.162",
112
- "@stacksjs/error-handling": "^0.70.162",
113
- "@stacksjs/events": "^0.70.162",
114
- "@stacksjs/git": "^0.70.162",
103
+ "@stacksjs/cloud": "^0.70.163",
104
+ "@stacksjs/collections": "^0.70.163",
105
+ "@stacksjs/config": "^0.70.163",
106
+ "@stacksjs/database": "^0.70.163",
107
+ "@stacksjs/desktop": "^0.70.163",
108
+ "@stacksjs/dns": "^0.70.163",
109
+ "@stacksjs/email": "^0.70.163",
110
+ "@stacksjs/enums": "^0.70.163",
111
+ "@stacksjs/error-handling": "^0.70.163",
112
+ "@stacksjs/events": "^0.70.163",
113
+ "@stacksjs/git": "^0.70.163",
115
114
  "@stacksjs/gitit": "^0.2.5",
116
- "@stacksjs/health": "^0.70.162",
115
+ "@stacksjs/health": "^0.70.163",
117
116
  "@stacksjs/dnsx": "^0.2.3",
118
117
  "@stacksjs/httx": "^0.1.10",
119
- "@stacksjs/lint": "^0.70.162",
120
- "@stacksjs/logging": "^0.70.162",
121
- "@stacksjs/notifications": "^0.70.162",
122
- "@stacksjs/objects": "^0.70.162",
123
- "@stacksjs/orm": "^0.70.162",
124
- "@stacksjs/path": "^0.70.162",
125
- "@stacksjs/payments": "^0.70.162",
126
- "@stacksjs/realtime": "^0.70.162",
127
- "@stacksjs/router": "^0.70.162",
118
+ "@stacksjs/lint": "^0.70.163",
119
+ "@stacksjs/logging": "^0.70.163",
120
+ "@stacksjs/notifications": "^0.70.163",
121
+ "@stacksjs/objects": "^0.70.163",
122
+ "@stacksjs/orm": "^0.70.163",
123
+ "@stacksjs/path": "^0.70.163",
124
+ "@stacksjs/payments": "^0.70.163",
125
+ "@stacksjs/realtime": "^0.70.163",
126
+ "@stacksjs/router": "^0.70.163",
128
127
  "@stacksjs/rpx": "^0.11.31",
129
- "@stacksjs/search-engine": "^0.70.162",
130
- "@stacksjs/security": "^0.70.162",
131
- "@stacksjs/server": "^0.70.162",
132
- "@stacksjs/storage": "^0.70.162",
133
- "@stacksjs/strings": "^0.70.162",
134
- "@stacksjs/testing": "^0.70.162",
135
- "@stacksjs/tunnel": "^0.70.162",
136
- "@stacksjs/types": "^0.70.162",
137
- "@stacksjs/ui": "^0.70.162",
138
- "@stacksjs/utils": "^0.70.162",
139
- "@stacksjs/validation": "^0.70.162",
128
+ "@stacksjs/search-engine": "^0.70.163",
129
+ "@stacksjs/security": "^0.70.163",
130
+ "@stacksjs/server": "^0.70.163",
131
+ "@stacksjs/storage": "^0.70.163",
132
+ "@stacksjs/strings": "^0.70.163",
133
+ "@stacksjs/testing": "^0.70.163",
134
+ "@stacksjs/tunnel": "^0.70.163",
135
+ "@stacksjs/types": "^0.70.163",
136
+ "@stacksjs/ui": "^0.70.163",
137
+ "@stacksjs/utils": "^0.70.163",
138
+ "@stacksjs/validation": "^0.70.163",
140
139
  "@stacksjs/ts-cloud": "^0.7.56"
141
140
  },
142
141
  "devDependencies": {