kennel 0.2.30 → 0.2.32
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/cli/dispatch.js +5 -0
- package/dist/cli/usage.js +4 -0
- package/dist/core/runtime.js +160 -127
- package/dist/llm/clipboard.d.ts +1 -0
- package/dist/llm/clipboard.js +59 -0
- package/dist/llm/command.d.ts +1 -0
- package/dist/llm/command.js +219 -0
- package/dist/llm/worker.d.ts +19 -0
- package/dist/llm/worker.js +866 -0
- package/dist/package/preview.js +164 -130
- package/dist/plugins/external.d.ts +6 -0
- package/dist/plugins/external.js +16 -0
- package/dist/site/server-plugins.d.ts +12 -0
- package/dist/site/server-plugins.js +82 -2
- package/package.json +3 -2
- package/templates/plugin/package.json +1 -0
- package/templates/plugin/server.ts +43 -0
- package/templates/plugin/tsconfig.json +1 -1
- package/dist/site/folder-icons.d.ts +0 -39
- package/dist/site/folder-icons.js +0 -68
package/dist/package/preview.js
CHANGED
|
@@ -38723,6 +38723,10 @@ function assetKey(value) {
|
|
|
38723
38723
|
function encodeBase64Url(value) {
|
|
38724
38724
|
return Buffer.from(value, "utf8").toString("base64").replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
38725
38725
|
}
|
|
38726
|
+
function decodeBase64Url2(value) {
|
|
38727
|
+
const padded = `${value}${"=".repeat((4 - value.length % 4) % 4)}`;
|
|
38728
|
+
return Buffer.from(padded.replaceAll("-", "+").replaceAll("_", "/"), "base64").toString("utf8");
|
|
38729
|
+
}
|
|
38726
38730
|
function iconUrl(folder, hash, mediaType) {
|
|
38727
38731
|
return `/api/folders/${encodeBase64Url(folder)}/assets/${hash}.${iconExtensions.get(mediaType)}`;
|
|
38728
38732
|
}
|
|
@@ -38746,6 +38750,54 @@ async function folderIcon(folder, assets, assetFiles, fallback) {
|
|
|
38746
38750
|
};
|
|
38747
38751
|
return key;
|
|
38748
38752
|
}
|
|
38753
|
+
async function searchIconDataUrl(icon) {
|
|
38754
|
+
if (!icon)
|
|
38755
|
+
return;
|
|
38756
|
+
if (icon.startsWith("data:"))
|
|
38757
|
+
return icon;
|
|
38758
|
+
const mediaType = searchIconTypes.get(extname4(icon).toLowerCase());
|
|
38759
|
+
if (!mediaType)
|
|
38760
|
+
return;
|
|
38761
|
+
const details = await stat5(icon).catch(() => {
|
|
38762
|
+
return;
|
|
38763
|
+
});
|
|
38764
|
+
if (!details?.size || details.size > maxSearchIconBytes)
|
|
38765
|
+
return;
|
|
38766
|
+
return `data:${mediaType};base64,${(await readFile8(icon)).toString("base64")}`;
|
|
38767
|
+
}
|
|
38768
|
+
async function faviconAsset(folderName, assets) {
|
|
38769
|
+
if (process.env.KENNEL_EXTERNAL_ICONS !== "1")
|
|
38770
|
+
return;
|
|
38771
|
+
const host = await folderWebsite(folderName);
|
|
38772
|
+
if (!host)
|
|
38773
|
+
return;
|
|
38774
|
+
const key = `favicon:${host}`;
|
|
38775
|
+
assets[key] = {
|
|
38776
|
+
mediaType: "image/png",
|
|
38777
|
+
source: "favicon",
|
|
38778
|
+
url: `/api/folder-favicon?path=${encodeURIComponent(folderName)}&host=${encodeURIComponent(host)}`
|
|
38779
|
+
};
|
|
38780
|
+
return key;
|
|
38781
|
+
}
|
|
38782
|
+
async function iconWithFaviconFallback(folder, assets, assetFiles) {
|
|
38783
|
+
return folderIcon(folder, assets, assetFiles, (folderName) => faviconAsset(folderName, assets));
|
|
38784
|
+
}
|
|
38785
|
+
async function hydrateFolderIcons(metadata, assetFiles) {
|
|
38786
|
+
const assets = {};
|
|
38787
|
+
const projects = metadata.projects?.items ?? [];
|
|
38788
|
+
const [iconAssetKey, projectItems] = await Promise.all([
|
|
38789
|
+
iconWithFaviconFallback(metadata.folder, assets, assetFiles),
|
|
38790
|
+
Promise.all(projects.map(async (project) => ({
|
|
38791
|
+
...project,
|
|
38792
|
+
iconAssetKey: await iconWithFaviconFallback(project.worktree, assets, assetFiles)
|
|
38793
|
+
})))
|
|
38794
|
+
]);
|
|
38795
|
+
return {
|
|
38796
|
+
assets,
|
|
38797
|
+
iconAssetKey,
|
|
38798
|
+
projects: { items: projectItems }
|
|
38799
|
+
};
|
|
38800
|
+
}
|
|
38749
38801
|
async function workspaceBreadcrumbIcons(breadcrumbs, assetFiles, assets = {}) {
|
|
38750
38802
|
return Promise.all(breadcrumbs.map(async (crumb) => {
|
|
38751
38803
|
const iconAssetKey = await folderIcon(crumb.path, assets, assetFiles);
|
|
@@ -38753,8 +38805,9 @@ async function workspaceBreadcrumbIcons(breadcrumbs, assetFiles, assets = {}) {
|
|
|
38753
38805
|
return icon ? { ...crumb, icon: { mediaType: icon.mediaType, url: icon.url } } : crumb;
|
|
38754
38806
|
}));
|
|
38755
38807
|
}
|
|
38756
|
-
var maxIconBytes2, iconTypes, iconExtensions;
|
|
38808
|
+
var maxIconBytes2, iconTypes, iconExtensions, maxSearchIconBytes, searchIconTypes;
|
|
38757
38809
|
var init_folder_icons = __esm(() => {
|
|
38810
|
+
init_folder_favicon();
|
|
38758
38811
|
maxIconBytes2 = 4 * 1024 * 1024;
|
|
38759
38812
|
iconTypes = new Map([
|
|
38760
38813
|
[".jpeg", "image/jpeg"],
|
|
@@ -38765,6 +38818,16 @@ var init_folder_icons = __esm(() => {
|
|
|
38765
38818
|
["image/jpeg", "jpg"],
|
|
38766
38819
|
["image/png", "png"]
|
|
38767
38820
|
]);
|
|
38821
|
+
maxSearchIconBytes = 512 * 1024;
|
|
38822
|
+
searchIconTypes = new Map([
|
|
38823
|
+
[".gif", "image/gif"],
|
|
38824
|
+
[".ico", "image/x-icon"],
|
|
38825
|
+
[".jpeg", "image/jpeg"],
|
|
38826
|
+
[".jpg", "image/jpeg"],
|
|
38827
|
+
[".png", "image/png"],
|
|
38828
|
+
[".svg", "image/svg+xml"],
|
|
38829
|
+
[".webp", "image/webp"]
|
|
38830
|
+
]);
|
|
38768
38831
|
});
|
|
38769
38832
|
|
|
38770
38833
|
// packages/notes/src/manifest.ts
|
|
@@ -38831,6 +38894,45 @@ async function loadNotes(folder, linkedFolders = []) {
|
|
|
38831
38894
|
status: "available"
|
|
38832
38895
|
};
|
|
38833
38896
|
}
|
|
38897
|
+
function codeSpan(text) {
|
|
38898
|
+
const escaped = text.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/gu, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
38899
|
+
const longest = Math.max(0, ...(escaped.match(/`+/g) ?? []).map((run) => run.length));
|
|
38900
|
+
const fence = "`".repeat(longest + 1);
|
|
38901
|
+
const pad = escaped.startsWith("`") || escaped.endsWith("`") ? " " : "";
|
|
38902
|
+
return `${fence}${pad}${escaped}${pad}${fence}`;
|
|
38903
|
+
}
|
|
38904
|
+
async function notesLlmSections({
|
|
38905
|
+
folder,
|
|
38906
|
+
linkedFolders = [],
|
|
38907
|
+
signal
|
|
38908
|
+
}) {
|
|
38909
|
+
const groups = [];
|
|
38910
|
+
const notes = [];
|
|
38911
|
+
for (const source of [folder, ...linkedFolders]) {
|
|
38912
|
+
signal.throwIfAborted();
|
|
38913
|
+
const files = await listMarkdownFiles(source);
|
|
38914
|
+
const paths = files.map((file) => resolve10(source, file.relativePath));
|
|
38915
|
+
groups.push({ folder: source, paths });
|
|
38916
|
+
files.forEach((file, index) => notes.push({ activityAt: file.activityAt, path: paths[index] }));
|
|
38917
|
+
}
|
|
38918
|
+
if (notes.length === 0)
|
|
38919
|
+
return [];
|
|
38920
|
+
const recent = notes.sort((left, right) => right.activityAt - left.activityAt || (left.path < right.path ? -1 : left.path > right.path ? 1 : 0)).slice(0, llmNoteLimit);
|
|
38921
|
+
return [
|
|
38922
|
+
{
|
|
38923
|
+
id: "notes",
|
|
38924
|
+
title: "Markdown notes",
|
|
38925
|
+
lines: [
|
|
38926
|
+
...groups.filter((group) => group.paths.length > 0).map((group) => `- Source: ${codeSpan(group.folder)} (${group.paths.length} ${group.paths.length === 1 ? "note" : "notes"})`),
|
|
38927
|
+
`- Total notes: ${notes.length}`,
|
|
38928
|
+
"",
|
|
38929
|
+
`Most recently changed (${recent.length} of ${notes.length}):`,
|
|
38930
|
+
"",
|
|
38931
|
+
...recent.map((note) => `- ${codeSpan(note.path)}`)
|
|
38932
|
+
]
|
|
38933
|
+
}
|
|
38934
|
+
];
|
|
38935
|
+
}
|
|
38834
38936
|
function createNotesPlugin() {
|
|
38835
38937
|
return {
|
|
38836
38938
|
...notesManifest,
|
|
@@ -38845,10 +38947,11 @@ function createNotesPlugin() {
|
|
|
38845
38947
|
linkedFolders
|
|
38846
38948
|
}) {
|
|
38847
38949
|
return { data: await loadNotes(folder, linkedFolders), kind: "available" };
|
|
38848
|
-
}
|
|
38950
|
+
},
|
|
38951
|
+
llm: notesLlmSections
|
|
38849
38952
|
};
|
|
38850
38953
|
}
|
|
38851
|
-
var markdownExtensions;
|
|
38954
|
+
var markdownExtensions, llmNoteLimit = 20;
|
|
38852
38955
|
var init_server3 = __esm(() => {
|
|
38853
38956
|
init_manifest();
|
|
38854
38957
|
markdownExtensions = new Set([".md", ".markdown"]);
|
|
@@ -38857,6 +38960,7 @@ var init_server3 = __esm(() => {
|
|
|
38857
38960
|
// packages/api/src/server/plugins.ts
|
|
38858
38961
|
import { pathToFileURL } from "node:url";
|
|
38859
38962
|
import { existsSync as existsSync4 } from "node:fs";
|
|
38963
|
+
import { mkdir as mkdir3, readFile as readFile9, stat as stat7, writeFile as writeFile5 } from "node:fs/promises";
|
|
38860
38964
|
import { dirname as dirname8, resolve as resolve11, sep as sep6 } from "node:path";
|
|
38861
38965
|
function configuredFolderServerRegistry(registry, resolvedConfig) {
|
|
38862
38966
|
const disabled = new Set(resolvedConfig.plugins.disable);
|
|
@@ -38867,6 +38971,41 @@ function configuredFolderServerRegistry(registry, resolvedConfig) {
|
|
|
38867
38971
|
];
|
|
38868
38972
|
return new Map(ids.map((id) => [id, registry.get(id)]));
|
|
38869
38973
|
}
|
|
38974
|
+
function stringArray(value) {
|
|
38975
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
38976
|
+
}
|
|
38977
|
+
function parsePluginRegistryConfig(content) {
|
|
38978
|
+
const parsed = JSON.parse(content);
|
|
38979
|
+
const config = parsed && typeof parsed === "object" ? parsed : {};
|
|
38980
|
+
return {
|
|
38981
|
+
plugins: {
|
|
38982
|
+
disable: stringArray(config.plugins?.disable),
|
|
38983
|
+
order: stringArray(config.plugins?.order)
|
|
38984
|
+
}
|
|
38985
|
+
};
|
|
38986
|
+
}
|
|
38987
|
+
async function nearestKennelConfig(folder) {
|
|
38988
|
+
for (let current = folder;; current = dirname8(current)) {
|
|
38989
|
+
const candidate = resolve11(current, "kennel.json");
|
|
38990
|
+
const details = await stat7(candidate).catch(() => {
|
|
38991
|
+
return;
|
|
38992
|
+
});
|
|
38993
|
+
if (details?.isFile())
|
|
38994
|
+
return candidate;
|
|
38995
|
+
if (dirname8(current) === current)
|
|
38996
|
+
return;
|
|
38997
|
+
}
|
|
38998
|
+
}
|
|
38999
|
+
async function folderPluginRegistryConfig(folder) {
|
|
39000
|
+
const path = await nearestKennelConfig(folder);
|
|
39001
|
+
if (!path)
|
|
39002
|
+
return defaultPluginRegistryConfig;
|
|
39003
|
+
try {
|
|
39004
|
+
return parsePluginRegistryConfig(await readFile9(path, "utf8"));
|
|
39005
|
+
} catch {
|
|
39006
|
+
return defaultPluginRegistryConfig;
|
|
39007
|
+
}
|
|
39008
|
+
}
|
|
38870
39009
|
function createFolderServerPlugins() {
|
|
38871
39010
|
return [createNotesPlugin()];
|
|
38872
39011
|
}
|
|
@@ -38947,8 +39086,12 @@ async function externalApiHandlers() {
|
|
|
38947
39086
|
}
|
|
38948
39087
|
return handlers;
|
|
38949
39088
|
}
|
|
39089
|
+
var defaultPluginRegistryConfig;
|
|
38950
39090
|
var init_plugins = __esm(() => {
|
|
38951
39091
|
init_server3();
|
|
39092
|
+
defaultPluginRegistryConfig = {
|
|
39093
|
+
plugins: { disable: [], order: [] }
|
|
39094
|
+
};
|
|
38952
39095
|
});
|
|
38953
39096
|
|
|
38954
39097
|
// packages/api/src/server/linked-folder-discovery.ts
|
|
@@ -39174,7 +39317,7 @@ var init_project_discovery = __esm(() => {
|
|
|
39174
39317
|
// packages/api/src/server/folder-metadata-plugin.ts
|
|
39175
39318
|
import { createHash as createHash5 } from "node:crypto";
|
|
39176
39319
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
39177
|
-
import { readdir as readdir5, readFile as
|
|
39320
|
+
import { readdir as readdir5, readFile as readFile10, stat as stat8 } from "node:fs/promises";
|
|
39178
39321
|
import { homedir as homedir3 } from "node:os";
|
|
39179
39322
|
import {
|
|
39180
39323
|
basename as basename7,
|
|
@@ -39243,42 +39386,6 @@ function apiHostAllowed(value, remote) {
|
|
|
39243
39386
|
const allowlist = (process.env.KENNEL_VIEWER_HOSTS ?? "").split(",").map((host) => host.trim().toLowerCase()).filter(Boolean);
|
|
39244
39387
|
return allowlist.includes(hostname);
|
|
39245
39388
|
}
|
|
39246
|
-
function decodeBase64Url2(value) {
|
|
39247
|
-
const padded = `${value}${"=".repeat((4 - value.length % 4) % 4)}`;
|
|
39248
|
-
return Buffer.from(padded.replaceAll("-", "+").replaceAll("_", "/"), "base64").toString("utf8");
|
|
39249
|
-
}
|
|
39250
|
-
async function searchIconDataUrl(icon) {
|
|
39251
|
-
if (!icon)
|
|
39252
|
-
return;
|
|
39253
|
-
if (icon.startsWith("data:"))
|
|
39254
|
-
return icon;
|
|
39255
|
-
const mediaType = searchIconTypes.get(extname6(icon).toLowerCase());
|
|
39256
|
-
if (!mediaType)
|
|
39257
|
-
return;
|
|
39258
|
-
const details = await stat7(icon).catch(() => {
|
|
39259
|
-
return;
|
|
39260
|
-
});
|
|
39261
|
-
if (!details?.size || details.size > maxIconBytes3)
|
|
39262
|
-
return;
|
|
39263
|
-
return `data:${mediaType};base64,${(await readFile9(icon)).toString("base64")}`;
|
|
39264
|
-
}
|
|
39265
|
-
async function faviconAsset(folderName, assets) {
|
|
39266
|
-
if (process.env.KENNEL_EXTERNAL_ICONS !== "1")
|
|
39267
|
-
return;
|
|
39268
|
-
const host = await folderWebsite(folderName);
|
|
39269
|
-
if (!host)
|
|
39270
|
-
return;
|
|
39271
|
-
const key = `favicon:${host}`;
|
|
39272
|
-
assets[key] = {
|
|
39273
|
-
mediaType: "image/png",
|
|
39274
|
-
source: "favicon",
|
|
39275
|
-
url: `/api/folder-favicon?path=${encodeURIComponent(folderName)}&host=${encodeURIComponent(host)}`
|
|
39276
|
-
};
|
|
39277
|
-
return key;
|
|
39278
|
-
}
|
|
39279
|
-
async function iconWithFaviconFallback(folder, assets, assetFiles) {
|
|
39280
|
-
return folderIcon(folder, assets, assetFiles, (folderName) => faviconAsset(folderName, assets));
|
|
39281
|
-
}
|
|
39282
39389
|
async function repositoryIdentity(folder, assets) {
|
|
39283
39390
|
const remoteUrl = await gitRemoteUrl(folder).catch(() => {
|
|
39284
39391
|
return;
|
|
@@ -39302,22 +39409,6 @@ async function repositoryIdentity(folder, assets) {
|
|
|
39302
39409
|
return;
|
|
39303
39410
|
}
|
|
39304
39411
|
}
|
|
39305
|
-
async function hydrateAssets(metadata, assetFiles) {
|
|
39306
|
-
const assets = {};
|
|
39307
|
-
const projects = metadata.projects?.items ?? [];
|
|
39308
|
-
const [iconAssetKey, projectItems] = await Promise.all([
|
|
39309
|
-
iconWithFaviconFallback(metadata.folder, assets, assetFiles),
|
|
39310
|
-
Promise.all(projects.map(async (project) => ({
|
|
39311
|
-
...project,
|
|
39312
|
-
iconAssetKey: await iconWithFaviconFallback(project.worktree, assets, assetFiles)
|
|
39313
|
-
})))
|
|
39314
|
-
]);
|
|
39315
|
-
return {
|
|
39316
|
-
assets,
|
|
39317
|
-
iconAssetKey,
|
|
39318
|
-
projects: { items: projectItems }
|
|
39319
|
-
};
|
|
39320
|
-
}
|
|
39321
39412
|
function unique(values) {
|
|
39322
39413
|
return [...new Set(values)];
|
|
39323
39414
|
}
|
|
@@ -39329,8 +39420,8 @@ async function shallowMetadata(folder) {
|
|
|
39329
39420
|
const visible = entries.filter((entry) => !entry.name.startsWith(".") && entry.name !== "node_modules");
|
|
39330
39421
|
const files = await Promise.all(visible.filter((entry) => entry.isFile() && [".markdown", ".md"].includes(extname6(entry.name).toLowerCase())).map(async (entry) => {
|
|
39331
39422
|
const path = resolve12(folder, entry.name);
|
|
39332
|
-
const details = await
|
|
39333
|
-
const content = await
|
|
39423
|
+
const details = await stat8(path);
|
|
39424
|
+
const content = await readFile10(path, "utf8").catch(() => "");
|
|
39334
39425
|
const title = markdownMetadata(content).title;
|
|
39335
39426
|
return {
|
|
39336
39427
|
activityAt: details.mtimeMs,
|
|
@@ -39384,7 +39475,7 @@ async function notesFiles(folder, files, origin) {
|
|
|
39384
39475
|
const path = file.path ?? (file.relativePath ? resolve12(folder, file.relativePath) : undefined);
|
|
39385
39476
|
if (!path)
|
|
39386
39477
|
return origin ? { ...file, folder: origin } : file;
|
|
39387
|
-
const content = [".markdown", ".md"].includes(extname6(path).toLowerCase()) ? await
|
|
39478
|
+
const content = [".markdown", ".md"].includes(extname6(path).toLowerCase()) ? await readFile10(path, "utf8").catch(() => "") : "";
|
|
39388
39479
|
const title = content ? markdownMetadata(content).title : undefined;
|
|
39389
39480
|
return {
|
|
39390
39481
|
...file,
|
|
@@ -39502,41 +39593,6 @@ async function projectSearchItem(project, match) {
|
|
|
39502
39593
|
worktree: project.worktree
|
|
39503
39594
|
};
|
|
39504
39595
|
}
|
|
39505
|
-
function stringArray(value) {
|
|
39506
|
-
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
39507
|
-
}
|
|
39508
|
-
function parsePluginRegistryConfig(content) {
|
|
39509
|
-
const parsed = JSON.parse(content);
|
|
39510
|
-
const config = parsed && typeof parsed === "object" ? parsed : {};
|
|
39511
|
-
return {
|
|
39512
|
-
plugins: {
|
|
39513
|
-
disable: stringArray(config.plugins?.disable),
|
|
39514
|
-
order: stringArray(config.plugins?.order)
|
|
39515
|
-
}
|
|
39516
|
-
};
|
|
39517
|
-
}
|
|
39518
|
-
async function nearestKennelConfig(folder) {
|
|
39519
|
-
for (let current = folder;; current = dirname10(current)) {
|
|
39520
|
-
const candidate = resolve12(current, "kennel.json");
|
|
39521
|
-
const details = await stat7(candidate).catch(() => {
|
|
39522
|
-
return;
|
|
39523
|
-
});
|
|
39524
|
-
if (details?.isFile())
|
|
39525
|
-
return candidate;
|
|
39526
|
-
if (dirname10(current) === current)
|
|
39527
|
-
return;
|
|
39528
|
-
}
|
|
39529
|
-
}
|
|
39530
|
-
async function folderPluginRegistryConfig(folder) {
|
|
39531
|
-
const path = await nearestKennelConfig(folder);
|
|
39532
|
-
if (!path)
|
|
39533
|
-
return defaultPluginRegistryConfig;
|
|
39534
|
-
try {
|
|
39535
|
-
return parsePluginRegistryConfig(await readFile9(path, "utf8"));
|
|
39536
|
-
} catch {
|
|
39537
|
-
return defaultPluginRegistryConfig;
|
|
39538
|
-
}
|
|
39539
|
-
}
|
|
39540
39596
|
function pluginConfigWarnings(registry, config) {
|
|
39541
39597
|
const ids = unique([...config.plugins.disable, ...config.plugins.order]);
|
|
39542
39598
|
return ids.filter((id) => !registry.has(id)).map((id) => `unknown plugin id "${id}" — not installed in this build`);
|
|
@@ -39712,7 +39768,7 @@ function createFolderApi({
|
|
|
39712
39768
|
"Content-Type": asset.mediaType,
|
|
39713
39769
|
ETag: `"${hash}"`
|
|
39714
39770
|
});
|
|
39715
|
-
response.end(await
|
|
39771
|
+
response.end(await readFile10(asset.path));
|
|
39716
39772
|
} catch (error) {
|
|
39717
39773
|
response.writeHead(404, {
|
|
39718
39774
|
"Content-Type": "text/plain; charset=utf-8"
|
|
@@ -39791,7 +39847,7 @@ function createFolderApi({
|
|
|
39791
39847
|
{
|
|
39792
39848
|
path: relative11(folder, target.path),
|
|
39793
39849
|
kind: target.kind,
|
|
39794
|
-
content: previewTextKind(target.kind) ? await
|
|
39850
|
+
content: previewTextKind(target.kind) ? await readFile10(target.path, "utf8") : ""
|
|
39795
39851
|
}
|
|
39796
39852
|
] : []
|
|
39797
39853
|
} : await readFolder(folder);
|
|
@@ -39807,7 +39863,7 @@ function createFolderApi({
|
|
|
39807
39863
|
documentError(response);
|
|
39808
39864
|
return;
|
|
39809
39865
|
}
|
|
39810
|
-
const details = await
|
|
39866
|
+
const details = await stat8(target.path);
|
|
39811
39867
|
if (!details.isFile()) {
|
|
39812
39868
|
documentError(response);
|
|
39813
39869
|
return;
|
|
@@ -39819,7 +39875,7 @@ function createFolderApi({
|
|
|
39819
39875
|
return;
|
|
39820
39876
|
}
|
|
39821
39877
|
watch(folder);
|
|
39822
|
-
const content = previewTextKind(target.kind) ? await
|
|
39878
|
+
const content = previewTextKind(target.kind) ? await readFile10(target.path, "utf8") : "";
|
|
39823
39879
|
sendContract(response, url, {
|
|
39824
39880
|
content,
|
|
39825
39881
|
kind: target.kind,
|
|
@@ -39848,7 +39904,7 @@ function createFolderApi({
|
|
|
39848
39904
|
documentError(response);
|
|
39849
39905
|
return;
|
|
39850
39906
|
}
|
|
39851
|
-
const details = await
|
|
39907
|
+
const details = await stat8(target.path);
|
|
39852
39908
|
if (!details.isFile()) {
|
|
39853
39909
|
documentError(response);
|
|
39854
39910
|
return;
|
|
@@ -39872,7 +39928,7 @@ function createFolderApi({
|
|
|
39872
39928
|
"Content-Disposition": `attachment; filename="${basename7(target.path)}"`
|
|
39873
39929
|
} : {}
|
|
39874
39930
|
});
|
|
39875
|
-
response.end(await
|
|
39931
|
+
response.end(await readFile10(target.path));
|
|
39876
39932
|
} catch (error) {
|
|
39877
39933
|
documentError(response);
|
|
39878
39934
|
}
|
|
@@ -39886,7 +39942,7 @@ function createFolderApi({
|
|
|
39886
39942
|
documentError(response);
|
|
39887
39943
|
return;
|
|
39888
39944
|
}
|
|
39889
|
-
const details = await
|
|
39945
|
+
const details = await stat8(target.path);
|
|
39890
39946
|
if (!details.isFile()) {
|
|
39891
39947
|
documentError(response);
|
|
39892
39948
|
return;
|
|
@@ -39898,7 +39954,7 @@ function createFolderApi({
|
|
|
39898
39954
|
return;
|
|
39899
39955
|
}
|
|
39900
39956
|
watch(folder);
|
|
39901
|
-
const data = await
|
|
39957
|
+
const data = await readFile10(target.path);
|
|
39902
39958
|
const baseHeaders = {
|
|
39903
39959
|
"Accept-Ranges": "bytes",
|
|
39904
39960
|
"Cache-Control": "private, max-age=0, must-revalidate",
|
|
@@ -40155,7 +40211,7 @@ function createFolderApi({
|
|
|
40155
40211
|
response.off("close", cancel);
|
|
40156
40212
|
}
|
|
40157
40213
|
const [hydrated, workspace] = await Promise.all([
|
|
40158
|
-
|
|
40214
|
+
hydrateFolderIcons({ folder, projects: { items: projects } }, assetFiles),
|
|
40159
40215
|
readWorkspace(folder)
|
|
40160
40216
|
]);
|
|
40161
40217
|
workspace.breadcrumbs = await workspaceBreadcrumbIcons(workspace.breadcrumbs ?? [], assetFiles, hydrated.assets);
|
|
@@ -40168,7 +40224,7 @@ function createFolderApi({
|
|
|
40168
40224
|
return;
|
|
40169
40225
|
}
|
|
40170
40226
|
const metadata = machine ? await shallowMetadata(folder) : await loadFolderData(folder);
|
|
40171
|
-
const assets = await
|
|
40227
|
+
const assets = await hydrateFolderIcons(metadata, assetFiles);
|
|
40172
40228
|
const body = {
|
|
40173
40229
|
...metadata,
|
|
40174
40230
|
...assets,
|
|
@@ -40186,7 +40242,7 @@ function createFolderApi({
|
|
|
40186
40242
|
}
|
|
40187
40243
|
};
|
|
40188
40244
|
}
|
|
40189
|
-
var applyProjectFavorites, buildProjectCatalog, buildProjectSearchIndex, catalogProjectToLocalProject, discoverProjectIcon, discoverProjectsUnderFolder, listLocalMarkdownFiles, projectRepositoryAccessory, projectSubtitle, projectTitle, readRaggleUserSettings, gitRemoteUrl, githubRepositoryPath, repositoryDisplayName, searchProjectIndex, sortProjectList,
|
|
40245
|
+
var applyProjectFavorites, buildProjectCatalog, buildProjectSearchIndex, catalogProjectToLocalProject, discoverProjectIcon, discoverProjectsUnderFolder, listLocalMarkdownFiles, projectRepositoryAccessory, projectSubtitle, projectTitle, readRaggleUserSettings, gitRemoteUrl, githubRepositoryPath, repositoryDisplayName, searchProjectIndex, sortProjectList, projectSearchCacheMs = 5000, metadataExtensions, loopbackServerHosts;
|
|
40190
40246
|
var init_folder_metadata_plugin = __esm(() => {
|
|
40191
40247
|
init_contracts();
|
|
40192
40248
|
init_markdown();
|
|
@@ -40225,25 +40281,6 @@ var init_folder_metadata_plugin = __esm(() => {
|
|
|
40225
40281
|
searchProjectIndex,
|
|
40226
40282
|
sortProjectList
|
|
40227
40283
|
} = local);
|
|
40228
|
-
maxIconBytes3 = 512 * 1024;
|
|
40229
|
-
iconTypes2 = new Map([
|
|
40230
|
-
[".jpeg", "image/jpeg"],
|
|
40231
|
-
[".jpg", "image/jpeg"],
|
|
40232
|
-
[".png", "image/png"]
|
|
40233
|
-
]);
|
|
40234
|
-
iconExtensions2 = new Map([
|
|
40235
|
-
["image/jpeg", "jpg"],
|
|
40236
|
-
["image/png", "png"]
|
|
40237
|
-
]);
|
|
40238
|
-
searchIconTypes = new Map([
|
|
40239
|
-
[".gif", "image/gif"],
|
|
40240
|
-
[".ico", "image/x-icon"],
|
|
40241
|
-
[".jpeg", "image/jpeg"],
|
|
40242
|
-
[".jpg", "image/jpeg"],
|
|
40243
|
-
[".png", "image/png"],
|
|
40244
|
-
[".svg", "image/svg+xml"],
|
|
40245
|
-
[".webp", "image/webp"]
|
|
40246
|
-
]);
|
|
40247
40284
|
metadataExtensions = new Set([
|
|
40248
40285
|
".html",
|
|
40249
40286
|
".jpeg",
|
|
@@ -40262,16 +40299,13 @@ var init_folder_metadata_plugin = __esm(() => {
|
|
|
40262
40299
|
undefined,
|
|
40263
40300
|
false
|
|
40264
40301
|
]);
|
|
40265
|
-
defaultPluginRegistryConfig = {
|
|
40266
|
-
plugins: { disable: [], order: [] }
|
|
40267
|
-
};
|
|
40268
40302
|
});
|
|
40269
40303
|
|
|
40270
40304
|
// packages/api/src/server/index.ts
|
|
40271
40305
|
import {
|
|
40272
40306
|
createServer
|
|
40273
40307
|
} from "node:http";
|
|
40274
|
-
import { readFile as
|
|
40308
|
+
import { readFile as readFile11, realpath as realpath5, stat as stat9 } from "node:fs/promises";
|
|
40275
40309
|
import { extname as extname7, isAbsolute as isAbsolute7, relative as relative12, resolve as resolve13 } from "node:path";
|
|
40276
40310
|
async function sendFile(request, response, directory, pathname, fallback) {
|
|
40277
40311
|
const root = await realpath5(directory);
|
|
@@ -40285,7 +40319,7 @@ async function sendFile(request, response, directory, pathname, fallback) {
|
|
|
40285
40319
|
response.end();
|
|
40286
40320
|
return;
|
|
40287
40321
|
}
|
|
40288
|
-
if (!(await
|
|
40322
|
+
if (!(await stat9(target).catch(() => null))?.isFile()) {
|
|
40289
40323
|
if (!fallback) {
|
|
40290
40324
|
response.writeHead(404);
|
|
40291
40325
|
response.end();
|
|
@@ -40303,7 +40337,7 @@ async function sendFile(request, response, directory, pathname, fallback) {
|
|
|
40303
40337
|
"Content-Type": contentTypes[extname7(target)] ?? "application/octet-stream",
|
|
40304
40338
|
"X-Content-Type-Options": "nosniff"
|
|
40305
40339
|
});
|
|
40306
|
-
response.end(request.method === "HEAD" ? undefined : await
|
|
40340
|
+
response.end(request.method === "HEAD" ? undefined : await readFile11(target));
|
|
40307
40341
|
}
|
|
40308
40342
|
function createKennelApi(options) {
|
|
40309
40343
|
const remote = options.remoteAccess || !isLoopbackServerHost(options.host);
|
|
@@ -83,6 +83,12 @@ export type PluginBuild = {
|
|
|
83
83
|
};
|
|
84
84
|
/** Points the core runtime at a plugin build. */
|
|
85
85
|
export declare function usePluginBuild(build: PluginBuild): void;
|
|
86
|
+
/**
|
|
87
|
+
* The pinned build of the installed plugins, read without building. `knl llm`
|
|
88
|
+
* uses it: a missing or stale build is an error, never a browser build or an
|
|
89
|
+
* install during context generation.
|
|
90
|
+
*/
|
|
91
|
+
export declare function readPluginBuild(plugins?: ExternalPluginEntry[]): Promise<PluginBuild>;
|
|
86
92
|
/**
|
|
87
93
|
* Compiles the installed external plugins once per plugin set. The host web
|
|
88
94
|
* app is never rebuilt: plugin browser modules load at runtime against the
|
package/dist/plugins/external.js
CHANGED
|
@@ -350,6 +350,8 @@ async function validateImports(entry) {
|
|
|
350
350
|
throw new Error("server id mismatch");
|
|
351
351
|
if (typeof server.detect !== "function" || typeof server.handle !== "function")
|
|
352
352
|
throw new Error("server export missing detect/handle");
|
|
353
|
+
if (server.llm !== undefined && typeof server.llm !== "function")
|
|
354
|
+
throw new Error("server export llm hook must be a function");
|
|
353
355
|
if (!client || typeof client !== "object" || client.id !== entry.id)
|
|
354
356
|
throw new Error("client id mismatch");
|
|
355
357
|
if (typeof client.component !== "function")
|
|
@@ -570,6 +572,20 @@ async function readBuildMarker(marker, key) {
|
|
|
570
572
|
return undefined;
|
|
571
573
|
}
|
|
572
574
|
}
|
|
575
|
+
/**
|
|
576
|
+
* The pinned build of the installed plugins, read without building. `knl llm`
|
|
577
|
+
* uses it: a missing or stale build is an error, never a browser build or an
|
|
578
|
+
* install during context generation.
|
|
579
|
+
*/
|
|
580
|
+
export async function readPluginBuild(plugins = readExternalPlugins()) {
|
|
581
|
+
const key = pluginSetKey(plugins);
|
|
582
|
+
if (plugins.length === 0)
|
|
583
|
+
return { key, serverEntries: [], apiEntries: [] };
|
|
584
|
+
const build = await readBuildMarker(join(buildRoot(key), "build.json"), key);
|
|
585
|
+
if (build)
|
|
586
|
+
return build;
|
|
587
|
+
throw new Error(`The installed plugin build is missing or stale (${plugins.map((plugin) => plugin.id).join(", ")}). Refresh each changed plugin with \`kennel plugin add <source>\`, or run knl llm --no-plugins.`);
|
|
588
|
+
}
|
|
573
589
|
function acquireBuildLock(key) {
|
|
574
590
|
const locks = join(cacheRoot(), "plugin-builds", ".locks");
|
|
575
591
|
mkdirSync(locks, { recursive: true });
|
|
@@ -19,6 +19,18 @@ export declare class FolderPluginFailure extends Error {
|
|
|
19
19
|
constructor(message: string);
|
|
20
20
|
}
|
|
21
21
|
export declare function folderServerRegistry(): Map<string, FolderPluginServer>;
|
|
22
|
+
/** Core plugins plus the installed plugins named by the active plugin build. */
|
|
23
|
+
export declare function folderServerRegistryAsync(): Promise<Map<string, FolderPluginServer>>;
|
|
24
|
+
export type FolderPluginRegistryConfig = {
|
|
25
|
+
plugins: {
|
|
26
|
+
disable: string[];
|
|
27
|
+
order: string[];
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
/** Applies kennel.json disable/order settings to a registry; never adds plugins. */
|
|
31
|
+
export declare function configuredFolderServerRegistry(registry: Map<string, FolderPluginServer>, config: FolderPluginRegistryConfig): Map<string, FolderPluginServer>;
|
|
32
|
+
/** Reads the plugin settings from the folder's nearest kennel.json. */
|
|
33
|
+
export declare function folderPluginRegistryConfig(folder: string): Promise<FolderPluginRegistryConfig>;
|
|
22
34
|
export type FolderPluginScaffoldFile = {
|
|
23
35
|
plugin: string;
|
|
24
36
|
path: string;
|