askdb 1.0.0-beta.29 → 1.0.0-beta.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -4
- package/dist/cli.js +50 -5
- package/dist/cli.js.map +1 -1
- package/dist/init.d.ts +69 -5
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +703 -137
- package/dist/init.js.map +1 -1
- package/package.json +15 -15
package/dist/init.js
CHANGED
|
@@ -4,88 +4,241 @@ import { createRequire } from "node:module";
|
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
const DEFAULT_CONFIG_PATH = "askdb.config.ts";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Config rendering
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
/** AI provider defaults for key/model env vars */
|
|
11
|
+
const AI_DEFAULTS = {
|
|
12
|
+
openai: { keyEnv: "OPENAI_API_KEY", modelEnv: "OPENAI_MODEL", modelField: "model" },
|
|
13
|
+
anthropic: { keyEnv: "ANTHROPIC_API_KEY", modelEnv: "ANTHROPIC_MODEL", modelField: "model" },
|
|
14
|
+
google: { keyEnv: "GOOGLE_GENERATIVE_AI_API_KEY", modelEnv: "GOOGLE_GENERATIVE_AI_MODEL", modelField: "model" },
|
|
15
|
+
azure: { keyEnv: "AZURE_OPENAI_API_KEY", modelEnv: "AZURE_OPENAI_DEPLOYMENT", modelField: "model" },
|
|
16
|
+
foundry: { keyEnv: "AZURE_OPENAI_API_KEY", modelEnv: "AZURE_OPENAI_DEPLOYMENT", modelField: "model" },
|
|
17
|
+
};
|
|
18
|
+
function renderAiSection(answers) {
|
|
19
|
+
const { aiProvider, aiKeyEnv, aiModelEnv } = answers;
|
|
20
|
+
const modelLine = aiModelEnv ? `\n ${AI_DEFAULTS[aiProvider].modelField}: env("${aiModelEnv}"),` : "";
|
|
21
|
+
return ` ai: {
|
|
22
|
+
provider: "${aiProvider}",
|
|
21
23
|
providerConfig: {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
apiKey: env("OPENAI_API_KEY"),
|
|
25
|
-
model: env("OPENAI_MODEL"),
|
|
24
|
+
${aiProvider}: {
|
|
25
|
+
apiKey: env("${aiKeyEnv}"),${modelLine}
|
|
26
26
|
},
|
|
27
27
|
},
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
},`;
|
|
29
|
+
}
|
|
30
|
+
function renderIntrospectionSection(answers) {
|
|
31
|
+
const { database, connectionEnv, sqliteFile, prismaSchema, schemaOut } = answers;
|
|
32
|
+
const outputDirLine = `\n outputDir: "${schemaOut}",`;
|
|
33
|
+
switch (database) {
|
|
34
|
+
case "postgres":
|
|
35
|
+
return ` introspection: {
|
|
32
36
|
provider: "postgres",
|
|
33
37
|
providerConfig: {
|
|
34
38
|
postgres: {
|
|
35
|
-
|
|
36
|
-
databaseUrl: env("DATABASE_URL"),
|
|
39
|
+
databaseUrl: env("${connectionEnv ?? "DATABASE_URL"}"),
|
|
37
40
|
},
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
embedderConfig: {
|
|
47
|
-
openai: {
|
|
48
|
-
model: env("ASKDB_RAG_EMBEDDER_MODEL"),
|
|
49
|
-
dimension: env("ASKDB_RAG_EMBEDDER_DIMENSIONS"),
|
|
50
|
-
apiKey: env("OPENAI_API_KEY"),
|
|
51
|
-
baseUrl: env("ASKDB_RAG_EMBEDDER_BASE_URL"),
|
|
41
|
+
},${outputDirLine}
|
|
42
|
+
},`;
|
|
43
|
+
case "mysql":
|
|
44
|
+
return ` introspection: {
|
|
45
|
+
provider: "mysql",
|
|
46
|
+
providerConfig: {
|
|
47
|
+
mysql: {
|
|
48
|
+
databaseUrl: env("${connectionEnv ?? "DATABASE_URL"}"),
|
|
52
49
|
},
|
|
53
|
-
}
|
|
54
|
-
|
|
50
|
+
},${outputDirLine}
|
|
51
|
+
},`;
|
|
52
|
+
case "sqlite": {
|
|
53
|
+
const fileExpr = sqliteFile && !sqliteFile.startsWith("./") && !sqliteFile.startsWith("/")
|
|
54
|
+
? `env("${sqliteFile}")`
|
|
55
|
+
: `env("SQLITE_FILE")`;
|
|
56
|
+
const resolvedFile = sqliteFile && (sqliteFile.startsWith("./") || sqliteFile.startsWith("/"))
|
|
57
|
+
? `"${sqliteFile}"`
|
|
58
|
+
: fileExpr;
|
|
59
|
+
return ` introspection: {
|
|
60
|
+
provider: "sqlite",
|
|
61
|
+
providerConfig: {
|
|
62
|
+
sqlite: {
|
|
63
|
+
file: ${resolvedFile},
|
|
64
|
+
},
|
|
65
|
+
},${outputDirLine}
|
|
66
|
+
},`;
|
|
67
|
+
}
|
|
68
|
+
case "sqlserver":
|
|
69
|
+
return ` introspection: {
|
|
70
|
+
provider: "sqlserver",
|
|
71
|
+
providerConfig: {
|
|
72
|
+
sqlserver: {
|
|
73
|
+
databaseUrl: env("${connectionEnv ?? "SQLSERVER_URL"}"),
|
|
74
|
+
},
|
|
75
|
+
},${outputDirLine}
|
|
76
|
+
},`;
|
|
77
|
+
case "prisma": {
|
|
78
|
+
const schemaLine = prismaSchema ? `\n schemaPath: "${prismaSchema}",` : "";
|
|
79
|
+
return ` introspection: {
|
|
80
|
+
provider: "prisma",
|
|
81
|
+
providerConfig: {
|
|
82
|
+
prisma: {${schemaLine}
|
|
83
|
+
},
|
|
84
|
+
},${outputDirLine}
|
|
85
|
+
},`;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function renderRagSection(answers) {
|
|
90
|
+
const { ragStore, pgvectorEnv } = answers;
|
|
91
|
+
switch (ragStore) {
|
|
92
|
+
case "file":
|
|
93
|
+
return ` rag: {
|
|
94
|
+
embedder: "mock",
|
|
95
|
+
embedderConfig: {},
|
|
55
96
|
store: "file",
|
|
56
97
|
storeConfig: {
|
|
57
98
|
file: {},
|
|
99
|
+
},
|
|
100
|
+
},`;
|
|
101
|
+
case "memory":
|
|
102
|
+
return ` rag: {
|
|
103
|
+
embedder: "mock",
|
|
104
|
+
embedderConfig: {},
|
|
105
|
+
store: "memory",
|
|
106
|
+
storeConfig: {
|
|
58
107
|
memory: {},
|
|
108
|
+
},
|
|
109
|
+
},`;
|
|
110
|
+
case "pgvector":
|
|
111
|
+
return ` rag: {
|
|
112
|
+
embedder: "mock",
|
|
113
|
+
embedderConfig: {},
|
|
114
|
+
store: "pgvector",
|
|
115
|
+
storeConfig: {
|
|
59
116
|
pgvector: {
|
|
60
|
-
databaseUrl: env("ASKDB_PGVECTOR_URL"),
|
|
61
|
-
dimensions: env("ASKDB_RAG_EMBEDDER_DIMENSIONS"),
|
|
117
|
+
databaseUrl: env("${pgvectorEnv ?? "ASKDB_PGVECTOR_URL"}"),
|
|
62
118
|
},
|
|
63
119
|
},
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
120
|
+
},`;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function renderStudioSection(answers) {
|
|
124
|
+
const { studioExecute } = answers;
|
|
125
|
+
if (!studioExecute.enabled)
|
|
126
|
+
return null;
|
|
127
|
+
const { provider } = studioExecute;
|
|
128
|
+
if (provider === "sqlite") {
|
|
129
|
+
const fileExpr = studioExecute.sqliteFile && (studioExecute.sqliteFile.startsWith("./") || studioExecute.sqliteFile.startsWith("/"))
|
|
130
|
+
? `"${studioExecute.sqliteFile}"`
|
|
131
|
+
: `env("${studioExecute.sqliteFile ?? "SQLITE_FILE"}")`;
|
|
132
|
+
return ` studio: {
|
|
76
133
|
execute: {
|
|
77
|
-
|
|
78
|
-
|
|
134
|
+
provider: "sqlite",
|
|
135
|
+
file: ${fileExpr},
|
|
79
136
|
},
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
137
|
+
},`;
|
|
138
|
+
}
|
|
139
|
+
const urlEnv = studioExecute.connectionEnv ?? (provider === "sqlserver" ? "SQLSERVER_URL" : "DATABASE_URL");
|
|
140
|
+
return ` studio: {
|
|
141
|
+
execute: {
|
|
142
|
+
provider: "${provider}",
|
|
143
|
+
databaseUrl: env("${urlEnv}"),
|
|
85
144
|
},
|
|
86
|
-
}
|
|
145
|
+
},`;
|
|
146
|
+
}
|
|
147
|
+
export function renderInitConfig(answers) {
|
|
148
|
+
const sections = [
|
|
149
|
+
renderAiSection(answers),
|
|
150
|
+
renderIntrospectionSection(answers),
|
|
151
|
+
renderRagSection(answers),
|
|
152
|
+
];
|
|
153
|
+
const studio = renderStudioSection(answers);
|
|
154
|
+
if (studio)
|
|
155
|
+
sections.push(studio);
|
|
156
|
+
return `import dotenv from "dotenv";
|
|
157
|
+
import { defineConfig, env, type AskDbConfig } from "@askdb/config";
|
|
158
|
+
|
|
159
|
+
dotenv.config({ quiet: true });
|
|
160
|
+
|
|
161
|
+
export default defineConfig({
|
|
162
|
+
${sections.join("\n")}
|
|
87
163
|
} satisfies AskDbConfig);
|
|
88
164
|
`;
|
|
165
|
+
}
|
|
166
|
+
export function resolveDefaultInitAnswers(overrides = {}) {
|
|
167
|
+
const database = overrides.database ?? "postgres";
|
|
168
|
+
const aiProvider = overrides.aiProvider ?? "openai";
|
|
169
|
+
const aiDefaults = AI_DEFAULTS[aiProvider];
|
|
170
|
+
let connectionEnv = overrides.connectionEnv;
|
|
171
|
+
if (!connectionEnv) {
|
|
172
|
+
if (database === "sqlserver")
|
|
173
|
+
connectionEnv = "SQLSERVER_URL";
|
|
174
|
+
else if (database === "sqlite")
|
|
175
|
+
connectionEnv = undefined;
|
|
176
|
+
else
|
|
177
|
+
connectionEnv = "DATABASE_URL";
|
|
178
|
+
}
|
|
179
|
+
const studioEnabled = overrides.studioExecute ?? false;
|
|
180
|
+
let studioExecute;
|
|
181
|
+
if (studioEnabled) {
|
|
182
|
+
const execProvider = overrides.studioExecuteProvider ??
|
|
183
|
+
(database !== "prisma" ? database : "postgres");
|
|
184
|
+
studioExecute = {
|
|
185
|
+
enabled: true,
|
|
186
|
+
provider: execProvider,
|
|
187
|
+
connectionEnv: overrides.studioExecuteConnectionEnv ?? (execProvider === "sqlite" ? undefined : (execProvider === "sqlserver" ? "SQLSERVER_URL" : "DATABASE_URL")),
|
|
188
|
+
sqliteFile: overrides.studioExecuteSqliteFile ?? (execProvider === "sqlite" ? overrides.sqliteFile : undefined),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
studioExecute = { enabled: false };
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
database,
|
|
196
|
+
connectionEnv,
|
|
197
|
+
sqliteFile: overrides.sqliteFile,
|
|
198
|
+
prismaSchema: overrides.prismaSchema,
|
|
199
|
+
schemaOut: overrides.schemaOut ?? "./askdb",
|
|
200
|
+
aiProvider,
|
|
201
|
+
aiKeyEnv: overrides.aiKeyEnv ?? aiDefaults.keyEnv,
|
|
202
|
+
aiModelEnv: overrides.aiModelEnv ?? aiDefaults.modelEnv,
|
|
203
|
+
ragStore: overrides.ragStore ?? "file",
|
|
204
|
+
pgvectorEnv: overrides.pgvectorEnv,
|
|
205
|
+
studioExecute,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/** Package maps — names come from an allowlist, not from user input */
|
|
209
|
+
const DB_DRIVER_PACKAGES = {
|
|
210
|
+
postgres: "pg",
|
|
211
|
+
mysql: "mysql2",
|
|
212
|
+
sqlite: "better-sqlite3",
|
|
213
|
+
sqlserver: "mssql",
|
|
214
|
+
};
|
|
215
|
+
export function buildInitInstallPlan(answers, specs) {
|
|
216
|
+
const packages = [
|
|
217
|
+
`@askdb/config@${specs.configSpec}`,
|
|
218
|
+
`dotenv@${specs.dotenvSpec}`,
|
|
219
|
+
];
|
|
220
|
+
const labels = ["@askdb/config", "dotenv"];
|
|
221
|
+
const driversNeeded = new Set();
|
|
222
|
+
// Live DB driver for introspection
|
|
223
|
+
if (answers.database !== "prisma") {
|
|
224
|
+
driversNeeded.add(answers.database);
|
|
225
|
+
}
|
|
226
|
+
// Live DB driver for Studio execute
|
|
227
|
+
if (answers.studioExecute.enabled) {
|
|
228
|
+
driversNeeded.add(answers.studioExecute.provider);
|
|
229
|
+
}
|
|
230
|
+
for (const db of driversNeeded) {
|
|
231
|
+
const pkg = DB_DRIVER_PACKAGES[db];
|
|
232
|
+
if (pkg) {
|
|
233
|
+
packages.push(pkg);
|
|
234
|
+
labels.push(pkg);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return { packages, labels };
|
|
238
|
+
}
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
// Package manager detection
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
89
242
|
const require = createRequire(import.meta.url);
|
|
90
243
|
/** Walk upward from `startDir` to find a directory containing `package.json`. */
|
|
91
244
|
export function findNearestPackageJsonDir(startDir) {
|
|
@@ -159,13 +312,7 @@ export function resolveInitDepSpecs() {
|
|
|
159
312
|
}
|
|
160
313
|
return { configSpec, dotenvSpec };
|
|
161
314
|
}
|
|
162
|
-
function
|
|
163
|
-
const configArg = `@askdb/config@${specs.configSpec}`;
|
|
164
|
-
const dotenvArg = `dotenv@${specs.dotenvSpec}`;
|
|
165
|
-
return [configArg, dotenvArg];
|
|
166
|
-
}
|
|
167
|
-
function runPackageManagerInstall(pm, packageDir, specs) {
|
|
168
|
-
const [c, d] = formatDepArgs(specs);
|
|
315
|
+
function defaultInstaller(pm, packageDir, packages) {
|
|
169
316
|
const env = { ...process.env, CI: process.env.CI ?? "true" };
|
|
170
317
|
const win = process.platform === "win32";
|
|
171
318
|
let cmd;
|
|
@@ -173,24 +320,389 @@ function runPackageManagerInstall(pm, packageDir, specs) {
|
|
|
173
320
|
switch (pm) {
|
|
174
321
|
case "pnpm":
|
|
175
322
|
cmd = win ? "pnpm.CMD" : "pnpm";
|
|
176
|
-
args = ["add",
|
|
323
|
+
args = ["add", ...packages];
|
|
177
324
|
break;
|
|
178
325
|
case "yarn":
|
|
179
326
|
cmd = win ? "yarn.cmd" : "yarn";
|
|
180
|
-
args = ["add",
|
|
327
|
+
args = ["add", ...packages];
|
|
181
328
|
break;
|
|
182
329
|
case "bun":
|
|
183
330
|
cmd = win ? "bun.exe" : "bun";
|
|
184
|
-
args = ["add",
|
|
331
|
+
args = ["add", ...packages];
|
|
185
332
|
break;
|
|
186
333
|
default:
|
|
187
334
|
cmd = win ? "npm.cmd" : "npm";
|
|
188
|
-
args = ["install", "--save",
|
|
335
|
+
args = ["install", "--save", ...packages];
|
|
189
336
|
}
|
|
190
337
|
const r = spawnSync(cmd, args, { cwd: packageDir, stdio: "inherit", env, shell: false });
|
|
191
338
|
return r.status === 0;
|
|
192
339
|
}
|
|
193
|
-
|
|
340
|
+
function formatManualInstallCommand(pm, packages) {
|
|
341
|
+
const pkgList = packages.join(" ");
|
|
342
|
+
switch (pm) {
|
|
343
|
+
case "pnpm":
|
|
344
|
+
return `pnpm add ${pkgList}`;
|
|
345
|
+
case "yarn":
|
|
346
|
+
return `yarn add ${pkgList}`;
|
|
347
|
+
case "bun":
|
|
348
|
+
return `bun add ${pkgList}`;
|
|
349
|
+
default:
|
|
350
|
+
return `npm install --save ${pkgList}`;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const VALID_DATABASES = ["postgres", "mysql", "sqlite", "sqlserver", "prisma"];
|
|
354
|
+
const VALID_AI_PROVIDERS = ["openai", "anthropic", "google", "azure", "foundry"];
|
|
355
|
+
const VALID_RAG_STORES = ["file", "memory", "pgvector"];
|
|
356
|
+
function parseOptions(argv) {
|
|
357
|
+
const opts = {
|
|
358
|
+
force: false,
|
|
359
|
+
path: DEFAULT_CONFIG_PATH,
|
|
360
|
+
skipInstall: false,
|
|
361
|
+
yes: false,
|
|
362
|
+
interactive: undefined,
|
|
363
|
+
noInteractive: false,
|
|
364
|
+
};
|
|
365
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
366
|
+
const arg = argv[i];
|
|
367
|
+
switch (arg) {
|
|
368
|
+
case "--force":
|
|
369
|
+
case "-f":
|
|
370
|
+
opts.force = true;
|
|
371
|
+
break;
|
|
372
|
+
case "--skip-install":
|
|
373
|
+
opts.skipInstall = true;
|
|
374
|
+
break;
|
|
375
|
+
case "--yes":
|
|
376
|
+
case "-y":
|
|
377
|
+
opts.yes = true;
|
|
378
|
+
break;
|
|
379
|
+
case "--interactive":
|
|
380
|
+
opts.interactive = true;
|
|
381
|
+
break;
|
|
382
|
+
case "--no-interactive":
|
|
383
|
+
opts.noInteractive = true;
|
|
384
|
+
break;
|
|
385
|
+
case "--studio-execute":
|
|
386
|
+
opts.studioExecute = true;
|
|
387
|
+
break;
|
|
388
|
+
case "--no-studio-execute":
|
|
389
|
+
opts.studioExecute = false;
|
|
390
|
+
break;
|
|
391
|
+
case "--path": {
|
|
392
|
+
const value = argv[++i];
|
|
393
|
+
if (!value || value.startsWith("--"))
|
|
394
|
+
throw new Error(`${arg} requires a value.`);
|
|
395
|
+
opts.path = value;
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
case "--database": {
|
|
399
|
+
const value = argv[++i];
|
|
400
|
+
if (!value || value.startsWith("--"))
|
|
401
|
+
throw new Error(`${arg} requires a value.`);
|
|
402
|
+
if (!VALID_DATABASES.includes(value)) {
|
|
403
|
+
throw new Error(`${arg}: invalid value "${value}". Allowed: ${VALID_DATABASES.join(", ")}.`);
|
|
404
|
+
}
|
|
405
|
+
opts.database = value;
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
case "--connection-env": {
|
|
409
|
+
const value = argv[++i];
|
|
410
|
+
if (!value || value.startsWith("--"))
|
|
411
|
+
throw new Error(`${arg} requires a value.`);
|
|
412
|
+
opts.connectionEnv = value;
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
case "--sqlite-file": {
|
|
416
|
+
const value = argv[++i];
|
|
417
|
+
if (!value || value.startsWith("--"))
|
|
418
|
+
throw new Error(`${arg} requires a value.`);
|
|
419
|
+
opts.sqliteFile = value;
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
case "--prisma-schema": {
|
|
423
|
+
const value = argv[++i];
|
|
424
|
+
if (!value || value.startsWith("--"))
|
|
425
|
+
throw new Error(`${arg} requires a value.`);
|
|
426
|
+
opts.prismaSchema = value;
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
case "--schema-out": {
|
|
430
|
+
const value = argv[++i];
|
|
431
|
+
if (!value || value.startsWith("--"))
|
|
432
|
+
throw new Error(`${arg} requires a value.`);
|
|
433
|
+
opts.schemaOut = value;
|
|
434
|
+
break;
|
|
435
|
+
}
|
|
436
|
+
case "--ai-provider": {
|
|
437
|
+
const value = argv[++i];
|
|
438
|
+
if (!value || value.startsWith("--"))
|
|
439
|
+
throw new Error(`${arg} requires a value.`);
|
|
440
|
+
if (!VALID_AI_PROVIDERS.includes(value)) {
|
|
441
|
+
throw new Error(`${arg}: invalid value "${value}". Allowed: ${VALID_AI_PROVIDERS.join(", ")}.`);
|
|
442
|
+
}
|
|
443
|
+
opts.aiProvider = value;
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
case "--ai-key-env": {
|
|
447
|
+
const value = argv[++i];
|
|
448
|
+
if (!value || value.startsWith("--"))
|
|
449
|
+
throw new Error(`${arg} requires a value.`);
|
|
450
|
+
opts.aiKeyEnv = value;
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
case "--ai-model-env": {
|
|
454
|
+
const value = argv[++i];
|
|
455
|
+
if (!value || value.startsWith("--"))
|
|
456
|
+
throw new Error(`${arg} requires a value.`);
|
|
457
|
+
opts.aiModelEnv = value;
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
460
|
+
case "--rag-store": {
|
|
461
|
+
const value = argv[++i];
|
|
462
|
+
if (!value || value.startsWith("--"))
|
|
463
|
+
throw new Error(`${arg} requires a value.`);
|
|
464
|
+
if (!VALID_RAG_STORES.includes(value)) {
|
|
465
|
+
throw new Error(`${arg}: invalid value "${value}". Allowed: ${VALID_RAG_STORES.join(", ")}.`);
|
|
466
|
+
}
|
|
467
|
+
opts.ragStore = value;
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
470
|
+
case "--pgvector-env": {
|
|
471
|
+
const value = argv[++i];
|
|
472
|
+
if (!value || value.startsWith("--"))
|
|
473
|
+
throw new Error(`${arg} requires a value.`);
|
|
474
|
+
opts.pgvectorEnv = value;
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
477
|
+
case "--help":
|
|
478
|
+
case "-h":
|
|
479
|
+
opts.path = "--help";
|
|
480
|
+
return opts;
|
|
481
|
+
default:
|
|
482
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
// Conflict checks
|
|
486
|
+
if (opts.yes && opts.interactive) {
|
|
487
|
+
throw new Error("--yes and --interactive cannot be used together.");
|
|
488
|
+
}
|
|
489
|
+
if (opts.interactive && opts.noInteractive) {
|
|
490
|
+
throw new Error("--interactive and --no-interactive cannot be used together.");
|
|
491
|
+
}
|
|
492
|
+
return opts;
|
|
493
|
+
}
|
|
494
|
+
// ---------------------------------------------------------------------------
|
|
495
|
+
// TTY wizard
|
|
496
|
+
// ---------------------------------------------------------------------------
|
|
497
|
+
const ENV_VAR_RE = /^[A-Z_][A-Z0-9_]*$/i;
|
|
498
|
+
function validateEnvVar(value) {
|
|
499
|
+
if (!value.trim())
|
|
500
|
+
return "Cannot be empty.";
|
|
501
|
+
if (!ENV_VAR_RE.test(value.trim()))
|
|
502
|
+
return "Use only letters, digits, and underscores (e.g. DATABASE_URL).";
|
|
503
|
+
return true;
|
|
504
|
+
}
|
|
505
|
+
function validatePath(value) {
|
|
506
|
+
if (!value.trim())
|
|
507
|
+
return "Cannot be empty.";
|
|
508
|
+
return true;
|
|
509
|
+
}
|
|
510
|
+
async function runWizard(prompter) {
|
|
511
|
+
const database = await prompter.select({
|
|
512
|
+
message: "Database source",
|
|
513
|
+
choices: [
|
|
514
|
+
{ name: "PostgreSQL", value: "postgres" },
|
|
515
|
+
{ name: "MySQL", value: "mysql" },
|
|
516
|
+
{ name: "SQLite", value: "sqlite" },
|
|
517
|
+
{ name: "SQL Server", value: "sqlserver" },
|
|
518
|
+
{ name: "Prisma (schema file, no live DB)", value: "prisma" },
|
|
519
|
+
],
|
|
520
|
+
default: "postgres",
|
|
521
|
+
});
|
|
522
|
+
let connectionEnv;
|
|
523
|
+
let sqliteFile;
|
|
524
|
+
let prismaSchema;
|
|
525
|
+
if (database === "sqlite") {
|
|
526
|
+
const raw = await prompter.input({
|
|
527
|
+
message: "SQLite file path or env var name (e.g. ./data/app.db or SQLITE_FILE)",
|
|
528
|
+
default: "SQLITE_FILE",
|
|
529
|
+
});
|
|
530
|
+
const trimmed = raw.trim();
|
|
531
|
+
if (trimmed.startsWith("./") || trimmed.startsWith("/")) {
|
|
532
|
+
sqliteFile = trimmed;
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
sqliteFile = trimmed;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
else if (database === "prisma") {
|
|
539
|
+
prismaSchema = await prompter.input({
|
|
540
|
+
message: "Path to schema.prisma",
|
|
541
|
+
default: "./prisma/schema.prisma",
|
|
542
|
+
validate: validatePath,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
const defaultEnv = database === "sqlserver" ? "SQLSERVER_URL" : "DATABASE_URL";
|
|
547
|
+
connectionEnv = await prompter.input({
|
|
548
|
+
message: `Env var name for connection URL`,
|
|
549
|
+
default: defaultEnv,
|
|
550
|
+
validate: validateEnvVar,
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
const schemaOut = await prompter.input({
|
|
554
|
+
message: "Schema output directory",
|
|
555
|
+
default: "./askdb",
|
|
556
|
+
validate: validatePath,
|
|
557
|
+
});
|
|
558
|
+
const aiProvider = await prompter.select({
|
|
559
|
+
message: "AI provider",
|
|
560
|
+
choices: [
|
|
561
|
+
{ name: "OpenAI", value: "openai" },
|
|
562
|
+
{ name: "Anthropic", value: "anthropic" },
|
|
563
|
+
{ name: "Google (Gemini)", value: "google" },
|
|
564
|
+
{ name: "Azure OpenAI", value: "azure" },
|
|
565
|
+
{ name: "Azure AI Foundry", value: "foundry" },
|
|
566
|
+
],
|
|
567
|
+
default: "openai",
|
|
568
|
+
});
|
|
569
|
+
const aiKeyEnv = await prompter.input({
|
|
570
|
+
message: `Env var for ${aiProvider} API key`,
|
|
571
|
+
default: AI_DEFAULTS[aiProvider].keyEnv,
|
|
572
|
+
validate: validateEnvVar,
|
|
573
|
+
});
|
|
574
|
+
const aiModelEnv = await prompter.input({
|
|
575
|
+
message: `Env var for ${aiProvider} model (leave blank to use provider default)`,
|
|
576
|
+
default: AI_DEFAULTS[aiProvider].modelEnv,
|
|
577
|
+
});
|
|
578
|
+
const ragStore = await prompter.select({
|
|
579
|
+
message: "RAG store",
|
|
580
|
+
choices: [
|
|
581
|
+
{ name: "File (default, no setup required)", value: "file" },
|
|
582
|
+
{ name: "Memory (fast, non-persistent)", value: "memory" },
|
|
583
|
+
{ name: "pgvector (Postgres vector store)", value: "pgvector" },
|
|
584
|
+
],
|
|
585
|
+
default: "file",
|
|
586
|
+
});
|
|
587
|
+
let pgvectorEnv;
|
|
588
|
+
if (ragStore === "pgvector") {
|
|
589
|
+
pgvectorEnv = await prompter.input({
|
|
590
|
+
message: "Env var for pgvector connection URL",
|
|
591
|
+
default: "ASKDB_PGVECTOR_URL",
|
|
592
|
+
validate: validateEnvVar,
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
const studioExecuteDefault = database !== "prisma";
|
|
596
|
+
const enableStudioExecute = await prompter.confirm({
|
|
597
|
+
message: "Enable Studio execute (run queries from the browser playground)?",
|
|
598
|
+
default: studioExecuteDefault,
|
|
599
|
+
});
|
|
600
|
+
let studioExecute;
|
|
601
|
+
if (!enableStudioExecute) {
|
|
602
|
+
studioExecute = { enabled: false };
|
|
603
|
+
}
|
|
604
|
+
else if (database === "prisma") {
|
|
605
|
+
// Prisma: need to choose a live provider
|
|
606
|
+
const execProvider = await prompter.select({
|
|
607
|
+
message: "Studio execute needs a live database provider. Which one?",
|
|
608
|
+
choices: [
|
|
609
|
+
{ name: "PostgreSQL", value: "postgres" },
|
|
610
|
+
{ name: "MySQL", value: "mysql" },
|
|
611
|
+
{ name: "SQLite", value: "sqlite" },
|
|
612
|
+
{ name: "SQL Server", value: "sqlserver" },
|
|
613
|
+
],
|
|
614
|
+
default: "postgres",
|
|
615
|
+
});
|
|
616
|
+
if (execProvider === "sqlite") {
|
|
617
|
+
const execSqliteFile = await prompter.input({
|
|
618
|
+
message: "SQLite file path or env var name for Studio execute",
|
|
619
|
+
default: "SQLITE_FILE",
|
|
620
|
+
});
|
|
621
|
+
studioExecute = {
|
|
622
|
+
enabled: true,
|
|
623
|
+
provider: "sqlite",
|
|
624
|
+
sqliteFile: execSqliteFile.trim(),
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
const execEnv = execProvider === "sqlserver" ? "SQLSERVER_URL" : "DATABASE_URL";
|
|
629
|
+
const execConnEnv = await prompter.input({
|
|
630
|
+
message: `Env var for Studio execute connection URL`,
|
|
631
|
+
default: execEnv,
|
|
632
|
+
validate: validateEnvVar,
|
|
633
|
+
});
|
|
634
|
+
studioExecute = {
|
|
635
|
+
enabled: true,
|
|
636
|
+
provider: execProvider,
|
|
637
|
+
connectionEnv: execConnEnv,
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
else {
|
|
642
|
+
const execProvider = database;
|
|
643
|
+
if (execProvider === "sqlite") {
|
|
644
|
+
studioExecute = {
|
|
645
|
+
enabled: true,
|
|
646
|
+
provider: "sqlite",
|
|
647
|
+
sqliteFile: sqliteFile,
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
651
|
+
studioExecute = {
|
|
652
|
+
enabled: true,
|
|
653
|
+
provider: execProvider,
|
|
654
|
+
connectionEnv: connectionEnv,
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
return {
|
|
659
|
+
database,
|
|
660
|
+
connectionEnv,
|
|
661
|
+
sqliteFile,
|
|
662
|
+
prismaSchema,
|
|
663
|
+
schemaOut,
|
|
664
|
+
aiProvider,
|
|
665
|
+
aiKeyEnv,
|
|
666
|
+
aiModelEnv: aiModelEnv.trim() || undefined,
|
|
667
|
+
ragStore,
|
|
668
|
+
pgvectorEnv,
|
|
669
|
+
studioExecute,
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
// ---------------------------------------------------------------------------
|
|
673
|
+
// Real Inquirer prompter
|
|
674
|
+
// ---------------------------------------------------------------------------
|
|
675
|
+
async function buildInquirerPrompter() {
|
|
676
|
+
const { select, input, confirm } = await import("@inquirer/prompts");
|
|
677
|
+
return {
|
|
678
|
+
select: (opts) => select({ message: opts.message, choices: opts.choices, default: opts.default }),
|
|
679
|
+
input: (opts) => input({ message: opts.message, default: opts.default, validate: opts.validate }),
|
|
680
|
+
confirm: (opts) => confirm({ message: opts.message, default: opts.default }),
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
// ---------------------------------------------------------------------------
|
|
684
|
+
// Next steps printer
|
|
685
|
+
// ---------------------------------------------------------------------------
|
|
686
|
+
function printNextSteps(answers) {
|
|
687
|
+
const lines = ["\nNext steps:"];
|
|
688
|
+
lines.push(" 1. Set env vars to match the `env(\"...\")` calls in askdb.config.ts.");
|
|
689
|
+
if (answers.database !== "prisma") {
|
|
690
|
+
lines.push(" 2. Introspect your database: askdb introspect");
|
|
691
|
+
}
|
|
692
|
+
else {
|
|
693
|
+
lines.push(" 2. Introspect your Prisma schema: askdb introspect");
|
|
694
|
+
}
|
|
695
|
+
if (answers.studioExecute.enabled) {
|
|
696
|
+
lines.push(" 3. Open Studio: askdb studio");
|
|
697
|
+
}
|
|
698
|
+
lines.push(` ${answers.studioExecute.enabled ? "4" : "3"}. Ask a question: askdb ask --question "Which customers signed up last week?"`);
|
|
699
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
700
|
+
}
|
|
701
|
+
// ---------------------------------------------------------------------------
|
|
702
|
+
// Main entry point
|
|
703
|
+
// ---------------------------------------------------------------------------
|
|
704
|
+
export async function runInitCli(argv, _installer) {
|
|
705
|
+
const installer = _installer ?? defaultInstaller;
|
|
194
706
|
let opts;
|
|
195
707
|
try {
|
|
196
708
|
opts = parseOptions(argv);
|
|
@@ -200,7 +712,7 @@ export function runInitCli(argv) {
|
|
|
200
712
|
process.stderr.write(`${msg}\n`);
|
|
201
713
|
return 1;
|
|
202
714
|
}
|
|
203
|
-
if (opts.path === "--help"
|
|
715
|
+
if (opts.path === "--help") {
|
|
204
716
|
printHelp();
|
|
205
717
|
return 0;
|
|
206
718
|
}
|
|
@@ -211,101 +723,155 @@ export function runInitCli(argv) {
|
|
|
211
723
|
`Use \`askdb init --force\` to overwrite.\n`);
|
|
212
724
|
return 1;
|
|
213
725
|
}
|
|
726
|
+
// Determine interactive mode
|
|
727
|
+
const isTTY = Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY);
|
|
728
|
+
const wantInteractive = opts.interactive === true || (!opts.yes && !opts.noInteractive && isTTY);
|
|
729
|
+
if (opts.interactive && !isTTY) {
|
|
730
|
+
process.stderr.write("--interactive requires stdin and stdout to be TTYs.\n");
|
|
731
|
+
return 1;
|
|
732
|
+
}
|
|
733
|
+
let answers;
|
|
734
|
+
if (wantInteractive) {
|
|
735
|
+
let prompter;
|
|
736
|
+
try {
|
|
737
|
+
prompter = await buildInquirerPrompter();
|
|
738
|
+
}
|
|
739
|
+
catch {
|
|
740
|
+
process.stderr.write("Failed to load @inquirer/prompts. Falling back to non-interactive mode.\n");
|
|
741
|
+
answers = resolveDefaultInitAnswers(optsToOverrides(opts));
|
|
742
|
+
return await finishInit(configTarget, answers, opts, installer);
|
|
743
|
+
}
|
|
744
|
+
let wizardResult;
|
|
745
|
+
try {
|
|
746
|
+
wizardResult = await runWizard(prompter);
|
|
747
|
+
}
|
|
748
|
+
catch {
|
|
749
|
+
process.stderr.write("\nSetup cancelled.\n");
|
|
750
|
+
return 1;
|
|
751
|
+
}
|
|
752
|
+
if (!wizardResult) {
|
|
753
|
+
process.stderr.write("\nSetup cancelled.\n");
|
|
754
|
+
return 1;
|
|
755
|
+
}
|
|
756
|
+
answers = wizardResult;
|
|
757
|
+
// Summary and confirmation
|
|
758
|
+
const specs = resolveInitDepSpecs();
|
|
759
|
+
const plan = buildInitInstallPlan(answers, specs);
|
|
760
|
+
process.stdout.write("\n--- Summary ---\n");
|
|
761
|
+
process.stdout.write(` Config path: ${configTarget}\n`);
|
|
762
|
+
process.stdout.write(` Database: ${answers.database}\n`);
|
|
763
|
+
process.stdout.write(` AI provider: ${answers.aiProvider}\n`);
|
|
764
|
+
process.stdout.write(` RAG store: ${answers.ragStore}\n`);
|
|
765
|
+
process.stdout.write(` Studio execute: ${answers.studioExecute.enabled ? `enabled (${answers.studioExecute.provider})` : "disabled"}\n`);
|
|
766
|
+
if (!opts.skipInstall) {
|
|
767
|
+
process.stdout.write(` Packages: ${plan.labels.join(", ")}\n`);
|
|
768
|
+
}
|
|
769
|
+
process.stdout.write("---------------\n\n");
|
|
770
|
+
let confirmed;
|
|
771
|
+
try {
|
|
772
|
+
confirmed = await prompter.confirm({ message: "Write config and install packages?", default: true });
|
|
773
|
+
}
|
|
774
|
+
catch {
|
|
775
|
+
process.stderr.write("\nSetup cancelled.\n");
|
|
776
|
+
return 1;
|
|
777
|
+
}
|
|
778
|
+
if (!confirmed) {
|
|
779
|
+
process.stdout.write("Cancelled. No files written.\n");
|
|
780
|
+
return 1;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
else {
|
|
784
|
+
answers = resolveDefaultInitAnswers(optsToOverrides(opts));
|
|
785
|
+
}
|
|
786
|
+
return await finishInit(configTarget, answers, opts, installer);
|
|
787
|
+
}
|
|
788
|
+
function optsToOverrides(opts) {
|
|
789
|
+
return {
|
|
790
|
+
database: opts.database,
|
|
791
|
+
connectionEnv: opts.connectionEnv,
|
|
792
|
+
sqliteFile: opts.sqliteFile,
|
|
793
|
+
prismaSchema: opts.prismaSchema,
|
|
794
|
+
schemaOut: opts.schemaOut,
|
|
795
|
+
aiProvider: opts.aiProvider,
|
|
796
|
+
aiKeyEnv: opts.aiKeyEnv,
|
|
797
|
+
aiModelEnv: opts.aiModelEnv,
|
|
798
|
+
ragStore: opts.ragStore,
|
|
799
|
+
pgvectorEnv: opts.pgvectorEnv,
|
|
800
|
+
studioExecute: opts.studioExecute,
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
async function finishInit(configTarget, answers, opts, installer) {
|
|
804
|
+
const configContent = renderInitConfig(answers);
|
|
214
805
|
try {
|
|
215
|
-
writeFileSync(configTarget,
|
|
806
|
+
writeFileSync(configTarget, configContent, { encoding: "utf8" });
|
|
216
807
|
}
|
|
217
808
|
catch (e) {
|
|
218
809
|
const msg = e instanceof Error ? e.message : String(e);
|
|
219
|
-
process.stderr.write(`Failed to write
|
|
810
|
+
process.stderr.write(`Failed to write config: ${msg}\n`);
|
|
220
811
|
return 1;
|
|
221
812
|
}
|
|
222
813
|
process.stdout.write(`Wrote:\n - ${configTarget}\n`);
|
|
223
814
|
if (!opts.skipInstall) {
|
|
224
815
|
const pkgRoot = findNearestPackageJsonDir(process.cwd());
|
|
225
816
|
const specs = resolveInitDepSpecs();
|
|
226
|
-
const
|
|
817
|
+
const plan = buildInitInstallPlan(answers, specs);
|
|
227
818
|
if (!pkgRoot) {
|
|
228
819
|
process.stdout.write("\nNo package.json found in this directory or any parent directory.\n" +
|
|
229
820
|
"Install the template imports yourself (from a project with a package.json):\n" +
|
|
230
|
-
` npm install --save ${
|
|
821
|
+
` npm install --save ${plan.packages.join(" ")}\n\n`);
|
|
231
822
|
}
|
|
232
823
|
else if (isLikelyWorkspaceRoot(pkgRoot)) {
|
|
233
824
|
process.stdout.write("\nSkipped automatic dependency install (workspace / monorepo root).\n" +
|
|
234
825
|
"Add these to the package that will load this config (or run `askdb init` from that package directory):\n" +
|
|
235
|
-
` npm install --save ${
|
|
826
|
+
` npm install --save ${plan.packages.join(" ")}\n\n`);
|
|
236
827
|
}
|
|
237
828
|
else {
|
|
238
829
|
const pm = detectPackageManager(pkgRoot);
|
|
239
|
-
process.stdout.write(`\nInstalling
|
|
240
|
-
const ok =
|
|
830
|
+
process.stdout.write(`\nInstalling dependencies with ${pm} in ${pkgRoot} …\n`);
|
|
831
|
+
const ok = installer(pm, pkgRoot, plan.packages);
|
|
241
832
|
if (!ok) {
|
|
242
|
-
process.stderr.write("Dependency install failed. The config file was written, but
|
|
833
|
+
process.stderr.write("Dependency install failed. The config file was written, but packages are not installed yet.\n" +
|
|
243
834
|
` cd ${pkgRoot}\n` +
|
|
244
|
-
` ${formatManualInstallCommand(pm,
|
|
835
|
+
` ${formatManualInstallCommand(pm, plan.packages)}\n`);
|
|
245
836
|
return 1;
|
|
246
837
|
}
|
|
247
|
-
process.stdout.write(
|
|
838
|
+
process.stdout.write(`Installed: ${plan.labels.join(", ")}.\n`);
|
|
248
839
|
}
|
|
249
840
|
}
|
|
250
|
-
|
|
251
|
-
" 1. Create a `.env` (optional) or export variables to match the `env(\"...\")` calls in this file — see the header comment for examples.\n" +
|
|
252
|
-
" 2. For live NL→SQL, set an OpenAI-compatible key (or use ASKDB_MOCK_SQL in tests).\n" +
|
|
253
|
-
" 3. For a Schema v2 directory from Postgres/Prisma: `askdb introspect ...` (see --help). With MY_INTROSPECT_OUTPUT_DIR / ASKDB_INTROSPECT_OUT you can omit `--out` when a default output dir is set.\n" +
|
|
254
|
-
" 4. With an existing schema artifact: `askdb ask --schema <path> --question \"...\"`.\n");
|
|
841
|
+
printNextSteps(answers);
|
|
255
842
|
return 0;
|
|
256
843
|
}
|
|
257
|
-
function formatManualInstallCommand(pm, specs) {
|
|
258
|
-
const [c, d] = formatDepArgs(specs);
|
|
259
|
-
switch (pm) {
|
|
260
|
-
case "pnpm":
|
|
261
|
-
return `pnpm add ${c} ${d}`;
|
|
262
|
-
case "yarn":
|
|
263
|
-
return `yarn add ${c} ${d}`;
|
|
264
|
-
case "bun":
|
|
265
|
-
return `bun add ${c} ${d}`;
|
|
266
|
-
default:
|
|
267
|
-
return `npm install --save ${c} ${d}`;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
function parseOptions(argv) {
|
|
271
|
-
const opts = { force: false, path: DEFAULT_CONFIG_PATH, skipInstall: false };
|
|
272
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
273
|
-
const arg = argv[i];
|
|
274
|
-
switch (arg) {
|
|
275
|
-
case "--force":
|
|
276
|
-
case "-f":
|
|
277
|
-
opts.force = true;
|
|
278
|
-
break;
|
|
279
|
-
case "--skip-install":
|
|
280
|
-
opts.skipInstall = true;
|
|
281
|
-
break;
|
|
282
|
-
case "--path": {
|
|
283
|
-
const value = argv[++i];
|
|
284
|
-
if (!value || value.startsWith("--")) {
|
|
285
|
-
throw new Error(`${arg} requires a value.`);
|
|
286
|
-
}
|
|
287
|
-
opts.path = value;
|
|
288
|
-
break;
|
|
289
|
-
}
|
|
290
|
-
case "--help":
|
|
291
|
-
case "-h":
|
|
292
|
-
opts.path = "--help";
|
|
293
|
-
return opts;
|
|
294
|
-
default:
|
|
295
|
-
throw new Error(`Unknown option: ${arg}`);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
return opts;
|
|
299
|
-
}
|
|
300
844
|
function printHelp() {
|
|
301
845
|
process.stdout.write([
|
|
302
|
-
"askdb init - Create askdb.config.ts
|
|
846
|
+
"askdb init - Create askdb.config.ts tailored to your database and AI provider",
|
|
303
847
|
"",
|
|
304
848
|
"Usage:",
|
|
305
|
-
" askdb init
|
|
849
|
+
" askdb init Start wizard in a TTY, or use defaults in CI",
|
|
850
|
+
" askdb init --yes Accept defaults without prompts",
|
|
851
|
+
" askdb init --no-interactive Same as --yes",
|
|
306
852
|
" askdb init --force Overwrite an existing askdb.config.ts",
|
|
307
|
-
" askdb init --path <file> Write
|
|
308
|
-
" askdb init --skip-install Only write
|
|
853
|
+
" askdb init --path <file> Write config to a custom file path",
|
|
854
|
+
" askdb init --skip-install Only write the file; do not install packages",
|
|
855
|
+
"",
|
|
856
|
+
"Database options:",
|
|
857
|
+
" --database <db> postgres|mysql|sqlite|sqlserver|prisma (default: postgres)",
|
|
858
|
+
" --connection-env <name> Env var name for connection URL",
|
|
859
|
+
" --sqlite-file <path-or-env> SQLite file path or env var name",
|
|
860
|
+
" --prisma-schema <path> Path to schema.prisma",
|
|
861
|
+
" --schema-out <dir> Schema output directory (default: ./askdb)",
|
|
862
|
+
"",
|
|
863
|
+
"AI options:",
|
|
864
|
+
" --ai-provider <name> openai|anthropic|google|azure|foundry (default: openai)",
|
|
865
|
+
" --ai-key-env <name> Env var name for API key",
|
|
866
|
+
" --ai-model-env <name> Env var name for model override",
|
|
867
|
+
"",
|
|
868
|
+
"RAG options:",
|
|
869
|
+
" --rag-store <name> file|memory|pgvector (default: file)",
|
|
870
|
+
" --pgvector-env <name> Env var name for pgvector URL",
|
|
871
|
+
"",
|
|
872
|
+
"Studio options:",
|
|
873
|
+
" --studio-execute Enable Studio execute (default: off)",
|
|
874
|
+
" --no-studio-execute Disable Studio execute",
|
|
309
875
|
"",
|
|
310
876
|
].join("\n"));
|
|
311
877
|
}
|