askdb 1.0.0-beta.48 → 1.0.0-beta.49

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.
Files changed (45) hide show
  1. package/dist/ask.d.ts +15 -0
  2. package/dist/ask.d.ts.map +1 -0
  3. package/dist/ask.js +168 -0
  4. package/dist/ask.js.map +1 -0
  5. package/dist/bundle.d.ts +10 -0
  6. package/dist/bundle.d.ts.map +1 -0
  7. package/dist/bundle.js +51 -0
  8. package/dist/bundle.js.map +1 -0
  9. package/dist/cli-error.d.ts +10 -0
  10. package/dist/cli-error.d.ts.map +1 -0
  11. package/dist/cli-error.js +39 -0
  12. package/dist/cli-error.js.map +1 -0
  13. package/dist/cli.js +27 -308
  14. package/dist/cli.js.map +1 -1
  15. package/dist/command.d.ts +56 -0
  16. package/dist/command.d.ts.map +1 -0
  17. package/dist/command.js +139 -0
  18. package/dist/command.js.map +1 -0
  19. package/dist/dispatch.d.ts +6 -0
  20. package/dist/dispatch.d.ts.map +1 -0
  21. package/dist/dispatch.js +65 -0
  22. package/dist/dispatch.js.map +1 -0
  23. package/dist/init.d.ts +33 -20
  24. package/dist/init.d.ts.map +1 -1
  25. package/dist/init.js +273 -591
  26. package/dist/init.js.map +1 -1
  27. package/dist/introspect.d.ts +21 -1
  28. package/dist/introspect.d.ts.map +1 -1
  29. package/dist/introspect.js +57 -156
  30. package/dist/introspect.js.map +1 -1
  31. package/dist/logger.d.ts +3 -0
  32. package/dist/logger.d.ts.map +1 -1
  33. package/dist/logger.js +34 -1
  34. package/dist/logger.js.map +1 -1
  35. package/dist/prisma-install.d.ts +1 -1
  36. package/dist/prisma-install.js +1 -1
  37. package/dist/rag.d.ts +31 -0
  38. package/dist/rag.d.ts.map +1 -1
  39. package/dist/rag.js +175 -185
  40. package/dist/rag.js.map +1 -1
  41. package/dist/version.d.ts +1 -1
  42. package/dist/version.d.ts.map +1 -1
  43. package/dist/version.js +1 -1
  44. package/dist/version.js.map +1 -1
  45. package/package.json +13 -14
