diffwiki 0.4.0 → 0.5.0-rc.202607230329.fb70592

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.
Files changed (2) hide show
  1. package/dist/bin.js +189 -13
  2. package/package.json +4 -2
package/dist/bin.js CHANGED
@@ -3,16 +3,26 @@
3
3
  // src/cli.ts
4
4
  import { Command } from "commander";
5
5
  import {
6
+ addExternalCollection,
6
7
  addTags,
8
+ collectionGitStatus,
7
9
  createArticle,
8
10
  createCollection,
9
11
  doctor,
10
12
  getConfigValue,
11
13
  initRepoWiki,
14
+ installPlugin,
12
15
  listCollections,
13
- query,
16
+ listAvailablePlugins,
17
+ listPlugins,
18
+ listSearchModes,
19
+ loadSearchProvider,
20
+ search,
21
+ registerPlugin,
14
22
  removeArticle,
23
+ removePlugin,
15
24
  removeTags,
25
+ resolveCwdCollection,
16
26
  setConfigValue,
17
27
  setTags,
18
28
  updateArticleBody
@@ -143,11 +153,38 @@ async function stopUi() {
143
153
  return state;
144
154
  }
145
155
 
156
+ // src/exporter.ts
157
+ import { spawn as spawn2 } from "child_process";
158
+ import fs2 from "fs";
159
+ import path2 from "path";
160
+ import { fileURLToPath as fileURLToPath2 } from "url";
161
+ function localAppExporter() {
162
+ const candidates = [
163
+ path2.resolve(path2.dirname(fileURLToPath2(import.meta.url)), "../../../apps/wiki/bin/export.mjs"),
164
+ path2.resolve(process.cwd(), "apps/wiki/bin/export.mjs")
165
+ ];
166
+ return candidates.find((p) => fs2.existsSync(p));
167
+ }
168
+ async function runExport(opts) {
169
+ const out = path2.resolve(opts.out);
170
+ const local = localAppExporter();
171
+ const override = process.env.DIFFWIKI_APP_CMD ?? (local ? `node ${local}` : void 0);
172
+ const [cmd, ...prefix] = override ? override.split(" ") : ["npx", "-y", "diffwiki-app@latest"];
173
+ const args = [...prefix, "export", "--out", out, "--base", opts.base];
174
+ if (override) console.log(`Exporting via ${override}`);
175
+ await new Promise((resolve, reject) => {
176
+ const child = spawn2(cmd, args, { stdio: "inherit", env: process.env });
177
+ child.on("error", reject);
178
+ child.on("close", (code) => code === 0 ? resolve() : reject(new Error(`export failed (exit ${code})`)));
179
+ });
180
+ console.log(`Static site written to ${out}`);
181
+ }
182
+
146
183
  // src/cli.ts
147
184
  var icon = (level) => level === "ok" ? "\u2713" : level === "warn" ? "!" : "\u2716";
148
185
  function createProgram() {
149
186
  const program = new Command();
150
- program.name("diffwiki").description("A git-based knowledge base").version(version).showHelpAfterError();
187
+ program.name("diffwiki").description("A git-based knowledge base").version(version).showHelpAfterError().option("-v, --verbose", "verbose logging (sets the log level to debug)").option("--log <level>", "log level: trace|debug|info|warning|error|fatal");
151
188
  program.action(() => {
152
189
  program.outputHelp();
153
190
  });
@@ -159,17 +196,28 @@ function createProgram() {
159
196
  return;
160
197
  }
161
198
  for (const c of collections) {
162
- console.log(`${c.name} [${c.type}] ${c.path}`);
199
+ const status = await collectionGitStatus(c);
200
+ const branch = status?.branch ? ` (${status.branch})` : "";
201
+ console.log(`${c.name} [${c.type}] ${c.path}${branch}`);
163
202
  }
164
203
  })
165
204
  );
