@nzpr/kb 0.1.0 → 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 CHANGED
@@ -1,38 +1,75 @@
1
1
  # Team Knowledge Base CLI
2
2
 
3
- `@nzpr/kb` is the npm package and CLI used by a separate knowledge-authority repository.
3
+ `@nzpr/kb` is a package for managing a knowledge base that agents can read from and that humans can keep curated.
4
4
 
5
- Each KB instance is defined by:
5
+ The core idea is simple:
6
6
 
7
- - `KB_DATABASE_URL`
8
- - `KB_GITHUB_REPO`
7
+ - initialize a knowledge base
8
+ - propose knowledge
9
+ - publish approved knowledge
10
+ - search the current knowledge
11
+ - ask grounded questions against it
9
12
 
10
- This repository is for the tool itself.
13
+ At the product level, a knowledge base is just a collection of documents, and each document should stay minimal:
11
14
 
12
- The actual knowledge base should live in a separate repository that owns:
15
+ - `title`
16
+ - `text`
13
17
 
14
- - `kb/docs/`
15
- - issue templates
16
- - review and approval workflow
17
- - CI that runs `kb publish`
18
+ Everything else is implementation detail.
18
19
 
19
- Bootstrap that repo with:
20
+ ## Public API
21
+
22
+ The public surface of `@nzpr/kb` is the `kb` CLI.
23
+
24
+ - `kb init-repo`
25
+ Initialize a knowledge base workspace.
26
+ Use `kb init-repo --interactive` for a guided setup flow.
27
+
28
+ - `kb create --title TEXT --text TEXT`
29
+ Propose a new knowledge document or a substantial update.
30
+
31
+ - `kb publish`
32
+ Make the current approved documents become the live knowledge base.
33
+
34
+ - `kb search "<query>"`
35
+ Return the closest matching knowledge documents.
36
+
37
+ - `kb ask "<question>"`
38
+ Return a grounded answer from the closest matching knowledge.
39
+
40
+ - `kb list`
41
+ List the current knowledge documents.
42
+
43
+ - `kb catalog`
44
+ Return the current knowledge inventory in a machine-friendly form.
45
+
46
+ - `kb doctor`
47
+ Check whether the knowledge base is healthy and queryable.
48
+
49
+ ## Lifecycle
50
+
51
+ The intended workflow is:
52
+
53
+ 1. Initialize a knowledge base.
54
+ 2. Propose knowledge.
55
+ 3. Review and approve knowledge.
56
+ 4. Publish knowledge.
57
+ 5. Query knowledge.
58
+
59
+ ## Install
60
+
61
+ From npm:
20
62
 
21
63
  ```bash
22
- kb init-repo --dir /path/to/knowledge-repo
64
+ npm install -g @nzpr/kb
65
+ kb --help
23
66
  ```
24
67
 
25
- Or bootstrap and configure it in one step:
68
+ From source:
26
69
 
27
70
  ```bash
28
- export GITHUB_TOKEN=...
29
- kb init-repo \
30
- --dir /path/to/knowledge-repo \
31
- --repo owner/knowledge-repo \
32
- --database-url postgresql://kb:kb@host:5432/kb \
33
- --embedding-mode bge-m3-openai \
34
- --embedding-api-url https://embeddings.example.com/v1/embeddings \
35
- --embedding-model BAAI/bge-m3
71
+ npm install
72
+ npm link
36
73
  ```
37
74
 
38
75
  ## Commands
@@ -45,25 +82,40 @@ kb init-repo \
45
82
  - `kb create --title TEXT --text TEXT [--path RELATIVE_PATH]`
46
83
  - `kb publish --docs-root PATH`
47
84
 
48
- ## Install
85
+ ## Knowledge Repo Workflow
49
86
 
50
- From npm:
87
+ In practice, the live knowledge usually lives in a separate repository that owns:
88
+
89
+ - `kb/docs/`
90
+ - the review flow for proposed knowledge
91
+ - the publish automation
92
+
93
+ Bootstrap that repo with:
51
94
 
52
95
  ```bash
53
- npm install -g @nzpr/kb
54
- kb --help
96
+ kb init-repo --dir /path/to/knowledge-repo
55
97
  ```
56
98
 
57
- From source:
99
+ If you want the CLI to walk you through setup and tell you what to do next:
58
100
 
59
101
  ```bash
60
- npm install
61
- npm link
102
+ kb init-repo --interactive
62
103
  ```
