diffwiki-core 0.14.0 → 0.15.0

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/dist/plugin.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/plugin.ts
@@ -22,9 +32,12 @@ var plugin_exports = {};
22
32
  __export(plugin_exports, {
23
33
  commandExists: () => commandExists,
24
34
  definePlugin: () => definePlugin,
35
+ ensureTool: () => ensureTool,
36
+ parseArticle: () => parseArticle,
25
37
  resolveDependency: () => resolveDependency,
26
38
  runProcess: () => runProcess,
27
- runProcessJson: () => runProcessJson
39
+ runProcessJson: () => runProcessJson,
40
+ stripFrontmatter: () => stripFrontmatter
28
41
  });
29
42
  module.exports = __toCommonJS(plugin_exports);
30
43
 
@@ -34,6 +47,71 @@ var import_node_util = require("util");
34
47
  var import_node_fs = require("fs");
35
48
  var import_node_path = require("path");
36
49
  var import_node_url = require("url");
50
+
51
+ // src/frontmatter.ts
52
+ var import_gray_matter = __toESM(require("gray-matter"), 1);
53
+ var import_yaml = require("yaml");
54
+ var YAML_ENGINE = {
55
+ parse: (input) => {
56
+ const data = (0, import_yaml.parse)(input);
57
+ return data && typeof data === "object" ? data : {};
58
+ },
59
+ stringify: (data) => (0, import_yaml.stringify)(data)
60
+ };
61
+ var MATTER_OPTS = { engines: { yaml: YAML_ENGINE } };
62
+ function coerceAudience(value) {
63
+ return value === "private" || value === "system" ? value : "public";
64
+ }
65
+ function coerceStatus(value) {
66
+ return value === "draft" || value === "deprecated" ? value : "published";
67
+ }
68
+ function parseFallback(raw) {
69
+ const fenceRe = /^---[ \t]*\n([\s\S]*?)\n---[ \t]*\n/;
70
+ const fenceMatch = fenceRe.exec(raw);
71
+ let title = "";
72
+ let audience = "public";
73
+ let status = "published";
74
+ let body;
75
+ const clean = (s) => s.trim().replace(/^["']|["']$/g, "");
76
+ if (fenceMatch) {
77
+ const inner = fenceMatch[1];
78
+ const titleMatch = /^title:\s*(.+)$/m.exec(inner);
79
+ if (titleMatch) {
80
+ title = clean(titleMatch[1]);
81
+ }
82
+ const audienceMatch = /^audience:\s*(.+)$/m.exec(inner);
83
+ if (audienceMatch) {
84
+ audience = coerceAudience(clean(audienceMatch[1]));
85
+ }
86
+ const statusMatch = /^status:\s*(.+)$/m.exec(inner);
87
+ if (statusMatch) {
88
+ status = coerceStatus(clean(statusMatch[1]));
89
+ }
90
+ body = raw.slice(fenceMatch[0].length);
91
+ } else {
92
+ body = raw;
93
+ }
94
+ return { title, tags: [], audience, status, body: body.replace(/^\n+/, "") };
95
+ }
96
+ var FRONTMATTER_BLOCK_RE = /^\uFEFF?[ \t]*---\r?\n[\s\S]*?\r?\n---[ \t]*(?:\r?\n|$)/;
97
+ function stripFrontmatter(raw) {
98
+ return raw.replace(FRONTMATTER_BLOCK_RE, "");
99
+ }
100
+ function parseArticle(raw) {
101
+ try {
102
+ const { data, content } = (0, import_gray_matter.default)(raw, MATTER_OPTS);
103
+ const title = typeof data.title === "string" ? data.title : "";
104
+ const tags = Array.isArray(data.tags) ? data.tags.map((t) => String(t)) : [];
105
+ const created = typeof data.created === "string" ? data.created : void 0;
106
+ const audience = coerceAudience(data.audience);
107
+ const status = coerceStatus(data.status);
108
+ return { title, tags, created, audience, status, body: content.replace(/^\n+/, "") };
109
+ } catch {
110
+ return parseFallback(raw);
111
+ }
112
+ }
113
+
114
+ // src/plugins/sdk.ts
37
115
  var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
38
116
  async function runProcess(file, args, opts) {
39
117
  const { stdout, stderr } = await execFileAsync(file, args, {
@@ -90,6 +168,57 @@ async function resolveDependency(dep, fromUrl) {
90
168
  }
91
169
  return { dependency: dep, command: null, source: "missing" };
92
170
  }
171
+ async function ensureTool(spec, opts) {
172
+ const exists = opts?.exists ?? commandExists;
173
+ const run = opts?.run ?? ((file, args) => runProcess(file, args));
174
+ const log = opts?.log;
175
+ if (await exists(spec.command)) {
176
+ return { command: spec.command, present: true, installed: false, message: `${spec.command} present` };
177
+ }
178
+ if (!opts?.install) {
179
+ return {
180
+ command: spec.command,
181
+ present: false,
182
+ installed: false,
183
+ message: `${spec.command} not found \u2014 ${spec.manual}`
184
+ };
185
+ }
186
+ const attempts = [];
187
+ if (spec.mise && await exists("mise"))
188
+ attempts.push({ method: "mise", bin: "mise", args: ["use", "-g", spec.mise] });
189
+ if (spec.brew && await exists("brew")) attempts.push({ method: "brew", bin: "brew", args: ["install", spec.brew] });
190
+ for (const a of attempts) {
191
+ try {
192
+ log?.("info", `installing ${spec.command} via ${a.method}\u2026`);
193
+ await run(a.bin, a.args);
194
+ if (await exists(spec.command)) {
195
+ return {
196
+ command: spec.command,
197
+ present: true,
198
+ installed: true,
199
+ method: a.method,
200
+ message: `installed ${spec.command} via ${a.method}`
201
+ };
202
+ }
203
+ return {
204
+ command: spec.command,
205
+ present: false,
206
+ installed: true,
207
+ method: a.method,
208
+ message: `installed ${spec.command} via ${a.method}, but it isn't on PATH yet \u2014 restart your shell (or activate ${a.method}) and retry`
209
+ };
210
+ } catch (err) {
211
+ log?.("warn", `${a.method} could not install ${spec.command}: ${err.message}`);
212
+ }
213
+ }
214
+ const aptHint = spec.apt ? ` (Debian/Ubuntu: sudo apt-get install -y ${spec.apt})` : "";
215
+ return {
216
+ command: spec.command,
217
+ present: false,
218
+ installed: false,
219
+ message: `could not auto-install ${spec.command} \u2014 ${spec.manual}${aptHint}`
220
+ };
221
+ }
93
222
  function definePlugin(def) {
94
223
  const { name, kind, version, capabilities, dependencies, handlers } = def;
95
224
  const describeResult = {
@@ -169,7 +298,10 @@ function definePlugin(def) {
169
298
  0 && (module.exports = {
170
299
  commandExists,
171
300
  definePlugin,
301
+ ensureTool,
302
+ parseArticle,
172
303
  resolveDependency,
173
304
  runProcess,
174
- runProcessJson
305
+ runProcessJson,
306
+ stripFrontmatter
175
307
  });
package/dist/plugin.d.cts CHANGED
@@ -1,179 +1,5 @@
1
- /**
2
- * Wire-protocol and SDK types shared by the plugin authoring SDK (`sdk.ts`) and
3
- * re-exported to plugin authors via the `diffwiki-core/plugin` subpath.
4
- *
5
- * @module
6
- */
7
- /** Log severity levels for plugin diagnostic messages. */
8
- type LogLevel = 'info' | 'warn' | 'error';
9
- /** A logging function that plugins use to emit diagnostic messages to stderr. */
10
- type PluginLogFn = (level: LogLevel, message: string) => void;
11
- /** A wire-format collection as received in handler params. */
12
- interface WireCollection {
13
- name: string;
14
- path: string;
15
- }
16
- /** Diagnostic entry returned by the health handler. */
17
- interface WireDiagnostic {
18
- level: 'ok' | 'warn' | 'error';
19
- message: string;
20
- }
21
- /** Readiness details returned by the health handler. */
22
- interface WireReadiness {
23
- toolInstalled: boolean;
24
- modelsPresent: boolean;
25
- indexBuilt: boolean;
26
- docsIndexed: number;
27
- embeddingsPending: number;
28
- firstSearchCost: 'instant' | 'model-load' | 'model-download';
29
- }
30
- /** A single search hit returned by the search handler. */
31
- interface SearchHandlerHit {
32
- collection: string;
33
- relPath: string;
34
- title: string;
35
- snippet?: string;
36
- score?: number;
37
- docid?: string;
38
- tags?: string[];
39
- }
40
- /** Result shape returned by the search handler. */
41
- interface SearchHandlerResult {
42
- hits: SearchHandlerHit[];
43
- }
44
- /** Result shape returned by the index handler. */
45
- interface IndexHandlerResult {
46
- indexed: number;
47
- }
48
- /** Result shape returned by the health handler. */
49
- interface HealthHandlerResult {
50
- ready: boolean;
51
- readiness?: WireReadiness;
52
- diagnostics: WireDiagnostic[];
53
- }
54
- /** Result shape returned by the setup handler. */
55
- interface SetupHandlerResult {
56
- ready: boolean;
57
- steps?: Array<{
58
- step: string;
59
- ok: boolean;
60
- message?: string;
61
- }>;
62
- }
63
- /** Result shape returned by the onEvent handler. */
64
- interface EventHandlerResult {
65
- accepted: boolean;
66
- }
67
- /** Parameters passed to the search handler. */
68
- interface SearchParams {
69
- term: string;
70
- collections: WireCollection[];
71
- collection?: string;
72
- type?: string;
73
- limit?: number;
74
- }
75
- /** Parameters passed to the index handler. */
76
- interface IndexParams {
77
- collections: WireCollection[];
78
- embed?: boolean;
79
- }
80
- /** Parameters passed to the setup handler. */
81
- interface SetupParams {
82
- collections: WireCollection[];
83
- embed?: boolean;
84
- prefetchModels?: boolean;
85
- }
86
- /** Parameters passed to the onEvent handler. */
87
- interface EventParams {
88
- event: string;
89
- collection: WireCollection;
90
- relPath?: string;
91
- collections: WireCollection[];
92
- }
93
- /** Parameters passed to the health handler. */
94
- interface HealthParams {
95
- collections: WireCollection[];
96
- }
97
- /** Plugin capabilities advertised during describe. */
98
- interface PluginCapabilities {
99
- searchTypes: string[];
100
- collectionFilter: boolean;
101
- events: boolean;
102
- }
103
- /**
104
- * An external executable a plugin needs at runtime (e.g. `qmd`, `rg`).
105
- *
106
- * Declaring a dependency lets the plugin resolve the binary uniformly and lets
107
- * `setup`/`health` report it. When `npm` is set, the package is expected to be a
108
- * regular dependency in the plugin's own `package.json`, so `diffwiki plugin
109
- * install` pulls it transitively and {@link ResolvedDependency} finds its bin in
110
- * the plugin's `node_modules` — no global install required.
111
- */
112
- interface PluginDependency {
113
- /** The executable name, e.g. `'qmd'` or `'rg'`. */
114
- command: string;
115
- /** npm package that provides the executable; when set, the local bin is preferred over PATH. */
116
- npm?: string;
117
- /** Human-facing manual install hint, shown when the dependency is unresolved. */
118
- hint?: string;
119
- }
120
- /**
121
- * The outcome of resolving a {@link PluginDependency}: the argv prefix to invoke
122
- * it, plus where it was found. `command` is null when unresolved.
123
- */
124
- interface ResolvedDependency {
125
- dependency: PluginDependency;
126
- /** Argv prefix to spawn the tool, or null if unresolved. */
127
- command: string[] | null;
128
- /** How it resolved: the npm-provided local bin, the system PATH, or nowhere. */
129
- source: 'npm' | 'path' | 'missing';
130
- }
131
- /** The handler functions a plugin provides. */
132
- interface PluginHandlers {
133
- /** Handle a search request. Required for search plugins. */
134
- search: (params: SearchParams, log: PluginLogFn) => Promise<SearchHandlerResult> | SearchHandlerResult;
135
- /** Handle a reindex request. Optional -- defaults to `{ indexed: 0 }`. */
136
- index?: (params: IndexParams, log: PluginLogFn) => Promise<IndexHandlerResult> | IndexHandlerResult;
137
- /** Handle an initial setup/provisioning request. Optional -- defaults to `{ ready: true }`. */
138
- setup?: (params: SetupParams, log: PluginLogFn) => Promise<SetupHandlerResult> | SetupHandlerResult;
139
- /** Handle a lifecycle event (article/collection add/update/remove). Optional. */
140
- onEvent?: (params: EventParams, log: PluginLogFn) => Promise<EventHandlerResult> | EventHandlerResult;
141
- /** Report plugin health and readiness. Optional. */
142
- health?: (params: HealthParams, log: PluginLogFn) => Promise<HealthHandlerResult> | HealthHandlerResult;
143
- }
144
- /** The definition object passed to `definePlugin()`. */
145
- interface PluginDefinition {
146
- /** Plugin name (must match the name used in `diffwiki plugin register`). */
147
- name: string;
148
- /** Plugin kind. Currently only `'search'` is supported. */
149
- kind: 'search';
150
- /** Semantic version string. */
151
- version?: string;
152
- /** Plugin capabilities advertised to the host. */
153
- capabilities: PluginCapabilities;
154
- /** External executables the plugin needs. Reported via `describe` and resolvable via {@link ResolvedDependency}. */
155
- dependencies?: PluginDependency[];
156
- /** Handler functions for each operation. */
157
- handlers: PluginHandlers;
158
- }
159
- /** The object returned by `definePlugin()`. Call `.run()` to serve one request. */
160
- interface DefinedPlugin {
161
- /** The plugin definition. */
162
- definition: PluginDefinition;
163
- /**
164
- * Read one JSON request from stdin, dispatch to the matching handler, write
165
- * the JSON response to stdout, and exit. Exit code 0 on success, 1 on
166
- * handler error. Logs go to stderr.
167
- */
168
- run: () => void;
169
- }
170
- /** Options for `runProcess` and `runProcessJson`. */
171
- interface RunProcessOptions {
172
- /** Environment variables for the child process. Defaults to `process.env`. */
173
- env?: NodeJS.ProcessEnv;
174
- /** Timeout in milliseconds. No timeout by default. */
175
- timeout?: number;
176
- }
1
+ import { Y as PluginDefinition, Z as DefinedPlugin, _ as PluginDependency, $ as ResolvedDependency, a0 as RunProcessOptions } from './frontmatter-ghMCpLNf.cjs';
2
+ export { a1 as EventHandlerResult, a2 as EventParams, a3 as HealthHandlerResult, a4 as HealthParams, a5 as IndexHandlerResult, a6 as IndexParams, a7 as LogLevel, j as ParsedArticle, a8 as PluginCapabilities, a9 as PluginHandlers, aa as PluginLogFn, J as SearchExpansion, ab as SearchHandlerHit, ac as SearchHandlerResult, ad as SearchParams, ae as SetupHandlerResult, af as SetupParams, ag as WireCollection, ah as WireDiagnostic, ai as WireReadiness, U as parseArticle, X as stripFrontmatter } from './frontmatter-ghMCpLNf.cjs';
177
3
 
178
4
  /**
179
5
  * diffwiki plugin authoring SDK — the public surface for plugin authors, imported
@@ -207,6 +33,57 @@ declare function commandExists(name: string): Promise<boolean>;
207
33
  * @param fromUrl The plugin bin's `import.meta.url`, used to anchor node resolution.
208
34
  */
209
35
  declare function resolveDependency(dep: PluginDependency, fromUrl: string): Promise<ResolvedDependency>;
36
+ /** Per-package-manager identifiers for a tool ensureTool can install. */
37
+ interface ToolInstallers {
38
+ /** mise tool id (installed with `mise use -g`), e.g. `'fzf'` or `'npm:@tobilu/qmd'`. */
39
+ mise?: string;
40
+ /** Homebrew formula (installed with `brew install`), e.g. `'fzf'`. */
41
+ brew?: string;
42
+ /** apt package name. NOT auto-run (needs sudo) — surfaced as guidance only. */
43
+ apt?: string;
44
+ }
45
+ /** A tool to ensure is on PATH, with the package ids + a manual fallback hint. */
46
+ interface EnsureToolSpec extends ToolInstallers {
47
+ /** Executable checked on PATH, e.g. `'fzf'`. */
48
+ command: string;
49
+ /** Manual instruction (URL or command) shown when auto-install can't proceed. */
50
+ manual: string;
51
+ }
52
+ /** The outcome of an {@link ensureTool} call. */
53
+ interface EnsureToolResult {
54
+ command: string;
55
+ /** True if the tool is on PATH after ensureTool ran. */
56
+ present: boolean;
57
+ /** True if ensureTool installed it during this call. */
58
+ installed: boolean;
59
+ /** The manager used, when an install was attempted and succeeded. */
60
+ method?: 'mise' | 'brew';
61
+ /** A human-facing message describing the outcome. */
62
+ message: string;
63
+ }
64
+ /** Injectable seams for {@link ensureTool} (defaults hit the real system). */
65
+ interface EnsureToolOptions {
66
+ /** Attempt installation when the tool is missing (default: false — check only). */
67
+ install?: boolean;
68
+ /** Diagnostic sink. */
69
+ log?: (level: 'info' | 'warn' | 'error', message: string) => void;
70
+ /** Existence probe (injected in tests). Defaults to {@link commandExists}. */
71
+ exists?: (command: string) => Promise<boolean>;
72
+ /** Command runner (injected in tests). Defaults to {@link runProcess}. */
73
+ run?: (file: string, args: readonly string[]) => Promise<unknown>;
74
+ }
75
+ /**
76
+ * Ensure a system tool is on PATH, optionally installing it via a detected
77
+ * package manager. Detection order: **mise**, then **Homebrew**. `apt` is
78
+ * intentionally never auto-run (it needs `sudo`, which would hang or prompt) —
79
+ * it is included in the guidance message instead. Safe to call when the tool
80
+ * already exists (returns immediately) and never throws.
81
+ *
82
+ * Note: a tool installed via mise may not be on PATH in the current process
83
+ * until mise's shims are active; that case is reported as `installed:true,
84
+ * present:false` with a message telling the user to restart their shell.
85
+ */
86
+ declare function ensureTool(spec: EnsureToolSpec, opts?: EnsureToolOptions): Promise<EnsureToolResult>;
210
87
  /**
211
88
  * Define a diffwiki plugin. The required entry point for all plugins: accepts a
212
89
  * {@link PluginDefinition} and returns a {@link DefinedPlugin} whose `.run()`
@@ -226,4 +103,4 @@ declare function resolveDependency(dep: PluginDependency, fromUrl: string): Prom
226
103
  */
227
104
  declare function definePlugin(def: PluginDefinition): DefinedPlugin;
228
105
 
229
- export { type DefinedPlugin, type EventHandlerResult, type EventParams, type HealthHandlerResult, type HealthParams, type IndexHandlerResult, type IndexParams, type LogLevel, type PluginCapabilities, type PluginDefinition, type PluginDependency, type PluginHandlers, type PluginLogFn, type ResolvedDependency, type RunProcessOptions, type SearchHandlerHit, type SearchHandlerResult, type SearchParams, type SetupHandlerResult, type SetupParams, type WireCollection, type WireDiagnostic, type WireReadiness, commandExists, definePlugin, resolveDependency, runProcess, runProcessJson };
106
+ export { DefinedPlugin, type EnsureToolOptions, type EnsureToolResult, type EnsureToolSpec, PluginDefinition, PluginDependency, ResolvedDependency, RunProcessOptions, type ToolInstallers, commandExists, definePlugin, ensureTool, resolveDependency, runProcess, runProcessJson };
package/dist/plugin.d.ts CHANGED
@@ -1,179 +1,5 @@
1
- /**
2
- * Wire-protocol and SDK types shared by the plugin authoring SDK (`sdk.ts`) and
3
- * re-exported to plugin authors via the `diffwiki-core/plugin` subpath.
4
- *
5
- * @module
6
- */
7
- /** Log severity levels for plugin diagnostic messages. */
8
- type LogLevel = 'info' | 'warn' | 'error';
9
- /** A logging function that plugins use to emit diagnostic messages to stderr. */
10
- type PluginLogFn = (level: LogLevel, message: string) => void;
11
- /** A wire-format collection as received in handler params. */
12
- interface WireCollection {
13
- name: string;
14
- path: string;
15
- }
16
- /** Diagnostic entry returned by the health handler. */
17
- interface WireDiagnostic {
18
- level: 'ok' | 'warn' | 'error';
19
- message: string;
20
- }
21
- /** Readiness details returned by the health handler. */
22
- interface WireReadiness {
23
- toolInstalled: boolean;
24
- modelsPresent: boolean;
25
- indexBuilt: boolean;
26
- docsIndexed: number;
27
- embeddingsPending: number;
28
- firstSearchCost: 'instant' | 'model-load' | 'model-download';
29
- }
30
- /** A single search hit returned by the search handler. */
31
- interface SearchHandlerHit {
32
- collection: string;
33
- relPath: string;
34
- title: string;
35
- snippet?: string;
36
- score?: number;
37
- docid?: string;
38
- tags?: string[];
39
- }
40
- /** Result shape returned by the search handler. */
41
- interface SearchHandlerResult {
42
- hits: SearchHandlerHit[];
43
- }
44
- /** Result shape returned by the index handler. */
45
- interface IndexHandlerResult {
46
- indexed: number;
47
- }
48
- /** Result shape returned by the health handler. */
49
- interface HealthHandlerResult {
50
- ready: boolean;
51
- readiness?: WireReadiness;
52
- diagnostics: WireDiagnostic[];
53
- }
54
- /** Result shape returned by the setup handler. */
55
- interface SetupHandlerResult {
56
- ready: boolean;
57
- steps?: Array<{
58
- step: string;
59
- ok: boolean;
60
- message?: string;
61
- }>;
62
- }
63
- /** Result shape returned by the onEvent handler. */
64
- interface EventHandlerResult {
65
- accepted: boolean;
66
- }
67
- /** Parameters passed to the search handler. */
68
- interface SearchParams {
69
- term: string;
70
- collections: WireCollection[];
71
- collection?: string;
72
- type?: string;
73
- limit?: number;
74
- }
75
- /** Parameters passed to the index handler. */
76
- interface IndexParams {
77
- collections: WireCollection[];
78
- embed?: boolean;
79
- }
80
- /** Parameters passed to the setup handler. */
81
- interface SetupParams {
82
- collections: WireCollection[];
83
- embed?: boolean;
84
- prefetchModels?: boolean;
85
- }
86
- /** Parameters passed to the onEvent handler. */
87
- interface EventParams {
88
- event: string;
89
- collection: WireCollection;
90
- relPath?: string;
91
- collections: WireCollection[];
92
- }
93
- /** Parameters passed to the health handler. */
94
- interface HealthParams {
95
- collections: WireCollection[];
96
- }
97
- /** Plugin capabilities advertised during describe. */
98
- interface PluginCapabilities {
99
- searchTypes: string[];
100
- collectionFilter: boolean;
101
- events: boolean;
102
- }
103
- /**
104
- * An external executable a plugin needs at runtime (e.g. `qmd`, `rg`).
105
- *
106
- * Declaring a dependency lets the plugin resolve the binary uniformly and lets
107
- * `setup`/`health` report it. When `npm` is set, the package is expected to be a
108
- * regular dependency in the plugin's own `package.json`, so `diffwiki plugin
109
- * install` pulls it transitively and {@link ResolvedDependency} finds its bin in
110
- * the plugin's `node_modules` — no global install required.
111
- */
112
- interface PluginDependency {
113
- /** The executable name, e.g. `'qmd'` or `'rg'`. */
114
- command: string;
115
- /** npm package that provides the executable; when set, the local bin is preferred over PATH. */
116
- npm?: string;
117
- /** Human-facing manual install hint, shown when the dependency is unresolved. */
118
- hint?: string;
119
- }
120
- /**
121
- * The outcome of resolving a {@link PluginDependency}: the argv prefix to invoke
122
- * it, plus where it was found. `command` is null when unresolved.
123
- */
124
- interface ResolvedDependency {
125
- dependency: PluginDependency;
126
- /** Argv prefix to spawn the tool, or null if unresolved. */
127
- command: string[] | null;
128
- /** How it resolved: the npm-provided local bin, the system PATH, or nowhere. */
129
- source: 'npm' | 'path' | 'missing';
130
- }
131
- /** The handler functions a plugin provides. */
132
- interface PluginHandlers {
133
- /** Handle a search request. Required for search plugins. */
134
- search: (params: SearchParams, log: PluginLogFn) => Promise<SearchHandlerResult> | SearchHandlerResult;
135
- /** Handle a reindex request. Optional -- defaults to `{ indexed: 0 }`. */
136
- index?: (params: IndexParams, log: PluginLogFn) => Promise<IndexHandlerResult> | IndexHandlerResult;
137
- /** Handle an initial setup/provisioning request. Optional -- defaults to `{ ready: true }`. */
138
- setup?: (params: SetupParams, log: PluginLogFn) => Promise<SetupHandlerResult> | SetupHandlerResult;
139
- /** Handle a lifecycle event (article/collection add/update/remove). Optional. */
140
- onEvent?: (params: EventParams, log: PluginLogFn) => Promise<EventHandlerResult> | EventHandlerResult;
141
- /** Report plugin health and readiness. Optional. */
142
- health?: (params: HealthParams, log: PluginLogFn) => Promise<HealthHandlerResult> | HealthHandlerResult;
143
- }
144
- /** The definition object passed to `definePlugin()`. */
145
- interface PluginDefinition {
146
- /** Plugin name (must match the name used in `diffwiki plugin register`). */
147
- name: string;
148
- /** Plugin kind. Currently only `'search'` is supported. */
149
- kind: 'search';
150
- /** Semantic version string. */
151
- version?: string;
152
- /** Plugin capabilities advertised to the host. */
153
- capabilities: PluginCapabilities;
154
- /** External executables the plugin needs. Reported via `describe` and resolvable via {@link ResolvedDependency}. */
155
- dependencies?: PluginDependency[];
156
- /** Handler functions for each operation. */
157
- handlers: PluginHandlers;
158
- }
159
- /** The object returned by `definePlugin()`. Call `.run()` to serve one request. */
160
- interface DefinedPlugin {
161
- /** The plugin definition. */
162
- definition: PluginDefinition;
163
- /**
164
- * Read one JSON request from stdin, dispatch to the matching handler, write
165
- * the JSON response to stdout, and exit. Exit code 0 on success, 1 on
166
- * handler error. Logs go to stderr.
167
- */
168
- run: () => void;
169
- }
170
- /** Options for `runProcess` and `runProcessJson`. */
171
- interface RunProcessOptions {
172
- /** Environment variables for the child process. Defaults to `process.env`. */
173
- env?: NodeJS.ProcessEnv;
174
- /** Timeout in milliseconds. No timeout by default. */
175
- timeout?: number;
176
- }
1
+ import { Y as PluginDefinition, Z as DefinedPlugin, _ as PluginDependency, $ as ResolvedDependency, a0 as RunProcessOptions } from './frontmatter-ghMCpLNf.js';
2
+ export { a1 as EventHandlerResult, a2 as EventParams, a3 as HealthHandlerResult, a4 as HealthParams, a5 as IndexHandlerResult, a6 as IndexParams, a7 as LogLevel, j as ParsedArticle, a8 as PluginCapabilities, a9 as PluginHandlers, aa as PluginLogFn, J as SearchExpansion, ab as SearchHandlerHit, ac as SearchHandlerResult, ad as SearchParams, ae as SetupHandlerResult, af as SetupParams, ag as WireCollection, ah as WireDiagnostic, ai as WireReadiness, U as parseArticle, X as stripFrontmatter } from './frontmatter-ghMCpLNf.js';
177
3
 
178
4
  /**
179
5
  * diffwiki plugin authoring SDK — the public surface for plugin authors, imported
@@ -207,6 +33,57 @@ declare function commandExists(name: string): Promise<boolean>;
207
33
  * @param fromUrl The plugin bin's `import.meta.url`, used to anchor node resolution.
208
34
  */
209
35
  declare function resolveDependency(dep: PluginDependency, fromUrl: string): Promise<ResolvedDependency>;
36
+ /** Per-package-manager identifiers for a tool ensureTool can install. */
37
+ interface ToolInstallers {
38
+ /** mise tool id (installed with `mise use -g`), e.g. `'fzf'` or `'npm:@tobilu/qmd'`. */
39
+ mise?: string;
40
+ /** Homebrew formula (installed with `brew install`), e.g. `'fzf'`. */
41
+ brew?: string;
42
+ /** apt package name. NOT auto-run (needs sudo) — surfaced as guidance only. */
43
+ apt?: string;
44
+ }
45
+ /** A tool to ensure is on PATH, with the package ids + a manual fallback hint. */
46
+ interface EnsureToolSpec extends ToolInstallers {
47
+ /** Executable checked on PATH, e.g. `'fzf'`. */
48
+ command: string;
49
+ /** Manual instruction (URL or command) shown when auto-install can't proceed. */
50
+ manual: string;
51
+ }
52
+ /** The outcome of an {@link ensureTool} call. */
53
+ interface EnsureToolResult {
54
+ command: string;
55
+ /** True if the tool is on PATH after ensureTool ran. */
56
+ present: boolean;
57
+ /** True if ensureTool installed it during this call. */
58
+ installed: boolean;
59
+ /** The manager used, when an install was attempted and succeeded. */
60
+ method?: 'mise' | 'brew';
61
+ /** A human-facing message describing the outcome. */
62
+ message: string;
63
+ }
64
+ /** Injectable seams for {@link ensureTool} (defaults hit the real system). */
65
+ interface EnsureToolOptions {
66
+ /** Attempt installation when the tool is missing (default: false — check only). */
67
+ install?: boolean;
68
+ /** Diagnostic sink. */
69
+ log?: (level: 'info' | 'warn' | 'error', message: string) => void;
70
+ /** Existence probe (injected in tests). Defaults to {@link commandExists}. */
71
+ exists?: (command: string) => Promise<boolean>;
72
+ /** Command runner (injected in tests). Defaults to {@link runProcess}. */
73
+ run?: (file: string, args: readonly string[]) => Promise<unknown>;
74
+ }
75
+ /**
76
+ * Ensure a system tool is on PATH, optionally installing it via a detected
77
+ * package manager. Detection order: **mise**, then **Homebrew**. `apt` is
78
+ * intentionally never auto-run (it needs `sudo`, which would hang or prompt) —
79
+ * it is included in the guidance message instead. Safe to call when the tool
80
+ * already exists (returns immediately) and never throws.
81
+ *
82
+ * Note: a tool installed via mise may not be on PATH in the current process
83
+ * until mise's shims are active; that case is reported as `installed:true,
84
+ * present:false` with a message telling the user to restart their shell.
85
+ */
86
+ declare function ensureTool(spec: EnsureToolSpec, opts?: EnsureToolOptions): Promise<EnsureToolResult>;
210
87
  /**
211
88
  * Define a diffwiki plugin. The required entry point for all plugins: accepts a
212
89
  * {@link PluginDefinition} and returns a {@link DefinedPlugin} whose `.run()`
@@ -226,4 +103,4 @@ declare function resolveDependency(dep: PluginDependency, fromUrl: string): Prom
226
103
  */
227
104
  declare function definePlugin(def: PluginDefinition): DefinedPlugin;
228
105
 
229
- export { type DefinedPlugin, type EventHandlerResult, type EventParams, type HealthHandlerResult, type HealthParams, type IndexHandlerResult, type IndexParams, type LogLevel, type PluginCapabilities, type PluginDefinition, type PluginDependency, type PluginHandlers, type PluginLogFn, type ResolvedDependency, type RunProcessOptions, type SearchHandlerHit, type SearchHandlerResult, type SearchParams, type SetupHandlerResult, type SetupParams, type WireCollection, type WireDiagnostic, type WireReadiness, commandExists, definePlugin, resolveDependency, runProcess, runProcessJson };
106
+ export { DefinedPlugin, type EnsureToolOptions, type EnsureToolResult, type EnsureToolSpec, PluginDefinition, PluginDependency, ResolvedDependency, RunProcessOptions, type ToolInstallers, commandExists, definePlugin, ensureTool, resolveDependency, runProcess, runProcessJson };