package/dist/init.js CHANGED
@@ -4,21 +4,20 @@ import { createRequire } from "node:module";
4
4
  import { dirname, join, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { getBuiltinAiProviderSetup, listBuiltinAiProviderSetups } from "@askdb/ai";
7
- import { ASKDB_AI_PROVIDERS } from "@askdb/config";
8
- import { renderAskDbAiConfigScaffold } from "@askdb/config/scaffold";
9
- import { PRISMA_BUILD_APPROVALS } from "./prisma-install.js";
7
+ import { ASKDB_AI_PROVIDERS, ASKDB_RAG_STORES } from "@askdb/config";
8
+ import { AskDbScaffoldInputError, detectPackageManager, findNearestPackageJsonDir, formatInstallCommand, isAskDbScaffoldEnvName, isLikelyWorkspaceRoot, packageManagerAddArgs, packageManagerSpawnSpec, planAskDbScaffold, supportedBuildApprovals, } from "@askdb/config/scaffold";
9
+ import { errorMessage } from "./cli-error.js";
10
+ import { runCommand } from "./command.js";
11
+ import { showable } from "./error-values.js";
10
12
  const DEFAULT_CONFIG_PATH = "askdb.config.ts";
11
- // ---------------------------------------------------------------------------
12
- // Config rendering
13
- // ---------------------------------------------------------------------------
14
13
  /**
15
14
  * Selectable AI providers, in `@askdb/ai`'s built-in table order: every id that has an
16
15
  * `askdb.config.*` branch (`ASKDB_AI_PROVIDERS`), with the key/model env var names to
17
16
  * scaffold. Derived from `@askdb/ai`'s `BUILTIN_AI_PROVIDERS` so it cannot drift.
18
17
  */
19
18
  const AI_PROVIDER_SETUPS = listBuiltinAiProviderSetups(ASKDB_AI_PROVIDERS);
20
- /** Every id `--ai-provider` accepts; `cli.ts` builds its help text from this list too. */
21
- export const VALID_AI_PROVIDERS = AI_PROVIDER_SETUPS.map((setup) => setup.id);
19
+ /** Every id `--ai-provider` accepts; its help line lists them. */
20
+ const VALID_AI_PROVIDERS = AI_PROVIDER_SETUPS.map((setup) => setup.id);
22
21
  function aiDefaults(provider) {
23
22
  const setup = getBuiltinAiProviderSetup(provider);
24
23
  if (!setup)
@@ -26,166 +25,69 @@ function aiDefaults(provider) {
26
25
  return setup;
27
26
  }
28
27
  /**
29
- * Render a value as a TypeScript string literal for the generated config.
30
- * Every interpolated value goes through this — the file is later executed
31
- * (via jiti), so a raw `"${value}"` would let a quote in a path inject code.
32
- * Mirrors `tsString` in `apps/studio/src/setup.ts`.
28
+ * `--sqlite-file`'s value as the config reads it: a value the planner accepts as an env var
29
+ * name (`SQLITE_FILE`, `UPPER_SNAKE_CASE`) is a variable, anything else (`data.db`,
30
+ * `../db/app.db`, `/srv/app.db`) a path. No value means the `SQLITE_FILE` variable.
33
31
  */
34
- function tsString(value) {
35
- return JSON.stringify(value);
36
- }
37
- /** An env var name as `--sqlite-file` accepts one, e.g. `SQLITE_FILE`. Anything else is a path. */
38
- const ENV_NAME_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
39
- /**
40
- * The env var a SQLite `file` setting reads, or `undefined` when it's a literal
41
- * path (`data.db`, `../db/app.db`, `/srv/app.db`, …). Only an env-name-shaped value
42
- * is a variable. No value means the `SQLITE_FILE` default. Rendering, the
43
- * `.env.example`, and the printed variable list all go through this.
44
- */
45
- function sqliteFileEnv(file) {
32
+ function sqliteFileValue(file) {
46
33
  if (!file)
47
- return "SQLITE_FILE";
48
- return ENV_NAME_PATTERN.test(file) ? file : undefined;
34
+ return { env: "SQLITE_FILE" };
35
+ return isAskDbScaffoldEnvName(file) ? { env: file } : { path: file };
36
+ }
37
+ /** `InitAnswers` as the planner in `@askdb/config/scaffold` takes them. It ignores what the engine doesn't read. */
38
+ function scaffoldAnswers(answers) {
39
+ const { studioExecute } = answers;
40
+ return {
41
+ database: answers.database,
42
+ connectionEnv: answers.connectionEnv ?? "DATABASE_URL",
43
+ sqliteFile: sqliteFileValue(answers.sqliteFile),
44
+ ...(answers.prismaSchema ? { prismaSchemaPath: answers.prismaSchema } : {}),
45
+ schemaOut: answers.schemaOut,
46
+ ai: {
47
+ provider: answers.aiProvider,
48
+ keyEnv: answers.aiKeyEnv,
49
+ ...(answers.aiModelEnv ? { modelEnv: answers.aiModelEnv } : {}),
50
+ },
51
+ rag: { store: answers.ragStore, pgvectorEnv: answers.pgvectorEnv ?? "ASKDB_PGVECTOR_URL" },
52
+ ...(studioExecute.enabled
53
+ ? {
54
+ studioExecute: {
55
+ provider: studioExecute.provider,
56
+ connectionEnv: studioExecute.connectionEnv ?? "DATABASE_URL",
57
+ sqliteFile: sqliteFileValue(studioExecute.sqliteFile),
58
+ },
59
+ }
60
+ : {}),
61
+ };
49
62
  }
63
+ /** The flag that sets each planner answer, for errors. */
64
+ const ANSWER_FLAGS = {
65
+ database: "--database",
66
+ connectionEnv: "--connection-env",
67
+ sqliteFile: "--sqlite-file",
68
+ prismaSchemaPath: "--prisma-schema",
69
+ schemaOut: "--schema-out",
70
+ "ai.provider": "--ai-provider",
71
+ "ai.keyEnv": "--ai-key-env",
72
+ "ai.modelEnv": "--ai-model-env",
73
+ "rag.store": "--rag-store",
74
+ "rag.pgvectorEnv": "--pgvector-env",
75
+ "studioExecute.sqliteFile": "--sqlite-file",
76
+ };
50
77
  /**
51
- * The `ai` block and the env vars it reads, from `@askdb/config/scaffold` (shared with
52
- * Studio's setup wizard, so provider-specific fields such as Azure's
53
- * `resourceName` live in one place).
78
+ * The config, `.env.example` and installs for `answers`, from `@askdb/config/scaffold`'s
79
+ * planner (shared with Studio's setup wizard). A bad answer fails with an error that names
80
+ * its flag, before anything is written.
54
81
  */
55
- function aiScaffold(answers) {
56
- return renderAskDbAiConfigScaffold({
57
- provider: answers.aiProvider,
58
- keyEnv: answers.aiKeyEnv,
59
- ...(answers.aiModelEnv ? { modelEnv: answers.aiModelEnv } : {}),
60
- });
61
- }
62
- function renderIntrospectionSection(answers) {
63
- const { database, connectionEnv, sqliteFile, prismaSchema, schemaOut } = answers;
64
- const outputDirLine = `\n outputDir: ${tsString(schemaOut)},`;
65
- const connectionEnvExpr = `env(${tsString(connectionEnv ?? "DATABASE_URL")})`;
66
- switch (database) {
67
- case "postgres":
68
- return ` introspection: {
69
- provider: "postgres",
70
- providerConfig: {
71
- postgres: {
72
- databaseUrl: ${connectionEnvExpr},
73
- },
74
- },${outputDirLine}
75
- },`;
76
- case "mysql":
77
- return ` introspection: {
78
- provider: "mysql",
79
- providerConfig: {
80
- mysql: {
81
- databaseUrl: ${connectionEnvExpr},
82
- },
83
- },${outputDirLine}
84
- },`;
85
- case "sqlite": {
86
- const fileEnv = sqliteFileEnv(sqliteFile);
87
- const resolvedFile = fileEnv ? `env(${tsString(fileEnv)})` : tsString(sqliteFile);
88
- return ` introspection: {
89
- provider: "sqlite",
90
- providerConfig: {
91
- sqlite: {
92
- file: ${resolvedFile},
93
- },
94
- },${outputDirLine}
95
- },`;
96
- }
97
- case "sqlserver":
98
- return ` introspection: {
99
- provider: "sqlserver",
100
- providerConfig: {
101
- sqlserver: {
102
- databaseUrl: ${connectionEnvExpr},
103
- },
104
- },${outputDirLine}
105
- },`;
106
- case "prisma": {
107
- const schemaLine = prismaSchema ? `\n schemaPath: ${tsString(prismaSchema)},` : "";
108
- return ` introspection: {
109
- provider: "prisma",
110
- providerConfig: {
111
- prisma: {${schemaLine}
112
- },
113
- },${outputDirLine}
114
- },`;
115
- }
116
- }
117
- }
118
- function renderRagSection(answers) {
119
- const { ragStore, pgvectorEnv } = answers;
120
- switch (ragStore) {
121
- case "file":
122
- return ` rag: {
123
- embedder: "mock",
124
- store: "file",
125
- storeConfig: {
126
- file: {},
127
- },
128
- },`;
129
- case "memory":
130
- return ` rag: {
131
- embedder: "mock",
132
- store: "memory",
133
- storeConfig: {
134
- memory: {},
135
- },
136
- },`;
137
- case "pgvector":
138
- return ` rag: {
139
- embedder: "mock",
140
- store: "pgvector",
141
- storeConfig: {
142
- pgvector: {
143
- databaseUrl: env(${tsString(pgvectorEnv ?? "ASKDB_PGVECTOR_URL")}),
144
- },
145
- },
146
- },`;
82
+ export function planInitScaffold(answers, specs) {
83
+ try {
84
+ return planAskDbScaffold(scaffoldAnswers(answers), { config: specs.configSpec, prisma: specs.prismaSpec });
147
85
  }
148
- }
149
- function renderStudioSection(answers) {
150
- const { studioExecute } = answers;
151
- if (!studioExecute.enabled)
152
- return null;
153
- const { provider } = studioExecute;
154
- if (provider === "sqlite") {
155
- const fileEnv = sqliteFileEnv(studioExecute.sqliteFile);
156
- const fileExpr = fileEnv ? `env(${tsString(fileEnv)})` : tsString(studioExecute.sqliteFile);
157
- return ` studio: {
158
- execute: {
159
- enabled: true,
160
- provider: "sqlite",
161
- file: ${fileExpr},
162
- },
163
- },`;
86
+ catch (error) {
87
+ if (!(error instanceof AskDbScaffoldInputError))
88
+ throw error;
89
+ throw new Error(`${ANSWER_FLAGS[error.field] ?? error.field} ${error.reason}`);
164
90
  }
165
- const urlEnv = studioExecute.connectionEnv ?? "DATABASE_URL";
166
- return ` studio: {
167
- execute: {
168
- enabled: true,
169
- provider: ${tsString(provider)},
170
- databaseUrl: env(${tsString(urlEnv)}),
171
- },
172
- },`;
173
- }
174
- export function renderInitConfig(answers) {
175
- const sections = [
176
- aiScaffold(answers).source,
177
- renderIntrospectionSection(answers),
178
- renderRagSection(answers),
179
- ];
180
- const studio = renderStudioSection(answers);
181
- if (studio)
182
- sections.push(studio);
183
- return `import { defineConfig, env, type AskDbConfig } from "@askdb/config";
184
-
185
- export default defineConfig({
186
- ${sections.join("\n")}
187
- } satisfies AskDbConfig);
188
- `;
189
91
  }
190
92
  export function resolveDefaultInitAnswers(overrides = {}) {
191
93
  const database = overrides.database ?? "postgres";
@@ -203,7 +105,9 @@ export function resolveDefaultInitAnswers(overrides = {}) {
203
105
  studioExecute = {
204
106
  enabled: true,
205
107
  provider: execProvider,
206
- connectionEnv: overrides.studioExecuteConnectionEnv ?? (execProvider === "sqlite" ? undefined : "DATABASE_URL"),
108
+ // On the introspection engine, execute reads the same connection env var, as the wizard does (#777).
109
+ connectionEnv: overrides.studioExecuteConnectionEnv ??
110
+ (execProvider === "sqlite" ? undefined : execProvider === database ? connectionEnv : "DATABASE_URL"),
207
111
  sqliteFile: overrides.studioExecuteSqliteFile ?? (execProvider === "sqlite" ? overrides.sqliteFile : undefined),
208
112
  };
209
113
  }
@@ -224,94 +128,7 @@ export function resolveDefaultInitAnswers(overrides = {}) {
224
128
  studioExecute,
225
129
  };
226
130
  }
227
- /** Package maps — names come from an allowlist, not from user input */
228
- const DB_DRIVER_PACKAGES = {
229
- postgres: "pg",
230
- mysql: "mysql2",
231
- sqlite: "better-sqlite3",
232
- sqlserver: "mssql",
233
- };
234
- export function buildInitInstallPlan(answers, specs) {
235
- const packages = [
236
- `@askdb/config@${specs.configSpec}`,
237
- ];
238
- const labels = ["@askdb/config"];
239
- const allowBuilds = [];
240
- // `@askdb/prisma` is an optional peer of askdb (#259). It depends on Prisma, whose
241
- // `@prisma/engines` postinstall pnpm 11 refuses to run until it's approved, and then
242
- // exits 1. A Prisma project approves it anyway, so the install does it here.
243
- // Studio's guided setup does the same (`apps/studio/src/setup.ts`).
244
- if (answers.database === "prisma") {
245
- packages.push(`@askdb/prisma@${specs.prismaSpec}`);
246
- labels.push("@askdb/prisma");
247
- allowBuilds.push(...PRISMA_BUILD_APPROVALS);
248
- }
249
- const driversNeeded = new Set();
250
- // Live DB driver for introspection
251
- if (answers.database !== "prisma") {
252
- driversNeeded.add(answers.database);
253
- }
254
- // Live DB driver for Studio execute
255
- if (answers.studioExecute.enabled) {
256
- driversNeeded.add(answers.studioExecute.provider);
257
- }
258
- for (const db of driversNeeded) {
259
- const pkg = DB_DRIVER_PACKAGES[db];
260
- if (pkg) {
261
- packages.push(pkg);
262
- labels.push(pkg);
263
- }
264
- }
265
- return { packages, labels, allowBuilds };
266
- }
267
- // ---------------------------------------------------------------------------
268
- // Package manager detection
269
- // ---------------------------------------------------------------------------
270
131
  const require = createRequire(import.meta.url);
271
- /** Walk upward from `startDir` to find a directory containing `package.json`. */
272
- export function findNearestPackageJsonDir(startDir) {
273
- let dir = resolve(startDir);
274
- for (let i = 0; i < 40; i += 1) {
275
- if (existsSync(join(dir, "package.json")))
276
- return dir;
277
- const parent = dirname(dir);
278
- if (parent === dir)
279
- return undefined;
280
- dir = parent;
281
- }
282
- return undefined;
283
- }
284
- /** Monorepo / workspace root — avoid mutating root `package.json` from `askdb init`. */
285
- export function isLikelyWorkspaceRoot(packageDir) {
286
- if (existsSync(join(packageDir, "pnpm-workspace.yaml")) || existsSync(join(packageDir, "pnpm-workspace.yml"))) {
287
- return true;
288
- }
289
- try {
290
- const pkg = JSON.parse(readFileSync(join(packageDir, "package.json"), "utf8"));
291
- return pkg.workspaces !== undefined && pkg.workspaces !== null;
292
- }
293
- catch {
294
- return false;
295
- }
296
- }
297
- export function detectPackageManager(packageDir) {
298
- let dir = packageDir;
299
- for (let i = 0; i < 40; i += 1) {
300
- if (existsSync(join(dir, "pnpm-lock.yaml")))
301
- return "pnpm";
302
- if (existsSync(join(dir, "bun.lockb")) || existsSync(join(dir, "bun.lock")))
303
- return "bun";
304
- if (existsSync(join(dir, "yarn.lock")))
305
- return "yarn";
306
- if (existsSync(join(dir, "package-lock.json")))
307
- return "npm";
308
- const parent = dirname(dir);
309
- if (parent === dir)
310
- break;
311
- dir = parent;
312
- }
313
- return "npm";
314
- }
315
132
  /**
316
133
  * The versions `askdb init` installs: `@askdb/config` and `@askdb/prisma` at the versions
317
134
  * this CLI was built with. Published, those are its `@askdb/config` dependency and the floor
@@ -342,196 +159,156 @@ function builtWithVersion(packageName, declared) {
342
159
  }
343
160
  function defaultInstaller(pm, packageDir, packages, allowBuilds) {
344
161
  const env = { ...process.env, CI: process.env.CI ?? "true" };
345
- const win = process.platform === "win32";
346
- const cmd = win ? { pnpm: "pnpm.CMD", yarn: "yarn.cmd", bun: "bun.exe", npm: "npm.cmd" }[pm] : pm;
347
- const r = spawnSync(cmd, installArgs(pm, packages, allowBuilds), { cwd: packageDir, stdio: "inherit", env, shell: false });
162
+ const { command, args, shell } = packageManagerSpawnSpec(pm, packageManagerAddArgs(pm, packages, allowBuilds));
163
+ const r = spawnSync(command, args, { cwd: packageDir, stdio: "inherit", env, shell });
348
164
  return r.status === 0;
349
165
  }
350
- function formatManualInstallCommand(pm, packages, allowBuilds) {
351
- return [pm, ...installArgs(pm, packages, allowBuilds)].join(" ");
352
- }
353
- /**
354
- * The build approvals to pass `pm` in `packageDir`: `allowBuilds`, or none for a pnpm older
355
- * than 10.4.0, whose `add` rejects `--allow-build` (pnpm 9 runs install scripts, and 10.0 to
356
- * 10.3 skip unapproved ones without failing). A pnpm whose version can't be read keeps them.
357
- * Studio's setup applies the same rule (`apps/studio/src/package-manager.ts`).
358
- */
359
- function supportedBuildApprovals(pm, packageDir, allowBuilds) {
360
- if (pm !== "pnpm" || allowBuilds.length === 0)
361
- return allowBuilds;
362
- const version = spawnSync("pnpm", ["--version"], {
363
- cwd: packageDir,
364
- encoding: "utf8",
365
- stdio: ["ignore", "pipe", "ignore"],
366
- // Windows runs pnpm through its `.cmd` shim, which needs a shell; the arguments are fixed.
367
- shell: process.platform === "win32",
368
- timeout: 15_000,
369
- });
370
- const match = version.status === 0 ? /^(\d+)\.(\d+)\./.exec(version.stdout.trim()) : null;
371
- if (!match)
372
- return allowBuilds;
373
- const [major, minor] = [Number(match[1]), Number(match[2])];
374
- return major > 10 || (major === 10 && minor >= 4) ? allowBuilds : [];
375
- }
376
- /**
377
- * The add command's arguments. Build approvals apply to pnpm only: npm and Yarn run install
378
- * scripts, and Bun skips untrusted ones without failing. Mirrors Studio's
379
- * `packageManagerAddArgs` (`apps/studio/src/package-manager.ts`); change both together.
380
- */
381
- function installArgs(pm, packages, allowBuilds) {
382
- switch (pm) {
383
- case "pnpm":
384
- return ["add", ...allowBuilds.map((name) => `--allow-build=${name}`), ...packages];
385
- case "yarn":
386
- case "bun":
387
- return ["add", ...packages];
388
- default:
389
- return ["install", "--save", ...packages];
390
- }
391
- }
392
166
  const VALID_DATABASES = ["postgres", "mysql", "sqlite", "sqlserver", "prisma"];
393
- const VALID_RAG_STORES = ["file", "memory", "pgvector"];
394
- function parseOptions(argv) {
395
- const opts = {
396
- force: false,
397
- path: DEFAULT_CONFIG_PATH,
398
- skipInstall: false,
399
- yes: false,
400
- interactive: undefined,
401
- noInteractive: false,
402
- };
403
- for (let i = 0; i < argv.length; i += 1) {
404
- const arg = argv[i];
405
- switch (arg) {
406
- case "--force":
407
- case "-f":
408
- opts.force = true;
409
- break;
410
- case "--skip-install":
411
- opts.skipInstall = true;
412
- break;
413
- case "--yes":
414
- case "-y":
415
- opts.yes = true;
416
- break;
417
- case "--interactive":
418
- opts.interactive = true;
419
- break;
420
- case "--no-interactive":
421
- opts.noInteractive = true;
422
- break;
423
- case "--studio-execute":
424
- opts.studioExecute = true;
425
- break;
426
- case "--no-studio-execute":
427
- opts.studioExecute = false;
428
- break;
429
- case "--path": {
430
- const value = argv[++i];
431
- if (!value || value.startsWith("--"))
432
- throw new Error(`${arg} requires a value.`);
433
- opts.path = value;
434
- break;
435
- }
436
- case "--database": {
437
- const value = argv[++i];
438
- if (!value || value.startsWith("--"))
439
- throw new Error(`${arg} requires a value.`);
440
- if (!VALID_DATABASES.includes(value)) {
441
- throw new Error(`${arg}: invalid value "${value}". Allowed: ${VALID_DATABASES.join(", ")}.`);
442
- }
443
- opts.database = value;
444
- break;
445
- }
446
- case "--connection-env": {
447
- const value = argv[++i];
448
- if (!value || value.startsWith("--"))
449
- throw new Error(`${arg} requires a value.`);
450
- opts.connectionEnv = value;
451
- break;
452
- }
453
- case "--sqlite-file": {
454
- const value = argv[++i];
455
- if (!value || value.startsWith("--"))
456
- throw new Error(`${arg} requires a value.`);
457
- opts.sqliteFile = value;
458
- break;
459
- }
460
- case "--prisma-schema": {
461
- const value = argv[++i];
462
- if (!value || value.startsWith("--"))
463
- throw new Error(`${arg} requires a value.`);
464
- opts.prismaSchema = value;
465
- break;
466
- }
467
- case "--schema-out": {
468
- const value = argv[++i];
469
- if (!value || value.startsWith("--"))
470
- throw new Error(`${arg} requires a value.`);
471
- opts.schemaOut = value;
472
- break;
473
- }
474
- case "--ai-provider": {
475
- const value = argv[++i];
476
- if (!value || value.startsWith("--"))
477
- throw new Error(`${arg} requires a value.`);
478
- if (!VALID_AI_PROVIDERS.includes(value)) {
479
- throw new Error(`${arg}: invalid value "${value}". Allowed: ${VALID_AI_PROVIDERS.join(", ")}.`);
480
- }
481
- opts.aiProvider = value;
482
- break;
483
- }
484
- case "--ai-key-env": {
485
- const value = argv[++i];
486
- if (!value || value.startsWith("--"))
487
- throw new Error(`${arg} requires a value.`);
488
- opts.aiKeyEnv = value;
489
- break;
490
- }
491
- case "--ai-model-env": {
492
- const value = argv[++i];
493
- if (!value || value.startsWith("--"))
494
- throw new Error(`${arg} requires a value.`);
495
- opts.aiModelEnv = value;
496
- break;
497
- }
498
- case "--rag-store": {
499
- const value = argv[++i];
500
- if (!value || value.startsWith("--"))
501
- throw new Error(`${arg} requires a value.`);
502
- if (!VALID_RAG_STORES.includes(value)) {
503
- throw new Error(`${arg}: invalid value "${value}". Allowed: ${VALID_RAG_STORES.join(", ")}.`);
504
- }
505
- opts.ragStore = value;
506
- break;
167
+ /** A flag that takes one of `allowed`; the error repeats a bad value only when it's `showable`. */
168
+ function oneOf(flag, allowed, value) {
169
+ if (allowed.includes(value))
170
+ return value;
171
+ const got = showable(value) ? ` "${value}"` : "";
172
+ throw new Error(`${flag}: invalid value${got}. Allowed: ${allowed.join(", ")}.`);
173
+ }
174
+ /** Every flag `askdb init` accepts, for the parser and the help. */
175
+ const INIT_FLAGS = [
176
+ { names: ["-y", "--yes"], help: "Accept defaults without prompts.", apply: (opts) => (opts.yes = true) },
177
+ {
178
+ names: ["--interactive"],
179
+ help: "Force wizard mode; errors if stdin/stdout are not TTYs.",
180
+ apply: (opts) => (opts.interactive = true),
181
+ },
182
+ { names: ["--no-interactive"], help: "Never prompt; use defaults or flags.", apply: (opts) => (opts.noInteractive = true) },
183
+ {
184
+ names: ["--path"],
185
+ value: "<file>",
186
+ help: `Write the config to this path. Default: ${DEFAULT_CONFIG_PATH}.`,
187
+ apply: (opts, value) => (opts.path = value),
188
+ },
189
+ { names: ["-f", "--force"], help: "Overwrite an existing file.", apply: (opts) => (opts.force = true) },
190
+ {
191
+ names: ["--skip-install"],
192
+ help: "Only write the file; do not install packages.",
193
+ apply: (opts) => (opts.skipInstall = true),
194
+ },
195
+ {
196
+ names: ["--database"],
197
+ value: "<db>",
198
+ group: "Database options",
199
+ help: `${VALID_DATABASES.join("|")} (default: postgres)`,
200
+ apply: (opts, value) => (opts.database = oneOf("--database", VALID_DATABASES, value)),
201
+ },
202
+ {
203
+ names: ["--connection-env"],
204
+ value: "<name>",
205
+ group: "Database options",
206
+ help: "Env var name for connection URL",
207
+ apply: (opts, value) => (opts.connectionEnv = value),
208
+ },
209
+ {
210
+ names: ["--sqlite-file"],
211
+ value: "<path-or-env>",
212
+ group: "Database options",
213
+ help: "SQLite file path, or an UPPER_SNAKE_CASE env var name",
214
+ apply: (opts, value) => (opts.sqliteFile = value),
215
+ },
216
+ {
217
+ names: ["--prisma-schema"],
218
+ value: "<path>",
219
+ group: "Database options",
220
+ help: "Path to schema.prisma",
221
+ apply: (opts, value) => (opts.prismaSchema = value),
222
+ },
223
+ {
224
+ names: ["--schema-out"],
225
+ value: "<dir>",
226
+ group: "Database options",
227
+ help: "Schema output directory (default: ./askdb)",
228
+ apply: (opts, value) => (opts.schemaOut = value),
229
+ },
230
+ {
231
+ names: ["--ai-provider"],
232
+ value: "<name>",
233
+ group: "AI options",
234
+ help: `${VALID_AI_PROVIDERS.join("|")} (default: openai)`,
235
+ apply: (opts, value) => (opts.aiProvider = oneOf("--ai-provider", VALID_AI_PROVIDERS, value)),
236
+ },
237
+ {
238
+ names: ["--ai-key-env"],
239
+ value: "<name>",
240
+ group: "AI options",
241
+ help: "Env var name for API key",
242
+ apply: (opts, value) => (opts.aiKeyEnv = value),
243
+ },
244
+ {
245
+ names: ["--ai-model-env"],
246
+ value: "<name>",
247
+ group: "AI options",
248
+ help: "Env var name for model override",
249
+ apply: (opts, value) => (opts.aiModelEnv = value),
250
+ },
251
+ {
252
+ names: ["--rag-store"],
253
+ value: "<name>",
254
+ group: "RAG options",
255
+ help: `${ASKDB_RAG_STORES.join("|")} (default: file)`,
256
+ apply: (opts, value) => (opts.ragStore = oneOf("--rag-store", ASKDB_RAG_STORES, value)),
257
+ },
258
+ {
259
+ names: ["--pgvector-env"],
260
+ value: "<name>",
261
+ group: "RAG options",
262
+ help: "Env var name for pgvector URL",
263
+ apply: (opts, value) => (opts.pgvectorEnv = value),
264
+ },
265
+ {
266
+ names: ["--studio-execute"],
267
+ group: "Studio options",
268
+ help: "Enable Studio execute (default: off)",
269
+ apply: (opts) => (opts.studioExecute = true),
270
+ },
271
+ {
272
+ names: ["--no-studio-execute"],
273
+ group: "Studio options",
274
+ help: "Disable Studio execute",
275
+ apply: (opts) => (opts.studioExecute = false),
276
+ },
277
+ ];
278
+ /** `askdb init`, installing packages with `installer`. It never reads askdb.config. */
279
+ export function initCommand(installer = defaultInstaller) {
280
+ return {
281
+ name: "init",
282
+ summary: "Create askdb.config.ts tailored to your database and AI provider",
283
+ usage: [
284
+ "askdb init Start wizard in a TTY, or use defaults in CI",
285
+ "askdb init --yes Accept defaults without prompts",
286
+ "askdb init --no-interactive Same as --yes",
287
+ "askdb init --force Overwrite an existing askdb.config.ts",
288
+ "askdb init --path <file> Write config to a custom file path",
289
+ "askdb init --skip-install Only write the file; do not install packages",
290
+ ],
291
+ flags: INIT_FLAGS,
292
+ initial: () => ({
293
+ force: false,
294
+ path: DEFAULT_CONFIG_PATH,
295
+ skipInstall: false,
296
+ yes: false,
297
+ interactive: undefined,
298
+ noInteractive: false,
299
+ }),
300
+ check(opts) {
301
+ if (opts.yes && opts.interactive) {
302
+ throw new Error("--yes and --interactive cannot be used together.");
507
303
  }
508
- case "--pgvector-env": {
509
- const value = argv[++i];
510
- if (!value || value.startsWith("--"))
511
- throw new Error(`${arg} requires a value.`);
512
- opts.pgvectorEnv = value;
513
- break;
304
+ if (opts.interactive && opts.noInteractive) {
305
+ throw new Error("--interactive and --no-interactive cannot be used together.");
514
306
  }
515
- case "--help":
516
- case "-h":
517
- opts.path = "--help";
518
- return opts;
519
- default:
520
- throw new Error(`Unknown option: ${arg}`);
521
- }
522
- }
523
- // Conflict checks
524
- if (opts.yes && opts.interactive) {
525
- throw new Error("--yes and --interactive cannot be used together.");
526
- }
527
- if (opts.interactive && opts.noInteractive) {
528
- throw new Error("--interactive and --no-interactive cannot be used together.");
529
- }
530
- return opts;
307
+ },
308
+ needsConfig: () => false,
309
+ run: (opts) => runInit(opts, installer),
310
+ };
531
311
  }
532
- // ---------------------------------------------------------------------------
533
- // TTY wizard
534
- // ---------------------------------------------------------------------------
535
312
  function validatePath(value) {
536
313
  if (!value.trim())
537
314
  return "Cannot be empty.";
@@ -671,9 +448,6 @@ export async function runWizard(prompter) {
671
448
  studioExecute,
672
449
  };
673
450
  }
674
- // ---------------------------------------------------------------------------
675
- // Real Inquirer prompter
676
- // ---------------------------------------------------------------------------
677
451
  async function buildInquirerPrompter() {
678
452
  const { select, input, confirm } = await import("@inquirer/prompts");
679
453
  return {
@@ -682,79 +456,6 @@ async function buildInquirerPrompter() {
682
456
  confirm: (opts) => confirm({ message: opts.message, default: opts.default }),
683
457
  };
684
458
  }
685
- // ---------------------------------------------------------------------------
686
- // Next steps printer
687
- // ---------------------------------------------------------------------------
688
- // ---------------------------------------------------------------------------
689
- // .env.example generation
690
- // ---------------------------------------------------------------------------
691
- const DB_URL_PLACEHOLDER = {
692
- postgres: "postgresql://<USERNAME>:<PASSWORD>@<DATABASE_HOST>:<DATABASE_PORT>/<DATABASE_NAME>",
693
- mysql: "mysql://<USERNAME>:<PASSWORD>@<DATABASE_HOST>:<DATABASE_PORT>/<DATABASE_NAME>",
694
- sqlserver: "Data Source=<DATABASE_HOST>,<DATABASE_PORT>;Initial Catalog=<DATABASE_NAME>;User ID=<USERNAME>;Password=<PASSWORD>;Trust Server Certificate=True;Authentication=SqlPassword;",
695
- };
696
- /**
697
- * The database-side env vars the generated config reads (the introspection URL,
698
- * SQLite file paths, the pgvector URL, and Studio execute's URL), each once.
699
- * Names fall back to the same defaults the renderers above use, so
700
- * `.env.example` and the printed variable list match the config.
701
- */
702
- function databaseEnvVars(answers) {
703
- const { database, studioExecute } = answers;
704
- const vars = new Map();
705
- const add = (envVar) => {
706
- if (!vars.has(envVar.name))
707
- vars.set(envVar.name, envVar);
708
- };
709
- if (database !== "sqlite" && database !== "prisma") {
710
- add({ name: answers.connectionEnv ?? "DATABASE_URL", placeholder: DB_URL_PLACEHOLDER[database] ?? "" });
711
- }
712
- const sqliteFileEnvs = [
713
- database === "sqlite" ? sqliteFileEnv(answers.sqliteFile) : undefined,
714
- studioExecute.enabled && studioExecute.provider === "sqlite" ? sqliteFileEnv(studioExecute.sqliteFile) : undefined,
715
- ];
716
- for (const name of sqliteFileEnvs) {
717
- if (name)
718
- add({ name, placeholder: "", purpose: "SQLite database file path" });
719
- }
720
- if (answers.ragStore === "pgvector") {
721
- add({ name: answers.pgvectorEnv ?? "ASKDB_PGVECTOR_URL", placeholder: DB_URL_PLACEHOLDER.postgres ?? "" });
722
- }
723
- if (studioExecute.enabled && studioExecute.provider !== "sqlite") {
724
- add({
725
- name: studioExecute.connectionEnv ?? "DATABASE_URL",
726
- placeholder: DB_URL_PLACEHOLDER[studioExecute.provider] ?? "",
727
- });
728
- }
729
- return [...vars.values()];
730
- }
731
- function buildEnvExample(answers) {
732
- const lines = [
733
- "# AskDB environment — copy to .env and fill in real values.",
734
- "# .env is read by the CLI, Studio, and the HTTP API; never commit it.",
735
- "",
736
- ];
737
- for (const envVar of databaseEnvVars(answers)) {
738
- if (envVar.purpose)
739
- lines.push(`# ${envVar.purpose}`);
740
- lines.push(`${envVar.name}=${envVar.placeholder}`);
741
- }
742
- for (const envVar of aiScaffold(answers).envVars) {
743
- lines.push(`# ${envVar.purpose}`);
744
- lines.push(`${envVar.name}=`);
745
- }
746
- lines.push("");
747
- return lines.join("\n");
748
- }
749
- /** Env var NAMES the generated config references — conventional defaults, not values. */
750
- function collectEnvVarNames(answers) {
751
- const names = new Set();
752
- for (const envVar of aiScaffold(answers).envVars)
753
- names.add(envVar.name);
754
- for (const envVar of databaseEnvVars(answers))
755
- names.add(envVar.name);
756
- return Array.from(names);
757
- }
758
459
  function printNextSteps(answers) {
759
460
  const lines = ["\nNext steps:"];
760
461
  lines.push(" 1. Copy .env.example → .env and fill in your credentials.");
@@ -771,24 +472,10 @@ function printNextSteps(answers) {
771
472
  lines.push(` ${answers.studioExecute.enabled ? "4" : "3"}. Ask a question: askdb ask --question "Which customers signed up last week?"`);
772
473
  process.stdout.write(lines.join("\n") + "\n");
773
474
  }
774
- // ---------------------------------------------------------------------------
775
- // Main entry point
776
- // ---------------------------------------------------------------------------
777
- export async function runInitCli(argv, _installer) {
778
- const installer = _installer ?? defaultInstaller;
779
- let opts;
780
- try {
781
- opts = parseOptions(argv);
782
- }
783
- catch (e) {
784
- const msg = e instanceof Error ? e.message : String(e);
785
- process.stderr.write(`${msg}\n`);
786
- return 1;
787
- }
788
- if (opts.path === "--help") {
789
- printHelp();
790
- return 0;
791
- }
475
+ export async function runInitCli(argv, installer) {
476
+ return runCommand(initCommand(installer), argv);
477
+ }
478
+ async function runInit(opts, installer) {
792
479
  const configTarget = resolve(process.cwd(), opts.path);
793
480
  if (existsSync(configTarget) && !opts.force) {
794
481
  process.stderr.write(`Refusing to overwrite existing file:\n` +
@@ -796,7 +483,6 @@ export async function runInitCli(argv, _installer) {
796
483
  `Use \`askdb init --force\` to overwrite.\n`);
797
484
  return 1;
798
485
  }
799
- // Determine interactive mode
800
486
  const isTTY = Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY);
801
487
  const wantInteractive = opts.interactive === true || (!opts.yes && !opts.noInteractive && isTTY);
802
488
  if (opts.interactive && !isTTY) {
@@ -804,6 +490,7 @@ export async function runInitCli(argv, _installer) {
804
490
  return 1;
805
491
  }
806
492
  let answers;
493
+ let plan;
807
494
  if (wantInteractive) {
808
495
  let prompter;
809
496
  try {
@@ -812,24 +499,16 @@ export async function runInitCli(argv, _installer) {
812
499
  catch {
813
500
  process.stderr.write("Failed to load @inquirer/prompts. Falling back to non-interactive mode.\n");
814
501
  answers = resolveDefaultInitAnswers(optsToOverrides(opts));
815
- return await finishInit(configTarget, answers, opts, installer);
502
+ return await finishInit(configTarget, answers, planInitScaffold(answers, resolveInitDepSpecs()), opts, installer);
816
503
  }
817
- let wizardResult;
818
504
  try {
819
- wizardResult = await runWizard(prompter);
505
+ answers = await runWizard(prompter);
820
506
  }
821
507
  catch {
822
508
  process.stderr.write("\nSetup cancelled.\n");
823
509
  return 1;
824
510
  }
825
- if (!wizardResult) {
826
- process.stderr.write("\nSetup cancelled.\n");
827
- return 1;
828
- }
829
- answers = wizardResult;
830
- // Summary and confirmation
831
- const specs = resolveInitDepSpecs();
832
- const plan = buildInitInstallPlan(answers, specs);
511
+ plan = planInitScaffold(answers, resolveInitDepSpecs());
833
512
  process.stdout.write("\n--- Summary ---\n");
834
513
  process.stdout.write(` Config path: ${configTarget}\n`);
835
514
  process.stdout.write(` Database: ${answers.database}\n`);
@@ -837,9 +516,10 @@ export async function runInitCli(argv, _installer) {
837
516
  process.stdout.write(` RAG store: ${answers.ragStore}\n`);
838
517
  process.stdout.write(` Studio execute: ${answers.studioExecute.enabled ? `enabled (${answers.studioExecute.provider})` : "disabled"}\n`);
839
518
  if (!opts.skipInstall) {
840
- process.stdout.write(` Packages: ${plan.labels.join(", ")}\n`);
519
+ const packages = packagesToInstall(plan, findNearestPackageJsonDir(process.cwd()));
520
+ process.stdout.write(` Packages: ${packages.map(packageNameOf).join(", ")}\n`);
841
521
  }
842
- process.stdout.write(` Env vars: ${collectEnvVarNames(answers).join(", ")}\n`);
522
+ process.stdout.write(` Env vars: ${plan.envVars.map((v) => v.name).join(", ")}\n`);
843
523
  process.stdout.write(' (defaults — rename via `env("...")` in askdb.config.ts if you\'d like)\n');
844
524
  process.stdout.write("---------------\n\n");
845
525
  let confirmed;
@@ -857,8 +537,42 @@ export async function runInitCli(argv, _installer) {
857
537
  }
858
538
  else {
859
539
  answers = resolveDefaultInitAnswers(optsToOverrides(opts));
540
+ plan = planInitScaffold(answers, resolveInitDepSpecs());
541
+ }
542
+ return await finishInit(configTarget, answers, plan, opts, installer);
543
+ }
544
+ /** `@askdb/prisma` from `@askdb/prisma@1.0.0`, `pg` from `pg`. */
545
+ function packageNameOf(spec) {
546
+ const at = spec.indexOf("@", 1);
547
+ return at === -1 ? spec : spec.slice(0, at);
548
+ }
549
+ /**
550
+ * The plan's packages to install in `pkgRoot`. AskDB's own packages always install, at the
551
+ * versions this CLI pins. Any other package (a driver, or `ai` and `zod` for Prisma) installs
552
+ * only when the project's `package.json` doesn't list it yet, so `init` never moves a range
553
+ * the project already chose, such as `ai@^6`. With no project, or at a workspace root, where
554
+ * `init` only prints the command for another package, every package is listed.
555
+ */
556
+ function packagesToInstall(plan, pkgRoot) {
557
+ const listed = pkgRoot && !isLikelyWorkspaceRoot(pkgRoot) ? listedDependencies(pkgRoot) : new Set();
558
+ return plan.install.packages.filter((spec) => {
559
+ const name = packageNameOf(spec);
560
+ return name.startsWith("@askdb/") || !listed.has(name);
561
+ });
562
+ }
563
+ /** Every package `dir/package.json` lists as a dependency of any kind. */
564
+ function listedDependencies(dir) {
565
+ try {
566
+ const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
567
+ const fields = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"];
568
+ return new Set(fields.flatMap((field) => {
569
+ const deps = pkg[field];
570
+ return deps !== null && typeof deps === "object" ? Object.keys(deps) : [];
571
+ }));
572
+ }
573
+ catch {
574
+ return new Set();
860
575
  }
861
- return await finishInit(configTarget, answers, opts, installer);
862
576
  }
863
577
  function optsToOverrides(opts) {
864
578
  return {
@@ -875,21 +589,19 @@ function optsToOverrides(opts) {
875
589
  studioExecute: opts.studioExecute,
876
590
  };
877
591
  }
878
- async function finishInit(configTarget, answers, opts, installer) {
879
- const configContent = renderInitConfig(answers);
592
+ async function finishInit(configTarget, answers, plan, opts, installer) {
880
593
  try {
881
- writeFileSync(configTarget, configContent, { encoding: "utf8" });
594
+ writeFileSync(configTarget, plan.configSource, { encoding: "utf8" });
882
595
  }
883
596
  catch (e) {
884
- const msg = e instanceof Error ? e.message : String(e);
885
- process.stderr.write(`Failed to write config: ${msg}\n`);
597
+ process.stderr.write(`Failed to write config: ${errorMessage(e)}\n`);
886
598
  return 1;
887
599
  }
888
600
  process.stdout.write(`Wrote:\n - ${configTarget}\n`);
889
601
  const envExamplePath = join(dirname(configTarget), ".env.example");
890
602
  if (!existsSync(envExamplePath)) {
891
603
  try {
892
- writeFileSync(envExamplePath, buildEnvExample(answers), { encoding: "utf8" });
604
+ writeFileSync(envExamplePath, plan.envExample, { encoding: "utf8" });
893
605
  process.stdout.write(` - ${envExamplePath}\n`);
894
606
  }
895
607
  catch {
@@ -898,67 +610,37 @@ async function finishInit(configTarget, answers, opts, installer) {
898
610
  }
899
611
  if (!opts.skipInstall) {
900
612
  const pkgRoot = findNearestPackageJsonDir(process.cwd());
901
- const specs = resolveInitDepSpecs();
902
- const plan = buildInitInstallPlan(answers, specs);
613
+ const packages = packagesToInstall(plan, pkgRoot);
614
+ const { allowBuilds } = plan.install;
615
+ // The manager comes from the project's lockfile or package.json, else from whoever
616
+ // ran init (`pnpm dlx`, `npx`, …); with no package.json, from the current directory.
617
+ const pmDir = pkgRoot ?? process.cwd();
618
+ const pm = detectPackageManager(pmDir);
619
+ const approvedBuilds = supportedBuildApprovals(pm, pmDir, allowBuilds);
620
+ const installCommand = formatInstallCommand(pm, packages, approvedBuilds);
903
621
  if (!pkgRoot) {
904
622
  process.stdout.write("\nNo package.json found in this directory or any parent directory.\n" +
905
623
  "Install the template imports yourself (from a project with a package.json):\n" +
906
- ` npm install --save ${plan.packages.join(" ")}\n\n`);
624
+ ` ${installCommand}\n\n`);
907
625
  }
908
626
  else if (isLikelyWorkspaceRoot(pkgRoot)) {
909
627
  process.stdout.write("\nSkipped automatic dependency install (workspace / monorepo root).\n" +
910
628
  "Add these to the package that will load this config (or run `askdb init` from that package directory):\n" +
911
- ` npm install --save ${plan.packages.join(" ")}\n\n`);
629
+ ` ${installCommand}\n\n`);
912
630
  }
913
631
  else {
914
- const pm = detectPackageManager(pkgRoot);
915
- const approvedBuilds = supportedBuildApprovals(pm, pkgRoot, plan.allowBuilds);
916
632
  process.stdout.write(`\nInstalling dependencies with ${pm} in ${pkgRoot} …\n`);
917
- const ok = installer(pm, pkgRoot, plan.packages, approvedBuilds);
633
+ const ok = installer(pm, pkgRoot, packages, approvedBuilds);
918
634
  if (!ok) {
919
635
  process.stderr.write("Dependency install failed. The config file was written, but packages are not installed yet.\n" +
920
636
  ` cd ${pkgRoot}\n` +
921
- ` ${formatManualInstallCommand(pm, plan.packages, approvedBuilds)}\n`);
637
+ ` ${installCommand}\n`);
922
638
  return 1;
923
639
  }
924
- process.stdout.write(`Installed: ${plan.labels.join(", ")}.\n`);
640
+ process.stdout.write(`Installed: ${packages.map(packageNameOf).join(", ")}.\n`);
925
641
  }
926
642
  }
927
643
  printNextSteps(answers);
928
644
  return 0;
929
645
  }
930
- function printHelp() {
931
- process.stdout.write([
932
- "askdb init - Create askdb.config.ts tailored to your database and AI provider",
933
- "",
934
- "Usage:",
935
- " askdb init Start wizard in a TTY, or use defaults in CI",
936
- " askdb init --yes Accept defaults without prompts",
937
- " askdb init --no-interactive Same as --yes",
938
- " askdb init --force Overwrite an existing askdb.config.ts",
939
- " askdb init --path <file> Write config to a custom file path",
940
- " askdb init --skip-install Only write the file; do not install packages",
941
- "",
942
- "Database options:",
943
- " --database <db> postgres|mysql|sqlite|sqlserver|prisma (default: postgres)",
944
- " --connection-env <name> Env var name for connection URL",
945
- " --sqlite-file <path-or-env> SQLite file path, or an UPPER_SNAKE_CASE env var name",
946
- " --prisma-schema <path> Path to schema.prisma",
947
- " --schema-out <dir> Schema output directory (default: ./askdb)",
948
- "",
949
- "AI options:",
950
- ` --ai-provider <name> ${VALID_AI_PROVIDERS.join("|")} (default: openai)`,
951
- " --ai-key-env <name> Env var name for API key",
952
- " --ai-model-env <name> Env var name for model override",
953
- "",
954
- "RAG options:",
955
- " --rag-store <name> file|memory|pgvector (default: file)",
956
- " --pgvector-env <name> Env var name for pgvector URL",
957
- "",
958
- "Studio options:",
959
- " --studio-execute Enable Studio execute (default: off)",
960
- " --no-studio-execute Disable Studio execute",
961
- "",
962
- ].join("\n"));
963
- }
964
646
  //# sourceMappingURL=init.js.map