@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 +93 -32
- package/lib/cli-common.js +1 -1
- package/lib/cli.js +44 -13
- package/lib/init-repo-interactive.js +222 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,38 +1,75 @@
|
|
|
1
1
|
# Team Knowledge Base CLI
|
|
2
2
|
|
|
3
|
-
`@nzpr/kb` is
|
|
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
|
-
|
|
5
|
+
The core idea is simple:
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
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
|
-
|
|
13
|
+
At the product level, a knowledge base is just a collection of documents, and each document should stay minimal:
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
- `title`
|
|
16
|
+
- `text`
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
- issue templates
|
|
16
|
-
- review and approval workflow
|
|
17
|
-
- CI that runs `kb publish`
|
|
18
|
+
Everything else is implementation detail.
|
|
18
19
|
|
|
19
|
-
|
|
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
|
-
|
|
64
|
+
npm install -g @nzpr/kb
|
|
65
|
+
kb --help
|
|
23
66
|
```
|
|
24
67
|
|
|
25
|
-
|
|
68
|
+
From source:
|
|
26
69
|
|
|
27
70
|
```bash
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
##
|
|
85
|
+
## Knowledge Repo Workflow
|
|
49
86
|
|
|
50
|
-
|
|
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
|
-
|
|
54
|
-
kb --help
|
|
96
|
+
kb init-repo --dir /path/to/knowledge-repo
|
|
55
97
|
```
|
|
56
98
|
|
|
57
|
-
|
|
99
|
+
If you want the CLI to walk you through setup and tell you what to do next:
|
|
58
100
|
|
|
59
101
|
```bash
|
|
60
|
-
|
|
61
|
-
npm link
|
|
102
|
+
kb init-repo --interactive
|
|
62
103
|
```
|
|
63
104
|
|
|
64
|
-
|
|
105
|
+
Or bootstrap and configure it in one step:
|
|
65
106
|
|
|
66
|
-
|
|
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
|
|
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
|
-
-
|
|
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
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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nzpr/kb",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
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"
|