@nzpr/kb 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/lib/cli-common.js +1 -1
- package/lib/cli.js +44 -13
- package/lib/init-repo-interactive.js +222 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@ The public surface of `@nzpr/kb` is the `kb` CLI.
|
|
|
23
23
|
|
|
24
24
|
- `kb init-repo`
|
|
25
25
|
Initialize a knowledge base workspace.
|
|
26
|
+
Use `kb init-repo --interactive` for a guided setup flow.
|
|
26
27
|
|
|
27
28
|
- `kb create --title TEXT --text TEXT`
|
|
28
29
|
Propose a new knowledge document or a substantial update.
|
|
@@ -95,6 +96,12 @@ Bootstrap that repo with:
|
|
|
95
96
|
kb init-repo --dir /path/to/knowledge-repo
|
|
96
97
|
```
|
|
97
98
|
|
|
99
|
+
If you want the CLI to walk you through setup and tell you what to do next:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
kb init-repo --interactive
|
|
103
|
+
```
|
|
104
|
+
|
|
98
105
|
Or bootstrap and configure it in one step:
|
|
99
106
|
|
|
100
107
|
```bash
|
|
@@ -122,6 +129,14 @@ Inside that knowledge repo, the normal flow is:
|
|
|
122
129
|
- database preflight and schema initialization
|
|
123
130
|
- GitHub repo labels, variables, and secrets
|
|
124
131
|
|
|
132
|
+
In interactive mode it also:
|
|
133
|
+
|
|
134
|
+
- asks which repo and directory you want to initialize
|
|
135
|
+
- asks whether to wire GitHub setup now
|
|
136
|
+
- asks whether to verify the database now
|
|
137
|
+
- collects embedding settings if you want remote embeddings
|
|
138
|
+
- prints the next actions after initialization
|
|
139
|
+
|
|
125
140
|
If one remote step fails, the scaffold still stays in place and the command tells you what to rerun.
|
|
126
141
|
|
|
127
142
|
## Runtime
|
package/lib/cli-common.js
CHANGED
package/lib/cli.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from "./config.js";
|
|
10
10
|
import { connect, initDb } from "./db.js";
|
|
11
11
|
import { ingestDocuments } from "./index.js";
|
|
12
|
+
import { collectInitRepoInteractiveOptions } from "./init-repo-interactive.js";
|
|
12
13
|
import { createGitHubIssueFromText } from "./kb-proposals.js";
|
|
13
14
|
import { bootstrapKnowledgeRepo } from "./repo-init.js";
|
|
14
15
|
import { askIndex, doctor, knowledgeCatalog, listDocuments, searchIndex, snippet } from "./search.js";
|
|
@@ -60,19 +61,32 @@ export async function main(argv) {
|
|
|
60
61
|
return 0;
|
|
61
62
|
}
|
|
62
63
|
case "init-repo": {
|
|
64
|
+
const initRepoOptions = flags.interactive
|
|
65
|
+
? await collectInitRepoInteractiveOptions({ flags })
|
|
66
|
+
: {
|
|
67
|
+
targetDir: flags.dir ?? process.cwd(),
|
|
68
|
+
repo: flags.repo ?? resolveGitHubRepository(),
|
|
69
|
+
databaseUrl: flags["database-url"] ?? process.env.KB_DATABASE_URL ?? null,
|
|
70
|
+
embeddingMode: flags["embedding-mode"] ?? process.env.KB_EMBEDDING_MODE ?? null,
|
|
71
|
+
embeddingApiUrl: flags["embedding-api-url"] ?? process.env.KB_EMBEDDING_API_URL ?? null,
|
|
72
|
+
embeddingModel: flags["embedding-model"] ?? process.env.KB_EMBEDDING_MODEL ?? null,
|
|
73
|
+
embeddingApiKey: flags["embedding-api-key"] ?? process.env.KB_EMBEDDING_API_KEY ?? null,
|
|
74
|
+
dbConnectTimeoutMs:
|
|
75
|
+
flags["db-connect-timeout-ms"] ?? process.env.KB_DB_CONNECT_TIMEOUT_MS ?? null,
|
|
76
|
+
repoAutomationToken:
|
|
77
|
+
flags["repo-automation-token"] ?? process.env.KB_REPO_AUTOMATION_TOKEN ?? null
|
|
78
|
+
};
|
|
63
79
|
const result = await bootstrapKnowledgeRepo({
|
|
64
|
-
targetDir:
|
|
65
|
-
repo:
|
|
80
|
+
targetDir: initRepoOptions.targetDir,
|
|
81
|
+
repo: initRepoOptions.repo,
|
|
66
82
|
githubToken: process.env.GITHUB_TOKEN ?? null,
|
|
67
|
-
databaseUrl:
|
|
68
|
-
embeddingMode:
|
|
69
|
-
embeddingApiUrl:
|
|
70
|
-
embeddingModel:
|
|
71
|
-
embeddingApiKey:
|
|
72
|
-
dbConnectTimeoutMs:
|
|
73
|
-
|
|
74
|
-
repoAutomationToken:
|
|
75
|
-
flags["repo-automation-token"] ?? process.env.KB_REPO_AUTOMATION_TOKEN ?? null
|
|
83
|
+
databaseUrl: initRepoOptions.databaseUrl,
|
|
84
|
+
embeddingMode: initRepoOptions.embeddingMode,
|
|
85
|
+
embeddingApiUrl: initRepoOptions.embeddingApiUrl,
|
|
86
|
+
embeddingModel: initRepoOptions.embeddingModel,
|
|
87
|
+
embeddingApiKey: initRepoOptions.embeddingApiKey,
|
|
88
|
+
dbConnectTimeoutMs: initRepoOptions.dbConnectTimeoutMs,
|
|
89
|
+
repoAutomationToken: initRepoOptions.repoAutomationToken
|
|
76
90
|
});
|
|
77
91
|
console.log(`initialized knowledge repo scaffold in ${result.root}`);
|
|
78
92
|
for (const relativePath of result.created) {
|
|
@@ -87,6 +101,7 @@ export async function main(argv) {
|
|
|
87
101
|
printInitRepoStatus(result);
|
|
88
102
|
printRepoConfiguration(result.configuration);
|
|
89
103
|
printInitRepoNextStep(result);
|
|
104
|
+
printInitRepoChecklist(result);
|
|
90
105
|
return result.ok ? 0 : 1;
|
|
91
106
|
}
|
|
92
107
|
case "search": {
|
|
@@ -231,7 +246,7 @@ function printHelp() {
|
|
|
231
246
|
console.log(`usage: kb <command> [options]
|
|
232
247
|
|
|
233
248
|
commands:
|
|
234
|
-
init-repo [--dir PATH] [--repo OWNER/REPO]
|
|
249
|
+
init-repo [--interactive] [--dir PATH] [--repo OWNER/REPO]
|
|
235
250
|
create --title TEXT --text TEXT [--path RELATIVE_PATH]
|
|
236
251
|
search <query>
|
|
237
252
|
ask <question>
|
|
@@ -256,7 +271,7 @@ search options:
|
|
|
256
271
|
function printCommandHelp(command) {
|
|
257
272
|
const commandHelp = {
|
|
258
273
|
"init-repo":
|
|
259
|
-
"usage: kb init-repo [--dir PATH] [--repo OWNER/REPO] [--database-url URL] [--embedding-mode MODE] [--embedding-api-url URL] [--embedding-model NAME] [--embedding-api-key KEY] [--db-connect-timeout-ms N] [--repo-automation-token TOKEN]\n\nScaffold a knowledge-authority repository, optionally configure its GitHub settings, and preflight the target database.",
|
|
274
|
+
"usage: kb init-repo [--interactive] [--dir PATH] [--repo OWNER/REPO] [--database-url URL] [--embedding-mode MODE] [--embedding-api-url URL] [--embedding-model NAME] [--embedding-api-key KEY] [--db-connect-timeout-ms N] [--repo-automation-token TOKEN]\n\nScaffold a knowledge-authority repository, optionally configure its GitHub settings, and preflight the target database. Use --interactive for a guided terminal walkthrough.",
|
|
260
275
|
create: `usage: kb create --title TEXT --text TEXT [--path RELATIVE_PATH] [--repo OWNER/REPO]\n\n${githubCreationHelp()}`,
|
|
261
276
|
search: `usage: kb search <query> [options]\n\n${databaseHelp()}\n --limit N`,
|
|
262
277
|
ask: `usage: kb ask <question> [options]\n\n${databaseHelp()}\n --limit N`,
|
|
@@ -389,3 +404,19 @@ function printInitRepoNextStep(result) {
|
|
|
389
404
|
"next: rerun kb init-repo with the missing repo or database inputs when you are ready, or commit the scaffold now and finish remote setup later"
|
|
390
405
|
);
|
|
391
406
|
}
|
|
407
|
+
|
|
408
|
+
function printInitRepoChecklist(result) {
|
|
409
|
+
console.log("");
|
|
410
|
+
console.log("what to do next:");
|
|
411
|
+
if (result.created.length || result.skipped.length) {
|
|
412
|
+
console.log(" 1. commit and push the scaffolded knowledge repo files");
|
|
413
|
+
}
|
|
414
|
+
if (result.github.status !== "configured") {
|
|
415
|
+
console.log(" 2. make sure the target repo exists and rerun with --repo once GitHub setup is ready");
|
|
416
|
+
}
|
|
417
|
+
if (result.database.status !== "verified") {
|
|
418
|
+
console.log(" 3. rerun with --database-url once the target database is ready");
|
|
419
|
+
}
|
|
420
|
+
console.log(" 4. use kb create to open a proposal issue for the first knowledge entry");
|
|
421
|
+
console.log(" 5. review the issue, add kb-approved, merge the generated PR, and let publish sync the live KB");
|
|
422
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import readline from "node:readline/promises";
|
|
3
|
+
import { stdin as defaultStdin, stdout as defaultStdout } from "node:process";
|
|
4
|
+
import { resolveGitHubRepository } from "./config.js";
|
|
5
|
+
|
|
6
|
+
export async function collectInitRepoInteractiveOptions({
|
|
7
|
+
flags,
|
|
8
|
+
env = process.env,
|
|
9
|
+
cwd = process.cwd(),
|
|
10
|
+
stdin = defaultStdin,
|
|
11
|
+
stdout = defaultStdout,
|
|
12
|
+
prompter = null
|
|
13
|
+
}) {
|
|
14
|
+
if (!prompter && (!stdin.isTTY || !stdout.isTTY)) {
|
|
15
|
+
throw new Error("interactive init-repo requires a TTY; rerun in a terminal or pass flags directly");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const prompt = prompter ?? createReadlinePrompter({ stdin, stdout });
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const defaults = buildInitRepoDefaults({ flags, env, cwd });
|
|
22
|
+
|
|
23
|
+
stdout.write("interactive kb init-repo\n");
|
|
24
|
+
stdout.write("this wizard scaffolds the knowledge repo and can also wire remote setup now.\n\n");
|
|
25
|
+
|
|
26
|
+
const targetDir = path.resolve(
|
|
27
|
+
cwd,
|
|
28
|
+
await prompt.askText("knowledge repo directory", defaults.targetDir, {
|
|
29
|
+
validate: requireValue
|
|
30
|
+
})
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const configureRepo = await prompt.askConfirm(
|
|
34
|
+
"configure the GitHub repo now",
|
|
35
|
+
Boolean(defaults.repo || env.GITHUB_TOKEN)
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
let repo = null;
|
|
39
|
+
if (configureRepo) {
|
|
40
|
+
repo = await prompt.askText("target GitHub repo (OWNER/REPO)", defaults.repo, {
|
|
41
|
+
validate: requireValue
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const verifyDatabase = await prompt.askConfirm(
|
|
46
|
+
"verify the database and initialize schema now",
|
|
47
|
+
Boolean(defaults.databaseUrl)
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
let databaseUrl = null;
|
|
51
|
+
if (verifyDatabase) {
|
|
52
|
+
databaseUrl = await prompt.askText("database URL", defaults.databaseUrl, {
|
|
53
|
+
validate: requireValue
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let embeddingMode = null;
|
|
58
|
+
let embeddingApiUrl = null;
|
|
59
|
+
let embeddingModel = null;
|
|
60
|
+
let embeddingApiKey = null;
|
|
61
|
+
let dbConnectTimeoutMs = null;
|
|
62
|
+
|
|
63
|
+
if (verifyDatabase) {
|
|
64
|
+
const useRemoteEmbeddings = await prompt.askConfirm(
|
|
65
|
+
"configure remote embeddings now",
|
|
66
|
+
defaults.embeddingMode === "bge-m3-openai"
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (useRemoteEmbeddings) {
|
|
70
|
+
embeddingMode = "bge-m3-openai";
|
|
71
|
+
embeddingApiUrl = await prompt.askText(
|
|
72
|
+
"embedding API URL",
|
|
73
|
+
defaults.embeddingApiUrl || "https://your-embeddings-host/v1/embeddings",
|
|
74
|
+
{ validate: requireValue }
|
|
75
|
+
);
|
|
76
|
+
embeddingModel = await prompt.askText(
|
|
77
|
+
"embedding model",
|
|
78
|
+
defaults.embeddingModel || "BAAI/bge-m3",
|
|
79
|
+
{ validate: requireValue }
|
|
80
|
+
);
|
|
81
|
+
embeddingApiKey = await prompt.askText(
|
|
82
|
+
"embedding API key (optional)",
|
|
83
|
+
defaults.embeddingApiKey
|
|
84
|
+
);
|
|
85
|
+
dbConnectTimeoutMs = await prompt.askText(
|
|
86
|
+
"database connect timeout ms",
|
|
87
|
+
defaults.dbConnectTimeoutMs || "20000",
|
|
88
|
+
{ validate: requirePositiveNumber }
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let repoAutomationToken = null;
|
|
94
|
+
if (configureRepo) {
|
|
95
|
+
const useAutomationToken = await prompt.askConfirm(
|
|
96
|
+
"set a dedicated repo automation token secret",
|
|
97
|
+
Boolean(defaults.repoAutomationToken)
|
|
98
|
+
);
|
|
99
|
+
if (useAutomationToken) {
|
|
100
|
+
repoAutomationToken = await prompt.askText(
|
|
101
|
+
"repo automation token",
|
|
102
|
+
defaults.repoAutomationToken
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
stdout.write("\nplan:\n");
|
|
108
|
+
stdout.write(` local scaffold: ${targetDir}\n`);
|
|
109
|
+
stdout.write(` github repo setup: ${repo ? repo : "skip for now"}\n`);
|
|
110
|
+
stdout.write(` database preflight: ${databaseUrl ? "yes" : "skip for now"}\n`);
|
|
111
|
+
stdout.write(
|
|
112
|
+
` embeddings config: ${embeddingMode === "bge-m3-openai" ? `${embeddingMode}/${embeddingModel}` : "skip for now"}\n`
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const proceed = await prompt.askConfirm("run init-repo with this plan", true);
|
|
116
|
+
if (!proceed) {
|
|
117
|
+
throw new Error("interactive init-repo cancelled");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
targetDir,
|
|
122
|
+
repo,
|
|
123
|
+
databaseUrl,
|
|
124
|
+
embeddingMode,
|
|
125
|
+
embeddingApiUrl,
|
|
126
|
+
embeddingModel,
|
|
127
|
+
embeddingApiKey: emptyToNull(embeddingApiKey),
|
|
128
|
+
dbConnectTimeoutMs: emptyToNull(dbConnectTimeoutMs),
|
|
129
|
+
repoAutomationToken: emptyToNull(repoAutomationToken)
|
|
130
|
+
};
|
|
131
|
+
} finally {
|
|
132
|
+
await prompt.close();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function buildInitRepoDefaults({ flags = {}, env = process.env, cwd = process.cwd() }) {
|
|
137
|
+
return {
|
|
138
|
+
targetDir: flags.dir ?? cwd,
|
|
139
|
+
repo: flags.repo ?? resolveGitHubRepository(env) ?? "",
|
|
140
|
+
databaseUrl: flags["database-url"] ?? env.KB_DATABASE_URL ?? "",
|
|
141
|
+
embeddingMode: flags["embedding-mode"] ?? env.KB_EMBEDDING_MODE ?? "",
|
|
142
|
+
embeddingApiUrl: flags["embedding-api-url"] ?? env.KB_EMBEDDING_API_URL ?? "",
|
|
143
|
+
embeddingModel: flags["embedding-model"] ?? env.KB_EMBEDDING_MODEL ?? "",
|
|
144
|
+
embeddingApiKey: flags["embedding-api-key"] ?? env.KB_EMBEDDING_API_KEY ?? "",
|
|
145
|
+
dbConnectTimeoutMs:
|
|
146
|
+
flags["db-connect-timeout-ms"] ?? env.KB_DB_CONNECT_TIMEOUT_MS ?? "",
|
|
147
|
+
repoAutomationToken:
|
|
148
|
+
flags["repo-automation-token"] ?? env.KB_REPO_AUTOMATION_TOKEN ?? ""
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function createReadlinePrompter({ stdin, stdout }) {
|
|
153
|
+
const rl = readline.createInterface({
|
|
154
|
+
input: stdin,
|
|
155
|
+
output: stdout
|
|
156
|
+
});
|
|
157
|
+
return {
|
|
158
|
+
askText(label, defaultValue = "", options = {}) {
|
|
159
|
+
return askText(rl, label, defaultValue, options);
|
|
160
|
+
},
|
|
161
|
+
askConfirm(label, defaultValue = false) {
|
|
162
|
+
return askConfirm(rl, label, defaultValue);
|
|
163
|
+
},
|
|
164
|
+
async close() {
|
|
165
|
+
rl.close();
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function askText(rl, label, defaultValue = "", { validate = null } = {}) {
|
|
171
|
+
while (true) {
|
|
172
|
+
const suffix = defaultValue ? ` [${defaultValue}]` : "";
|
|
173
|
+
const answer = (await rl.question(`${label}${suffix}: `)).trim();
|
|
174
|
+
const value = answer || String(defaultValue ?? "").trim();
|
|
175
|
+
|
|
176
|
+
if (!validate) {
|
|
177
|
+
return value;
|
|
178
|
+
}
|
|
179
|
+
const error = validate(value);
|
|
180
|
+
if (!error) {
|
|
181
|
+
return value;
|
|
182
|
+
}
|
|
183
|
+
rl.output.write(`${error}\n`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function askConfirm(rl, label, defaultValue = false) {
|
|
188
|
+
const hint = defaultValue ? "Y/n" : "y/N";
|
|
189
|
+
while (true) {
|
|
190
|
+
const answer = (await rl.question(`${label}? [${hint}]: `)).trim().toLowerCase();
|
|
191
|
+
if (!answer) {
|
|
192
|
+
return defaultValue;
|
|
193
|
+
}
|
|
194
|
+
if (["y", "yes"].includes(answer)) {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
if (["n", "no"].includes(answer)) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
rl.output.write("please answer yes or no\n");
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function requireValue(value) {
|
|
205
|
+
return String(value ?? "").trim() ? null : "a value is required";
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function requirePositiveNumber(value) {
|
|
209
|
+
if (!String(value ?? "").trim()) {
|
|
210
|
+
return "a value is required";
|
|
211
|
+
}
|
|
212
|
+
const number = Number(value);
|
|
213
|
+
if (!Number.isFinite(number) || number <= 0) {
|
|
214
|
+
return "enter a positive number";
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function emptyToNull(value) {
|
|
220
|
+
const normalized = String(value ?? "").trim();
|
|
221
|
+
return normalized ? normalized : null;
|
|
222
|
+
}
|