@nzpr/kb 0.1.3 → 0.1.4

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/lib/cli.js CHANGED
@@ -81,7 +81,7 @@ export async function main(argv) {
81
81
  targetDir: initRepoOptions.targetDir,
82
82
  layout: initRepoOptions.layout,
83
83
  repo: initRepoOptions.repo,
84
- githubToken: process.env.GITHUB_TOKEN ?? null,
84
+ githubToken: initRepoOptions.githubToken ?? process.env.GITHUB_TOKEN ?? null,
85
85
  databaseUrl: initRepoOptions.databaseUrl,
86
86
  embeddingMode: initRepoOptions.embeddingMode,
87
87
  embeddingApiUrl: initRepoOptions.embeddingApiUrl,
@@ -330,7 +330,7 @@ async function requirePublishAccess({ repo, token, apiBaseUrl }) {
330
330
 
331
331
  function printRepoConfiguration(configuration) {
332
332
  console.log("");
333
- console.log("configure the knowledge repo with these GitHub settings:");
333
+ console.log("configuration reference:");
334
334
  console.log("");
335
335
  console.log("required secrets:");
336
336
  for (const entry of configuration.requiredSecrets) {
@@ -354,7 +354,10 @@ function printRepoConfiguration(configuration) {
354
354
 
355
355
  function printInitRepoStatus(result) {
356
356
  console.log("");
357
- console.log("bootstrap status:");
357
+ console.log("summary:");
358
+ console.log(` docs layout: ${result.docsRootRelative}/`);
359
+ console.log(` files created: ${result.created.length}`);
360
+ console.log(` files kept: ${result.skipped.length}`);
358
361
  printInitRepoDatabaseStatus(result.database);
359
362
  printInitRepoGitHubStatus(result.github);
360
363
  }
@@ -376,6 +379,11 @@ function printInitRepoDatabaseStatus(database) {
376
379
  function printInitRepoGitHubStatus(github) {
377
380
  if (github.status === "configured") {
378
381
  console.log(` github: configured ${github.repo}`);
382
+ if (github.actions) {
383
+ console.log(
384
+ ` actions: enabled=${github.actions.enabled} workflow_permissions=${github.actions.defaultWorkflowPermissions} pr_creation=${github.actions.canApprovePullRequestReviews}`
385
+ );
386
+ }
379
387
  console.log(` labels: ${github.labels.join(", ")}`);
380
388
  if (github.secrets.length) {
381
389
  console.log(` secrets: ${github.secrets.join(", ")}`);
@@ -410,7 +418,7 @@ function printInitRepoNextStep(result) {
410
418
 
411
419
  function printInitRepoChecklist(result) {
412
420
  console.log("");
413
- console.log("what to do next:");
421
+ console.log("next steps:");
414
422
  if (result.created.length || result.skipped.length) {
415
423
  console.log(` 1. commit and push the scaffolded knowledge repo files, including ${result.docsRootRelative}/`);
416
424
  }
@@ -422,4 +430,14 @@ function printInitRepoChecklist(result) {
422
430
  }
423
431
  console.log(" 4. use kb create to open a proposal issue for the first knowledge entry");
424
432
  console.log(" 5. review the issue, add kb-approved, merge the generated PR, and let publish sync the live KB");
433
+ console.log("");
434
+ console.log("done in this run:");
435
+ if (result.created.length) {
436
+ console.log(` created: ${result.created.join(", ")}`);
437
+ } else {
438
+ console.log(" created: no new files");
439
+ }
440
+ if (result.skipped.length) {
441
+ console.log(` kept: ${result.skipped.join(", ")}`);
442
+ }
425
443
  }
@@ -1,5 +1,6 @@
1
1
  import path from "node:path";
2
2
  import fs from "node:fs";
3
+ import { execFileSync } from "node:child_process";
3
4
  import readline from "node:readline/promises";
4
5
  import { stdin as defaultStdin, stdout as defaultStdout } from "node:process";
5
6
  import { resolveGitHubRepository } from "./config.js";
@@ -10,7 +11,8 @@ export async function collectInitRepoInteractiveOptions({
10
11
  cwd = process.cwd(),
11
12
  stdin = defaultStdin,
12
13
  stdout = defaultStdout,
13
- prompter = null
14
+ prompter = null,
15
+ auth = defaultInteractiveGitHubAuth
14
16
  }) {
15
17
  if (!prompter && (!stdin.isTTY || !stdout.isTTY)) {
16
18
  throw new Error("interactive init-repo requires a TTY; rerun in a terminal or pass flags directly");
@@ -21,8 +23,8 @@ export async function collectInitRepoInteractiveOptions({
21
23
  try {
22
24
  const defaults = buildInitRepoDefaults({ flags, env, cwd });
23
25
 
24
- stdout.write("interactive kb init-repo\n");
25
- stdout.write("this wizard scaffolds the knowledge repo and can also wire remote setup now.\n\n");
26
+ stdout.write("KB Init Wizard\n");
27
+ stdout.write("This walkthrough prepares the knowledge repo and can wire GitHub and database setup now.\n\n");
26
28
 
27
29
  const targetDir = path.resolve(
28
30
  cwd,
@@ -42,6 +44,15 @@ export async function collectInitRepoInteractiveOptions({
42
44
  repo = await prompt.askText("target GitHub repo (OWNER/REPO)", defaults.repo, {
43
45
  validate: requireValue
44
46
  });
47
+ if (!defaults.githubToken) {
48
+ const authorized = await ensureInteractiveGitHubAccess({ prompt, stdout, auth });
49
+ if (authorized) {
50
+ defaults.githubToken = authorized;
51
+ } else {
52
+ stdout.write("skipping GitHub repo setup for now because GitHub auth is not ready.\n");
53
+ repo = null;
54
+ }
55
+ }
45
56
  }
46
57
 
47
58
  const verifyDatabase = await prompt.askConfirm(
@@ -95,7 +106,7 @@ export async function collectInitRepoInteractiveOptions({
95
106
  let repoAutomationToken = null;
96
107
  if (configureRepo) {
97
108
  const useAutomationToken = await prompt.askConfirm(
98
- "set a dedicated repo automation token secret",
109
+ "use a custom automation token instead of the default GitHub Actions token",
99
110
  Boolean(defaults.repoAutomationToken)
100
111
  );
101
112
  if (useAutomationToken) {
@@ -106,10 +117,10 @@ export async function collectInitRepoInteractiveOptions({
106
117
  }
107
118
  }
108
119
 
109
- stdout.write("\nplan:\n");
110
- stdout.write(` local scaffold: ${targetDir}\n`);
120
+ stdout.write("\nPlan\n");
121
+ stdout.write(` target directory: ${targetDir}\n`);
111
122
  stdout.write(` document layout: ${layout === "repo-root" ? "docs/" : "kb/docs/"}\n`);
112
- stdout.write(` github repo setup: ${repo ? repo : "skip for now"}\n`);
123
+ stdout.write(` GitHub repo setup: ${repo ? repo : "skip for now"}\n`);
113
124
  stdout.write(` database preflight: ${databaseUrl ? "yes" : "skip for now"}\n`);
114
125
  stdout.write(
115
126
  ` embeddings config: ${embeddingMode === "bge-m3-openai" ? `${embeddingMode}/${embeddingModel}` : "skip for now"}\n`
@@ -124,6 +135,7 @@ export async function collectInitRepoInteractiveOptions({
124
135
  targetDir,
125
136
  layout,
126
137
  repo,
138
+ githubToken: defaults.githubToken || null,
127
139
  databaseUrl,
128
140
  embeddingMode,
129
141
  embeddingApiUrl,
@@ -142,6 +154,7 @@ export function buildInitRepoDefaults({ flags = {}, env = process.env, cwd = pro
142
154
  targetDir: flags.dir ?? cwd,
143
155
  layout:
144
156
  flags.layout ?? inferDefaultKnowledgeLayout(flags.dir ? path.resolve(cwd, flags.dir) : cwd),
157
+ githubToken: env.GITHUB_TOKEN ?? "",
145
158
  repo: flags.repo ?? resolveGitHubRepository(env) ?? "",
146
159
  databaseUrl: flags["database-url"] ?? env.KB_DATABASE_URL ?? "",
147
160
  embeddingMode: flags["embedding-mode"] ?? env.KB_EMBEDDING_MODE ?? "",
@@ -251,3 +264,45 @@ function emptyToNull(value) {
251
264
  const normalized = String(value ?? "").trim();
252
265
  return normalized ? normalized : null;
253
266
  }
267
+
268
+ async function ensureInteractiveGitHubAccess({ prompt, stdout, auth }) {
269
+ const existingToken = auth.getToken();
270
+ if (existingToken) {
271
+ stdout.write("using existing GitHub CLI authentication for repo setup.\n");
272
+ return existingToken;
273
+ }
274
+
275
+ stdout.write("\nGitHub repo setup needs GitHub authentication.\n");
276
+ stdout.write("The wizard can open GitHub CLI login in your browser and then continue.\n");
277
+ const loginNow = await prompt.askConfirm("authorize GitHub CLI now", true);
278
+ if (!loginNow) {
279
+ return null;
280
+ }
281
+
282
+ auth.login();
283
+ const token = auth.getToken();
284
+ if (token) {
285
+ stdout.write("GitHub CLI authentication is ready.\n");
286
+ return token;
287
+ }
288
+
289
+ throw new Error("GitHub authentication did not complete; rerun gh auth login or set GITHUB_TOKEN");
290
+ }
291
+
292
+ const defaultInteractiveGitHubAuth = {
293
+ getToken() {
294
+ try {
295
+ return execFileSync("gh", ["auth", "token"], {
296
+ encoding: "utf8",
297
+ stdio: ["ignore", "pipe", "ignore"]
298
+ }).trim();
299
+ } catch {
300
+ return null;
301
+ }
302
+ },
303
+ login() {
304
+ execFileSync("gh", ["auth", "login", "--web", "--git-protocol", "https"], {
305
+ stdio: "inherit"
306
+ });
307
+ }
308
+ };
package/lib/repo-init.js CHANGED
@@ -112,7 +112,8 @@ export async function bootstrapKnowledgeRepo({
112
112
  result.github = {
113
113
  status: "failed",
114
114
  repo,
115
- error: "GITHUB_TOKEN is required when configuring a knowledge repo"
115
+ error:
116
+ "GITHUB_TOKEN is required when configuring a knowledge repo; set GITHUB_TOKEN or run gh auth login before rerunning"
116
117
  };
117
118
  return result;
118
119
  }
@@ -226,6 +227,11 @@ async function configureKnowledgeRepo({
226
227
  runGitHubCommand
227
228
  }) {
228
229
  await runGitHubCommand(["repo", "edit", repo, "--enable-issues"], { githubToken });
230
+ const actions = await ensureGitHubActionsPermissions({
231
+ repo,
232
+ githubToken,
233
+ runGitHubCommand
234
+ });
229
235
 
230
236
  for (const label of LABELS) {
231
237
  await runGitHubCommand(
@@ -259,12 +265,72 @@ async function configureKnowledgeRepo({
259
265
 
260
266
  return {
261
267
  repo,
268
+ actions,
262
269
  labels: LABELS.map((label) => label.name),
263
270
  secrets: [...secrets.keys()],
264
271
  variables: [...variables.keys()]
265
272
  };
266
273
  }
267
274
 
275
+ async function ensureGitHubActionsPermissions({ repo, githubToken, runGitHubCommand }) {
276
+ const actionsPermissions = await runGitHubJson(
277
+ ["api", `repos/${repo}/actions/permissions`],
278
+ { githubToken, runGitHubCommand }
279
+ );
280
+
281
+ if (actionsPermissions.enabled === false) {
282
+ await runGitHubCommand(
283
+ [
284
+ "api",
285
+ "--method",
286
+ "PUT",
287
+ `repos/${repo}/actions/permissions`,
288
+ "-F",
289
+ "enabled=true",
290
+ "-f",
291
+ `allowed_actions=${actionsPermissions.allowed_actions ?? "all"}`
292
+ ],
293
+ { githubToken }
294
+ );
295
+ }
296
+
297
+ const workflowPermissions = await runGitHubJson(
298
+ ["api", `repos/${repo}/actions/permissions/workflow`],
299
+ { githubToken, runGitHubCommand }
300
+ );
301
+
302
+ if (
303
+ workflowPermissions.default_workflow_permissions !== "write" ||
304
+ workflowPermissions.can_approve_pull_request_reviews !== true
305
+ ) {
306
+ await runGitHubCommand(
307
+ [
308
+ "api",
309
+ "--method",
310
+ "PUT",
311
+ `repos/${repo}/actions/permissions/workflow`,
312
+ "-f",
313
+ "default_workflow_permissions=write",
314
+ "-F",
315
+ "can_approve_pull_request_reviews=true"
316
+ ],
317
+ { githubToken }
318
+ );
319
+ }
320
+
321
+ return {
322
+ enabled: true,
323
+ allowedActions: actionsPermissions.allowed_actions ?? "all",
324
+ defaultWorkflowPermissions: "write",
325
+ canApprovePullRequestReviews: true
326
+ };
327
+ }
328
+
329
+ async function runGitHubJson(args, { githubToken, runGitHubCommand }) {
330
+ const output = await runGitHubCommand(args, { githubToken });
331
+ return JSON.parse(output);
332
+ }
333
+
268
334
  async function defaultVerifyDatabaseReady({ databaseUrl }) {
269
335
  const client = await connect(databaseUrl);
270
336
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nzpr/kb",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Knowledge base CLI for proposing, publishing, and querying curated agent knowledge.",
5
5
  "repository": {
6
6
  "type": "git",