63
104
 
64
- ## Knowledge Repo Workflow
105
+ Or bootstrap and configure it in one step:
65
106
 
66
- In the separate knowledge-authority repo:
107
+ ```bash
108
+ export GITHUB_TOKEN=...
109
+ kb init-repo \
110
+ --dir /path/to/knowledge-repo \
111
+ --repo owner/knowledge-repo \
112
+ --database-url postgresql://kb:kb@host:5432/kb \
113
+ --embedding-mode bge-m3-openai \
114
+ --embedding-api-url https://embeddings.example.com/v1/embeddings \
115
+ --embedding-model BAAI/bge-m3
116
+ ```
117
+
118
+ Inside that knowledge repo, the normal flow is:
67
119
 
68
120
  1. Run `kb init-repo` once to scaffold the repo. If you provide `--repo`, `--database-url`, and `GITHUB_TOKEN`, it also configures the target repo and preflights the database.
69
121
  2. Open or update a knowledge proposal issue.
@@ -77,6 +129,14 @@ In the separate knowledge-authority repo:
77
129
  - database preflight and schema initialization
78
130
  - GitHub repo labels, variables, and secrets
79
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
+
80
140
  If one remote step fails, the scaffold still stays in place and the command tells you what to rerun.
81
141
 
82
142
  ## Runtime
@@ -89,7 +149,7 @@ Query commands need:
89
149
  export KB_DATABASE_URL=postgresql://kb:kb@localhost:5432/kb
90
150
  ```
91
151
 
92
- Higher-quality embeddings with a self-hosted OpenAI-compatible `BAAI/bge-m3` server:
152
+ Higher-quality retrieval can use a self-hosted embeddings service:
93
153
 
94
154
  ```bash
95
155
  export KB_EMBEDDING_MODE=bge-m3-openai
@@ -150,7 +210,8 @@ The package is published as `@nzpr/kb`.
150
210
  - local validation: `npm test`
151
211
  - local package preview: `npm pack --dry-run`
152
212
  - GitHub Actions publish: push a tag like `v0.1.0` or run the `npm-publish` workflow manually
153
- - required repository secret: `NPM_TOKEN`
213
+ - npm authentication: use npm trusted publishing with GitHub Actions OIDC, not a long-lived publish token
214
+ - required npm setup: in npm package settings, configure trusted publishing for `nzpr/kb` and workflow file `.github/workflows/npm-publish.yml`
154
215
 
155
216
  ## Using From A Knowledge Repo
156
217
 
package/lib/cli-common.js CHANGED
@@ -8,7 +8,7 @@ export function parseFlags(args) {
8
8
  continue;
9
9
  }
10
10
  const key = arg.slice(2);
11
- if (key === "json" || key === "include-drafts") {
11
+ if (key === "json" || key === "include-drafts" || key === "interactive") {
12
12
  flags[key] = true;
13
13
  continue;
14
14
  }
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: flags.dir ?? process.cwd(),
65
- repo: flags.repo ?? resolveGitHubRepository(),
80
+ targetDir: initRepoOptions.targetDir,
81
+ repo: initRepoOptions.repo,
66
82
  githubToken: process.env.GITHUB_TOKEN ?? null,
67
- databaseUrl: flags["database-url"] ?? process.env.KB_DATABASE_URL ?? null,
68
- embeddingMode: flags["embedding-mode"] ?? process.env.KB_EMBEDDING_MODE ?? null,
69
- embeddingApiUrl: flags["embedding-api-url"] ?? process.env.KB_EMBEDDING_API_URL ?? null,
70
- embeddingModel: flags["embedding-model"] ?? process.env.KB_EMBEDDING_MODEL ?? null,
71
- embeddingApiKey: flags["embedding-api-key"] ?? process.env.KB_EMBEDDING_API_KEY ?? null,
72
- dbConnectTimeoutMs:
73
- flags["db-connect-timeout-ms"] ?? process.env.KB_DB_CONNECT_TIMEOUT_MS ?? null,
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
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nzpr/kb",
3
- "version": "0.1.0",
4
- "description": "Postgres/pgvector-backed knowledge base CLI for LLM agents and engineering teams.",
3
+ "version": "0.1.2",
4
+ "description": "Knowledge base CLI for proposing, publishing, and querying curated agent knowledge.",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/nzpr/kb.git"