askdb 1.0.0-beta.27 → 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 +74 -64
- 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 +704 -136
- package/dist/init.js.map +1 -1
- package/package.json +17 -16
package/dist/init.js
CHANGED
|
@@ -4,86 +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
|
-
|
|
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}",
|
|
19
23
|
providerConfig: {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
apiKey: env("OPENAI_API_KEY"),
|
|
23
|
-
model: env("OPENAI_MODEL"),
|
|
24
|
+
${aiProvider}: {
|
|
25
|
+
apiKey: env("${aiKeyEnv}"),${modelLine}
|
|
24
26
|
},
|
|
25
27
|
},
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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: {
|
|
30
36
|
provider: "postgres",
|
|
31
37
|
providerConfig: {
|
|
32
38
|
postgres: {
|
|
33
|
-
|
|
34
|
-
databaseUrl: env("DATABASE_URL"),
|
|
39
|
+
databaseUrl: env("${connectionEnv ?? "DATABASE_URL"}"),
|
|
35
40
|
},
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
embedderConfig: {
|
|
45
|
-
openai: {
|
|
46
|
-
model: env("ASKDB_RAG_EMBEDDER_MODEL"),
|
|
47
|
-
dimension: env("ASKDB_RAG_EMBEDDER_DIMENSIONS"),
|
|
48
|
-
apiKey: env("OPENAI_API_KEY"),
|
|
49
|
-
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"}"),
|
|
50
49
|
},
|
|
51
|
-
}
|
|
52
|
-
|
|
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: {},
|
|
53
96
|
store: "file",
|
|
54
97
|
storeConfig: {
|
|
55
98
|
file: {},
|
|
99
|
+
},
|
|
100
|
+
},`;
|
|
101
|
+
case "memory":
|
|
102
|
+
return ` rag: {
|
|
103
|
+
embedder: "mock",
|
|
104
|
+
embedderConfig: {},
|
|
105
|
+
store: "memory",
|
|
106
|
+
storeConfig: {
|
|
56
107
|
memory: {},
|
|
108
|
+
},
|
|
109
|
+
},`;
|
|
110
|
+
case "pgvector":
|
|
111
|
+
return ` rag: {
|
|
112
|
+
embedder: "mock",
|
|
113
|
+
embedderConfig: {},
|
|
114
|
+
store: "pgvector",
|
|
115
|
+
storeConfig: {
|
|
57
116
|
pgvector: {
|
|
58
|
-
databaseUrl: env("ASKDB_PGVECTOR_URL"),
|
|
59
|
-
dimensions: env("ASKDB_RAG_EMBEDDER_DIMENSIONS"),
|
|
117
|
+
databaseUrl: env("${pgvectorEnv ?? "ASKDB_PGVECTOR_URL"}"),
|
|
60
118
|
},
|
|
61
119
|
},
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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: {
|
|
74
133
|
execute: {
|
|
75
|
-
|
|
76
|
-
|
|
134
|
+
provider: "sqlite",
|
|
135
|
+
file: ${fileExpr},
|
|
77
136
|
},
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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}"),
|
|
83
144
|
},
|
|
84
|
-
}
|
|
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")}
|
|
85
163
|
} satisfies AskDbConfig);
|
|
86
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
|
+
// ---------------------------------------------------------------------------
|
|
87
242
|
const require = createRequire(import.meta.url);
|
|
88
243
|
/** Walk upward from `startDir` to find a directory containing `package.json`. */
|
|
89
244
|
export function findNearestPackageJsonDir(startDir) {
|
|
@@ -145,7 +300,7 @@ export function resolveInitDepSpecs() {
|
|
|
145
300
|
configSpec = "latest";
|
|
146
301
|
}
|
|
147
302
|
}
|
|
148
|
-
let dotenvSpec = "^
|
|
303
|
+
let dotenvSpec = "^17.4.2";
|
|
149
304
|
try {
|
|
150
305
|
const cfgPkgPath = require.resolve("@askdb/config/package.json");
|
|
151
306
|
const cfg = JSON.parse(readFileSync(cfgPkgPath, "utf8"));
|
|
@@ -157,13 +312,7 @@ export function resolveInitDepSpecs() {
|
|
|
157
312
|
}
|
|
158
313
|
return { configSpec, dotenvSpec };
|
|
159
314
|
}
|
|
160
|
-
function
|
|
161
|
-
const configArg = `@askdb/config@${specs.configSpec}`;
|
|
162
|
-
const dotenvArg = `dotenv@${specs.dotenvSpec}`;
|
|
163
|
-
return [configArg, dotenvArg];
|
|
164
|
-
}
|
|
165
|
-
function runPackageManagerInstall(pm, packageDir, specs) {
|
|
166
|
-
const [c, d] = formatDepArgs(specs);
|
|
315
|
+
function defaultInstaller(pm, packageDir, packages) {
|
|
167
316
|
const env = { ...process.env, CI: process.env.CI ?? "true" };
|
|
168
317
|
const win = process.platform === "win32";
|
|
169
318
|
let cmd;
|
|
@@ -171,24 +320,389 @@ function runPackageManagerInstall(pm, packageDir, specs) {
|
|
|
171
320
|
switch (pm) {
|
|
172
321
|
case "pnpm":
|
|
173
322
|
cmd = win ? "pnpm.CMD" : "pnpm";
|
|
174
|
-
args = ["add",
|
|
323
|
+
args = ["add", ...packages];
|
|
175
324
|
break;
|
|
176
325
|
case "yarn":
|
|
177
326
|
cmd = win ? "yarn.cmd" : "yarn";
|
|
178
|
-
args = ["add",
|
|
327
|
+
args = ["add", ...packages];
|
|
179
328
|
break;
|
|
180
329
|
case "bun":
|
|
181
330
|
cmd = win ? "bun.exe" : "bun";
|
|
182
|
-
args = ["add",
|
|
331
|
+
args = ["add", ...packages];
|
|
183
332
|
break;
|
|
184
333
|
default:
|
|
185
334
|
cmd = win ? "npm.cmd" : "npm";
|
|
186
|
-
args = ["install", "--save",
|
|
335
|
+
args = ["install", "--save", ...packages];
|
|
187
336
|
}
|
|
188
337
|
const r = spawnSync(cmd, args, { cwd: packageDir, stdio: "inherit", env, shell: false });
|
|
189
338
|
return r.status === 0;
|
|
190
339
|
}
|
|
191
|
-
|
|
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;
|
|
192
706
|
let opts;
|
|
193
707
|
try {
|
|
194
708
|
opts = parseOptions(argv);
|
|
@@ -198,7 +712,7 @@ export function runInitCli(argv) {
|
|
|
198
712
|
process.stderr.write(`${msg}\n`);
|
|
199
713
|
return 1;
|
|
200
714
|
}
|
|
201
|
-
if (opts.path === "--help"
|
|
715
|
+
if (opts.path === "--help") {
|
|
202
716
|
printHelp();
|
|
203
717
|
return 0;
|
|
204
718
|
}
|
|
@@ -209,101 +723,155 @@ export function runInitCli(argv) {
|
|
|
209
723
|
`Use \`askdb init --force\` to overwrite.\n`);
|
|
210
724
|
return 1;
|
|
211
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);
|
|
212
805
|
try {
|
|
213
|
-
writeFileSync(configTarget,
|
|
806
|
+
writeFileSync(configTarget, configContent, { encoding: "utf8" });
|
|
214
807
|
}
|
|
215
808
|
catch (e) {
|
|
216
809
|
const msg = e instanceof Error ? e.message : String(e);
|
|
217
|
-
process.stderr.write(`Failed to write
|
|
810
|
+
process.stderr.write(`Failed to write config: ${msg}\n`);
|
|
218
811
|
return 1;
|
|
219
812
|
}
|
|
220
813
|
process.stdout.write(`Wrote:\n - ${configTarget}\n`);
|
|
221
814
|
if (!opts.skipInstall) {
|
|
222
815
|
const pkgRoot = findNearestPackageJsonDir(process.cwd());
|
|
223
816
|
const specs = resolveInitDepSpecs();
|
|
224
|
-
const
|
|
817
|
+
const plan = buildInitInstallPlan(answers, specs);
|
|
225
818
|
if (!pkgRoot) {
|
|
226
819
|
process.stdout.write("\nNo package.json found in this directory or any parent directory.\n" +
|
|
227
820
|
"Install the template imports yourself (from a project with a package.json):\n" +
|
|
228
|
-
` npm install --save ${
|
|
821
|
+
` npm install --save ${plan.packages.join(" ")}\n\n`);
|
|
229
822
|
}
|
|
230
823
|
else if (isLikelyWorkspaceRoot(pkgRoot)) {
|
|
231
824
|
process.stdout.write("\nSkipped automatic dependency install (workspace / monorepo root).\n" +
|
|
232
825
|
"Add these to the package that will load this config (or run `askdb init` from that package directory):\n" +
|
|
233
|
-
` npm install --save ${
|
|
826
|
+
` npm install --save ${plan.packages.join(" ")}\n\n`);
|
|
234
827
|
}
|
|
235
828
|
else {
|
|
236
829
|
const pm = detectPackageManager(pkgRoot);
|
|
237
|
-
process.stdout.write(`\nInstalling
|
|
238
|
-
const ok =
|
|
830
|
+
process.stdout.write(`\nInstalling dependencies with ${pm} in ${pkgRoot} …\n`);
|
|
831
|
+
const ok = installer(pm, pkgRoot, plan.packages);
|
|
239
832
|
if (!ok) {
|
|
240
|
-
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" +
|
|
241
834
|
` cd ${pkgRoot}\n` +
|
|
242
|
-
` ${formatManualInstallCommand(pm,
|
|
835
|
+
` ${formatManualInstallCommand(pm, plan.packages)}\n`);
|
|
243
836
|
return 1;
|
|
244
837
|
}
|
|
245
|
-
process.stdout.write(
|
|
838
|
+
process.stdout.write(`Installed: ${plan.labels.join(", ")}.\n`);
|
|
246
839
|
}
|
|
247
840
|
}
|
|
248
|
-
|
|
249
|
-
" 1. Create a `.env` (optional) or export variables to match the `env(\"...\")` calls in this file — see the header comment for examples.\n" +
|
|
250
|
-
" 2. For live NL→SQL, set an OpenAI-compatible key (or use ASKDB_MOCK_SQL in tests).\n" +
|
|
251
|
-
" 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" +
|
|
252
|
-
" 4. With an existing schema artifact: `askdb ask --schema <path> --question \"...\"`.\n");
|
|
841
|
+
printNextSteps(answers);
|
|
253
842
|
return 0;
|
|
254
843
|
}
|
|
255
|
-
function formatManualInstallCommand(pm, specs) {
|
|
256
|
-
const [c, d] = formatDepArgs(specs);
|
|
257
|
-
switch (pm) {
|
|
258
|
-
case "pnpm":
|
|
259
|
-
return `pnpm add ${c} ${d}`;
|
|
260
|
-
case "yarn":
|
|
261
|
-
return `yarn add ${c} ${d}`;
|
|
262
|
-
case "bun":
|
|
263
|
-
return `bun add ${c} ${d}`;
|
|
264
|
-
default:
|
|
265
|
-
return `npm install --save ${c} ${d}`;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
function parseOptions(argv) {
|
|
269
|
-
const opts = { force: false, path: DEFAULT_CONFIG_PATH, skipInstall: false };
|
|
270
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
271
|
-
const arg = argv[i];
|
|
272
|
-
switch (arg) {
|
|
273
|
-
case "--force":
|
|
274
|
-
case "-f":
|
|
275
|
-
opts.force = true;
|
|
276
|
-
break;
|
|
277
|
-
case "--skip-install":
|
|
278
|
-
opts.skipInstall = true;
|
|
279
|
-
break;
|
|
280
|
-
case "--path": {
|
|
281
|
-
const value = argv[++i];
|
|
282
|
-
if (!value || value.startsWith("--")) {
|
|
283
|
-
throw new Error(`${arg} requires a value.`);
|
|
284
|
-
}
|
|
285
|
-
opts.path = value;
|
|
286
|
-
break;
|
|
287
|
-
}
|
|
288
|
-
case "--help":
|
|
289
|
-
case "-h":
|
|
290
|
-
opts.path = "--help";
|
|
291
|
-
return opts;
|
|
292
|
-
default:
|
|
293
|
-
throw new Error(`Unknown option: ${arg}`);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
return opts;
|
|
297
|
-
}
|
|
298
844
|
function printHelp() {
|
|
299
845
|
process.stdout.write([
|
|
300
|
-
"askdb init - Create askdb.config.ts
|
|
846
|
+
"askdb init - Create askdb.config.ts tailored to your database and AI provider",
|
|
301
847
|
"",
|
|
302
848
|
"Usage:",
|
|
303
|
-
" 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",
|
|
304
852
|
" askdb init --force Overwrite an existing askdb.config.ts",
|
|
305
|
-
" askdb init --path <file> Write
|
|
306
|
-
" 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",
|
|
307
875
|
"",
|
|
308
876
|
].join("\n"));
|
|
309
877
|
}
|