205
+ program.command("add").description("Add the current git repo as an external collection (docs dir, no diffwiki.yaml)").option("-d, --docs <dir>", "content dir relative to repo root", "docs").action(
206
+ withErrors(async (opts) => {
207
+ const entry = await addExternalCollection({ cwd: process.cwd(), docsDir: opts.docs });
208
+ const status = await collectionGitStatus(entry);
209
+ const branch = status?.branch ? ` (${status.branch})` : "";
210
+ console.log(`Added "${entry.name}"${branch} \u2192 ${entry.path}`);
211
+ console.log("Registered globally \u2014 discoverable via: diffwiki list");
212
+ })
213
+ );
166
214
  program.command("create").description("Create a new global collection").argument("<name>", "collection name").action(
167
215
  withErrors(async (name) => {
168
216
  const entry = await createCollection(name);
169
217
  console.log(`Created collection "${entry.name}" at ${entry.path}`);
170
218
  })
171
219
  );
172
- program.command("add").description("Add an article to a collection").requiredOption("-p, --path <target>", '"[collection:]title"').option("-t, --tag <tag>", "tag (repeatable, comma-separated)", collect, []).argument("[body]", "markdown body").action(
220
+ program.command("add-article").description("Add an article to a collection").requiredOption("-p, --path <target>", '"[collection:]title"').option("-t, --tag <tag>", "tag (repeatable, comma-separated)", collect, []).argument("[body]", "markdown body").action(
173
221
  withErrors(async (body, opts) => {
174
222
  const article = await createArticle({
175
223
  target: opts.path,
@@ -179,13 +227,13 @@ function createProgram() {
179
227
  console.log(article.path);
180
228
  })
181
229
  );
182
- program.command("update").description("Replace an article's body").requiredOption("-p, --path <target>", '"collection:path"').argument("<body>", "new markdown body").action(
230
+ program.command("update-article").description("Replace an article's body").requiredOption("-p, --path <target>", '"collection:path"').argument("<body>", "new markdown body").action(
183
231
  withErrors(async (body, opts) => {
184
232
  const article = await updateArticleBody(opts.path, body);
185
233
  console.log(`Updated ${article.path}`);
186
234
  })
187
235
  );
188
- program.command("remove").description("Delete an article").requiredOption("-p, --path <target>", '"collection:path"').action(
236
+ program.command("remove-article").description("Delete an article").requiredOption("-p, --path <target>", '"collection:path"').action(
189
237
  withErrors(async (opts) => {
190
238
  const removed = await removeArticle(opts.path);
191
239
  console.log(`Removed ${removed}`);
@@ -202,15 +250,89 @@ function createProgram() {
202
250
  registerTagCommand("add-tags", addTags);
203
251
  registerTagCommand("remove-tags", removeTags);
204
252
  registerTagCommand("update-tags", setTags);
205
- program.command("query").description("Search articles (dummy \u2014 QMD coming later)").option("-c, --collection <name>", "limit to a collection").argument("[term]", "search term", "").action(
253
+ program.command("index").description("Rebuild the search index via the active plugin").option("--embed", "also build vector embeddings (may download models on first run)").action(
254
+ withErrors(async (opts) => {
255
+ const provider = await loadSearchProvider();
256
+ if (!provider) {
257
+ const installable = (await listSearchModes()).filter((m) => m.plugin !== "native");
258
+ console.log(
259
+ installable.length > 0 ? `No index-backed plugin active. Available modes: ${installable.map((m) => `${m.plugin}\xB7${m.type}`).join(", ")}` : "No search plugin installed \u2014 using native search (nothing to index). Install one with: diffwiki plugin install <spec>"
260
+ );
261
+ return;
262
+ }
263
+ const indexed = await provider.reindex(await listCollections(), { embed: !!opts.embed });
264
+ console.log(`Indexed ${indexed} document(s).`);
265
+ })
266
+ );
267
+ program.command("query").description("Search articles via the resolved search engine (native fallback if none)").option("--engine <name>", "search engine/plugin (default: config defaultSearch, else native)").option("-c, --collection <name>", "limit to a collection").option("-t, --type <type>", "search type (engine-defined, e.g. keyword|semantic|hybrid)").argument("[term]", "search term", "").action(
206
268
  withErrors(async (term, opts) => {
207
- const hits = await query(term, opts.collection);
208
- console.log(`(dummy search \u2014 QMD coming later) ${hits.length} result(s)`);
269
+ const queryOpts = { engine: opts.engine, collection: opts.collection, type: opts.type };
270
+ const outcome = await search(term, queryOpts);
271
+ const hits = outcome.hits;
272
+ console.log(`${hits.length} result(s)`);
209
273
  for (const hit of hits) {
210
- console.log(`${hit.collection} ${hit.title} ${hit.path}`);
274
+ const score = typeof hit.score === "number" ? hit.score.toFixed(3) : "-";
275
+ const line = `${score} ${hit.collection} ${hit.title} ${hit.path}`;
276
+ console.log(hit.snippet ? `${line} ${hit.snippet}` : line);
277
+ }
278
+ if (outcome.error) {
279
+ console.error(`\u26A0 engine "${outcome.engine}" failed: ${outcome.error} \u2014 used native (${hits.length} results)`);
280
+ }
281
+ })
282
+ );
283
+ const plugin = program.command("plugin").description("Manage search plugins");
284
+ plugin.command("install").description("Install a search plugin from an npm spec and provision it (setup)").argument("<spec>", "npm package spec (name, name@version, or path)").option("--no-setup", "skip the end-to-end setup after install").option("--prefetch-models", "pre-download models during setup (heavy)").action(
285
+ withErrors(async (spec, opts) => {
286
+ const { record, setup } = await installPlugin(spec, {
287
+ setup: opts.setup,
288
+ prefetchModels: opts.prefetchModels
289
+ });
290
+ console.log(`Installed "${record.name}"${record.version ? ` v${record.version}` : ""}`);
291
+ if (!opts.setup) {
292
+ console.log("Skipped setup (--no-setup) \u2014 run: diffwiki index");
293
+ } else if (setup && !setup.ready) {
294
+ const reason = setup.steps.find((s) => !s.ok)?.message ?? "setup did not complete";
295
+ console.log(`\u26A0 Not ready: ${reason}`);
296
+ console.log(" \u2192 run: diffwiki doctor");
211
297
  }
212
298
  })
213
299
  );
300
+ plugin.command("register").description("Register an existing executable as a search plugin").argument("<name>", "plugin name").requiredOption("--command <cmd...>", "command argv to spawn the plugin (e.g. node /abs/adapter.js)").action(
301
+ withErrors(async (name, opts) => {
302
+ const record = await registerPlugin(name, opts.command);
303
+ console.log(`Registered "${record.name}" \u2192 ${record.command.join(" ")}`);
304
+ })
305
+ );
306
+ plugin.command("list").description("List installed and available search plugins").action(
307
+ withErrors(async () => {
308
+ const installed = await listPlugins();
309
+ const installedNames = new Set(installed.map((p) => p.name));
310
+ console.log("Installed:");
311
+ if (installed.length === 0) {
312
+ console.log(" (none)");
313
+ } else {
314
+ for (const p of installed) {
315
+ console.log(
316
+ ` ${p.name} ${p.kind} ${p.version ?? "-"} enabled=${p.enabled} managed=${!!p.managed} spawnable=${p.spawnable}`
317
+ );
318
+ }
319
+ }
320
+ const available = listAvailablePlugins().filter((k) => !installedNames.has(k.name));
321
+ if (available.length > 0) {
322
+ console.log("\nAvailable (install with: diffwiki plugin install <name>):");
323
+ for (const k of available) {
324
+ console.log(` ${k.name} [${k.kind}] ${k.description}`);
325
+ console.log(` requires ${k.requires ?? "nothing"}`);
326
+ }
327
+ }
328
+ })
329
+ );
330
+ plugin.command("remove").description("Remove a search plugin").argument("<name>", "plugin name").action(
331
+ withErrors(async (name) => {
332
+ await removePlugin(name);
333
+ console.log(`Removed "${name}"`);
334
+ })
335
+ );
214
336
  program.command("init").description("Initialize an in-repo wiki and register it globally").option("-c, --collection <name>", "collection name (default: repo name)").option("-p, --path <path>", "wiki path within the repo (default: wiki)").action(
215
337
  withErrors(async (opts) => {
216
338
  const entry = await initRepoWiki({
@@ -222,7 +344,12 @@ function createProgram() {
222
344
  console.log("Registered globally \u2014 discoverable via: diffwiki list");
223
345
  })
224
346
  );
225
- program.command("config-set").description("Set a config value (e.g. defaultCollection)").argument("<key>", "config key").argument("<value>", "config value").action(
347
+ program.command("export").description("Generate a static site (deployable to GitHub Pages)").requiredOption("-o, --out <dir>", "output directory").option("-b, --base <path>", "base URL path (e.g. /my-repo/ for GitHub Pages)", "/").action(
348
+ withErrors(async (opts) => {
349
+ await runExport({ out: opts.out, base: opts.base });
350
+ })
351
+ );
352
+ program.command("config-set").description("Set a config value (e.g. defaultCollection, or defaultSearch <engine[:type]>)").argument("<key>", "config key").argument("<value>", "config value").action(
226
353
  withErrors(async (key, value) => {
227
354
  await setConfigValue(key, value);
228
355
  console.log(`Set ${key} = ${value}`);
@@ -282,12 +409,61 @@ function createProgram() {
282
409
  state = await startUi(Number(opts.port));
283
410
  await waitForServer(state.url);
284
411
  }
285
- openBrowser(state.url);
286
- console.log(`Opening ${state.url}`);
412
+ const coll = await resolveCwdCollection(process.cwd());
413
+ const target = coll ? `${state.url}/${encodeURIComponent(coll.name)}` : state.url;
414
+ openBrowser(target);
415
+ console.log(`Opening ${target}`);
287
416
  })
288
417
  );
289
418
  return program;
290
419
  }
291
420
 
421
+ // src/logging.ts
422
+ import { mkdirSync } from "fs";
423
+ import os from "os";
424
+ import path3 from "path";
425
+ import { configureSync, getConfig } from "@logtape/logtape";
426
+ import { getRotatingFileSink } from "@logtape/file";
427
+ import { resolveLogLevel } from "diffwiki-core";
428
+ var configured = false;
429
+ function diffwikiHome() {
430
+ const override = process.env.DIFFWIKI_HOME;
431
+ return override && override.length > 0 ? override : path3.join(os.homedir(), ".diffwiki");
432
+ }
433
+ function resolveCliLevel(argv) {
434
+ if (argv.includes("-v") || argv.includes("--verbose")) return "debug";
435
+ const flagIdx = argv.indexOf("--log");
436
+ const flagValue = flagIdx !== -1 ? argv[flagIdx + 1] : void 0;
437
+ return resolveLogLevel(flagValue ? { DIFFWIKI_LOG: flagValue } : process.env);
438
+ }
439
+ function formatLine(record) {
440
+ const category = record.category.join(".");
441
+ const message = record.message.map((part) => typeof part === "string" ? part : String(part)).join("");
442
+ return `${record.level.toUpperCase()} [${category}] ${message}`;
443
+ }
444
+ function configureCliLogging(argv = process.argv) {
445
+ if (configured || getConfig() != null) return;
446
+ configured = true;
447
+ const level = resolveCliLevel(argv);
448
+ const logsDir = path3.join(diffwikiHome(), "logs");
449
+ mkdirSync(logsDir, { recursive: true });
450
+ const logFile = path3.join(logsDir, "diffwiki.log");
451
+ const stderrSink = (record) => {
452
+ process.stderr.write(formatLine(record) + "\n");
453
+ };
454
+ configureSync({
455
+ sinks: {
456
+ stderr: stderrSink,
457
+ file: getRotatingFileSink(logFile)
458
+ },
459
+ loggers: [
460
+ { category: ["diffwiki"], lowestLevel: level, sinks: ["stderr", "file"] },
461
+ // Silence everything else (incl. LogTape meta logs).
462
+ { category: [], lowestLevel: "fatal", sinks: [] }
463
+ ]
464
+ });
465
+ }
466
+
292
467
  // src/bin.ts
468
+ configureCliLogging(process.argv);
293
469
  createProgram().parseAsync(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "diffwiki",
3
- "version": "0.4.0",
3
+ "version": "0.5.0-rc.202607230329.fb70592",
4
4
  "description": "diffwiki command-line interface",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -14,8 +14,10 @@
14
14
  "access": "public"
15
15
  },
16
16
  "dependencies": {
17
+ "@logtape/logtape": "^2.2.4",
18
+ "@logtape/file": "^2.2.4",
17
19
  "commander": "^13.0.0",
18
- "diffwiki-core": "0.4.0"
20
+ "diffwiki-core": "0.5.0-rc.202607230329.fb70592"
19
21
  },
20
22
  "devDependencies": {
21
23
  "@types/node": "^22.0.0"