kennel 0.2.31 → 0.2.33
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 +10 -0
- package/dist/cli/usage.js +9 -0
- package/dist/core/runtime.js +531 -197
- 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 +226 -0
- package/dist/llm/worker.d.ts +19 -0
- package/dist/llm/worker.js +1131 -0
- package/dist/notes/command.d.ts +1 -0
- package/dist/notes/command.js +556 -0
- package/dist/package/preview.js +554 -219
- 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 +132 -11
- package/package.json +4 -2
- package/schemas/config.schema.json +11 -0
- package/templates/plugin/package.json +1 -0
- package/templates/plugin/server.ts +43 -0
- package/templates/plugin/tsconfig.json +1 -1
- package/web/assets/{abnfDiagram-VRR7QNED-DPZyna8O.js → abnfDiagram-VRR7QNED-CpP1VHKI.js} +1 -1
- package/web/assets/{arc-BAcU9y_V.js → arc-kL4AxfvR.js} +1 -1
- package/web/assets/{architectureDiagram-ZJ3FMSHR-D5yd7Cag.js → architectureDiagram-ZJ3FMSHR-BWaRktRJ.js} +1 -1
- package/web/assets/{ask-tray-PMT6VVXN-C4dJH21Q.js → ask-tray-PMT6VVXN-FqPDpwtw.js} +3 -3
- package/web/assets/{blockDiagram-677ZJIJ3-C7LSGyxV.js → blockDiagram-677ZJIJ3-B07uVoPU.js} +1 -1
- package/web/assets/{c4Diagram-LMCZKHZV-BW2KfoQr.js → c4Diagram-LMCZKHZV-uvoaQC09.js} +1 -1
- package/web/assets/channel-Djmf1Ik1.js +1 -0
- package/web/assets/{chunk-2Q5K7J3B-4Og6VAFG.js → chunk-2Q5K7J3B-BH3SaHqk.js} +1 -1
- package/web/assets/{chunk-32BRIVSS-BYOowQl5.js → chunk-32BRIVSS-SPD8Eraw.js} +1 -1
- package/web/assets/{chunk-5VM5RSS4-C1m-lq64.js → chunk-5VM5RSS4-DNeOgOuZ.js} +1 -1
- package/web/assets/{chunk-EX3LRPZG-B2hriRpJ.js → chunk-EX3LRPZG-SJQ-vKGz.js} +1 -1
- package/web/assets/{chunk-JWPE2WC7-C6RsnzVA.js → chunk-JWPE2WC7-Dy_9wZ2Y.js} +1 -1
- package/web/assets/{chunk-MOJQB5TN-yKbMmUWf.js → chunk-MOJQB5TN-CuIx80Vn.js} +1 -1
- package/web/assets/{chunk-RYQCIY6F-BOokm3Qc.js → chunk-RYQCIY6F-DDG61RKR.js} +1 -1
- package/web/assets/{chunk-V7JOEXUC-CKW-H5An.js → chunk-V7JOEXUC-7OMVhaoH.js} +1 -1
- package/web/assets/{chunk-VR4S4FIN-BU5pFrup.js → chunk-VR4S4FIN-Y-HNaSmV.js} +1 -1
- package/web/assets/{chunk-XXDRQBXY-DYtdSJHU.js → chunk-XXDRQBXY-CtG0MPgC.js} +1 -1
- package/web/assets/classDiagram-OUVF2IWQ-hlDS9RWf.js +1 -0
- package/web/assets/classDiagram-v2-EOCWNBFH-hlDS9RWf.js +1 -0
- package/web/assets/{cose-bilkent-JH36ORCC-BZeXtC6E.js → cose-bilkent-JH36ORCC-BZaPMYcl.js} +1 -1
- package/web/assets/{cynefin-VYW2F7L2-BSDVyzMI.js → cynefin-VYW2F7L2-DxAlRHA1.js} +1 -1
- package/web/assets/{cynefinDiagram-TSTJHNR4-CReoDBYo.js → cynefinDiagram-TSTJHNR4-gjgzmagS.js} +1 -1
- package/web/assets/{dagre-VKFMJZFB-CBLLzMxd.js → dagre-VKFMJZFB-DvLPeYuw.js} +1 -1
- package/web/assets/{diagram-FQU43EPY-BcGWiZXZ.js → diagram-FQU43EPY-443LFnzG.js} +1 -1
- package/web/assets/{diagram-G47NLZAW-BXUYZA9N.js → diagram-G47NLZAW-CqfUewot.js} +1 -1
- package/web/assets/{diagram-NH7WQ7WH-BrvrZs7N.js → diagram-NH7WQ7WH-D-FElUGW.js} +1 -1
- package/web/assets/{diagram-OA4YK3LP-7lnVcjXL.js → diagram-OA4YK3LP-DbidUAp5.js} +1 -1
- package/web/assets/{diagram-WEI45ONY-s58_bMFf.js → diagram-WEI45ONY-CPLeckCZ.js} +1 -1
- package/web/assets/{ebnfDiagram-CCIWWBDH-BemWecoD.js → ebnfDiagram-CCIWWBDH-DR0rqZzN.js} +1 -1
- package/web/assets/{entity-search-page-G6QEQNXT--sKP5dP3.js → entity-search-page-G6QEQNXT-CnG8d-at.js} +1 -1
- package/web/assets/{erDiagram-Q63AITRT-ClVJddch.js → erDiagram-Q63AITRT-DMXv_8Tj.js} +1 -1
- package/web/assets/{flowDiagram-23GEKE2U-xzUJu-i_.js → flowDiagram-23GEKE2U-DnbQjZs0.js} +1 -1
- package/web/assets/{ganttDiagram-NO4QXBWP-CXQmtF5g.js → ganttDiagram-NO4QXBWP-DBKIrjj8.js} +1 -1
- package/web/assets/{gitGraphDiagram-IHSO6WYX-DCdljcLl.js → gitGraphDiagram-IHSO6WYX-BqqxKWT_.js} +1 -1
- package/web/assets/{index-CtTx1o2d.js → index-Bd3ghcW-.js} +5 -5
- package/web/assets/{index-vjrhIAJn.css → index-Dt8OzQCe.css} +1 -1
- package/web/assets/{infoDiagram-FWYZ7A6U-B11SJYSK.js → infoDiagram-FWYZ7A6U-Ba6Fs17y.js} +1 -1
- package/web/assets/{ishikawaDiagram-FXEZZL3T-B2CCGlAm.js → ishikawaDiagram-FXEZZL3T-D3BOtFjy.js} +1 -1
- package/web/assets/{journeyDiagram-5HDEW3XC-CBPETzOk.js → journeyDiagram-5HDEW3XC-MbzFPQm8.js} +1 -1
- package/web/assets/{kanban-definition-HUTT4EX6-unhQ3RPE.js → kanban-definition-HUTT4EX6-CurBmwu1.js} +1 -1
- package/web/assets/{linear-DuP_pG51.js → linear-CQIr4JjI.js} +1 -1
- package/web/assets/{linked-session-header-M7WY6IGF-DwlNCJlj.js → linked-session-header-M7WY6IGF-BBgN8zTq.js} +1 -1
- package/web/assets/{mermaid.core-BlYNzkpn.js → mermaid.core-bE6Vl3bZ.js} +4 -4
- package/web/assets/{mindmap-definition-LN4V7U3C-CtKHN6O1.js → mindmap-definition-LN4V7U3C-C1a46OGU.js} +1 -1
- package/web/assets/{pegDiagram-2B236MQR-CRyz6XWY.js → pegDiagram-2B236MQR-DZxMOxek.js} +1 -1
- package/web/assets/{pieDiagram-ENE6RG2P-BXfkPm3Q.js → pieDiagram-ENE6RG2P-BN0XOKLi.js} +1 -1
- package/web/assets/{plugin-host-modules-5NQgs5Cq.js → plugin-host-modules-CaRD5_5q.js} +1 -1
- package/web/assets/{project-search-dialog-HJFGW4DG-DPly4OiB.js → project-search-dialog-HJFGW4DG-h_dHeh7g.js} +1 -1
- package/web/assets/{project-search-page-NKQEBGPF-Cc6HV7BN.js → project-search-page-NKQEBGPF-2AzjjLEC.js} +1 -1
- package/web/assets/{quadrantDiagram-ABIIQ3AL-Drb_qWex.js → quadrantDiagram-ABIIQ3AL-C6IegTP-.js} +1 -1
- package/web/assets/{railroadDiagram-RFXS5EU6-DCKk2Pix.js → railroadDiagram-RFXS5EU6-bsjyAl-q.js} +1 -1
- package/web/assets/{requirementDiagram-TGXJPOKE-6WwZwcpD.js → requirementDiagram-TGXJPOKE-Cl8EZWy7.js} +1 -1
- package/web/assets/{sankeyDiagram-HTMAVEWB-DGGk3WCE.js → sankeyDiagram-HTMAVEWB-DhK4rRux.js} +1 -1
- package/web/assets/{selection-popup-532EHNCZ-yPhTrk7L.js → selection-popup-532EHNCZ-E2iQ2gQQ.js} +1 -1
- package/web/assets/{sequenceDiagram-DBY2YBRQ-a4tGi3DH.js → sequenceDiagram-DBY2YBRQ-DQTaPSD6.js} +1 -1
- package/web/assets/{sizeCapture-X5ZJPWSS-CHehBmy9.js → sizeCapture-X5ZJPWSS-k77bZ1zg.js} +1 -1
- package/web/assets/{stateDiagram-2N3HPSRC-Bn9GSWgV.js → stateDiagram-2N3HPSRC-Cv_5WuZJ.js} +1 -1
- package/web/assets/{stateDiagram-v2-6OUMAXLB-DI7fdBd7.js → stateDiagram-v2-6OUMAXLB-DJ1Lrskh.js} +1 -1
- package/web/assets/{swimlanes-5IMT3BWC-BX0WRzfR.js → swimlanes-5IMT3BWC-gBtAjG3z.js} +2 -2
- package/web/assets/swimlanesDiagram-G3AALYLV-DF1K9voG.js +8 -0
- package/web/assets/{timeline-definition-FHXFAJF6-9Ni2kIzq.js → timeline-definition-FHXFAJF6-CFjnM1Qq.js} +1 -1
- package/web/assets/{vennDiagram-L72KCM5P-rfBSNh3U.js → vennDiagram-L72KCM5P-CeMWrThY.js} +1 -1
- package/web/assets/{wardleyDiagram-EHGQE667-CzziImCC.js → wardleyDiagram-EHGQE667-R-ZaAOAI.js} +1 -1
- package/web/assets/{xychartDiagram-FW5EYKEG-DTLI4h5R.js → xychartDiagram-FW5EYKEG-BFXoMXiN.js} +1 -1
- package/web/index.html +2 -2
- package/web/assets/channel-um92dqpm.js +0 -1
- package/web/assets/classDiagram-OUVF2IWQ-h7sFEJBg.js +0 -1
- package/web/assets/classDiagram-v2-EOCWNBFH-h7sFEJBg.js +0 -1
- package/web/assets/swimlanesDiagram-G3AALYLV-OGHC9nqi.js +0 -8
package/dist/core/runtime.js
CHANGED
|
@@ -17027,8 +17027,8 @@ var require__2020 = __commonJS(function(exports, module) {
|
|
|
17027
17027
|
import {
|
|
17028
17028
|
createServer
|
|
17029
17029
|
} from "node:http";
|
|
17030
|
-
import { readFile as
|
|
17031
|
-
import { extname as extname7, isAbsolute as
|
|
17030
|
+
import { readFile as readFile11, realpath as realpath8, stat as stat10 } from "node:fs/promises";
|
|
17031
|
+
import { extname as extname7, isAbsolute as isAbsolute8, relative as relative12, resolve as resolve13 } from "node:path";
|
|
17032
17032
|
|
|
17033
17033
|
// packages/api/src/server/watcher.ts
|
|
17034
17034
|
import { existsSync, statSync, watch } from "node:fs";
|
|
@@ -21993,16 +21993,16 @@ 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
|
|
21997
|
-
import { homedir as
|
|
21996
|
+
import { readdir as readdir6, readFile as readFile10, realpath as realpath7, stat as stat9 } from "node:fs/promises";
|
|
21997
|
+
import { homedir as homedir4 } from "node:os";
|
|
21998
21998
|
import {
|
|
21999
|
-
basename as
|
|
22000
|
-
dirname as
|
|
21999
|
+
basename as basename7,
|
|
22000
|
+
dirname as dirname10,
|
|
22001
22001
|
extname as extname6,
|
|
22002
|
-
isAbsolute as
|
|
22003
|
-
relative as
|
|
22004
|
-
resolve as
|
|
22005
|
-
sep as
|
|
22002
|
+
isAbsolute as isAbsolute7,
|
|
22003
|
+
relative as relative11,
|
|
22004
|
+
resolve as resolve12,
|
|
22005
|
+
sep as sep8
|
|
22006
22006
|
} from "node:path";
|
|
22007
22007
|
|
|
22008
22008
|
// node_modules/.bun/@raggle-ai+barkdown@0.7.1+22117d1132b05fb8/node_modules/@raggle-ai/barkdown/dist/react.js
|
|
@@ -35561,7 +35561,7 @@ async function readFolder(root) {
|
|
|
35561
35561
|
}
|
|
35562
35562
|
|
|
35563
35563
|
// packages/api/src/server/folder-metadata-plugin.ts
|
|
35564
|
-
import { execFile as
|
|
35564
|
+
import { execFile as execFile5 } from "node:child_process";
|
|
35565
35565
|
|
|
35566
35566
|
// packages/plugins/src/endpoint.ts
|
|
35567
35567
|
var statusByCode = {
|
|
@@ -36688,19 +36688,215 @@ function createSchemaCollectionServer(host, options = {}) {
|
|
|
36688
36688
|
// packages/api/src/server/folder-metadata-plugin.ts
|
|
36689
36689
|
import local from "@raggle-ai/local";
|
|
36690
36690
|
|
|
36691
|
-
// packages/
|
|
36692
|
-
import {
|
|
36691
|
+
// packages/notes/src/store.ts
|
|
36692
|
+
import { execFile } from "node:child_process";
|
|
36693
|
+
import {
|
|
36694
|
+
mkdir as mkdir2,
|
|
36695
|
+
readdir as readdir3,
|
|
36696
|
+
readFile as readFile4,
|
|
36697
|
+
realpath as realpath3,
|
|
36698
|
+
rename as rename2,
|
|
36699
|
+
rm,
|
|
36700
|
+
stat as stat3,
|
|
36701
|
+
writeFile as writeFile2
|
|
36702
|
+
} from "node:fs/promises";
|
|
36693
36703
|
import { homedir as homedir2 } from "node:os";
|
|
36704
|
+
import { basename as basename4, dirname as dirname5, join as join2, relative as relative6, resolve as resolve6, sep as sep4 } from "node:path";
|
|
36705
|
+
import { promisify } from "node:util";
|
|
36706
|
+
var run = promisify(execFile);
|
|
36707
|
+
var REPOSITORY_ID_KEY = "kennel.repoId";
|
|
36708
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u;
|
|
36709
|
+
var markdownExtensions = new Set([".md", ".markdown"]);
|
|
36710
|
+
var caseInsensitiveHosts = new Set(["github.com", "gitlab.com", "bitbucket.org"]);
|
|
36711
|
+
class RepositoryNotesError extends Error {
|
|
36712
|
+
code;
|
|
36713
|
+
constructor(code, message) {
|
|
36714
|
+
super(message);
|
|
36715
|
+
this.name = "RepositoryNotesError";
|
|
36716
|
+
this.code = code;
|
|
36717
|
+
}
|
|
36718
|
+
}
|
|
36719
|
+
function kennelConfigHome() {
|
|
36720
|
+
const xdg = process.env.XDG_CONFIG_HOME?.trim();
|
|
36721
|
+
return xdg ? xdg : join2(homedir2(), ".config");
|
|
36722
|
+
}
|
|
36723
|
+
function kennelConfigFile() {
|
|
36724
|
+
return join2(kennelConfigHome(), "kennel", "config.json");
|
|
36725
|
+
}
|
|
36726
|
+
function repositoryNotesRoot() {
|
|
36727
|
+
return join2(kennelConfigHome(), "kennel", "repos");
|
|
36728
|
+
}
|
|
36729
|
+
function isRepositoryId(value) {
|
|
36730
|
+
return typeof value === "string" && UUID.test(value);
|
|
36731
|
+
}
|
|
36732
|
+
function managedNotesDirectory(id) {
|
|
36733
|
+
if (!isRepositoryId(id)) {
|
|
36734
|
+
throw new RepositoryNotesError("invalid-id", `Invalid repository id: ${id}`);
|
|
36735
|
+
}
|
|
36736
|
+
return join2(repositoryNotesRoot(), id);
|
|
36737
|
+
}
|
|
36738
|
+
async function readRepositoryNotesSettings() {
|
|
36739
|
+
const file = kennelConfigFile();
|
|
36740
|
+
let text;
|
|
36741
|
+
try {
|
|
36742
|
+
text = await readFile4(file, "utf8");
|
|
36743
|
+
} catch (error) {
|
|
36744
|
+
if (error.code === "ENOENT") {
|
|
36745
|
+
return { enabled: false };
|
|
36746
|
+
}
|
|
36747
|
+
throw new RepositoryNotesError("config-invalid", `Cannot read ${file}: ${errorMessage(error)}`);
|
|
36748
|
+
}
|
|
36749
|
+
let value;
|
|
36750
|
+
try {
|
|
36751
|
+
value = JSON.parse(text);
|
|
36752
|
+
} catch (error) {
|
|
36753
|
+
throw new RepositoryNotesError("config-invalid", `Invalid JSON in ${file}: ${errorMessage(error)}`);
|
|
36754
|
+
}
|
|
36755
|
+
if (!isRecord(value)) {
|
|
36756
|
+
throw new RepositoryNotesError("config-invalid", `${file} must hold an object`);
|
|
36757
|
+
}
|
|
36758
|
+
const section = value.repositoryNotes;
|
|
36759
|
+
if (section === undefined)
|
|
36760
|
+
return { enabled: false };
|
|
36761
|
+
if (!isRecord(section) || section.enabled !== undefined && typeof section.enabled !== "boolean") {
|
|
36762
|
+
throw new RepositoryNotesError("config-invalid", `${file}: repositoryNotes must be an object with a boolean "enabled" field`);
|
|
36763
|
+
}
|
|
36764
|
+
return { enabled: section.enabled === true };
|
|
36765
|
+
}
|
|
36766
|
+
async function git(args, cwd) {
|
|
36767
|
+
const { stdout } = await run("git", args, {
|
|
36768
|
+
cwd,
|
|
36769
|
+
env: { ...process.env, GIT_OPTIONAL_LOCKS: "0", GIT_TERMINAL_PROMPT: "0" },
|
|
36770
|
+
maxBuffer: 1024 * 1024
|
|
36771
|
+
});
|
|
36772
|
+
return stdout.replace(/\r?\n$/u, "");
|
|
36773
|
+
}
|
|
36774
|
+
function gitExitCode(error) {
|
|
36775
|
+
const code = error.code;
|
|
36776
|
+
return typeof code === "number" ? code : undefined;
|
|
36777
|
+
}
|
|
36778
|
+
async function resolveCheckout(folder) {
|
|
36779
|
+
let start;
|
|
36780
|
+
try {
|
|
36781
|
+
start = await realpath3(resolve6(folder));
|
|
36782
|
+
if (!(await stat3(start)).isDirectory())
|
|
36783
|
+
start = dirname5(start);
|
|
36784
|
+
} catch {
|
|
36785
|
+
throw new RepositoryNotesError("not-git", `Folder does not exist: ${folder}`);
|
|
36786
|
+
}
|
|
36787
|
+
let output;
|
|
36788
|
+
try {
|
|
36789
|
+
output = await git(["rev-parse", "--path-format=absolute", "--show-toplevel", "--git-common-dir"], start);
|
|
36790
|
+
} catch (error) {
|
|
36791
|
+
if (gitExitCode(error) === 128) {
|
|
36792
|
+
throw new RepositoryNotesError("not-git", `Not a Git repository: ${start}. Repository notes need a Git checkout.`);
|
|
36793
|
+
}
|
|
36794
|
+
throw error;
|
|
36795
|
+
}
|
|
36796
|
+
const [top, common] = output.split(/\r?\n/u);
|
|
36797
|
+
if (!top || !common) {
|
|
36798
|
+
throw new RepositoryNotesError("not-git", `Not a Git work tree: ${start}`);
|
|
36799
|
+
}
|
|
36800
|
+
const root = await realpath3(top);
|
|
36801
|
+
const commonDir = await realpath3(common);
|
|
36802
|
+
return { root, commonDir, configFile: join2(commonDir, "config") };
|
|
36803
|
+
}
|
|
36804
|
+
async function readBinding(checkout) {
|
|
36805
|
+
let value;
|
|
36806
|
+
try {
|
|
36807
|
+
value = await git(["config", "--file", checkout.configFile, "--get", REPOSITORY_ID_KEY], checkout.root);
|
|
36808
|
+
} catch (error) {
|
|
36809
|
+
if (gitExitCode(error) === 1)
|
|
36810
|
+
return;
|
|
36811
|
+
throw error;
|
|
36812
|
+
}
|
|
36813
|
+
const id = value.trim();
|
|
36814
|
+
if (!id)
|
|
36815
|
+
return;
|
|
36816
|
+
if (!isRepositoryId(id)) {
|
|
36817
|
+
throw new RepositoryNotesError("invalid-binding", `${checkout.configFile} has an invalid ${REPOSITORY_ID_KEY} value. Remove it or run "kennel notes new-identity".`);
|
|
36818
|
+
}
|
|
36819
|
+
return id;
|
|
36820
|
+
}
|
|
36821
|
+
function isRecord(value) {
|
|
36822
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
36823
|
+
}
|
|
36824
|
+
function errorMessage(error) {
|
|
36825
|
+
return error instanceof Error ? error.message : String(error);
|
|
36826
|
+
}
|
|
36827
|
+
async function lookupRepositoryNotes(folder) {
|
|
36828
|
+
let checkout;
|
|
36829
|
+
try {
|
|
36830
|
+
checkout = await resolveCheckout(folder);
|
|
36831
|
+
} catch (error) {
|
|
36832
|
+
if (error instanceof RepositoryNotesError && error.code === "not-git") {
|
|
36833
|
+
return { status: "not-git", folder };
|
|
36834
|
+
}
|
|
36835
|
+
throw error;
|
|
36836
|
+
}
|
|
36837
|
+
const id = await readBinding(checkout);
|
|
36838
|
+
if (!id)
|
|
36839
|
+
return { status: "unregistered", checkout, root: checkout.root };
|
|
36840
|
+
return {
|
|
36841
|
+
status: "registered",
|
|
36842
|
+
checkout,
|
|
36843
|
+
id,
|
|
36844
|
+
notesDirectory: managedNotesDirectory(id),
|
|
36845
|
+
root: checkout.root
|
|
36846
|
+
};
|
|
36847
|
+
}
|
|
36848
|
+
async function listManagedNotes(directory) {
|
|
36849
|
+
const notes = [];
|
|
36850
|
+
const visit = async (current) => {
|
|
36851
|
+
let entries;
|
|
36852
|
+
try {
|
|
36853
|
+
entries = await readdir3(current, { withFileTypes: true });
|
|
36854
|
+
} catch (error) {
|
|
36855
|
+
if (error.code === "ENOENT")
|
|
36856
|
+
return;
|
|
36857
|
+
throw error;
|
|
36858
|
+
}
|
|
36859
|
+
for (const entry of entries) {
|
|
36860
|
+
if (entry.name.startsWith(".") || entry.isSymbolicLink())
|
|
36861
|
+
continue;
|
|
36862
|
+
const path = join2(current, entry.name);
|
|
36863
|
+
if (entry.isDirectory()) {
|
|
36864
|
+
await visit(path);
|
|
36865
|
+
continue;
|
|
36866
|
+
}
|
|
36867
|
+
if (!entry.isFile())
|
|
36868
|
+
continue;
|
|
36869
|
+
const dot = entry.name.lastIndexOf(".");
|
|
36870
|
+
const extension = dot === -1 ? "" : entry.name.slice(dot).toLowerCase();
|
|
36871
|
+
if (!markdownExtensions.has(extension))
|
|
36872
|
+
continue;
|
|
36873
|
+
const details = await stat3(path);
|
|
36874
|
+
notes.push({
|
|
36875
|
+
activityAt: details.mtimeMs,
|
|
36876
|
+
name: dot === -1 ? entry.name : entry.name.slice(0, dot),
|
|
36877
|
+
path,
|
|
36878
|
+
relativePath: relative6(directory, path).split(sep4).join("/"),
|
|
36879
|
+
size: details.size
|
|
36880
|
+
});
|
|
36881
|
+
}
|
|
36882
|
+
};
|
|
36883
|
+
await visit(directory);
|
|
36884
|
+
return notes.sort((left, right) => right.activityAt - left.activityAt);
|
|
36885
|
+
}
|
|
36886
|
+
|
|
36887
|
+
// packages/api/src/server/entity-folder.ts
|
|
36888
|
+
import { readdir as readdir4, readFile as readFile5, realpath as realpath4, stat as stat4 } from "node:fs/promises";
|
|
36889
|
+
import { homedir as homedir3 } from "node:os";
|
|
36694
36890
|
import {
|
|
36695
|
-
basename as
|
|
36696
|
-
dirname as
|
|
36891
|
+
basename as basename5,
|
|
36892
|
+
dirname as dirname6,
|
|
36697
36893
|
extname as extname2,
|
|
36698
36894
|
isAbsolute as isAbsolute4,
|
|
36699
|
-
join as
|
|
36700
|
-
relative as
|
|
36701
|
-
resolve as
|
|
36895
|
+
join as join3,
|
|
36896
|
+
relative as relative7,
|
|
36897
|
+
resolve as resolve7
|
|
36702
36898
|
} from "node:path";
|
|
36703
|
-
var CONFIG_FILE =
|
|
36899
|
+
var CONFIG_FILE = join3("kennel", "config.json");
|
|
36704
36900
|
var IGNORED_DIRECTORIES = new Set([
|
|
36705
36901
|
".git",
|
|
36706
36902
|
".gtl",
|
|
@@ -36741,11 +36937,11 @@ function paragraph(body) {
|
|
|
36741
36937
|
}
|
|
36742
36938
|
function configPath() {
|
|
36743
36939
|
const xdg = process.env.XDG_CONFIG_HOME?.trim();
|
|
36744
|
-
return xdg ?
|
|
36940
|
+
return xdg ? join3(xdg, CONFIG_FILE) : join3(homedir3(), ".config", CONFIG_FILE);
|
|
36745
36941
|
}
|
|
36746
36942
|
async function readConfig(path) {
|
|
36747
36943
|
try {
|
|
36748
|
-
return parseConfig(await
|
|
36944
|
+
return parseConfig(await readFile5(path, "utf8"));
|
|
36749
36945
|
} catch (error) {
|
|
36750
36946
|
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT")
|
|
36751
36947
|
return null;
|
|
@@ -36762,6 +36958,8 @@ function parseConfig(source) {
|
|
|
36762
36958
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
36763
36959
|
throw new Error("Invalid Kennel entity root config");
|
|
36764
36960
|
const roots = value.allowedRoots;
|
|
36961
|
+
if (roots === undefined)
|
|
36962
|
+
return {};
|
|
36765
36963
|
if (!Array.isArray(roots))
|
|
36766
36964
|
throw new Error("Invalid Kennel entity root config");
|
|
36767
36965
|
const allowedRoots = roots.map((root) => {
|
|
@@ -36773,12 +36971,12 @@ function parseConfig(source) {
|
|
|
36773
36971
|
}
|
|
36774
36972
|
async function allowedRoots(currentWorkspaceRoot) {
|
|
36775
36973
|
const config = await readConfig(configPath());
|
|
36776
|
-
if (config)
|
|
36974
|
+
if (config?.allowedRoots)
|
|
36777
36975
|
return config.allowedRoots;
|
|
36778
36976
|
return currentWorkspaceRoot?.trim() ? [currentWorkspaceRoot] : [];
|
|
36779
36977
|
}
|
|
36780
36978
|
function inside4(root, path) {
|
|
36781
|
-
const local =
|
|
36979
|
+
const local = relative7(root, path);
|
|
36782
36980
|
return local === "" || !local.startsWith("..") && !isAbsolute4(local);
|
|
36783
36981
|
}
|
|
36784
36982
|
async function normalizeRoot(rootPath, currentWorkspaceRoot) {
|
|
@@ -36786,13 +36984,13 @@ async function normalizeRoot(rootPath, currentWorkspaceRoot) {
|
|
|
36786
36984
|
const fallback = currentWorkspaceRoot?.trim();
|
|
36787
36985
|
if (!trimmed && !fallback)
|
|
36788
36986
|
return null;
|
|
36789
|
-
const requested =
|
|
36790
|
-
const resolved = await
|
|
36791
|
-
const info = await
|
|
36987
|
+
const requested = resolve7(trimmed || fallback);
|
|
36988
|
+
const resolved = await realpath4(requested);
|
|
36989
|
+
const info = await stat4(resolved);
|
|
36792
36990
|
if (!info.isDirectory())
|
|
36793
36991
|
throw new Error(`${resolved} is not a folder`);
|
|
36794
36992
|
const roots = await allowedRoots(currentWorkspaceRoot);
|
|
36795
|
-
const realAllowedRoots = await Promise.all(roots.map((root) =>
|
|
36993
|
+
const realAllowedRoots = await Promise.all(roots.map((root) => realpath4(root)));
|
|
36796
36994
|
const allowed = realAllowedRoots.some((root) => inside4(root, resolved));
|
|
36797
36995
|
if (!allowed)
|
|
36798
36996
|
throw new Error("Entity root is outside the configured roots");
|
|
@@ -36801,11 +36999,11 @@ async function normalizeRoot(rootPath, currentWorkspaceRoot) {
|
|
|
36801
36999
|
async function markdownFiles(folderPath) {
|
|
36802
37000
|
const files = [];
|
|
36803
37001
|
async function walk(currentPath) {
|
|
36804
|
-
const entries = await
|
|
37002
|
+
const entries = await readdir4(currentPath, { withFileTypes: true });
|
|
36805
37003
|
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
36806
37004
|
if (entry.name.startsWith(".") || IGNORED_DIRECTORIES.has(entry.name))
|
|
36807
37005
|
continue;
|
|
36808
|
-
const entryPath =
|
|
37006
|
+
const entryPath = join3(currentPath, entry.name);
|
|
36809
37007
|
if (entry.isDirectory()) {
|
|
36810
37008
|
if (entryPath !== folderPath && await hasIndex(entryPath))
|
|
36811
37009
|
continue;
|
|
@@ -36820,36 +37018,36 @@ async function markdownFiles(folderPath) {
|
|
|
36820
37018
|
return files;
|
|
36821
37019
|
}
|
|
36822
37020
|
async function hasIndex(folderPath) {
|
|
36823
|
-
return
|
|
37021
|
+
return stat4(join3(folderPath, "index.md")).then((value) => value.isFile()).catch(() => false);
|
|
36824
37022
|
}
|
|
36825
37023
|
async function schemaPath(folderPath, rootPath) {
|
|
36826
37024
|
let current = folderPath;
|
|
36827
37025
|
while (current === rootPath || current.startsWith(`${rootPath}/`)) {
|
|
36828
37026
|
for (const name of ["schema.json", "_schema.json"]) {
|
|
36829
|
-
const candidate =
|
|
36830
|
-
if (await
|
|
37027
|
+
const candidate = join3(current, name);
|
|
37028
|
+
if (await stat4(candidate).then((value) => value.isFile()).catch(() => false))
|
|
36831
37029
|
return candidate;
|
|
36832
37030
|
}
|
|
36833
37031
|
if (current === rootPath)
|
|
36834
37032
|
return null;
|
|
36835
|
-
current =
|
|
37033
|
+
current = dirname6(current);
|
|
36836
37034
|
}
|
|
36837
37035
|
return null;
|
|
36838
37036
|
}
|
|
36839
37037
|
async function readEntity(folderPath, rootPath) {
|
|
36840
|
-
const indexPath =
|
|
36841
|
-
const index = parseMarkdown(await
|
|
37038
|
+
const indexPath = join3(folderPath, "index.md");
|
|
37039
|
+
const index = parseMarkdown(await readFile5(indexPath, "utf8"));
|
|
36842
37040
|
const files = await markdownFiles(folderPath);
|
|
36843
37041
|
const documents = await Promise.all(files.map(async (filePath) => {
|
|
36844
|
-
const document2 = parseMarkdown(await
|
|
37042
|
+
const document2 = parseMarkdown(await readFile5(filePath, "utf8"));
|
|
36845
37043
|
return {
|
|
36846
37044
|
content: document2.body,
|
|
36847
|
-
path:
|
|
36848
|
-
title: stringValue(document2.frontmatter.title) ?? heading(document2.body) ??
|
|
37045
|
+
path: relative7(folderPath, filePath),
|
|
37046
|
+
title: stringValue(document2.frontmatter.title) ?? heading(document2.body) ?? basename5(filePath, extname2(filePath))
|
|
36849
37047
|
};
|
|
36850
37048
|
}));
|
|
36851
|
-
const title = stringValue(index.frontmatter.title) ?? heading(index.body) ??
|
|
36852
|
-
const relativePath =
|
|
37049
|
+
const title = stringValue(index.frontmatter.title) ?? heading(index.body) ?? basename5(folderPath);
|
|
37050
|
+
const relativePath = relative7(rootPath, folderPath) || ".";
|
|
36853
37051
|
return {
|
|
36854
37052
|
aliases: stringValues(index.frontmatter.aliases),
|
|
36855
37053
|
content: [
|
|
@@ -36877,11 +37075,11 @@ async function discover(rootPath) {
|
|
|
36877
37075
|
async function walk(folderPath) {
|
|
36878
37076
|
if (await hasIndex(folderPath))
|
|
36879
37077
|
folders.push(folderPath);
|
|
36880
|
-
const entries = await
|
|
37078
|
+
const entries = await readdir4(folderPath, { withFileTypes: true });
|
|
36881
37079
|
for (const entry of entries) {
|
|
36882
37080
|
if (!entry.isDirectory() || entry.name.startsWith(".") || IGNORED_DIRECTORIES.has(entry.name))
|
|
36883
37081
|
continue;
|
|
36884
|
-
await walk(
|
|
37082
|
+
await walk(join3(folderPath, entry.name));
|
|
36885
37083
|
}
|
|
36886
37084
|
}
|
|
36887
37085
|
await walk(rootPath);
|
|
@@ -36921,20 +37119,20 @@ async function loadEntityWorkspace(root, query, selectedPath, currentWorkspaceRo
|
|
|
36921
37119
|
// packages/api/src/server/api/document-edit.ts
|
|
36922
37120
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
36923
37121
|
import {
|
|
36924
|
-
readFile as
|
|
36925
|
-
realpath as
|
|
36926
|
-
rename as
|
|
36927
|
-
stat as
|
|
37122
|
+
readFile as readFile6,
|
|
37123
|
+
realpath as realpath5,
|
|
37124
|
+
rename as rename3,
|
|
37125
|
+
stat as stat5,
|
|
36928
37126
|
unlink as unlink2,
|
|
36929
|
-
writeFile as
|
|
37127
|
+
writeFile as writeFile3
|
|
36930
37128
|
} from "node:fs/promises";
|
|
36931
37129
|
import {
|
|
36932
|
-
dirname as
|
|
37130
|
+
dirname as dirname7,
|
|
36933
37131
|
extname as extname3,
|
|
36934
37132
|
isAbsolute as isAbsolute5,
|
|
36935
|
-
relative as
|
|
36936
|
-
resolve as
|
|
36937
|
-
sep as
|
|
37133
|
+
relative as relative8,
|
|
37134
|
+
resolve as resolve8,
|
|
37135
|
+
sep as sep5
|
|
36938
37136
|
} from "node:path";
|
|
36939
37137
|
|
|
36940
37138
|
// packages/api/src/shared/selection.ts
|
|
@@ -36952,10 +37150,10 @@ async function editDocument(folder, input) {
|
|
|
36952
37150
|
throw apiError("bad-request", "Invalid document edit.");
|
|
36953
37151
|
if (![".md", ".markdown"].includes(extname3(path).toLowerCase()))
|
|
36954
37152
|
throw apiError("bad-request", "Only Markdown notes can be edited.");
|
|
36955
|
-
const root = await
|
|
36956
|
-
const target = await
|
|
36957
|
-
const local =
|
|
36958
|
-
if (!local || local === ".." || local.startsWith(`..${
|
|
37153
|
+
const root = await realpath5(folder);
|
|
37154
|
+
const target = await realpath5(resolve8(root, path));
|
|
37155
|
+
const local = relative8(root, target);
|
|
37156
|
+
if (!local || local === ".." || local.startsWith(`..${sep5}`) || isAbsolute5(local))
|
|
36959
37157
|
throw apiError("forbidden", "The document must be inside its folder.");
|
|
36960
37158
|
let content;
|
|
36961
37159
|
try {
|
|
@@ -36965,20 +37163,20 @@ async function editDocument(folder, input) {
|
|
|
36965
37163
|
}
|
|
36966
37164
|
const previous = pending.get(target) ?? Promise.resolve();
|
|
36967
37165
|
const operation = previous.catch(() => {}).then(async () => {
|
|
36968
|
-
const details = await
|
|
37166
|
+
const details = await stat5(target);
|
|
36969
37167
|
if (!details.isFile() || details.nlink !== 1)
|
|
36970
37168
|
throw apiError("forbidden", "Only regular, unlinked files can be edited.");
|
|
36971
|
-
if (await
|
|
37169
|
+
if (await readFile6(target, "utf8") !== original)
|
|
36972
37170
|
throw apiError("conflict", "This note changed on disk. Close this preview, reload the note, and try again.");
|
|
36973
|
-
const temporary =
|
|
37171
|
+
const temporary = resolve8(dirname7(target), `.kennel-edit-${randomUUID2()}.tmp`);
|
|
36974
37172
|
try {
|
|
36975
|
-
await
|
|
37173
|
+
await writeFile3(temporary, content, {
|
|
36976
37174
|
flag: "wx",
|
|
36977
37175
|
mode: details.mode & 511
|
|
36978
37176
|
});
|
|
36979
|
-
if (await
|
|
37177
|
+
if (await readFile6(target, "utf8") !== original)
|
|
36980
37178
|
throw apiError("conflict", "This note changed on disk. Reload before applying an edit.");
|
|
36981
|
-
await
|
|
37179
|
+
await rename3(temporary, target);
|
|
36982
37180
|
} finally {
|
|
36983
37181
|
await unlink2(temporary).catch(() => {});
|
|
36984
37182
|
}
|
|
@@ -37001,7 +37199,7 @@ var documentEditRoute = {
|
|
|
37001
37199
|
throw apiError("bad-request", "Invalid document edit.");
|
|
37002
37200
|
const input = parsed.data;
|
|
37003
37201
|
const folder = context.authorize(input.folder);
|
|
37004
|
-
if (context.lockedFile && (typeof input.path !== "string" || await
|
|
37202
|
+
if (context.lockedFile && (typeof input.path !== "string" || await realpath5(resolve8(folder, input.path)) !== context.lockedFile))
|
|
37005
37203
|
throw apiError("forbidden", "Only the selected document can be edited.");
|
|
37006
37204
|
await editDocument(folder, input);
|
|
37007
37205
|
jsonResponse(response, 200, routes.documentEdit.response.parse({ saved: true }));
|
|
@@ -37009,8 +37207,8 @@ var documentEditRoute = {
|
|
|
37009
37207
|
};
|
|
37010
37208
|
|
|
37011
37209
|
// packages/api/src/server/api/create-agent-file.ts
|
|
37012
|
-
import { writeFile as
|
|
37013
|
-
import { resolve as
|
|
37210
|
+
import { writeFile as writeFile4 } from "node:fs/promises";
|
|
37211
|
+
import { resolve as resolve9 } from "node:path";
|
|
37014
37212
|
var createAgentFileRoute = {
|
|
37015
37213
|
method: "POST",
|
|
37016
37214
|
maxBodyBytes: 0,
|
|
@@ -37021,9 +37219,9 @@ var createAgentFileRoute = {
|
|
|
37021
37219
|
} catch (error) {
|
|
37022
37220
|
throw apiError("not-found", error instanceof Error ? error.message : "Folder not found");
|
|
37023
37221
|
}
|
|
37024
|
-
const target =
|
|
37222
|
+
const target = resolve9(folder, "AGENTS.md");
|
|
37025
37223
|
try {
|
|
37026
|
-
await
|
|
37224
|
+
await writeFile4(target, "", { flag: "wx" });
|
|
37027
37225
|
} catch (error) {
|
|
37028
37226
|
if (error && typeof error === "object" && "code" in error && error.code === "EEXIST") {
|
|
37029
37227
|
throw apiError("conflict", "AGENTS.md already exists in this folder");
|
|
@@ -37035,7 +37233,7 @@ var createAgentFileRoute = {
|
|
|
37035
37233
|
};
|
|
37036
37234
|
|
|
37037
37235
|
// packages/api/src/server/api/open-path.ts
|
|
37038
|
-
import { execFile } from "node:child_process";
|
|
37236
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
37039
37237
|
var openPathRoute = {
|
|
37040
37238
|
method: "GET",
|
|
37041
37239
|
async handle({ context, query, response }) {
|
|
@@ -37046,7 +37244,7 @@ var openPathRoute = {
|
|
|
37046
37244
|
throw apiError("not-found", error instanceof Error ? error.message : "Path not found");
|
|
37047
37245
|
}
|
|
37048
37246
|
await new Promise((done, fail) => {
|
|
37049
|
-
|
|
37247
|
+
execFile2("/usr/bin/open", [target], (error) => {
|
|
37050
37248
|
if (error)
|
|
37051
37249
|
fail(apiError("failed", "Could not open path"));
|
|
37052
37250
|
else
|
|
@@ -37059,11 +37257,11 @@ var openPathRoute = {
|
|
|
37059
37257
|
};
|
|
37060
37258
|
|
|
37061
37259
|
// packages/api/src/server/folder-favicon.ts
|
|
37062
|
-
import { readFile as
|
|
37063
|
-
import { join as
|
|
37260
|
+
import { readFile as readFile7 } from "node:fs/promises";
|
|
37261
|
+
import { join as join4 } from "node:path";
|
|
37064
37262
|
async function folderWebsite(folder) {
|
|
37065
37263
|
for (const name of ["AGENTS.md", "README.md"]) {
|
|
37066
|
-
const content = await
|
|
37264
|
+
const content = await readFile7(join4(folder, name), "utf8").catch(() => "");
|
|
37067
37265
|
const entry = markdownMetadata(content).entries.find(([key]) => key === "website");
|
|
37068
37266
|
if (entry)
|
|
37069
37267
|
return websiteHost(entry[1]);
|
|
@@ -37073,7 +37271,7 @@ async function folderWebsite(folder) {
|
|
|
37073
37271
|
|
|
37074
37272
|
// packages/api/src/server/folder-icons.ts
|
|
37075
37273
|
import { createHash as createHash4 } from "node:crypto";
|
|
37076
|
-
import { readFile as
|
|
37274
|
+
import { readFile as readFile8, stat as stat6 } from "node:fs/promises";
|
|
37077
37275
|
import { extname as extname4 } from "node:path";
|
|
37078
37276
|
import { discoverFolderIcon } from "@raggle-ai/local";
|
|
37079
37277
|
var maxIconBytes2 = 4 * 1024 * 1024;
|
|
@@ -37112,13 +37310,13 @@ function iconUrl(folder, hash, mediaType) {
|
|
|
37112
37310
|
async function folderIcon(folder, assets, assetFiles, fallback) {
|
|
37113
37311
|
const icon = discoverFolderIcon(folder);
|
|
37114
37312
|
const mediaType = icon ? iconTypes.get(extname4(icon).toLowerCase()) : null;
|
|
37115
|
-
const details = icon && mediaType ? await
|
|
37313
|
+
const details = icon && mediaType ? await stat6(icon).catch(() => {
|
|
37116
37314
|
return;
|
|
37117
37315
|
}) : undefined;
|
|
37118
37316
|
if (!icon || !mediaType || !details?.size || details.size > maxIconBytes2) {
|
|
37119
37317
|
return fallback ? await fallback(folder, assets) : undefined;
|
|
37120
37318
|
}
|
|
37121
|
-
const data = await
|
|
37319
|
+
const data = await readFile8(icon);
|
|
37122
37320
|
const hash = assetKey(data);
|
|
37123
37321
|
const key = `local:${hash}`;
|
|
37124
37322
|
assetFiles.set(hash, { mediaType, path: icon });
|
|
@@ -37137,12 +37335,12 @@ async function searchIconDataUrl(icon) {
|
|
|
37137
37335
|
const mediaType = searchIconTypes.get(extname4(icon).toLowerCase());
|
|
37138
37336
|
if (!mediaType)
|
|
37139
37337
|
return;
|
|
37140
|
-
const details = await
|
|
37338
|
+
const details = await stat6(icon).catch(() => {
|
|
37141
37339
|
return;
|
|
37142
37340
|
});
|
|
37143
37341
|
if (!details?.size || details.size > maxSearchIconBytes)
|
|
37144
37342
|
return;
|
|
37145
|
-
return `data:${mediaType};base64,${(await
|
|
37343
|
+
return `data:${mediaType};base64,${(await readFile8(icon)).toString("base64")}`;
|
|
37146
37344
|
}
|
|
37147
37345
|
async function faviconAsset(folderName, assets) {
|
|
37148
37346
|
if (process.env.KENNEL_EXTERNAL_ICONS !== "1")
|
|
@@ -37188,11 +37386,19 @@ async function workspaceBreadcrumbIcons(breadcrumbs, assetFiles, assets = {}) {
|
|
|
37188
37386
|
// packages/api/src/server/plugins.ts
|
|
37189
37387
|
import { pathToFileURL } from "node:url";
|
|
37190
37388
|
import { existsSync as existsSync3 } from "node:fs";
|
|
37191
|
-
import {
|
|
37389
|
+
import { mkdir as mkdir3, readFile as readFile9, stat as stat8, writeFile as writeFile5 } from "node:fs/promises";
|
|
37390
|
+
import { dirname as dirname8, resolve as resolve11, sep as sep7 } from "node:path";
|
|
37192
37391
|
|
|
37193
37392
|
// packages/notes/src/server.ts
|
|
37194
|
-
import { readdir as
|
|
37195
|
-
import {
|
|
37393
|
+
import { readdir as readdir5, realpath as realpath6, stat as stat7 } from "node:fs/promises";
|
|
37394
|
+
import {
|
|
37395
|
+
basename as basename6,
|
|
37396
|
+
extname as extname5,
|
|
37397
|
+
isAbsolute as isAbsolute6,
|
|
37398
|
+
relative as relative9,
|
|
37399
|
+
resolve as resolve10,
|
|
37400
|
+
sep as sep6
|
|
37401
|
+
} from "node:path";
|
|
37196
37402
|
|
|
37197
37403
|
// packages/notes/src/manifest.ts
|
|
37198
37404
|
var notesManifest = {
|
|
@@ -37208,52 +37414,122 @@ var notesManifest = {
|
|
|
37208
37414
|
};
|
|
37209
37415
|
|
|
37210
37416
|
// packages/notes/src/server.ts
|
|
37211
|
-
var
|
|
37417
|
+
var markdownExtensions2 = new Set([".md", ".markdown"]);
|
|
37418
|
+
var llmNoteLimit = 20;
|
|
37212
37419
|
async function listMarkdownFiles(folder) {
|
|
37213
37420
|
const files = [];
|
|
37214
37421
|
const visit = async (current) => {
|
|
37215
|
-
const entries = await
|
|
37422
|
+
const entries = await readdir5(current, { withFileTypes: true }).catch(() => []);
|
|
37216
37423
|
for (const entry of entries) {
|
|
37217
37424
|
if (entry.name === "node_modules" || entry.name.startsWith("."))
|
|
37218
37425
|
continue;
|
|
37219
|
-
const path =
|
|
37426
|
+
const path = resolve10(current, entry.name);
|
|
37220
37427
|
if (entry.isDirectory()) {
|
|
37221
37428
|
await visit(path);
|
|
37222
37429
|
continue;
|
|
37223
37430
|
}
|
|
37224
|
-
if (!entry.isFile() || !
|
|
37431
|
+
if (!entry.isFile() || !markdownExtensions2.has(extname5(entry.name))) {
|
|
37225
37432
|
continue;
|
|
37226
37433
|
}
|
|
37227
|
-
const details = await
|
|
37434
|
+
const details = await stat7(path).catch(() => {
|
|
37228
37435
|
return;
|
|
37229
37436
|
});
|
|
37230
37437
|
files.push({
|
|
37231
37438
|
activityAt: details?.mtimeMs ?? 0,
|
|
37232
|
-
name:
|
|
37233
|
-
relativePath:
|
|
37439
|
+
name: basename6(entry.name, extname5(entry.name)),
|
|
37440
|
+
relativePath: relative9(folder, path).split(sep6).join("/")
|
|
37234
37441
|
});
|
|
37235
37442
|
}
|
|
37236
37443
|
};
|
|
37237
37444
|
await visit(folder);
|
|
37238
37445
|
return files.sort((left, right) => right.activityAt - left.activityAt);
|
|
37239
37446
|
}
|
|
37240
|
-
|
|
37447
|
+
var canonical = (path) => realpath6(path).catch(() => path);
|
|
37448
|
+
function inside5(path, root) {
|
|
37449
|
+
const local = relative9(root, path);
|
|
37450
|
+
return local === "" || !local.startsWith("..") && !isAbsolute6(local);
|
|
37451
|
+
}
|
|
37452
|
+
async function withoutManaged(groups, managedRoots) {
|
|
37453
|
+
if (!managedRoots.length)
|
|
37454
|
+
return groups;
|
|
37455
|
+
const roots = await Promise.all(managedRoots.map(canonical));
|
|
37456
|
+
return Promise.all(groups.map(async (group) => {
|
|
37457
|
+
if (roots.includes(await canonical(group.folder))) {
|
|
37458
|
+
return { ...group, files: [] };
|
|
37459
|
+
}
|
|
37460
|
+
const files = await Promise.all(group.files.map(async (file) => {
|
|
37461
|
+
const real = await canonical(resolve10(group.folder, file.relativePath));
|
|
37462
|
+
return roots.some((root) => inside5(real, root)) ? undefined : file;
|
|
37463
|
+
}));
|
|
37464
|
+
return { ...group, files: files.filter((file) => file !== undefined) };
|
|
37465
|
+
}));
|
|
37466
|
+
}
|
|
37467
|
+
async function loadNotes(folder, linkedFolders = [], managedFolders = []) {
|
|
37241
37468
|
const localFiles = await listMarkdownFiles(folder);
|
|
37242
|
-
const
|
|
37243
|
-
files: await listMarkdownFiles(
|
|
37244
|
-
folder:
|
|
37469
|
+
const listGroups = (folders) => Promise.all(folders.map(async (source) => ({
|
|
37470
|
+
files: await listMarkdownFiles(source),
|
|
37471
|
+
folder: source
|
|
37245
37472
|
})));
|
|
37473
|
+
const [managed, linked] = await Promise.all([
|
|
37474
|
+
listGroups(managedFolders),
|
|
37475
|
+
listGroups(linkedFolders).then((groups) => withoutManaged(groups, managedFolders))
|
|
37476
|
+
]);
|
|
37246
37477
|
return {
|
|
37247
37478
|
files: {
|
|
37248
37479
|
items: [
|
|
37249
37480
|
...localFiles,
|
|
37250
|
-
...linked.flatMap((group) => group.files.map((file) => ({ ...file, folder: group.folder })))
|
|
37481
|
+
...[...managed, ...linked].flatMap((group) => group.files.map((file) => ({ ...file, folder: group.folder })))
|
|
37251
37482
|
]
|
|
37252
37483
|
},
|
|
37253
37484
|
folder,
|
|
37254
37485
|
status: "available"
|
|
37255
37486
|
};
|
|
37256
37487
|
}
|
|
37488
|
+
function codeSpan(text) {
|
|
37489
|
+
const escaped = text.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/gu, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
37490
|
+
const longest = Math.max(0, ...(escaped.match(/`+/g) ?? []).map((run) => run.length));
|
|
37491
|
+
const fence = "`".repeat(longest + 1);
|
|
37492
|
+
const pad = escaped.startsWith("`") || escaped.endsWith("`") ? " " : "";
|
|
37493
|
+
return `${fence}${pad}${escaped}${pad}${fence}`;
|
|
37494
|
+
}
|
|
37495
|
+
async function notesLlmSections({
|
|
37496
|
+
folder,
|
|
37497
|
+
linkedFolders = [],
|
|
37498
|
+
managedFolders = [],
|
|
37499
|
+
signal
|
|
37500
|
+
}) {
|
|
37501
|
+
const groups = [];
|
|
37502
|
+
const notes = [];
|
|
37503
|
+
const seen = new Set;
|
|
37504
|
+
for (const source of [folder, ...managedFolders, ...linkedFolders]) {
|
|
37505
|
+
signal.throwIfAborted();
|
|
37506
|
+
const identity = await canonical(source);
|
|
37507
|
+
if (seen.has(identity))
|
|
37508
|
+
continue;
|
|
37509
|
+
seen.add(identity);
|
|
37510
|
+
const files = await listMarkdownFiles(source);
|
|
37511
|
+
const paths = files.map((file) => resolve10(source, file.relativePath));
|
|
37512
|
+
groups.push({ folder: source, paths });
|
|
37513
|
+
files.forEach((file, index) => notes.push({ activityAt: file.activityAt, path: paths[index] }));
|
|
37514
|
+
}
|
|
37515
|
+
if (notes.length === 0)
|
|
37516
|
+
return [];
|
|
37517
|
+
const recent = notes.sort((left, right) => right.activityAt - left.activityAt || (left.path < right.path ? -1 : left.path > right.path ? 1 : 0)).slice(0, llmNoteLimit);
|
|
37518
|
+
return [
|
|
37519
|
+
{
|
|
37520
|
+
id: "notes",
|
|
37521
|
+
title: "Markdown notes",
|
|
37522
|
+
lines: [
|
|
37523
|
+
...groups.filter((group) => group.paths.length > 0).map((group) => `- Source: ${codeSpan(group.folder)} (${group.paths.length} ${group.paths.length === 1 ? "note" : "notes"})`),
|
|
37524
|
+
`- Total notes: ${notes.length}`,
|
|
37525
|
+
"",
|
|
37526
|
+
`Most recently changed (${recent.length} of ${notes.length}):`,
|
|
37527
|
+
"",
|
|
37528
|
+
...recent.map((note) => `- ${codeSpan(note.path)}`)
|
|
37529
|
+
]
|
|
37530
|
+
}
|
|
37531
|
+
];
|
|
37532
|
+
}
|
|
37257
37533
|
function createNotesPlugin() {
|
|
37258
37534
|
return {
|
|
37259
37535
|
...notesManifest,
|
|
@@ -37265,10 +37541,15 @@ function createNotesPlugin() {
|
|
|
37265
37541
|
},
|
|
37266
37542
|
async handle({
|
|
37267
37543
|
folder,
|
|
37268
|
-
linkedFolders
|
|
37544
|
+
linkedFolders,
|
|
37545
|
+
managedFolders
|
|
37269
37546
|
}) {
|
|
37270
|
-
return {
|
|
37271
|
-
|
|
37547
|
+
return {
|
|
37548
|
+
data: await loadNotes(folder, linkedFolders, managedFolders),
|
|
37549
|
+
kind: "available"
|
|
37550
|
+
};
|
|
37551
|
+
},
|
|
37552
|
+
llm: notesLlmSections
|
|
37272
37553
|
};
|
|
37273
37554
|
}
|
|
37274
37555
|
|
|
@@ -37282,6 +37563,44 @@ function configuredFolderServerRegistry(registry, resolvedConfig) {
|
|
|
37282
37563
|
];
|
|
37283
37564
|
return new Map(ids.map((id) => [id, registry.get(id)]));
|
|
37284
37565
|
}
|
|
37566
|
+
var defaultPluginRegistryConfig = {
|
|
37567
|
+
plugins: { disable: [], order: [] }
|
|
37568
|
+
};
|
|
37569
|
+
function stringArray(value) {
|
|
37570
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
37571
|
+
}
|
|
37572
|
+
function parsePluginRegistryConfig(content) {
|
|
37573
|
+
const parsed = JSON.parse(content);
|
|
37574
|
+
const config = parsed && typeof parsed === "object" ? parsed : {};
|
|
37575
|
+
return {
|
|
37576
|
+
plugins: {
|
|
37577
|
+
disable: stringArray(config.plugins?.disable),
|
|
37578
|
+
order: stringArray(config.plugins?.order)
|
|
37579
|
+
}
|
|
37580
|
+
};
|
|
37581
|
+
}
|
|
37582
|
+
async function nearestKennelConfig(folder) {
|
|
37583
|
+
for (let current = folder;; current = dirname8(current)) {
|
|
37584
|
+
const candidate = resolve11(current, "kennel.json");
|
|
37585
|
+
const details = await stat8(candidate).catch(() => {
|
|
37586
|
+
return;
|
|
37587
|
+
});
|
|
37588
|
+
if (details?.isFile())
|
|
37589
|
+
return candidate;
|
|
37590
|
+
if (dirname8(current) === current)
|
|
37591
|
+
return;
|
|
37592
|
+
}
|
|
37593
|
+
}
|
|
37594
|
+
async function folderPluginRegistryConfig(folder) {
|
|
37595
|
+
const path = await nearestKennelConfig(folder);
|
|
37596
|
+
if (!path)
|
|
37597
|
+
return defaultPluginRegistryConfig;
|
|
37598
|
+
try {
|
|
37599
|
+
return parsePluginRegistryConfig(await readFile9(path, "utf8"));
|
|
37600
|
+
} catch {
|
|
37601
|
+
return defaultPluginRegistryConfig;
|
|
37602
|
+
}
|
|
37603
|
+
}
|
|
37285
37604
|
function createFolderServerPlugins() {
|
|
37286
37605
|
return [createNotesPlugin()];
|
|
37287
37606
|
}
|
|
@@ -37325,7 +37644,7 @@ async function folderServerRegistryAsync() {
|
|
|
37325
37644
|
...server,
|
|
37326
37645
|
async detect(context) {
|
|
37327
37646
|
const detection = await server.detect(context);
|
|
37328
|
-
if (detection.status === "unavailable" && existsSync3(
|
|
37647
|
+
if (detection.status === "unavailable" && existsSync3(resolve11(context.folder, entry.trigger))) {
|
|
37329
37648
|
return { status: "available" };
|
|
37330
37649
|
}
|
|
37331
37650
|
return detection;
|
|
@@ -37364,13 +37683,13 @@ async function externalApiHandlers() {
|
|
|
37364
37683
|
}
|
|
37365
37684
|
|
|
37366
37685
|
// packages/api/src/server/project-discovery.ts
|
|
37367
|
-
import { execFile as
|
|
37686
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
37368
37687
|
|
|
37369
37688
|
// packages/api/src/server/linked-folder-discovery.ts
|
|
37370
|
-
import { execFile as
|
|
37689
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
37371
37690
|
import { existsSync as existsSync4 } from "node:fs";
|
|
37372
37691
|
import { createRequire as createRequire2 } from "node:module";
|
|
37373
|
-
import { dirname as
|
|
37692
|
+
import { dirname as dirname9, join as join5 } from "node:path";
|
|
37374
37693
|
import { fileURLToPath } from "node:url";
|
|
37375
37694
|
var script = `
|
|
37376
37695
|
const local = require(process.argv[1]);
|
|
@@ -37393,19 +37712,19 @@ function resolveLocalModule(from = import.meta.url) {
|
|
|
37393
37712
|
try {
|
|
37394
37713
|
candidates.push(require2.resolve("@raggle-ai/local"));
|
|
37395
37714
|
} catch {}
|
|
37396
|
-
let directory =
|
|
37397
|
-
while (
|
|
37398
|
-
if (existsSync4(
|
|
37715
|
+
let directory = dirname9(fileURLToPath(from));
|
|
37716
|
+
while (dirname9(directory) !== directory) {
|
|
37717
|
+
if (existsSync4(join5(directory, "raggle.json"))) {
|
|
37399
37718
|
for (const candidate of [
|
|
37400
|
-
|
|
37401
|
-
|
|
37719
|
+
join5(dirname9(directory), "raggle-ai-scout", "packages", "local"),
|
|
37720
|
+
join5(directory, "..", "..", "raggle-ai-scout", "packages", "local")
|
|
37402
37721
|
]) {
|
|
37403
37722
|
if (!candidates.includes(candidate))
|
|
37404
37723
|
candidates.push(candidate);
|
|
37405
37724
|
}
|
|
37406
37725
|
break;
|
|
37407
37726
|
}
|
|
37408
|
-
directory =
|
|
37727
|
+
directory = dirname9(directory);
|
|
37409
37728
|
}
|
|
37410
37729
|
for (const candidate of candidates) {
|
|
37411
37730
|
try {
|
|
@@ -37414,11 +37733,11 @@ function resolveLocalModule(from = import.meta.url) {
|
|
|
37414
37733
|
return candidate;
|
|
37415
37734
|
} catch {}
|
|
37416
37735
|
}
|
|
37417
|
-
return candidates[0] ??
|
|
37736
|
+
return candidates[0] ?? join5(dirname9(fileURLToPath(from)), "..", "..", "..", "raggle-ai-scout", "packages", "local");
|
|
37418
37737
|
}
|
|
37419
37738
|
function discoverLinkedFolders(folder, signal, modulePath = resolveLocalModule()) {
|
|
37420
37739
|
return new Promise((resolve, reject) => {
|
|
37421
|
-
|
|
37740
|
+
execFile3(process.execPath, ["-e", script, modulePath, folder], {
|
|
37422
37741
|
signal,
|
|
37423
37742
|
timeout: 60000,
|
|
37424
37743
|
maxBuffer: 8 * 1024 * 1024,
|
|
@@ -37509,7 +37828,7 @@ local.discoverProjectsUnderFolder({folder}).then(projects => {
|
|
|
37509
37828
|
`;
|
|
37510
37829
|
function discoverProjects(folder, signal) {
|
|
37511
37830
|
return new Promise((resolve, reject) => {
|
|
37512
|
-
|
|
37831
|
+
execFile4(process.execPath, ["-e", script2, resolveLocalModule(), folder], {
|
|
37513
37832
|
signal,
|
|
37514
37833
|
timeout: 60000,
|
|
37515
37834
|
maxBuffer: 8 * 1024 * 1024,
|
|
@@ -37766,10 +38085,10 @@ var metadataExtensions = new Set([
|
|
|
37766
38085
|
".txt"
|
|
37767
38086
|
]);
|
|
37768
38087
|
function metadataFile(path, root) {
|
|
37769
|
-
const candidate =
|
|
37770
|
-
if (!candidate || candidate.startsWith("..") ||
|
|
38088
|
+
const candidate = relative11(root, path);
|
|
38089
|
+
if (!candidate || candidate.startsWith("..") || isAbsolute7(candidate))
|
|
37771
38090
|
return false;
|
|
37772
|
-
const parts = candidate.split(
|
|
38091
|
+
const parts = candidate.split(sep8);
|
|
37773
38092
|
if (parts.some((part) => part === "node_modules"))
|
|
37774
38093
|
return false;
|
|
37775
38094
|
return metadataExtensions.has(extname6(candidate).toLowerCase());
|
|
@@ -37780,11 +38099,11 @@ async function configuredAiInputModule(folder) {
|
|
|
37780
38099
|
return typeof module === "string" && module.trim() ? module.trim() : null;
|
|
37781
38100
|
}
|
|
37782
38101
|
function folderPath(value, root, lockedRoot) {
|
|
37783
|
-
const folder = realpathSync2(
|
|
38102
|
+
const folder = realpathSync2(resolve12(value ?? root));
|
|
37784
38103
|
if (!lockedRoot)
|
|
37785
38104
|
return folder;
|
|
37786
|
-
const path =
|
|
37787
|
-
if (!path || !path.startsWith("..") && !
|
|
38105
|
+
const path = relative11(lockedRoot, folder);
|
|
38106
|
+
if (!path || !path.startsWith("..") && !isAbsolute7(path))
|
|
37788
38107
|
return folder;
|
|
37789
38108
|
throw new Error(`Folder is outside the viewer root: ${folder}`);
|
|
37790
38109
|
}
|
|
@@ -37855,15 +38174,15 @@ function unique(values) {
|
|
|
37855
38174
|
return [...new Set(values)];
|
|
37856
38175
|
}
|
|
37857
38176
|
function isMachineRoot(folder) {
|
|
37858
|
-
return folder ===
|
|
38177
|
+
return folder === homedir4() || dirname10(folder) === folder;
|
|
37859
38178
|
}
|
|
37860
38179
|
async function shallowMetadata(folder) {
|
|
37861
|
-
const entries = await
|
|
38180
|
+
const entries = await readdir6(folder, { withFileTypes: true });
|
|
37862
38181
|
const visible = entries.filter((entry) => !entry.name.startsWith(".") && entry.name !== "node_modules");
|
|
37863
38182
|
const files = await Promise.all(visible.filter((entry) => entry.isFile() && [".markdown", ".md"].includes(extname6(entry.name).toLowerCase())).map(async (entry) => {
|
|
37864
|
-
const path =
|
|
37865
|
-
const details = await
|
|
37866
|
-
const content = await
|
|
38183
|
+
const path = resolve12(folder, entry.name);
|
|
38184
|
+
const details = await stat9(path);
|
|
38185
|
+
const content = await readFile10(path, "utf8").catch(() => "");
|
|
37867
38186
|
const title = markdownMetadata(content).title;
|
|
37868
38187
|
return {
|
|
37869
38188
|
activityAt: details.mtimeMs,
|
|
@@ -37876,10 +38195,10 @@ async function shallowMetadata(folder) {
|
|
|
37876
38195
|
};
|
|
37877
38196
|
}));
|
|
37878
38197
|
const projects = visible.filter((entry) => entry.isDirectory()).map((entry) => ({
|
|
37879
|
-
id: createHash5("sha1").update(
|
|
38198
|
+
id: createHash5("sha1").update(resolve12(folder, entry.name)).digest("hex"),
|
|
37880
38199
|
name: entry.name,
|
|
37881
38200
|
relativePath: entry.name,
|
|
37882
|
-
worktree:
|
|
38201
|
+
worktree: resolve12(folder, entry.name)
|
|
37883
38202
|
})).sort((left, right) => left.name.localeCompare(right.name));
|
|
37884
38203
|
return {
|
|
37885
38204
|
changes: { items: [] },
|
|
@@ -37905,8 +38224,8 @@ async function loadFolderData(folder) {
|
|
|
37905
38224
|
items: projects.map((project) => ({
|
|
37906
38225
|
id: project.id,
|
|
37907
38226
|
worktree: project.worktree,
|
|
37908
|
-
name: project.name ??
|
|
37909
|
-
relativePath:
|
|
38227
|
+
name: project.name ?? basename7(project.worktree),
|
|
38228
|
+
relativePath: relative11(folder, project.worktree)
|
|
37910
38229
|
}))
|
|
37911
38230
|
}
|
|
37912
38231
|
};
|
|
@@ -37914,10 +38233,10 @@ async function loadFolderData(folder) {
|
|
|
37914
38233
|
async function notesFiles(folder, files, origin) {
|
|
37915
38234
|
return {
|
|
37916
38235
|
items: await Promise.all(files.map(async (file) => {
|
|
37917
|
-
const path = file.path ?? (file.relativePath ?
|
|
38236
|
+
const path = file.path ?? (file.relativePath ? resolve12(folder, file.relativePath) : undefined);
|
|
37918
38237
|
if (!path)
|
|
37919
38238
|
return origin ? { ...file, folder: origin } : file;
|
|
37920
|
-
const content = [".markdown", ".md"].includes(extname6(path).toLowerCase()) ? await
|
|
38239
|
+
const content = [".markdown", ".md"].includes(extname6(path).toLowerCase()) ? await readFile10(path, "utf8").catch(() => "") : "";
|
|
37921
38240
|
const title = content ? markdownMetadata(content).title : undefined;
|
|
37922
38241
|
return {
|
|
37923
38242
|
...file,
|
|
@@ -37928,6 +38247,31 @@ async function notesFiles(folder, files, origin) {
|
|
|
37928
38247
|
}))
|
|
37929
38248
|
};
|
|
37930
38249
|
}
|
|
38250
|
+
async function managedNotesFiles(directory) {
|
|
38251
|
+
const notes = await listManagedNotes(directory);
|
|
38252
|
+
return Promise.all(notes.map(async (note) => {
|
|
38253
|
+
const content = await readFile10(note.path, "utf8").catch(() => "");
|
|
38254
|
+
const title = content ? markdownMetadata(content).title : undefined;
|
|
38255
|
+
return {
|
|
38256
|
+
...note,
|
|
38257
|
+
folder: directory,
|
|
38258
|
+
name: title ?? note.name,
|
|
38259
|
+
openHref: openPathHref(note.path)
|
|
38260
|
+
};
|
|
38261
|
+
}));
|
|
38262
|
+
}
|
|
38263
|
+
var canonicalPath = (path) => realpath7(path).catch(() => path);
|
|
38264
|
+
function insideFolder(path, root) {
|
|
38265
|
+
const local = relative11(root, path);
|
|
38266
|
+
return local === "" || !local.startsWith("..") && !isAbsolute7(local);
|
|
38267
|
+
}
|
|
38268
|
+
async function withoutManagedFiles(items, managedRoot) {
|
|
38269
|
+
if (!managedRoot)
|
|
38270
|
+
return items;
|
|
38271
|
+
const root = await canonicalPath(managedRoot);
|
|
38272
|
+
const outside = await Promise.all(items.map(async (item) => !item.path || !insideFolder(await canonicalPath(item.path), root)));
|
|
38273
|
+
return items.filter((_, index) => outside[index]);
|
|
38274
|
+
}
|
|
37931
38275
|
async function localProjectSearchCache(cache) {
|
|
37932
38276
|
if (cache && Date.now() - cache.loadedAt < projectSearchCacheMs)
|
|
37933
38277
|
return cache;
|
|
@@ -37950,16 +38294,16 @@ async function localProjectSearchCache(cache) {
|
|
|
37950
38294
|
function mediaTarget(folder, file, lockedTarget) {
|
|
37951
38295
|
if (!file)
|
|
37952
38296
|
return;
|
|
37953
|
-
const rootReal = realpathSync2(
|
|
37954
|
-
const path =
|
|
38297
|
+
const rootReal = realpathSync2(resolve12(folder));
|
|
38298
|
+
const path = resolve12(folder, file);
|
|
37955
38299
|
let pathReal;
|
|
37956
38300
|
try {
|
|
37957
38301
|
pathReal = realpathSync2(path);
|
|
37958
38302
|
} catch {
|
|
37959
38303
|
return;
|
|
37960
38304
|
}
|
|
37961
|
-
const rel =
|
|
37962
|
-
if (rel.startsWith("..") ||
|
|
38305
|
+
const rel = relative11(rootReal, pathReal);
|
|
38306
|
+
if (rel.startsWith("..") || isAbsolute7(rel))
|
|
37963
38307
|
return;
|
|
37964
38308
|
if (lockedTarget && pathReal !== lockedTarget)
|
|
37965
38309
|
return;
|
|
@@ -37998,16 +38342,16 @@ function documentError(response) {
|
|
|
37998
38342
|
function documentTarget(folder, file, lockedTarget) {
|
|
37999
38343
|
if (!file)
|
|
38000
38344
|
return;
|
|
38001
|
-
const rootReal = realpathSync2(
|
|
38002
|
-
const path =
|
|
38345
|
+
const rootReal = realpathSync2(resolve12(folder));
|
|
38346
|
+
const path = resolve12(folder, file);
|
|
38003
38347
|
let pathReal;
|
|
38004
38348
|
try {
|
|
38005
38349
|
pathReal = realpathSync2(path);
|
|
38006
38350
|
} catch {
|
|
38007
38351
|
return;
|
|
38008
38352
|
}
|
|
38009
|
-
const rel =
|
|
38010
|
-
if (rel.startsWith("..") ||
|
|
38353
|
+
const rel = relative11(rootReal, pathReal);
|
|
38354
|
+
if (rel.startsWith("..") || isAbsolute7(rel))
|
|
38011
38355
|
return;
|
|
38012
38356
|
if (lockedTarget && pathReal !== lockedTarget)
|
|
38013
38357
|
return;
|
|
@@ -38035,44 +38379,6 @@ async function projectSearchItem(project, match) {
|
|
|
38035
38379
|
worktree: project.worktree
|
|
38036
38380
|
};
|
|
38037
38381
|
}
|
|
38038
|
-
var defaultPluginRegistryConfig = {
|
|
38039
|
-
plugins: { disable: [], order: [] }
|
|
38040
|
-
};
|
|
38041
|
-
function stringArray(value) {
|
|
38042
|
-
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
38043
|
-
}
|
|
38044
|
-
function parsePluginRegistryConfig(content) {
|
|
38045
|
-
const parsed = JSON.parse(content);
|
|
38046
|
-
const config = parsed && typeof parsed === "object" ? parsed : {};
|
|
38047
|
-
return {
|
|
38048
|
-
plugins: {
|
|
38049
|
-
disable: stringArray(config.plugins?.disable),
|
|
38050
|
-
order: stringArray(config.plugins?.order)
|
|
38051
|
-
}
|
|
38052
|
-
};
|
|
38053
|
-
}
|
|
38054
|
-
async function nearestKennelConfig(folder) {
|
|
38055
|
-
for (let current = folder;; current = dirname9(current)) {
|
|
38056
|
-
const candidate = resolve11(current, "kennel.json");
|
|
38057
|
-
const details = await stat7(candidate).catch(() => {
|
|
38058
|
-
return;
|
|
38059
|
-
});
|
|
38060
|
-
if (details?.isFile())
|
|
38061
|
-
return candidate;
|
|
38062
|
-
if (dirname9(current) === current)
|
|
38063
|
-
return;
|
|
38064
|
-
}
|
|
38065
|
-
}
|
|
38066
|
-
async function folderPluginRegistryConfig(folder) {
|
|
38067
|
-
const path = await nearestKennelConfig(folder);
|
|
38068
|
-
if (!path)
|
|
38069
|
-
return defaultPluginRegistryConfig;
|
|
38070
|
-
try {
|
|
38071
|
-
return parsePluginRegistryConfig(await readFile8(path, "utf8"));
|
|
38072
|
-
} catch {
|
|
38073
|
-
return defaultPluginRegistryConfig;
|
|
38074
|
-
}
|
|
38075
|
-
}
|
|
38076
38382
|
function pluginConfigWarnings(registry, config) {
|
|
38077
38383
|
const ids = unique([...config.plugins.disable, ...config.plugins.order]);
|
|
38078
38384
|
return ids.filter((id) => !registry.has(id)).map((id) => `unknown plugin id "${id}" — not installed in this build`);
|
|
@@ -38123,6 +38429,18 @@ function createFolderApi({
|
|
|
38123
38429
|
const linkedDiscovery = createLinkedFolderDiscovery(linkedFolderLoader, (folder) => discoveryChanged(folder));
|
|
38124
38430
|
const linkedState = (folder) => lockedRoot || isMachineRoot(folder) ? { folders: [], status: "ready" } : linkedDiscovery.read(folder);
|
|
38125
38431
|
const resolveLinkedFolders = (folder) => lockedRoot || isMachineRoot(folder) ? Promise.resolve([]) : linkedDiscovery.folders(folder);
|
|
38432
|
+
const managedNotesFolder = async (folder) => {
|
|
38433
|
+
if (lockedRoot || lockedTarget || isMachineRoot(folder))
|
|
38434
|
+
return;
|
|
38435
|
+
try {
|
|
38436
|
+
if (!(await readRepositoryNotesSettings()).enabled)
|
|
38437
|
+
return;
|
|
38438
|
+
const lookup = await lookupRepositoryNotes(folder);
|
|
38439
|
+
return lookup.status === "registered" ? lookup.notesDirectory : undefined;
|
|
38440
|
+
} catch {
|
|
38441
|
+
return;
|
|
38442
|
+
}
|
|
38443
|
+
};
|
|
38126
38444
|
const favicon = faviconService();
|
|
38127
38445
|
return {
|
|
38128
38446
|
name: "kennel-folder-metadata",
|
|
@@ -38165,7 +38483,7 @@ function createFolderApi({
|
|
|
38165
38483
|
if (!folder)
|
|
38166
38484
|
return;
|
|
38167
38485
|
server.emit({
|
|
38168
|
-
data: { path:
|
|
38486
|
+
data: { path: relative11(folder, path) },
|
|
38169
38487
|
event: "kennel:metadata-changed",
|
|
38170
38488
|
type: "custom"
|
|
38171
38489
|
});
|
|
@@ -38248,7 +38566,7 @@ function createFolderApi({
|
|
|
38248
38566
|
"Content-Type": asset.mediaType,
|
|
38249
38567
|
ETag: `"${hash}"`
|
|
38250
38568
|
});
|
|
38251
|
-
response.end(await
|
|
38569
|
+
response.end(await readFile10(asset.path));
|
|
38252
38570
|
} catch (error) {
|
|
38253
38571
|
response.writeHead(404, {
|
|
38254
38572
|
"Content-Type": "text/plain; charset=utf-8"
|
|
@@ -38313,21 +38631,21 @@ function createFolderApi({
|
|
|
38313
38631
|
}
|
|
38314
38632
|
const folder = folderPath(url.searchParams.get("path"), root, lockedRoot);
|
|
38315
38633
|
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "explorer.exe" : "xdg-open";
|
|
38316
|
-
await new Promise((done, fail) =>
|
|
38634
|
+
await new Promise((done, fail) => execFile5(command, [folder], (error) => error ? fail(error) : done()));
|
|
38317
38635
|
response.writeHead(204);
|
|
38318
38636
|
response.end();
|
|
38319
38637
|
return;
|
|
38320
38638
|
}
|
|
38321
38639
|
if (url.pathname === "/api/documents") {
|
|
38322
38640
|
const folder = folderPath(url.searchParams.get("path"), root, lockedRoot);
|
|
38323
|
-
const target = lockedTarget ? documentTarget(folder,
|
|
38641
|
+
const target = lockedTarget ? documentTarget(folder, relative11(folder, lockedTarget), lockedTarget) : undefined;
|
|
38324
38642
|
const dataset = lockedTarget ? {
|
|
38325
38643
|
root: folder,
|
|
38326
38644
|
documents: target ? [
|
|
38327
38645
|
{
|
|
38328
|
-
path:
|
|
38646
|
+
path: relative11(folder, target.path),
|
|
38329
38647
|
kind: target.kind,
|
|
38330
|
-
content: previewTextKind(target.kind) ? await
|
|
38648
|
+
content: previewTextKind(target.kind) ? await readFile10(target.path, "utf8") : ""
|
|
38331
38649
|
}
|
|
38332
38650
|
] : []
|
|
38333
38651
|
} : await readFolder(folder);
|
|
@@ -38343,7 +38661,7 @@ function createFolderApi({
|
|
|
38343
38661
|
documentError(response);
|
|
38344
38662
|
return;
|
|
38345
38663
|
}
|
|
38346
|
-
const details = await
|
|
38664
|
+
const details = await stat9(target.path);
|
|
38347
38665
|
if (!details.isFile()) {
|
|
38348
38666
|
documentError(response);
|
|
38349
38667
|
return;
|
|
@@ -38355,16 +38673,16 @@ function createFolderApi({
|
|
|
38355
38673
|
return;
|
|
38356
38674
|
}
|
|
38357
38675
|
watch(folder);
|
|
38358
|
-
const content = previewTextKind(target.kind) ? await
|
|
38676
|
+
const content = previewTextKind(target.kind) ? await readFile10(target.path, "utf8") : "";
|
|
38359
38677
|
sendContract(response, url, {
|
|
38360
38678
|
content,
|
|
38361
38679
|
kind: target.kind,
|
|
38362
|
-
path:
|
|
38680
|
+
path: relative11(target.root, target.path),
|
|
38363
38681
|
root: folder,
|
|
38364
38682
|
...previewTextKind(target.kind) ? {} : {
|
|
38365
38683
|
rawUrl: `/api/raw?${new URLSearchParams({
|
|
38366
38684
|
path: folder,
|
|
38367
|
-
file:
|
|
38685
|
+
file: relative11(target.root, target.path)
|
|
38368
38686
|
})}`
|
|
38369
38687
|
}
|
|
38370
38688
|
}, {
|
|
@@ -38384,7 +38702,7 @@ function createFolderApi({
|
|
|
38384
38702
|
documentError(response);
|
|
38385
38703
|
return;
|
|
38386
38704
|
}
|
|
38387
|
-
const details = await
|
|
38705
|
+
const details = await stat9(target.path);
|
|
38388
38706
|
if (!details.isFile()) {
|
|
38389
38707
|
documentError(response);
|
|
38390
38708
|
return;
|
|
@@ -38405,10 +38723,10 @@ function createFolderApi({
|
|
|
38405
38723
|
ETag: etag,
|
|
38406
38724
|
"X-Content-Type-Options": "nosniff",
|
|
38407
38725
|
...download ? {
|
|
38408
|
-
"Content-Disposition": `attachment; filename="${
|
|
38726
|
+
"Content-Disposition": `attachment; filename="${basename7(target.path)}"`
|
|
38409
38727
|
} : {}
|
|
38410
38728
|
});
|
|
38411
|
-
response.end(await
|
|
38729
|
+
response.end(await readFile10(target.path));
|
|
38412
38730
|
} catch (error) {
|
|
38413
38731
|
documentError(response);
|
|
38414
38732
|
}
|
|
@@ -38422,7 +38740,7 @@ function createFolderApi({
|
|
|
38422
38740
|
documentError(response);
|
|
38423
38741
|
return;
|
|
38424
38742
|
}
|
|
38425
|
-
const details = await
|
|
38743
|
+
const details = await stat9(target.path);
|
|
38426
38744
|
if (!details.isFile()) {
|
|
38427
38745
|
documentError(response);
|
|
38428
38746
|
return;
|
|
@@ -38434,7 +38752,7 @@ function createFolderApi({
|
|
|
38434
38752
|
return;
|
|
38435
38753
|
}
|
|
38436
38754
|
watch(folder);
|
|
38437
|
-
const data = await
|
|
38755
|
+
const data = await readFile10(target.path);
|
|
38438
38756
|
const baseHeaders = {
|
|
38439
38757
|
"Accept-Ranges": "bytes",
|
|
38440
38758
|
"Cache-Control": "private, max-age=0, must-revalidate",
|
|
@@ -38488,9 +38806,13 @@ function createFolderApi({
|
|
|
38488
38806
|
if (!lockedRoot && !isMachineRoot(folder) && request.headers["x-kennel-retry-linked"] === "1")
|
|
38489
38807
|
linkedDiscovery.retry(folder);
|
|
38490
38808
|
const discovery = linkedState(folder);
|
|
38809
|
+
const managed = await managedNotesFolder(folder);
|
|
38810
|
+
if (managed)
|
|
38811
|
+
watch(managed);
|
|
38491
38812
|
const result = await plugin.handle({
|
|
38492
38813
|
folder,
|
|
38493
|
-
linkedFolders: discovery.folders
|
|
38814
|
+
linkedFolders: discovery.folders,
|
|
38815
|
+
...managed ? { managedFolders: [managed] } : {}
|
|
38494
38816
|
}, subpath);
|
|
38495
38817
|
if (result.kind === "unavailable") {
|
|
38496
38818
|
response.writeHead(200, {
|
|
@@ -38593,7 +38915,7 @@ function createFolderApi({
|
|
|
38593
38915
|
workspace.breadcrumbs = await workspaceBreadcrumbIcons(workspace.breadcrumbs ?? [], assetFiles);
|
|
38594
38916
|
const files = [
|
|
38595
38917
|
workspace.configPath,
|
|
38596
|
-
|
|
38918
|
+
resolve12(workspace.root, "raggle.json")
|
|
38597
38919
|
].filter((path) => Boolean(path));
|
|
38598
38920
|
for (const path of files)
|
|
38599
38921
|
contexts.add(path);
|
|
@@ -38617,7 +38939,14 @@ function createFolderApi({
|
|
|
38617
38939
|
const machine = isMachineRoot(folder);
|
|
38618
38940
|
if (part === "notes") {
|
|
38619
38941
|
const scope = url.searchParams.get("notesScope");
|
|
38942
|
+
const managed = await managedNotesFolder(folder);
|
|
38620
38943
|
const localFiles = scope === "linked" ? Promise.resolve({ items: [] }) : (machine ? shallowMetadata(folder).then((data) => data.files.items) : listLocalMarkdownFiles({ folder, limit: 200 })).then((files) => notesFiles(folder, files));
|
|
38944
|
+
const managedFiles = async () => {
|
|
38945
|
+
if (scope === "linked" || !managed)
|
|
38946
|
+
return [];
|
|
38947
|
+
watch(managed);
|
|
38948
|
+
return managedNotesFiles(managed);
|
|
38949
|
+
};
|
|
38621
38950
|
const linkedFiles = async () => {
|
|
38622
38951
|
if (scope === "local" || machine || lockedRoot)
|
|
38623
38952
|
return [];
|
|
@@ -38628,20 +38957,25 @@ function createFolderApi({
|
|
|
38628
38957
|
throw new Error("Linked notes discovery failed. Retry to try again.");
|
|
38629
38958
|
return Promise.all(linkedFolders.map(async (linkedFolder) => {
|
|
38630
38959
|
watch(linkedFolder);
|
|
38631
|
-
|
|
38960
|
+
const group = await notesFiles(linkedFolder, await listLocalMarkdownFiles({
|
|
38632
38961
|
folder: linkedFolder,
|
|
38633
38962
|
limit: 200
|
|
38634
38963
|
}), linkedFolder);
|
|
38964
|
+
return {
|
|
38965
|
+
items: await withoutManagedFiles(group.items, managed)
|
|
38966
|
+
};
|
|
38635
38967
|
}));
|
|
38636
38968
|
};
|
|
38637
|
-
const [files, linked] = await Promise.all([
|
|
38969
|
+
const [files, managedItems, linked] = await Promise.all([
|
|
38638
38970
|
localFiles,
|
|
38971
|
+
managedFiles(),
|
|
38639
38972
|
linkedFiles()
|
|
38640
38973
|
]);
|
|
38641
38974
|
sendContract(response, url, {
|
|
38642
38975
|
files: {
|
|
38643
38976
|
items: [
|
|
38644
38977
|
...files.items,
|
|
38978
|
+
...managedItems,
|
|
38645
38979
|
...linked.flatMap((group) => group.items)
|
|
38646
38980
|
]
|
|
38647
38981
|
},
|
|
@@ -38740,26 +39074,26 @@ var contentTypes = {
|
|
|
38740
39074
|
".ttf": "font/ttf"
|
|
38741
39075
|
};
|
|
38742
39076
|
async function sendFile(request, response, directory, pathname, fallback) {
|
|
38743
|
-
const root = await
|
|
39077
|
+
const root = await realpath8(directory);
|
|
38744
39078
|
const inside = (path) => {
|
|
38745
|
-
const local =
|
|
38746
|
-
return local !== ".." && !local.startsWith("../") && !
|
|
39079
|
+
const local = relative12(root, path);
|
|
39080
|
+
return local !== ".." && !local.startsWith("../") && !isAbsolute8(local);
|
|
38747
39081
|
};
|
|
38748
|
-
let target =
|
|
39082
|
+
let target = resolve13(root, `.${decodeURIComponent(pathname)}`);
|
|
38749
39083
|
if (!inside(target)) {
|
|
38750
39084
|
response.writeHead(403);
|
|
38751
39085
|
response.end();
|
|
38752
39086
|
return;
|
|
38753
39087
|
}
|
|
38754
|
-
if (!(await
|
|
39088
|
+
if (!(await stat10(target).catch(() => null))?.isFile()) {
|
|
38755
39089
|
if (!fallback) {
|
|
38756
39090
|
response.writeHead(404);
|
|
38757
39091
|
response.end();
|
|
38758
39092
|
return;
|
|
38759
39093
|
}
|
|
38760
|
-
target =
|
|
39094
|
+
target = resolve13(root, fallback);
|
|
38761
39095
|
}
|
|
38762
|
-
target = await
|
|
39096
|
+
target = await realpath8(target);
|
|
38763
39097
|
if (!inside(target)) {
|
|
38764
39098
|
response.writeHead(403);
|
|
38765
39099
|
response.end();
|
|
@@ -38769,7 +39103,7 @@ async function sendFile(request, response, directory, pathname, fallback) {
|
|
|
38769
39103
|
"Content-Type": contentTypes[extname7(target)] ?? "application/octet-stream",
|
|
38770
39104
|
"X-Content-Type-Options": "nosniff"
|
|
38771
39105
|
});
|
|
38772
|
-
response.end(request.method === "HEAD" ? undefined : await
|
|
39106
|
+
response.end(request.method === "HEAD" ? undefined : await readFile11(target));
|
|
38773
39107
|
}
|
|
38774
39108
|
function createKennelApi(options) {
|
|
38775
39109
|
const remote = options.remoteAccess || !isLoopbackServerHost(options.host);
|