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/cli/dispatch.js
CHANGED
|
@@ -102,6 +102,11 @@ export async function dispatch(argv) {
|
|
|
102
102
|
deploy(args);
|
|
103
103
|
return;
|
|
104
104
|
}
|
|
105
|
+
if (command === 'llm') {
|
|
106
|
+
const { llm } = await import('../llm/command.js');
|
|
107
|
+
await llm(args);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
105
110
|
// Built-in commands win; use an explicit path (./tasks) to open a folder
|
|
106
111
|
// whose name is also an installed plugin command.
|
|
107
112
|
if (/^[a-z][a-z0-9-]*$/u.test(command)) {
|
package/dist/cli/usage.js
CHANGED
|
@@ -12,6 +12,7 @@ export function usage() {
|
|
|
12
12
|
kennel view [folder] [--host <host>] [--port <port>] [--dry-run]
|
|
13
13
|
kennel open|o [folder] [--open] [--host <host>] [--port <port>] [--dry-run]
|
|
14
14
|
knl <plugin-id|alias> [folder] [--item <id>] [--open] [--dry-run]
|
|
15
|
+
knl llm [folder|--folder <path>] [full|--depth <count>] [--profile <name>|--no-instructions] [--no-plugins] [--copy]
|
|
15
16
|
kennel serve [folder] [--host <host>] [--port <port>] [--lan|--tailscale]
|
|
16
17
|
kennel share|display [folder] [--lan|--tailscale] [--port <port>] [--dry-run]
|
|
17
18
|
kennel dev [path] [--config <json-file>] [--port <port>] [--script <script>] [--dry-run] [-- <args...>]
|
|
@@ -34,5 +35,8 @@ Without an interactive terminal, the viewer prints its URL without a menu.
|
|
|
34
35
|
Installed plugin ids are commands, for example knl tasks or knl calendar.
|
|
35
36
|
Plugins can declare extra aliases, for example knl mail for plugin id email.
|
|
36
37
|
Use --item <id> for a plugin detail view. Built-in commands take priority.
|
|
38
|
+
|
|
39
|
+
knl llm prints only agent instructions, profiles, and the research write destination.
|
|
40
|
+
Use knl llm full for repository details and plugin sections. Run knl llm --help for details.
|
|
37
41
|
`);
|
|
38
42
|
}
|
package/dist/core/runtime.js
CHANGED
|
@@ -17027,7 +17027,7 @@ var require__2020 = __commonJS(function(exports, module) {
|
|
|
17027
17027
|
import {
|
|
17028
17028
|
createServer
|
|
17029
17029
|
} from "node:http";
|
|
17030
|
-
import { readFile as
|
|
17030
|
+
import { readFile as readFile10, realpath as realpath5, stat as stat9 } from "node:fs/promises";
|
|
17031
17031
|
import { extname as extname7, isAbsolute as isAbsolute7, relative as relative11, resolve as resolve12 } from "node:path";
|
|
17032
17032
|
|
|
17033
17033
|
// packages/api/src/server/watcher.ts
|
|
@@ -21993,7 +21993,7 @@ function sendContract(response, url, body, headers = {}) {
|
|
|
21993
21993
|
|
|
21994
21994
|
// packages/api/src/server/folder-metadata-plugin.ts
|
|
21995
21995
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
21996
|
-
import { readdir as readdir5, readFile as
|
|
21996
|
+
import { readdir as readdir5, readFile as readFile9, stat as stat8 } from "node:fs/promises";
|
|
21997
21997
|
import { homedir as homedir3 } from "node:os";
|
|
21998
21998
|
import {
|
|
21999
21999
|
basename as basename6,
|
|
@@ -37086,12 +37086,26 @@ var iconExtensions = new Map([
|
|
|
37086
37086
|
["image/jpeg", "jpg"],
|
|
37087
37087
|
["image/png", "png"]
|
|
37088
37088
|
]);
|
|
37089
|
+
var maxSearchIconBytes = 512 * 1024;
|
|
37090
|
+
var searchIconTypes = new Map([
|
|
37091
|
+
[".gif", "image/gif"],
|
|
37092
|
+
[".ico", "image/x-icon"],
|
|
37093
|
+
[".jpeg", "image/jpeg"],
|
|
37094
|
+
[".jpg", "image/jpeg"],
|
|
37095
|
+
[".png", "image/png"],
|
|
37096
|
+
[".svg", "image/svg+xml"],
|
|
37097
|
+
[".webp", "image/webp"]
|
|
37098
|
+
]);
|
|
37089
37099
|
function assetKey(value) {
|
|
37090
37100
|
return createHash4("sha256").update(value).digest("hex");
|
|
37091
37101
|
}
|
|
37092
37102
|
function encodeBase64Url(value) {
|
|
37093
37103
|
return Buffer.from(value, "utf8").toString("base64").replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
37094
37104
|
}
|
|
37105
|
+
function decodeBase64Url(value) {
|
|
37106
|
+
const padded = `${value}${"=".repeat((4 - value.length % 4) % 4)}`;
|
|
37107
|
+
return Buffer.from(padded.replaceAll("-", "+").replaceAll("_", "/"), "base64").toString("utf8");
|
|
37108
|
+
}
|
|
37095
37109
|
function iconUrl(folder, hash, mediaType) {
|
|
37096
37110
|
return `/api/folders/${encodeBase64Url(folder)}/assets/${hash}.${iconExtensions.get(mediaType)}`;
|
|
37097
37111
|
}
|
|
@@ -37115,6 +37129,54 @@ async function folderIcon(folder, assets, assetFiles, fallback) {
|
|
|
37115
37129
|
};
|
|
37116
37130
|
return key;
|
|
37117
37131
|
}
|
|
37132
|
+
async function searchIconDataUrl(icon) {
|
|
37133
|
+
if (!icon)
|
|
37134
|
+
return;
|
|
37135
|
+
if (icon.startsWith("data:"))
|
|
37136
|
+
return icon;
|
|
37137
|
+
const mediaType = searchIconTypes.get(extname4(icon).toLowerCase());
|
|
37138
|
+
if (!mediaType)
|
|
37139
|
+
return;
|
|
37140
|
+
const details = await stat5(icon).catch(() => {
|
|
37141
|
+
return;
|
|
37142
|
+
});
|
|
37143
|
+
if (!details?.size || details.size > maxSearchIconBytes)
|
|
37144
|
+
return;
|
|
37145
|
+
return `data:${mediaType};base64,${(await readFile7(icon)).toString("base64")}`;
|
|
37146
|
+
}
|
|
37147
|
+
async function faviconAsset(folderName, assets) {
|
|
37148
|
+
if (process.env.KENNEL_EXTERNAL_ICONS !== "1")
|
|
37149
|
+
return;
|
|
37150
|
+
const host = await folderWebsite(folderName);
|
|
37151
|
+
if (!host)
|
|
37152
|
+
return;
|
|
37153
|
+
const key = `favicon:${host}`;
|
|
37154
|
+
assets[key] = {
|
|
37155
|
+
mediaType: "image/png",
|
|
37156
|
+
source: "favicon",
|
|
37157
|
+
url: `/api/folder-favicon?path=${encodeURIComponent(folderName)}&host=${encodeURIComponent(host)}`
|
|
37158
|
+
};
|
|
37159
|
+
return key;
|
|
37160
|
+
}
|
|
37161
|
+
async function iconWithFaviconFallback(folder, assets, assetFiles) {
|
|
37162
|
+
return folderIcon(folder, assets, assetFiles, (folderName) => faviconAsset(folderName, assets));
|
|
37163
|
+
}
|
|
37164
|
+
async function hydrateFolderIcons(metadata, assetFiles) {
|
|
37165
|
+
const assets = {};
|
|
37166
|
+
const projects = metadata.projects?.items ?? [];
|
|
37167
|
+
const [iconAssetKey, projectItems] = await Promise.all([
|
|
37168
|
+
iconWithFaviconFallback(metadata.folder, assets, assetFiles),
|
|
37169
|
+
Promise.all(projects.map(async (project) => ({
|
|
37170
|
+
...project,
|
|
37171
|
+
iconAssetKey: await iconWithFaviconFallback(project.worktree, assets, assetFiles)
|
|
37172
|
+
})))
|
|
37173
|
+
]);
|
|
37174
|
+
return {
|
|
37175
|
+
assets,
|
|
37176
|
+
iconAssetKey,
|
|
37177
|
+
projects: { items: projectItems }
|
|
37178
|
+
};
|
|
37179
|
+
}
|
|
37118
37180
|
async function workspaceBreadcrumbIcons(breadcrumbs, assetFiles, assets = {}) {
|
|
37119
37181
|
return Promise.all(breadcrumbs.map(async (crumb) => {
|
|
37120
37182
|
const iconAssetKey = await folderIcon(crumb.path, assets, assetFiles);
|
|
@@ -37126,6 +37188,7 @@ async function workspaceBreadcrumbIcons(breadcrumbs, assetFiles, assets = {}) {
|
|
|
37126
37188
|
// packages/api/src/server/plugins.ts
|
|
37127
37189
|
import { pathToFileURL } from "node:url";
|
|
37128
37190
|
import { existsSync as existsSync3 } from "node:fs";
|
|
37191
|
+
import { mkdir as mkdir2, readFile as readFile8, stat as stat7, writeFile as writeFile4 } from "node:fs/promises";
|
|
37129
37192
|
import { dirname as dirname7, resolve as resolve10, sep as sep6 } from "node:path";
|
|
37130
37193
|
|
|
37131
37194
|
// packages/notes/src/server.ts
|
|
@@ -37147,6 +37210,7 @@ var notesManifest = {
|
|
|
37147
37210
|
|
|
37148
37211
|
// packages/notes/src/server.ts
|
|
37149
37212
|
var markdownExtensions = new Set([".md", ".markdown"]);
|
|
37213
|
+
var llmNoteLimit = 20;
|
|
37150
37214
|
async function listMarkdownFiles(folder) {
|
|
37151
37215
|
const files = [];
|
|
37152
37216
|
const visit = async (current) => {
|
|
@@ -37192,6 +37256,45 @@ async function loadNotes(folder, linkedFolders = []) {
|
|
|
37192
37256
|
status: "available"
|
|
37193
37257
|
};
|
|
37194
37258
|
}
|
|
37259
|
+
function codeSpan(text) {
|
|
37260
|
+
const escaped = text.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/gu, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
37261
|
+
const longest = Math.max(0, ...(escaped.match(/`+/g) ?? []).map((run) => run.length));
|
|
37262
|
+
const fence = "`".repeat(longest + 1);
|
|
37263
|
+
const pad = escaped.startsWith("`") || escaped.endsWith("`") ? " " : "";
|
|
37264
|
+
return `${fence}${pad}${escaped}${pad}${fence}`;
|
|
37265
|
+
}
|
|
37266
|
+
async function notesLlmSections({
|
|
37267
|
+
folder,
|
|
37268
|
+
linkedFolders = [],
|
|
37269
|
+
signal
|
|
37270
|
+
}) {
|
|
37271
|
+
const groups = [];
|
|
37272
|
+
const notes = [];
|
|
37273
|
+
for (const source of [folder, ...linkedFolders]) {
|
|
37274
|
+
signal.throwIfAborted();
|
|
37275
|
+
const files = await listMarkdownFiles(source);
|
|
37276
|
+
const paths = files.map((file) => resolve9(source, file.relativePath));
|
|
37277
|
+
groups.push({ folder: source, paths });
|
|
37278
|
+
files.forEach((file, index) => notes.push({ activityAt: file.activityAt, path: paths[index] }));
|
|
37279
|
+
}
|
|
37280
|
+
if (notes.length === 0)
|
|
37281
|
+
return [];
|
|
37282
|
+
const recent = notes.sort((left, right) => right.activityAt - left.activityAt || (left.path < right.path ? -1 : left.path > right.path ? 1 : 0)).slice(0, llmNoteLimit);
|
|
37283
|
+
return [
|
|
37284
|
+
{
|
|
37285
|
+
id: "notes",
|
|
37286
|
+
title: "Markdown notes",
|
|
37287
|
+
lines: [
|
|
37288
|
+
...groups.filter((group) => group.paths.length > 0).map((group) => `- Source: ${codeSpan(group.folder)} (${group.paths.length} ${group.paths.length === 1 ? "note" : "notes"})`),
|
|
37289
|
+
`- Total notes: ${notes.length}`,
|
|
37290
|
+
"",
|
|
37291
|
+
`Most recently changed (${recent.length} of ${notes.length}):`,
|
|
37292
|
+
"",
|
|
37293
|
+
...recent.map((note) => `- ${codeSpan(note.path)}`)
|
|
37294
|
+
]
|
|
37295
|
+
}
|
|
37296
|
+
];
|
|
37297
|
+
}
|
|
37195
37298
|
function createNotesPlugin() {
|
|
37196
37299
|
return {
|
|
37197
37300
|
...notesManifest,
|
|
@@ -37206,7 +37309,8 @@ function createNotesPlugin() {
|
|
|
37206
37309
|
linkedFolders
|
|
37207
37310
|
}) {
|
|
37208
37311
|
return { data: await loadNotes(folder, linkedFolders), kind: "available" };
|
|
37209
|
-
}
|
|
37312
|
+
},
|
|
37313
|
+
llm: notesLlmSections
|
|
37210
37314
|
};
|
|
37211
37315
|
}
|
|
37212
37316
|
|
|
@@ -37220,6 +37324,44 @@ function configuredFolderServerRegistry(registry, resolvedConfig) {
|
|
|
37220
37324
|
];
|
|
37221
37325
|
return new Map(ids.map((id) => [id, registry.get(id)]));
|
|
37222
37326
|
}
|
|
37327
|
+
var defaultPluginRegistryConfig = {
|
|
37328
|
+
plugins: { disable: [], order: [] }
|
|
37329
|
+
};
|
|
37330
|
+
function stringArray(value) {
|
|
37331
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
37332
|
+
}
|
|
37333
|
+
function parsePluginRegistryConfig(content) {
|
|
37334
|
+
const parsed = JSON.parse(content);
|
|
37335
|
+
const config = parsed && typeof parsed === "object" ? parsed : {};
|
|
37336
|
+
return {
|
|
37337
|
+
plugins: {
|
|
37338
|
+
disable: stringArray(config.plugins?.disable),
|
|
37339
|
+
order: stringArray(config.plugins?.order)
|
|
37340
|
+
}
|
|
37341
|
+
};
|
|
37342
|
+
}
|
|
37343
|
+
async function nearestKennelConfig(folder) {
|
|
37344
|
+
for (let current = folder;; current = dirname7(current)) {
|
|
37345
|
+
const candidate = resolve10(current, "kennel.json");
|
|
37346
|
+
const details = await stat7(candidate).catch(() => {
|
|
37347
|
+
return;
|
|
37348
|
+
});
|
|
37349
|
+
if (details?.isFile())
|
|
37350
|
+
return candidate;
|
|
37351
|
+
if (dirname7(current) === current)
|
|
37352
|
+
return;
|
|
37353
|
+
}
|
|
37354
|
+
}
|
|
37355
|
+
async function folderPluginRegistryConfig(folder) {
|
|
37356
|
+
const path = await nearestKennelConfig(folder);
|
|
37357
|
+
if (!path)
|
|
37358
|
+
return defaultPluginRegistryConfig;
|
|
37359
|
+
try {
|
|
37360
|
+
return parsePluginRegistryConfig(await readFile8(path, "utf8"));
|
|
37361
|
+
} catch {
|
|
37362
|
+
return defaultPluginRegistryConfig;
|
|
37363
|
+
}
|
|
37364
|
+
}
|
|
37223
37365
|
function createFolderServerPlugins() {
|
|
37224
37366
|
return [createNotesPlugin()];
|
|
37225
37367
|
}
|
|
@@ -37691,25 +37833,6 @@ var {
|
|
|
37691
37833
|
searchProjectIndex,
|
|
37692
37834
|
sortProjectList
|
|
37693
37835
|
} = local;
|
|
37694
|
-
var maxIconBytes3 = 512 * 1024;
|
|
37695
|
-
var iconTypes2 = new Map([
|
|
37696
|
-
[".jpeg", "image/jpeg"],
|
|
37697
|
-
[".jpg", "image/jpeg"],
|
|
37698
|
-
[".png", "image/png"]
|
|
37699
|
-
]);
|
|
37700
|
-
var iconExtensions2 = new Map([
|
|
37701
|
-
["image/jpeg", "jpg"],
|
|
37702
|
-
["image/png", "png"]
|
|
37703
|
-
]);
|
|
37704
|
-
var searchIconTypes = new Map([
|
|
37705
|
-
[".gif", "image/gif"],
|
|
37706
|
-
[".ico", "image/x-icon"],
|
|
37707
|
-
[".jpeg", "image/jpeg"],
|
|
37708
|
-
[".jpg", "image/jpeg"],
|
|
37709
|
-
[".png", "image/png"],
|
|
37710
|
-
[".svg", "image/svg+xml"],
|
|
37711
|
-
[".webp", "image/webp"]
|
|
37712
|
-
]);
|
|
37713
37836
|
var projectSearchCacheMs = 5000;
|
|
37714
37837
|
var metadataExtensions = new Set([
|
|
37715
37838
|
".html",
|
|
@@ -37785,42 +37908,6 @@ function apiHostAllowed(value, remote) {
|
|
|
37785
37908
|
const allowlist = (process.env.KENNEL_VIEWER_HOSTS ?? "").split(",").map((host) => host.trim().toLowerCase()).filter(Boolean);
|
|
37786
37909
|
return allowlist.includes(hostname);
|
|
37787
37910
|
}
|
|
37788
|
-
function decodeBase64Url(value) {
|
|
37789
|
-
const padded = `${value}${"=".repeat((4 - value.length % 4) % 4)}`;
|
|
37790
|
-
return Buffer.from(padded.replaceAll("-", "+").replaceAll("_", "/"), "base64").toString("utf8");
|
|
37791
|
-
}
|
|
37792
|
-
async function searchIconDataUrl(icon) {
|
|
37793
|
-
if (!icon)
|
|
37794
|
-
return;
|
|
37795
|
-
if (icon.startsWith("data:"))
|
|
37796
|
-
return icon;
|
|
37797
|
-
const mediaType = searchIconTypes.get(extname6(icon).toLowerCase());
|
|
37798
|
-
if (!mediaType)
|
|
37799
|
-
return;
|
|
37800
|
-
const details = await stat7(icon).catch(() => {
|
|
37801
|
-
return;
|
|
37802
|
-
});
|
|
37803
|
-
if (!details?.size || details.size > maxIconBytes3)
|
|
37804
|
-
return;
|
|
37805
|
-
return `data:${mediaType};base64,${(await readFile8(icon)).toString("base64")}`;
|
|
37806
|
-
}
|
|
37807
|
-
async function faviconAsset(folderName, assets) {
|
|
37808
|
-
if (process.env.KENNEL_EXTERNAL_ICONS !== "1")
|
|
37809
|
-
return;
|
|
37810
|
-
const host = await folderWebsite(folderName);
|
|
37811
|
-
if (!host)
|
|
37812
|
-
return;
|
|
37813
|
-
const key = `favicon:${host}`;
|
|
37814
|
-
assets[key] = {
|
|
37815
|
-
mediaType: "image/png",
|
|
37816
|
-
source: "favicon",
|
|
37817
|
-
url: `/api/folder-favicon?path=${encodeURIComponent(folderName)}&host=${encodeURIComponent(host)}`
|
|
37818
|
-
};
|
|
37819
|
-
return key;
|
|
37820
|
-
}
|
|
37821
|
-
async function iconWithFaviconFallback(folder, assets, assetFiles) {
|
|
37822
|
-
return folderIcon(folder, assets, assetFiles, (folderName) => faviconAsset(folderName, assets));
|
|
37823
|
-
}
|
|
37824
37911
|
async function repositoryIdentity(folder, assets) {
|
|
37825
37912
|
const remoteUrl = await gitRemoteUrl(folder).catch(() => {
|
|
37826
37913
|
return;
|
|
@@ -37844,22 +37931,6 @@ async function repositoryIdentity(folder, assets) {
|
|
|
37844
37931
|
return;
|
|
37845
37932
|
}
|
|
37846
37933
|
}
|
|
37847
|
-
async function hydrateAssets(metadata, assetFiles) {
|
|
37848
|
-
const assets = {};
|
|
37849
|
-
const projects = metadata.projects?.items ?? [];
|
|
37850
|
-
const [iconAssetKey, projectItems] = await Promise.all([
|
|
37851
|
-
iconWithFaviconFallback(metadata.folder, assets, assetFiles),
|
|
37852
|
-
Promise.all(projects.map(async (project) => ({
|
|
37853
|
-
...project,
|
|
37854
|
-
iconAssetKey: await iconWithFaviconFallback(project.worktree, assets, assetFiles)
|
|
37855
|
-
})))
|
|
37856
|
-
]);
|
|
37857
|
-
return {
|
|
37858
|
-
assets,
|
|
37859
|
-
iconAssetKey,
|
|
37860
|
-
projects: { items: projectItems }
|
|
37861
|
-
};
|
|
37862
|
-
}
|
|
37863
37934
|
function unique(values) {
|
|
37864
37935
|
return [...new Set(values)];
|
|
37865
37936
|
}
|
|
@@ -37871,8 +37942,8 @@ async function shallowMetadata(folder) {
|
|
|
37871
37942
|
const visible = entries.filter((entry) => !entry.name.startsWith(".") && entry.name !== "node_modules");
|
|
37872
37943
|
const files = await Promise.all(visible.filter((entry) => entry.isFile() && [".markdown", ".md"].includes(extname6(entry.name).toLowerCase())).map(async (entry) => {
|
|
37873
37944
|
const path = resolve11(folder, entry.name);
|
|
37874
|
-
const details = await
|
|
37875
|
-
const content = await
|
|
37945
|
+
const details = await stat8(path);
|
|
37946
|
+
const content = await readFile9(path, "utf8").catch(() => "");
|
|
37876
37947
|
const title = markdownMetadata(content).title;
|
|
37877
37948
|
return {
|
|
37878
37949
|
activityAt: details.mtimeMs,
|
|
@@ -37926,7 +37997,7 @@ async function notesFiles(folder, files, origin) {
|
|
|
37926
37997
|
const path = file.path ?? (file.relativePath ? resolve11(folder, file.relativePath) : undefined);
|
|
37927
37998
|
if (!path)
|
|
37928
37999
|
return origin ? { ...file, folder: origin } : file;
|
|
37929
|
-
const content = [".markdown", ".md"].includes(extname6(path).toLowerCase()) ? await
|
|
38000
|
+
const content = [".markdown", ".md"].includes(extname6(path).toLowerCase()) ? await readFile9(path, "utf8").catch(() => "") : "";
|
|
37930
38001
|
const title = content ? markdownMetadata(content).title : undefined;
|
|
37931
38002
|
return {
|
|
37932
38003
|
...file,
|
|
@@ -38044,44 +38115,6 @@ async function projectSearchItem(project, match) {
|
|
|
38044
38115
|
worktree: project.worktree
|
|
38045
38116
|
};
|
|
38046
38117
|
}
|
|
38047
|
-
var defaultPluginRegistryConfig = {
|
|
38048
|
-
plugins: { disable: [], order: [] }
|
|
38049
|
-
};
|
|
38050
|
-
function stringArray(value) {
|
|
38051
|
-
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
38052
|
-
}
|
|
38053
|
-
function parsePluginRegistryConfig(content) {
|
|
38054
|
-
const parsed = JSON.parse(content);
|
|
38055
|
-
const config = parsed && typeof parsed === "object" ? parsed : {};
|
|
38056
|
-
return {
|
|
38057
|
-
plugins: {
|
|
38058
|
-
disable: stringArray(config.plugins?.disable),
|
|
38059
|
-
order: stringArray(config.plugins?.order)
|
|
38060
|
-
}
|
|
38061
|
-
};
|
|
38062
|
-
}
|
|
38063
|
-
async function nearestKennelConfig(folder) {
|
|
38064
|
-
for (let current = folder;; current = dirname9(current)) {
|
|
38065
|
-
const candidate = resolve11(current, "kennel.json");
|
|
38066
|
-
const details = await stat7(candidate).catch(() => {
|
|
38067
|
-
return;
|
|
38068
|
-
});
|
|
38069
|
-
if (details?.isFile())
|
|
38070
|
-
return candidate;
|
|
38071
|
-
if (dirname9(current) === current)
|
|
38072
|
-
return;
|
|
38073
|
-
}
|
|
38074
|
-
}
|
|
38075
|
-
async function folderPluginRegistryConfig(folder) {
|
|
38076
|
-
const path = await nearestKennelConfig(folder);
|
|
38077
|
-
if (!path)
|
|
38078
|
-
return defaultPluginRegistryConfig;
|
|
38079
|
-
try {
|
|
38080
|
-
return parsePluginRegistryConfig(await readFile8(path, "utf8"));
|
|
38081
|
-
} catch {
|
|
38082
|
-
return defaultPluginRegistryConfig;
|
|
38083
|
-
}
|
|
38084
|
-
}
|
|
38085
38118
|
function pluginConfigWarnings(registry, config) {
|
|
38086
38119
|
const ids = unique([...config.plugins.disable, ...config.plugins.order]);
|
|
38087
38120
|
return ids.filter((id) => !registry.has(id)).map((id) => `unknown plugin id "${id}" — not installed in this build`);
|
|
@@ -38257,7 +38290,7 @@ function createFolderApi({
|
|
|
38257
38290
|
"Content-Type": asset.mediaType,
|
|
38258
38291
|
ETag: `"${hash}"`
|
|
38259
38292
|
});
|
|
38260
|
-
response.end(await
|
|
38293
|
+
response.end(await readFile9(asset.path));
|
|
38261
38294
|
} catch (error) {
|
|
38262
38295
|
response.writeHead(404, {
|
|
38263
38296
|
"Content-Type": "text/plain; charset=utf-8"
|
|
@@ -38336,7 +38369,7 @@ function createFolderApi({
|
|
|
38336
38369
|
{
|
|
38337
38370
|
path: relative10(folder, target.path),
|
|
38338
38371
|
kind: target.kind,
|
|
38339
|
-
content: previewTextKind(target.kind) ? await
|
|
38372
|
+
content: previewTextKind(target.kind) ? await readFile9(target.path, "utf8") : ""
|
|
38340
38373
|
}
|
|
38341
38374
|
] : []
|
|
38342
38375
|
} : await readFolder(folder);
|
|
@@ -38352,7 +38385,7 @@ function createFolderApi({
|
|
|
38352
38385
|
documentError(response);
|
|
38353
38386
|
return;
|
|
38354
38387
|
}
|
|
38355
|
-
const details = await
|
|
38388
|
+
const details = await stat8(target.path);
|
|
38356
38389
|
if (!details.isFile()) {
|
|
38357
38390
|
documentError(response);
|
|
38358
38391
|
return;
|
|
@@ -38364,7 +38397,7 @@ function createFolderApi({
|
|
|
38364
38397
|
return;
|
|
38365
38398
|
}
|
|
38366
38399
|
watch(folder);
|
|
38367
|
-
const content = previewTextKind(target.kind) ? await
|
|
38400
|
+
const content = previewTextKind(target.kind) ? await readFile9(target.path, "utf8") : "";
|
|
38368
38401
|
sendContract(response, url, {
|
|
38369
38402
|
content,
|
|
38370
38403
|
kind: target.kind,
|
|
@@ -38393,7 +38426,7 @@ function createFolderApi({
|
|
|
38393
38426
|
documentError(response);
|
|
38394
38427
|
return;
|
|
38395
38428
|
}
|
|
38396
|
-
const details = await
|
|
38429
|
+
const details = await stat8(target.path);
|
|
38397
38430
|
if (!details.isFile()) {
|
|
38398
38431
|
documentError(response);
|
|
38399
38432
|
return;
|
|
@@ -38417,7 +38450,7 @@ function createFolderApi({
|
|
|
38417
38450
|
"Content-Disposition": `attachment; filename="${basename6(target.path)}"`
|
|
38418
38451
|
} : {}
|
|
38419
38452
|
});
|
|
38420
|
-
response.end(await
|
|
38453
|
+
response.end(await readFile9(target.path));
|
|
38421
38454
|
} catch (error) {
|
|
38422
38455
|
documentError(response);
|
|
38423
38456
|
}
|
|
@@ -38431,7 +38464,7 @@ function createFolderApi({
|
|
|
38431
38464
|
documentError(response);
|
|
38432
38465
|
return;
|
|
38433
38466
|
}
|
|
38434
|
-
const details = await
|
|
38467
|
+
const details = await stat8(target.path);
|
|
38435
38468
|
if (!details.isFile()) {
|
|
38436
38469
|
documentError(response);
|
|
38437
38470
|
return;
|
|
@@ -38443,7 +38476,7 @@ function createFolderApi({
|
|
|
38443
38476
|
return;
|
|
38444
38477
|
}
|
|
38445
38478
|
watch(folder);
|
|
38446
|
-
const data = await
|
|
38479
|
+
const data = await readFile9(target.path);
|
|
38447
38480
|
const baseHeaders = {
|
|
38448
38481
|
"Accept-Ranges": "bytes",
|
|
38449
38482
|
"Cache-Control": "private, max-age=0, must-revalidate",
|
|
@@ -38700,7 +38733,7 @@ function createFolderApi({
|
|
|
38700
38733
|
response.off("close", cancel);
|
|
38701
38734
|
}
|
|
38702
38735
|
const [hydrated, workspace] = await Promise.all([
|
|
38703
|
-
|
|
38736
|
+
hydrateFolderIcons({ folder, projects: { items: projects } }, assetFiles),
|
|
38704
38737
|
readWorkspace(folder)
|
|
38705
38738
|
]);
|
|
38706
38739
|
workspace.breadcrumbs = await workspaceBreadcrumbIcons(workspace.breadcrumbs ?? [], assetFiles, hydrated.assets);
|
|
@@ -38713,7 +38746,7 @@ function createFolderApi({
|
|
|
38713
38746
|
return;
|
|
38714
38747
|
}
|
|
38715
38748
|
const metadata = machine ? await shallowMetadata(folder) : await loadFolderData(folder);
|
|
38716
|
-
const assets = await
|
|
38749
|
+
const assets = await hydrateFolderIcons(metadata, assetFiles);
|
|
38717
38750
|
const body = {
|
|
38718
38751
|
...metadata,
|
|
38719
38752
|
...assets,
|
|
@@ -38760,7 +38793,7 @@ async function sendFile(request, response, directory, pathname, fallback) {
|
|
|
38760
38793
|
response.end();
|
|
38761
38794
|
return;
|
|
38762
38795
|
}
|
|
38763
|
-
if (!(await
|
|
38796
|
+
if (!(await stat9(target).catch(() => null))?.isFile()) {
|
|
38764
38797
|
if (!fallback) {
|
|
38765
38798
|
response.writeHead(404);
|
|
38766
38799
|
response.end();
|
|
@@ -38778,7 +38811,7 @@ async function sendFile(request, response, directory, pathname, fallback) {
|
|
|
38778
38811
|
"Content-Type": contentTypes[extname7(target)] ?? "application/octet-stream",
|
|
38779
38812
|
"X-Content-Type-Options": "nosniff"
|
|
38780
38813
|
});
|
|
38781
|
-
response.end(request.method === "HEAD" ? undefined : await
|
|
38814
|
+
response.end(request.method === "HEAD" ? undefined : await readFile10(target));
|
|
38782
38815
|
}
|
|
38783
38816
|
function createKennelApi(options) {
|
|
38784
38817
|
const remote = options.remoteAccess || !isLoopbackServerHost(options.host);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function copyToClipboard(text: string): Promise<void>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { accessSync, constants } from 'node:fs';
|
|
3
|
+
import { delimiter, join } from 'node:path';
|
|
4
|
+
function onPath(command) {
|
|
5
|
+
return (process.env.PATH ?? '').split(delimiter).some((directory) => {
|
|
6
|
+
if (!directory)
|
|
7
|
+
return false;
|
|
8
|
+
try {
|
|
9
|
+
accessSync(join(directory, command), constants.X_OK);
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/** The platform clipboard tool. No shell runs, so the text is never interpreted. */
|
|
18
|
+
function clipboardTool() {
|
|
19
|
+
if (process.platform === 'darwin')
|
|
20
|
+
return { command: 'pbcopy', args: [], encoding: 'utf8' };
|
|
21
|
+
// clip.exe reads UTF-16LE text that starts with a byte order mark.
|
|
22
|
+
if (process.platform === 'win32')
|
|
23
|
+
return { command: 'clip', args: [], encoding: 'utf16le' };
|
|
24
|
+
if (process.env.WAYLAND_DISPLAY && onPath('wl-copy'))
|
|
25
|
+
return { command: 'wl-copy', args: [], encoding: 'utf8' };
|
|
26
|
+
if (process.env.DISPLAY && onPath('xclip'))
|
|
27
|
+
return { command: 'xclip', args: ['-selection', 'clipboard'], encoding: 'utf8' };
|
|
28
|
+
if (process.env.DISPLAY && onPath('xsel'))
|
|
29
|
+
return { command: 'xsel', args: ['--clipboard', '--input'], encoding: 'utf8' };
|
|
30
|
+
throw new Error('No clipboard tool is available. Install wl-copy (Wayland) or xclip or xsel (X11) in a desktop session, or run knl llm without --copy.');
|
|
31
|
+
}
|
|
32
|
+
export function copyToClipboard(text) {
|
|
33
|
+
const tool = clipboardTool();
|
|
34
|
+
const input = tool.encoding === 'utf16le' ? Buffer.from(`\ufeff${text}`, 'utf16le') : Buffer.from(text, 'utf8');
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
// stderr is inherited: wl-copy keeps serving the selection in a forked
|
|
37
|
+
// process, and a pipe it holds open would keep this command alive.
|
|
38
|
+
const child = spawn(tool.command, tool.args, { stdio: ['pipe', 'ignore', 'inherit'] });
|
|
39
|
+
const timer = setTimeout(() => {
|
|
40
|
+
child.kill('SIGKILL');
|
|
41
|
+
reject(new Error(`${tool.command} did not finish within 10 seconds`));
|
|
42
|
+
}, 10_000);
|
|
43
|
+
child.once('error', (error) => {
|
|
44
|
+
clearTimeout(timer);
|
|
45
|
+
reject(new Error(`Cannot run ${tool.command}: ${error.message}`));
|
|
46
|
+
});
|
|
47
|
+
child.once('exit', (code, signal) => {
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
if (code === 0)
|
|
50
|
+
resolve();
|
|
51
|
+
else
|
|
52
|
+
reject(new Error(`${tool.command} failed ${signal ? `with signal ${signal}` : `with exit code ${code}`}`));
|
|
53
|
+
});
|
|
54
|
+
child.stdin.on('error', () => {
|
|
55
|
+
// The exit status reports the failure.
|
|
56
|
+
});
|
|
57
|
+
child.stdin.end(input);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function llm(args: string[]): Promise<void>;
|