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.
Files changed (90) hide show
  1. package/dist/cli/dispatch.js +10 -0
  2. package/dist/cli/usage.js +9 -0
  3. package/dist/core/runtime.js +531 -197
  4. package/dist/llm/clipboard.d.ts +1 -0
  5. package/dist/llm/clipboard.js +59 -0
  6. package/dist/llm/command.d.ts +1 -0
  7. package/dist/llm/command.js +226 -0
  8. package/dist/llm/worker.d.ts +19 -0
  9. package/dist/llm/worker.js +1131 -0
  10. package/dist/notes/command.d.ts +1 -0
  11. package/dist/notes/command.js +556 -0
  12. package/dist/package/preview.js +554 -219
  13. package/dist/plugins/external.d.ts +6 -0
  14. package/dist/plugins/external.js +16 -0
  15. package/dist/site/server-plugins.d.ts +12 -0
  16. package/dist/site/server-plugins.js +132 -11
  17. package/package.json +4 -2
  18. package/schemas/config.schema.json +11 -0
  19. package/templates/plugin/package.json +1 -0
  20. package/templates/plugin/server.ts +43 -0
  21. package/templates/plugin/tsconfig.json +1 -1
  22. package/web/assets/{abnfDiagram-VRR7QNED-DPZyna8O.js → abnfDiagram-VRR7QNED-CpP1VHKI.js} +1 -1
  23. package/web/assets/{arc-BAcU9y_V.js → arc-kL4AxfvR.js} +1 -1
  24. package/web/assets/{architectureDiagram-ZJ3FMSHR-D5yd7Cag.js → architectureDiagram-ZJ3FMSHR-BWaRktRJ.js} +1 -1
  25. package/web/assets/{ask-tray-PMT6VVXN-C4dJH21Q.js → ask-tray-PMT6VVXN-FqPDpwtw.js} +3 -3
  26. package/web/assets/{blockDiagram-677ZJIJ3-C7LSGyxV.js → blockDiagram-677ZJIJ3-B07uVoPU.js} +1 -1
  27. package/web/assets/{c4Diagram-LMCZKHZV-BW2KfoQr.js → c4Diagram-LMCZKHZV-uvoaQC09.js} +1 -1
  28. package/web/assets/channel-Djmf1Ik1.js +1 -0
  29. package/web/assets/{chunk-2Q5K7J3B-4Og6VAFG.js → chunk-2Q5K7J3B-BH3SaHqk.js} +1 -1
  30. package/web/assets/{chunk-32BRIVSS-BYOowQl5.js → chunk-32BRIVSS-SPD8Eraw.js} +1 -1
  31. package/web/assets/{chunk-5VM5RSS4-C1m-lq64.js → chunk-5VM5RSS4-DNeOgOuZ.js} +1 -1
  32. package/web/assets/{chunk-EX3LRPZG-B2hriRpJ.js → chunk-EX3LRPZG-SJQ-vKGz.js} +1 -1
  33. package/web/assets/{chunk-JWPE2WC7-C6RsnzVA.js → chunk-JWPE2WC7-Dy_9wZ2Y.js} +1 -1
  34. package/web/assets/{chunk-MOJQB5TN-yKbMmUWf.js → chunk-MOJQB5TN-CuIx80Vn.js} +1 -1
  35. package/web/assets/{chunk-RYQCIY6F-BOokm3Qc.js → chunk-RYQCIY6F-DDG61RKR.js} +1 -1
  36. package/web/assets/{chunk-V7JOEXUC-CKW-H5An.js → chunk-V7JOEXUC-7OMVhaoH.js} +1 -1
  37. package/web/assets/{chunk-VR4S4FIN-BU5pFrup.js → chunk-VR4S4FIN-Y-HNaSmV.js} +1 -1
  38. package/web/assets/{chunk-XXDRQBXY-DYtdSJHU.js → chunk-XXDRQBXY-CtG0MPgC.js} +1 -1
  39. package/web/assets/classDiagram-OUVF2IWQ-hlDS9RWf.js +1 -0
  40. package/web/assets/classDiagram-v2-EOCWNBFH-hlDS9RWf.js +1 -0
  41. package/web/assets/{cose-bilkent-JH36ORCC-BZeXtC6E.js → cose-bilkent-JH36ORCC-BZaPMYcl.js} +1 -1
  42. package/web/assets/{cynefin-VYW2F7L2-BSDVyzMI.js → cynefin-VYW2F7L2-DxAlRHA1.js} +1 -1
  43. package/web/assets/{cynefinDiagram-TSTJHNR4-CReoDBYo.js → cynefinDiagram-TSTJHNR4-gjgzmagS.js} +1 -1
  44. package/web/assets/{dagre-VKFMJZFB-CBLLzMxd.js → dagre-VKFMJZFB-DvLPeYuw.js} +1 -1
  45. package/web/assets/{diagram-FQU43EPY-BcGWiZXZ.js → diagram-FQU43EPY-443LFnzG.js} +1 -1
  46. package/web/assets/{diagram-G47NLZAW-BXUYZA9N.js → diagram-G47NLZAW-CqfUewot.js} +1 -1
  47. package/web/assets/{diagram-NH7WQ7WH-BrvrZs7N.js → diagram-NH7WQ7WH-D-FElUGW.js} +1 -1
  48. package/web/assets/{diagram-OA4YK3LP-7lnVcjXL.js → diagram-OA4YK3LP-DbidUAp5.js} +1 -1
  49. package/web/assets/{diagram-WEI45ONY-s58_bMFf.js → diagram-WEI45ONY-CPLeckCZ.js} +1 -1
  50. package/web/assets/{ebnfDiagram-CCIWWBDH-BemWecoD.js → ebnfDiagram-CCIWWBDH-DR0rqZzN.js} +1 -1
  51. package/web/assets/{entity-search-page-G6QEQNXT--sKP5dP3.js → entity-search-page-G6QEQNXT-CnG8d-at.js} +1 -1
  52. package/web/assets/{erDiagram-Q63AITRT-ClVJddch.js → erDiagram-Q63AITRT-DMXv_8Tj.js} +1 -1
  53. package/web/assets/{flowDiagram-23GEKE2U-xzUJu-i_.js → flowDiagram-23GEKE2U-DnbQjZs0.js} +1 -1
  54. package/web/assets/{ganttDiagram-NO4QXBWP-CXQmtF5g.js → ganttDiagram-NO4QXBWP-DBKIrjj8.js} +1 -1
  55. package/web/assets/{gitGraphDiagram-IHSO6WYX-DCdljcLl.js → gitGraphDiagram-IHSO6WYX-BqqxKWT_.js} +1 -1
  56. package/web/assets/{index-CtTx1o2d.js → index-Bd3ghcW-.js} +5 -5
  57. package/web/assets/{index-vjrhIAJn.css → index-Dt8OzQCe.css} +1 -1
  58. package/web/assets/{infoDiagram-FWYZ7A6U-B11SJYSK.js → infoDiagram-FWYZ7A6U-Ba6Fs17y.js} +1 -1
  59. package/web/assets/{ishikawaDiagram-FXEZZL3T-B2CCGlAm.js → ishikawaDiagram-FXEZZL3T-D3BOtFjy.js} +1 -1
  60. package/web/assets/{journeyDiagram-5HDEW3XC-CBPETzOk.js → journeyDiagram-5HDEW3XC-MbzFPQm8.js} +1 -1
  61. package/web/assets/{kanban-definition-HUTT4EX6-unhQ3RPE.js → kanban-definition-HUTT4EX6-CurBmwu1.js} +1 -1
  62. package/web/assets/{linear-DuP_pG51.js → linear-CQIr4JjI.js} +1 -1
  63. package/web/assets/{linked-session-header-M7WY6IGF-DwlNCJlj.js → linked-session-header-M7WY6IGF-BBgN8zTq.js} +1 -1
  64. package/web/assets/{mermaid.core-BlYNzkpn.js → mermaid.core-bE6Vl3bZ.js} +4 -4
  65. package/web/assets/{mindmap-definition-LN4V7U3C-CtKHN6O1.js → mindmap-definition-LN4V7U3C-C1a46OGU.js} +1 -1
  66. package/web/assets/{pegDiagram-2B236MQR-CRyz6XWY.js → pegDiagram-2B236MQR-DZxMOxek.js} +1 -1
  67. package/web/assets/{pieDiagram-ENE6RG2P-BXfkPm3Q.js → pieDiagram-ENE6RG2P-BN0XOKLi.js} +1 -1
  68. package/web/assets/{plugin-host-modules-5NQgs5Cq.js → plugin-host-modules-CaRD5_5q.js} +1 -1
  69. package/web/assets/{project-search-dialog-HJFGW4DG-DPly4OiB.js → project-search-dialog-HJFGW4DG-h_dHeh7g.js} +1 -1
  70. package/web/assets/{project-search-page-NKQEBGPF-Cc6HV7BN.js → project-search-page-NKQEBGPF-2AzjjLEC.js} +1 -1
  71. package/web/assets/{quadrantDiagram-ABIIQ3AL-Drb_qWex.js → quadrantDiagram-ABIIQ3AL-C6IegTP-.js} +1 -1
  72. package/web/assets/{railroadDiagram-RFXS5EU6-DCKk2Pix.js → railroadDiagram-RFXS5EU6-bsjyAl-q.js} +1 -1
  73. package/web/assets/{requirementDiagram-TGXJPOKE-6WwZwcpD.js → requirementDiagram-TGXJPOKE-Cl8EZWy7.js} +1 -1
  74. package/web/assets/{sankeyDiagram-HTMAVEWB-DGGk3WCE.js → sankeyDiagram-HTMAVEWB-DhK4rRux.js} +1 -1
  75. package/web/assets/{selection-popup-532EHNCZ-yPhTrk7L.js → selection-popup-532EHNCZ-E2iQ2gQQ.js} +1 -1
  76. package/web/assets/{sequenceDiagram-DBY2YBRQ-a4tGi3DH.js → sequenceDiagram-DBY2YBRQ-DQTaPSD6.js} +1 -1
  77. package/web/assets/{sizeCapture-X5ZJPWSS-CHehBmy9.js → sizeCapture-X5ZJPWSS-k77bZ1zg.js} +1 -1
  78. package/web/assets/{stateDiagram-2N3HPSRC-Bn9GSWgV.js → stateDiagram-2N3HPSRC-Cv_5WuZJ.js} +1 -1
  79. package/web/assets/{stateDiagram-v2-6OUMAXLB-DI7fdBd7.js → stateDiagram-v2-6OUMAXLB-DJ1Lrskh.js} +1 -1
  80. package/web/assets/{swimlanes-5IMT3BWC-BX0WRzfR.js → swimlanes-5IMT3BWC-gBtAjG3z.js} +2 -2
  81. package/web/assets/swimlanesDiagram-G3AALYLV-DF1K9voG.js +8 -0
  82. package/web/assets/{timeline-definition-FHXFAJF6-9Ni2kIzq.js → timeline-definition-FHXFAJF6-CFjnM1Qq.js} +1 -1
  83. package/web/assets/{vennDiagram-L72KCM5P-rfBSNh3U.js → vennDiagram-L72KCM5P-CeMWrThY.js} +1 -1
  84. package/web/assets/{wardleyDiagram-EHGQE667-CzziImCC.js → wardleyDiagram-EHGQE667-R-ZaAOAI.js} +1 -1
  85. package/web/assets/{xychartDiagram-FW5EYKEG-DTLI4h5R.js → xychartDiagram-FW5EYKEG-BFXoMXiN.js} +1 -1
  86. package/web/index.html +2 -2
  87. package/web/assets/channel-um92dqpm.js +0 -1
  88. package/web/assets/classDiagram-OUVF2IWQ-h7sFEJBg.js +0 -1
  89. package/web/assets/classDiagram-v2-EOCWNBFH-h7sFEJBg.js +0 -1
  90. package/web/assets/swimlanesDiagram-G3AALYLV-OGHC9nqi.js +0 -8
@@ -38305,17 +38305,215 @@ var init_server2 = __esm(() => {
38305
38305
  init_node3();
38306
38306
  });
38307
38307
 
38308
- // packages/api/src/server/entity-folder.ts
38309
- import { readdir as readdir3, readFile as readFile5, realpath as realpath3, stat as stat3 } from "node:fs/promises";
38308
+ // packages/notes/src/store.ts
38309
+ import { execFile as execFile2 } from "node:child_process";
38310
+ import {
38311
+ mkdir as mkdir3,
38312
+ readdir as readdir3,
38313
+ readFile as readFile5,
38314
+ realpath as realpath3,
38315
+ rename as rename2,
38316
+ rm as rm2,
38317
+ stat as stat3,
38318
+ writeFile as writeFile3
38319
+ } from "node:fs/promises";
38310
38320
  import { homedir as homedir2 } from "node:os";
38321
+ import { basename as basename5, dirname as dirname6, join as join2, relative as relative7, resolve as resolve7, sep as sep4 } from "node:path";
38322
+ import { promisify } from "node:util";
38323
+ function kennelConfigHome() {
38324
+ const xdg = process.env.XDG_CONFIG_HOME?.trim();
38325
+ return xdg ? xdg : join2(homedir2(), ".config");
38326
+ }
38327
+ function kennelConfigFile() {
38328
+ return join2(kennelConfigHome(), "kennel", "config.json");
38329
+ }
38330
+ function repositoryNotesRoot() {
38331
+ return join2(kennelConfigHome(), "kennel", "repos");
38332
+ }
38333
+ function isRepositoryId(value) {
38334
+ return typeof value === "string" && UUID.test(value);
38335
+ }
38336
+ function managedNotesDirectory(id) {
38337
+ if (!isRepositoryId(id)) {
38338
+ throw new RepositoryNotesError("invalid-id", `Invalid repository id: ${id}`);
38339
+ }
38340
+ return join2(repositoryNotesRoot(), id);
38341
+ }
38342
+ async function readRepositoryNotesSettings() {
38343
+ const file = kennelConfigFile();
38344
+ let text;
38345
+ try {
38346
+ text = await readFile5(file, "utf8");
38347
+ } catch (error) {
38348
+ if (error.code === "ENOENT") {
38349
+ return { enabled: false };
38350
+ }
38351
+ throw new RepositoryNotesError("config-invalid", `Cannot read ${file}: ${errorMessage(error)}`);
38352
+ }
38353
+ let value;
38354
+ try {
38355
+ value = JSON.parse(text);
38356
+ } catch (error) {
38357
+ throw new RepositoryNotesError("config-invalid", `Invalid JSON in ${file}: ${errorMessage(error)}`);
38358
+ }
38359
+ if (!isRecord(value)) {
38360
+ throw new RepositoryNotesError("config-invalid", `${file} must hold an object`);
38361
+ }
38362
+ const section = value.repositoryNotes;
38363
+ if (section === undefined)
38364
+ return { enabled: false };
38365
+ if (!isRecord(section) || section.enabled !== undefined && typeof section.enabled !== "boolean") {
38366
+ throw new RepositoryNotesError("config-invalid", `${file}: repositoryNotes must be an object with a boolean "enabled" field`);
38367
+ }
38368
+ return { enabled: section.enabled === true };
38369
+ }
38370
+ async function git(args, cwd) {
38371
+ const { stdout } = await run("git", args, {
38372
+ cwd,
38373
+ env: { ...process.env, GIT_OPTIONAL_LOCKS: "0", GIT_TERMINAL_PROMPT: "0" },
38374
+ maxBuffer: 1024 * 1024
38375
+ });
38376
+ return stdout.replace(/\r?\n$/u, "");
38377
+ }
38378
+ function gitExitCode(error) {
38379
+ const code = error.code;
38380
+ return typeof code === "number" ? code : undefined;
38381
+ }
38382
+ async function resolveCheckout(folder) {
38383
+ let start;
38384
+ try {
38385
+ start = await realpath3(resolve7(folder));
38386
+ if (!(await stat3(start)).isDirectory())
38387
+ start = dirname6(start);
38388
+ } catch {
38389
+ throw new RepositoryNotesError("not-git", `Folder does not exist: ${folder}`);
38390
+ }
38391
+ let output;
38392
+ try {
38393
+ output = await git(["rev-parse", "--path-format=absolute", "--show-toplevel", "--git-common-dir"], start);
38394
+ } catch (error) {
38395
+ if (gitExitCode(error) === 128) {
38396
+ throw new RepositoryNotesError("not-git", `Not a Git repository: ${start}. Repository notes need a Git checkout.`);
38397
+ }
38398
+ throw error;
38399
+ }
38400
+ const [top, common] = output.split(/\r?\n/u);
38401
+ if (!top || !common) {
38402
+ throw new RepositoryNotesError("not-git", `Not a Git work tree: ${start}`);
38403
+ }
38404
+ const root = await realpath3(top);
38405
+ const commonDir = await realpath3(common);
38406
+ return { root, commonDir, configFile: join2(commonDir, "config") };
38407
+ }
38408
+ async function readBinding(checkout) {
38409
+ let value;
38410
+ try {
38411
+ value = await git(["config", "--file", checkout.configFile, "--get", REPOSITORY_ID_KEY], checkout.root);
38412
+ } catch (error) {
38413
+ if (gitExitCode(error) === 1)
38414
+ return;
38415
+ throw error;
38416
+ }
38417
+ const id = value.trim();
38418
+ if (!id)
38419
+ return;
38420
+ if (!isRepositoryId(id)) {
38421
+ throw new RepositoryNotesError("invalid-binding", `${checkout.configFile} has an invalid ${REPOSITORY_ID_KEY} value. Remove it or run "kennel notes new-identity".`);
38422
+ }
38423
+ return id;
38424
+ }
38425
+ function isRecord(value) {
38426
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
38427
+ }
38428
+ function errorMessage(error) {
38429
+ return error instanceof Error ? error.message : String(error);
38430
+ }
38431
+ async function lookupRepositoryNotes(folder) {
38432
+ let checkout;
38433
+ try {
38434
+ checkout = await resolveCheckout(folder);
38435
+ } catch (error) {
38436
+ if (error instanceof RepositoryNotesError && error.code === "not-git") {
38437
+ return { status: "not-git", folder };
38438
+ }
38439
+ throw error;
38440
+ }
38441
+ const id = await readBinding(checkout);
38442
+ if (!id)
38443
+ return { status: "unregistered", checkout, root: checkout.root };
38444
+ return {
38445
+ status: "registered",
38446
+ checkout,
38447
+ id,
38448
+ notesDirectory: managedNotesDirectory(id),
38449
+ root: checkout.root
38450
+ };
38451
+ }
38452
+ async function listManagedNotes(directory) {
38453
+ const notes = [];
38454
+ const visit = async (current) => {
38455
+ let entries;
38456
+ try {
38457
+ entries = await readdir3(current, { withFileTypes: true });
38458
+ } catch (error) {
38459
+ if (error.code === "ENOENT")
38460
+ return;
38461
+ throw error;
38462
+ }
38463
+ for (const entry of entries) {
38464
+ if (entry.name.startsWith(".") || entry.isSymbolicLink())
38465
+ continue;
38466
+ const path = join2(current, entry.name);
38467
+ if (entry.isDirectory()) {
38468
+ await visit(path);
38469
+ continue;
38470
+ }
38471
+ if (!entry.isFile())
38472
+ continue;
38473
+ const dot = entry.name.lastIndexOf(".");
38474
+ const extension = dot === -1 ? "" : entry.name.slice(dot).toLowerCase();
38475
+ if (!markdownExtensions.has(extension))
38476
+ continue;
38477
+ const details = await stat3(path);
38478
+ notes.push({
38479
+ activityAt: details.mtimeMs,
38480
+ name: dot === -1 ? entry.name : entry.name.slice(0, dot),
38481
+ path,
38482
+ relativePath: relative7(directory, path).split(sep4).join("/"),
38483
+ size: details.size
38484
+ });
38485
+ }
38486
+ };
38487
+ await visit(directory);
38488
+ return notes.sort((left, right) => right.activityAt - left.activityAt);
38489
+ }
38490
+ var run, REPOSITORY_ID_KEY = "kennel.repoId", UUID, markdownExtensions, caseInsensitiveHosts, RepositoryNotesError;
38491
+ var init_store = __esm(() => {
38492
+ run = promisify(execFile2);
38493
+ UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u;
38494
+ markdownExtensions = new Set([".md", ".markdown"]);
38495
+ caseInsensitiveHosts = new Set(["github.com", "gitlab.com", "bitbucket.org"]);
38496
+ RepositoryNotesError = class RepositoryNotesError extends Error {
38497
+ code;
38498
+ constructor(code, message) {
38499
+ super(message);
38500
+ this.name = "RepositoryNotesError";
38501
+ this.code = code;
38502
+ }
38503
+ };
38504
+ });
38505
+
38506
+ // packages/api/src/server/entity-folder.ts
38507
+ import { readdir as readdir4, readFile as readFile6, realpath as realpath4, stat as stat4 } from "node:fs/promises";
38508
+ import { homedir as homedir3 } from "node:os";
38311
38509
  import {
38312
- basename as basename5,
38313
- dirname as dirname6,
38510
+ basename as basename6,
38511
+ dirname as dirname7,
38314
38512
  extname as extname2,
38315
38513
  isAbsolute as isAbsolute4,
38316
- join as join2,
38317
- relative as relative7,
38318
- resolve as resolve7
38514
+ join as join3,
38515
+ relative as relative8,
38516
+ resolve as resolve8
38319
38517
  } from "node:path";
38320
38518
  function parseMarkdown(source) {
38321
38519
  if (!source.startsWith(`---
@@ -38350,11 +38548,11 @@ function paragraph(body) {
38350
38548
  }
38351
38549
  function configPath() {
38352
38550
  const xdg = process.env.XDG_CONFIG_HOME?.trim();
38353
- return xdg ? join2(xdg, CONFIG_FILE) : join2(homedir2(), ".config", CONFIG_FILE);
38551
+ return xdg ? join3(xdg, CONFIG_FILE) : join3(homedir3(), ".config", CONFIG_FILE);
38354
38552
  }
38355
38553
  async function readConfig(path) {
38356
38554
  try {
38357
- return parseConfig(await readFile5(path, "utf8"));
38555
+ return parseConfig(await readFile6(path, "utf8"));
38358
38556
  } catch (error) {
38359
38557
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT")
38360
38558
  return null;
@@ -38371,6 +38569,8 @@ function parseConfig(source) {
38371
38569
  if (!value || typeof value !== "object" || Array.isArray(value))
38372
38570
  throw new Error("Invalid Kennel entity root config");
38373
38571
  const roots = value.allowedRoots;
38572
+ if (roots === undefined)
38573
+ return {};
38374
38574
  if (!Array.isArray(roots))
38375
38575
  throw new Error("Invalid Kennel entity root config");
38376
38576
  const allowedRoots = roots.map((root) => {
@@ -38382,12 +38582,12 @@ function parseConfig(source) {
38382
38582
  }
38383
38583
  async function allowedRoots(currentWorkspaceRoot) {
38384
38584
  const config = await readConfig(configPath());
38385
- if (config)
38585
+ if (config?.allowedRoots)
38386
38586
  return config.allowedRoots;
38387
38587
  return currentWorkspaceRoot?.trim() ? [currentWorkspaceRoot] : [];
38388
38588
  }
38389
38589
  function inside4(root, path) {
38390
- const local = relative7(root, path);
38590
+ const local = relative8(root, path);
38391
38591
  return local === "" || !local.startsWith("..") && !isAbsolute4(local);
38392
38592
  }
38393
38593
  async function normalizeRoot(rootPath, currentWorkspaceRoot) {
@@ -38395,13 +38595,13 @@ async function normalizeRoot(rootPath, currentWorkspaceRoot) {
38395
38595
  const fallback = currentWorkspaceRoot?.trim();
38396
38596
  if (!trimmed && !fallback)
38397
38597
  return null;
38398
- const requested = resolve7(trimmed || fallback);
38399
- const resolved = await realpath3(requested);
38400
- const info = await stat3(resolved);
38598
+ const requested = resolve8(trimmed || fallback);
38599
+ const resolved = await realpath4(requested);
38600
+ const info = await stat4(resolved);
38401
38601
  if (!info.isDirectory())
38402
38602
  throw new Error(`${resolved} is not a folder`);
38403
38603
  const roots = await allowedRoots(currentWorkspaceRoot);
38404
- const realAllowedRoots = await Promise.all(roots.map((root) => realpath3(root)));
38604
+ const realAllowedRoots = await Promise.all(roots.map((root) => realpath4(root)));
38405
38605
  const allowed = realAllowedRoots.some((root) => inside4(root, resolved));
38406
38606
  if (!allowed)
38407
38607
  throw new Error("Entity root is outside the configured roots");
@@ -38410,11 +38610,11 @@ async function normalizeRoot(rootPath, currentWorkspaceRoot) {
38410
38610
  async function markdownFiles(folderPath) {
38411
38611
  const files = [];
38412
38612
  async function walk(currentPath) {
38413
- const entries = await readdir3(currentPath, { withFileTypes: true });
38613
+ const entries = await readdir4(currentPath, { withFileTypes: true });
38414
38614
  for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
38415
38615
  if (entry.name.startsWith(".") || IGNORED_DIRECTORIES.has(entry.name))
38416
38616
  continue;
38417
- const entryPath = join2(currentPath, entry.name);
38617
+ const entryPath = join3(currentPath, entry.name);
38418
38618
  if (entry.isDirectory()) {
38419
38619
  if (entryPath !== folderPath && await hasIndex(entryPath))
38420
38620
  continue;
@@ -38429,36 +38629,36 @@ async function markdownFiles(folderPath) {
38429
38629
  return files;
38430
38630
  }
38431
38631
  async function hasIndex(folderPath) {
38432
- return stat3(join2(folderPath, "index.md")).then((value) => value.isFile()).catch(() => false);
38632
+ return stat4(join3(folderPath, "index.md")).then((value) => value.isFile()).catch(() => false);
38433
38633
  }
38434
38634
  async function schemaPath(folderPath, rootPath) {
38435
38635
  let current = folderPath;
38436
38636
  while (current === rootPath || current.startsWith(`${rootPath}/`)) {
38437
38637
  for (const name of ["schema.json", "_schema.json"]) {
38438
- const candidate = join2(current, name);
38439
- if (await stat3(candidate).then((value) => value.isFile()).catch(() => false))
38638
+ const candidate = join3(current, name);
38639
+ if (await stat4(candidate).then((value) => value.isFile()).catch(() => false))
38440
38640
  return candidate;
38441
38641
  }
38442
38642
  if (current === rootPath)
38443
38643
  return null;
38444
- current = dirname6(current);
38644
+ current = dirname7(current);
38445
38645
  }
38446
38646
  return null;
38447
38647
  }
38448
38648
  async function readEntity(folderPath, rootPath) {
38449
- const indexPath = join2(folderPath, "index.md");
38450
- const index = parseMarkdown(await readFile5(indexPath, "utf8"));
38649
+ const indexPath = join3(folderPath, "index.md");
38650
+ const index = parseMarkdown(await readFile6(indexPath, "utf8"));
38451
38651
  const files = await markdownFiles(folderPath);
38452
38652
  const documents = await Promise.all(files.map(async (filePath) => {
38453
- const document2 = parseMarkdown(await readFile5(filePath, "utf8"));
38653
+ const document2 = parseMarkdown(await readFile6(filePath, "utf8"));
38454
38654
  return {
38455
38655
  content: document2.body,
38456
- path: relative7(folderPath, filePath),
38457
- title: stringValue(document2.frontmatter.title) ?? heading(document2.body) ?? basename5(filePath, extname2(filePath))
38656
+ path: relative8(folderPath, filePath),
38657
+ title: stringValue(document2.frontmatter.title) ?? heading(document2.body) ?? basename6(filePath, extname2(filePath))
38458
38658
  };
38459
38659
  }));
38460
- const title = stringValue(index.frontmatter.title) ?? heading(index.body) ?? basename5(folderPath);
38461
- const relativePath = relative7(rootPath, folderPath) || ".";
38660
+ const title = stringValue(index.frontmatter.title) ?? heading(index.body) ?? basename6(folderPath);
38661
+ const relativePath = relative8(rootPath, folderPath) || ".";
38462
38662
  return {
38463
38663
  aliases: stringValues(index.frontmatter.aliases),
38464
38664
  content: [
@@ -38486,11 +38686,11 @@ async function discover(rootPath) {
38486
38686
  async function walk(folderPath) {
38487
38687
  if (await hasIndex(folderPath))
38488
38688
  folders.push(folderPath);
38489
- const entries = await readdir3(folderPath, { withFileTypes: true });
38689
+ const entries = await readdir4(folderPath, { withFileTypes: true });
38490
38690
  for (const entry of entries) {
38491
38691
  if (!entry.isDirectory() || entry.name.startsWith(".") || IGNORED_DIRECTORIES.has(entry.name))
38492
38692
  continue;
38493
- await walk(join2(folderPath, entry.name));
38693
+ await walk(join3(folderPath, entry.name));
38494
38694
  }
38495
38695
  }
38496
38696
  await walk(rootPath);
@@ -38529,7 +38729,7 @@ async function loadEntityWorkspace(root, query, selectedPath, currentWorkspaceRo
38529
38729
  var CONFIG_FILE, IGNORED_DIRECTORIES;
38530
38730
  var init_entity_folder = __esm(() => {
38531
38731
  init_dist3();
38532
- CONFIG_FILE = join2("kennel", "config.json");
38732
+ CONFIG_FILE = join3("kennel", "config.json");
38533
38733
  IGNORED_DIRECTORIES = new Set([
38534
38734
  ".git",
38535
38735
  ".gtl",
@@ -38549,20 +38749,20 @@ function replaceSelection(content, start, end, replacement) {
38549
38749
  // packages/api/src/server/api/document-edit.ts
38550
38750
  import { randomUUID as randomUUID3 } from "node:crypto";
38551
38751
  import {
38552
- readFile as readFile6,
38553
- realpath as realpath4,
38554
- rename as rename2,
38555
- stat as stat4,
38752
+ readFile as readFile7,
38753
+ realpath as realpath5,
38754
+ rename as rename3,
38755
+ stat as stat5,
38556
38756
  unlink as unlink2,
38557
- writeFile as writeFile3
38757
+ writeFile as writeFile4
38558
38758
  } from "node:fs/promises";
38559
38759
  import {
38560
- dirname as dirname7,
38760
+ dirname as dirname8,
38561
38761
  extname as extname3,
38562
38762
  isAbsolute as isAbsolute5,
38563
- relative as relative8,
38564
- resolve as resolve8,
38565
- sep as sep4
38763
+ relative as relative9,
38764
+ resolve as resolve9,
38765
+ sep as sep5
38566
38766
  } from "node:path";
38567
38767
  async function editDocument(folder, input) {
38568
38768
  const { path, original, start, end, replacement } = input;
@@ -38570,10 +38770,10 @@ async function editDocument(folder, input) {
38570
38770
  throw apiError("bad-request", "Invalid document edit.");
38571
38771
  if (![".md", ".markdown"].includes(extname3(path).toLowerCase()))
38572
38772
  throw apiError("bad-request", "Only Markdown notes can be edited.");
38573
- const root = await realpath4(folder);
38574
- const target = await realpath4(resolve8(root, path));
38575
- const local = relative8(root, target);
38576
- if (!local || local === ".." || local.startsWith(`..${sep4}`) || isAbsolute5(local))
38773
+ const root = await realpath5(folder);
38774
+ const target = await realpath5(resolve9(root, path));
38775
+ const local = relative9(root, target);
38776
+ if (!local || local === ".." || local.startsWith(`..${sep5}`) || isAbsolute5(local))
38577
38777
  throw apiError("forbidden", "The document must be inside its folder.");
38578
38778
  let content;
38579
38779
  try {
@@ -38583,20 +38783,20 @@ async function editDocument(folder, input) {
38583
38783
  }
38584
38784
  const previous = pending.get(target) ?? Promise.resolve();
38585
38785
  const operation = previous.catch(() => {}).then(async () => {
38586
- const details = await stat4(target);
38786
+ const details = await stat5(target);
38587
38787
  if (!details.isFile() || details.nlink !== 1)
38588
38788
  throw apiError("forbidden", "Only regular, unlinked files can be edited.");
38589
- if (await readFile6(target, "utf8") !== original)
38789
+ if (await readFile7(target, "utf8") !== original)
38590
38790
  throw apiError("conflict", "This note changed on disk. Close this preview, reload the note, and try again.");
38591
- const temporary = resolve8(dirname7(target), `.kennel-edit-${randomUUID3()}.tmp`);
38791
+ const temporary = resolve9(dirname8(target), `.kennel-edit-${randomUUID3()}.tmp`);
38592
38792
  try {
38593
- await writeFile3(temporary, content, {
38793
+ await writeFile4(temporary, content, {
38594
38794
  flag: "wx",
38595
38795
  mode: details.mode & 511
38596
38796
  });
38597
- if (await readFile6(target, "utf8") !== original)
38797
+ if (await readFile7(target, "utf8") !== original)
38598
38798
  throw apiError("conflict", "This note changed on disk. Reload before applying an edit.");
38599
- await rename2(temporary, target);
38799
+ await rename3(temporary, target);
38600
38800
  } finally {
38601
38801
  await unlink2(temporary).catch(() => {});
38602
38802
  }
@@ -38624,7 +38824,7 @@ var init_document_edit = __esm(() => {
38624
38824
  throw apiError("bad-request", "Invalid document edit.");
38625
38825
  const input = parsed.data;
38626
38826
  const folder = context.authorize(input.folder);
38627
- if (context.lockedFile && (typeof input.path !== "string" || await realpath4(resolve8(folder, input.path)) !== context.lockedFile))
38827
+ if (context.lockedFile && (typeof input.path !== "string" || await realpath5(resolve9(folder, input.path)) !== context.lockedFile))
38628
38828
  throw apiError("forbidden", "Only the selected document can be edited.");
38629
38829
  await editDocument(folder, input);
38630
38830
  jsonResponse(response, 200, routes.documentEdit.response.parse({ saved: true }));
@@ -38633,8 +38833,8 @@ var init_document_edit = __esm(() => {
38633
38833
  });
38634
38834
 
38635
38835
  // packages/api/src/server/api/create-agent-file.ts
38636
- import { writeFile as writeFile4 } from "node:fs/promises";
38637
- import { resolve as resolve9 } from "node:path";
38836
+ import { writeFile as writeFile5 } from "node:fs/promises";
38837
+ import { resolve as resolve10 } from "node:path";
38638
38838
  var createAgentFileRoute;
38639
38839
  var init_create_agent_file = __esm(() => {
38640
38840
  init_endpoint();
@@ -38648,9 +38848,9 @@ var init_create_agent_file = __esm(() => {
38648
38848
  } catch (error) {
38649
38849
  throw apiError("not-found", error instanceof Error ? error.message : "Folder not found");
38650
38850
  }
38651
- const target = resolve9(folder, "AGENTS.md");
38851
+ const target = resolve10(folder, "AGENTS.md");
38652
38852
  try {
38653
- await writeFile4(target, "", { flag: "wx" });
38853
+ await writeFile5(target, "", { flag: "wx" });
38654
38854
  } catch (error) {
38655
38855
  if (error && typeof error === "object" && "code" in error && error.code === "EEXIST") {
38656
38856
  throw apiError("conflict", "AGENTS.md already exists in this folder");
@@ -38663,7 +38863,7 @@ var init_create_agent_file = __esm(() => {
38663
38863
  });
38664
38864
 
38665
38865
  // packages/api/src/server/api/open-path.ts
38666
- import { execFile as execFile2 } from "node:child_process";
38866
+ import { execFile as execFile3 } from "node:child_process";
38667
38867
  var openPathRoute;
38668
38868
  var init_open_path = __esm(() => {
38669
38869
  init_endpoint();
@@ -38677,7 +38877,7 @@ var init_open_path = __esm(() => {
38677
38877
  throw apiError("not-found", error instanceof Error ? error.message : "Path not found");
38678
38878
  }
38679
38879
  await new Promise((done, fail) => {
38680
- execFile2("/usr/bin/open", [target], (error) => {
38880
+ execFile3("/usr/bin/open", [target], (error) => {
38681
38881
  if (error)
38682
38882
  fail(apiError("failed", "Could not open path"));
38683
38883
  else
@@ -38696,11 +38896,11 @@ var init_favicon2 = __esm(() => {
38696
38896
  });
38697
38897
 
38698
38898
  // packages/api/src/server/folder-favicon.ts
38699
- import { readFile as readFile7 } from "node:fs/promises";
38700
- import { join as join3 } from "node:path";
38899
+ import { readFile as readFile8 } from "node:fs/promises";
38900
+ import { join as join4 } from "node:path";
38701
38901
  async function folderWebsite(folder) {
38702
38902
  for (const name of ["AGENTS.md", "README.md"]) {
38703
- const content = await readFile7(join3(folder, name), "utf8").catch(() => "");
38903
+ const content = await readFile8(join4(folder, name), "utf8").catch(() => "");
38704
38904
  const entry = markdownMetadata(content).entries.find(([key]) => key === "website");
38705
38905
  if (entry)
38706
38906
  return websiteHost(entry[1]);
@@ -38714,7 +38914,7 @@ var init_folder_favicon = __esm(() => {
38714
38914
 
38715
38915
  // packages/api/src/server/folder-icons.ts
38716
38916
  import { createHash as createHash4 } from "node:crypto";
38717
- import { readFile as readFile8, stat as stat5 } from "node:fs/promises";
38917
+ import { readFile as readFile9, stat as stat6 } from "node:fs/promises";
38718
38918
  import { extname as extname4 } from "node:path";
38719
38919
  import { discoverFolderIcon } from "@raggle-ai/local";
38720
38920
  function assetKey(value) {
@@ -38733,13 +38933,13 @@ function iconUrl(folder, hash, mediaType) {
38733
38933
  async function folderIcon(folder, assets, assetFiles, fallback) {
38734
38934
  const icon = discoverFolderIcon(folder);
38735
38935
  const mediaType = icon ? iconTypes.get(extname4(icon).toLowerCase()) : null;
38736
- const details = icon && mediaType ? await stat5(icon).catch(() => {
38936
+ const details = icon && mediaType ? await stat6(icon).catch(() => {
38737
38937
  return;
38738
38938
  }) : undefined;
38739
38939
  if (!icon || !mediaType || !details?.size || details.size > maxIconBytes2) {
38740
38940
  return fallback ? await fallback(folder, assets) : undefined;
38741
38941
  }
38742
- const data = await readFile8(icon);
38942
+ const data = await readFile9(icon);
38743
38943
  const hash = assetKey(data);
38744
38944
  const key = `local:${hash}`;
38745
38945
  assetFiles.set(hash, { mediaType, path: icon });
@@ -38758,12 +38958,12 @@ async function searchIconDataUrl(icon) {
38758
38958
  const mediaType = searchIconTypes.get(extname4(icon).toLowerCase());
38759
38959
  if (!mediaType)
38760
38960
  return;
38761
- const details = await stat5(icon).catch(() => {
38961
+ const details = await stat6(icon).catch(() => {
38762
38962
  return;
38763
38963
  });
38764
38964
  if (!details?.size || details.size > maxSearchIconBytes)
38765
38965
  return;
38766
- return `data:${mediaType};base64,${(await readFile8(icon)).toString("base64")}`;
38966
+ return `data:${mediaType};base64,${(await readFile9(icon)).toString("base64")}`;
38767
38967
  }
38768
38968
  async function faviconAsset(folderName, assets) {
38769
38969
  if (process.env.KENNEL_EXTERNAL_ICONS !== "1")
@@ -38847,53 +39047,128 @@ var init_manifest = __esm(() => {
38847
39047
  });
38848
39048
 
38849
39049
  // packages/notes/src/server.ts
38850
- import { readdir as readdir4, stat as stat6 } from "node:fs/promises";
38851
- import { basename as basename6, extname as extname5, relative as relative9, resolve as resolve10, sep as sep5 } from "node:path";
39050
+ import { readdir as readdir5, realpath as realpath6, stat as stat7 } from "node:fs/promises";
39051
+ import {
39052
+ basename as basename7,
39053
+ extname as extname5,
39054
+ isAbsolute as isAbsolute6,
39055
+ relative as relative10,
39056
+ resolve as resolve11,
39057
+ sep as sep6
39058
+ } from "node:path";
38852
39059
  async function listMarkdownFiles(folder) {
38853
39060
  const files = [];
38854
39061
  const visit = async (current) => {
38855
- const entries = await readdir4(current, { withFileTypes: true }).catch(() => []);
39062
+ const entries = await readdir5(current, { withFileTypes: true }).catch(() => []);
38856
39063
  for (const entry of entries) {
38857
39064
  if (entry.name === "node_modules" || entry.name.startsWith("."))
38858
39065
  continue;
38859
- const path = resolve10(current, entry.name);
39066
+ const path = resolve11(current, entry.name);
38860
39067
  if (entry.isDirectory()) {
38861
39068
  await visit(path);
38862
39069
  continue;
38863
39070
  }
38864
- if (!entry.isFile() || !markdownExtensions.has(extname5(entry.name))) {
39071
+ if (!entry.isFile() || !markdownExtensions2.has(extname5(entry.name))) {
38865
39072
  continue;
38866
39073
  }
38867
- const details = await stat6(path).catch(() => {
39074
+ const details = await stat7(path).catch(() => {
38868
39075
  return;
38869
39076
  });
38870
39077
  files.push({
38871
39078
  activityAt: details?.mtimeMs ?? 0,
38872
- name: basename6(entry.name, extname5(entry.name)),
38873
- relativePath: relative9(folder, path).split(sep5).join("/")
39079
+ name: basename7(entry.name, extname5(entry.name)),
39080
+ relativePath: relative10(folder, path).split(sep6).join("/")
38874
39081
  });
38875
39082
  }
38876
39083
  };
38877
39084
  await visit(folder);
38878
39085
  return files.sort((left, right) => right.activityAt - left.activityAt);
38879
39086
  }
38880
- async function loadNotes(folder, linkedFolders = []) {
39087
+ function inside5(path, root) {
39088
+ const local = relative10(root, path);
39089
+ return local === "" || !local.startsWith("..") && !isAbsolute6(local);
39090
+ }
39091
+ async function withoutManaged(groups, managedRoots) {
39092
+ if (!managedRoots.length)
39093
+ return groups;
39094
+ const roots = await Promise.all(managedRoots.map(canonical));
39095
+ return Promise.all(groups.map(async (group) => {
39096
+ if (roots.includes(await canonical(group.folder))) {
39097
+ return { ...group, files: [] };
39098
+ }
39099
+ const files = await Promise.all(group.files.map(async (file) => {
39100
+ const real = await canonical(resolve11(group.folder, file.relativePath));
39101
+ return roots.some((root) => inside5(real, root)) ? undefined : file;
39102
+ }));
39103
+ return { ...group, files: files.filter((file) => file !== undefined) };
39104
+ }));
39105
+ }
39106
+ async function loadNotes(folder, linkedFolders = [], managedFolders = []) {
38881
39107
  const localFiles = await listMarkdownFiles(folder);
38882
- const linked = await Promise.all(linkedFolders.map(async (linkedFolder) => ({
38883
- files: await listMarkdownFiles(linkedFolder),
38884
- folder: linkedFolder
39108
+ const listGroups = (folders) => Promise.all(folders.map(async (source) => ({
39109
+ files: await listMarkdownFiles(source),
39110
+ folder: source
38885
39111
  })));
39112
+ const [managed, linked] = await Promise.all([
39113
+ listGroups(managedFolders),
39114
+ listGroups(linkedFolders).then((groups) => withoutManaged(groups, managedFolders))
39115
+ ]);
38886
39116
  return {
38887
39117
  files: {
38888
39118
  items: [
38889
39119
  ...localFiles,
38890
- ...linked.flatMap((group) => group.files.map((file) => ({ ...file, folder: group.folder })))
39120
+ ...[...managed, ...linked].flatMap((group) => group.files.map((file) => ({ ...file, folder: group.folder })))
38891
39121
  ]
38892
39122
  },
38893
39123
  folder,
38894
39124
  status: "available"
38895
39125
  };
38896
39126
  }
39127
+ function codeSpan(text) {
39128
+ const escaped = text.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/gu, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
39129
+ const longest = Math.max(0, ...(escaped.match(/`+/g) ?? []).map((run) => run.length));
39130
+ const fence = "`".repeat(longest + 1);
39131
+ const pad = escaped.startsWith("`") || escaped.endsWith("`") ? " " : "";
39132
+ return `${fence}${pad}${escaped}${pad}${fence}`;
39133
+ }
39134
+ async function notesLlmSections({
39135
+ folder,
39136
+ linkedFolders = [],
39137
+ managedFolders = [],
39138
+ signal
39139
+ }) {
39140
+ const groups = [];
39141
+ const notes = [];
39142
+ const seen = new Set;
39143
+ for (const source of [folder, ...managedFolders, ...linkedFolders]) {
39144
+ signal.throwIfAborted();
39145
+ const identity = await canonical(source);
39146
+ if (seen.has(identity))
39147
+ continue;
39148
+ seen.add(identity);
39149
+ const files = await listMarkdownFiles(source);
39150
+ const paths = files.map((file) => resolve11(source, file.relativePath));
39151
+ groups.push({ folder: source, paths });
39152
+ files.forEach((file, index) => notes.push({ activityAt: file.activityAt, path: paths[index] }));
39153
+ }
39154
+ if (notes.length === 0)
39155
+ return [];
39156
+ const recent = notes.sort((left, right) => right.activityAt - left.activityAt || (left.path < right.path ? -1 : left.path > right.path ? 1 : 0)).slice(0, llmNoteLimit);
39157
+ return [
39158
+ {
39159
+ id: "notes",
39160
+ title: "Markdown notes",
39161
+ lines: [
39162
+ ...groups.filter((group) => group.paths.length > 0).map((group) => `- Source: ${codeSpan(group.folder)} (${group.paths.length} ${group.paths.length === 1 ? "note" : "notes"})`),
39163
+ `- Total notes: ${notes.length}`,
39164
+ "",
39165
+ `Most recently changed (${recent.length} of ${notes.length}):`,
39166
+ "",
39167
+ ...recent.map((note) => `- ${codeSpan(note.path)}`)
39168
+ ]
39169
+ }
39170
+ ];
39171
+ }
38897
39172
  function createNotesPlugin() {
38898
39173
  return {
38899
39174
  ...notesManifest,
@@ -38905,22 +39180,28 @@ function createNotesPlugin() {
38905
39180
  },
38906
39181
  async handle({
38907
39182
  folder,
38908
- linkedFolders
39183
+ linkedFolders,
39184
+ managedFolders
38909
39185
  }) {
38910
- return { data: await loadNotes(folder, linkedFolders), kind: "available" };
38911
- }
39186
+ return {
39187
+ data: await loadNotes(folder, linkedFolders, managedFolders),
39188
+ kind: "available"
39189
+ };
39190
+ },
39191
+ llm: notesLlmSections
38912
39192
  };
38913
39193
  }
38914
- var markdownExtensions;
39194
+ var markdownExtensions2, llmNoteLimit = 20, canonical = (path) => realpath6(path).catch(() => path);
38915
39195
  var init_server3 = __esm(() => {
38916
39196
  init_manifest();
38917
- markdownExtensions = new Set([".md", ".markdown"]);
39197
+ markdownExtensions2 = new Set([".md", ".markdown"]);
38918
39198
  });
38919
39199
 
38920
39200
  // packages/api/src/server/plugins.ts
38921
39201
  import { pathToFileURL } from "node:url";
38922
39202
  import { existsSync as existsSync4 } from "node:fs";
38923
- import { dirname as dirname8, resolve as resolve11, sep as sep6 } from "node:path";
39203
+ import { mkdir as mkdir4, readFile as readFile10, stat as stat8, writeFile as writeFile6 } from "node:fs/promises";
39204
+ import { dirname as dirname9, resolve as resolve12, sep as sep7 } from "node:path";
38924
39205
  function configuredFolderServerRegistry(registry, resolvedConfig) {
38925
39206
  const disabled = new Set(resolvedConfig.plugins.disable);
38926
39207
  const orderedIds = resolvedConfig.plugins.order.filter((id) => registry.has(id) && !disabled.has(id));
@@ -38930,6 +39211,41 @@ function configuredFolderServerRegistry(registry, resolvedConfig) {
38930
39211
  ];
38931
39212
  return new Map(ids.map((id) => [id, registry.get(id)]));
38932
39213
  }
39214
+ function stringArray(value) {
39215
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
39216
+ }
39217
+ function parsePluginRegistryConfig(content) {
39218
+ const parsed = JSON.parse(content);
39219
+ const config = parsed && typeof parsed === "object" ? parsed : {};
39220
+ return {
39221
+ plugins: {
39222
+ disable: stringArray(config.plugins?.disable),
39223
+ order: stringArray(config.plugins?.order)
39224
+ }
39225
+ };
39226
+ }
39227
+ async function nearestKennelConfig(folder) {
39228
+ for (let current = folder;; current = dirname9(current)) {
39229
+ const candidate = resolve12(current, "kennel.json");
39230
+ const details = await stat8(candidate).catch(() => {
39231
+ return;
39232
+ });
39233
+ if (details?.isFile())
39234
+ return candidate;
39235
+ if (dirname9(current) === current)
39236
+ return;
39237
+ }
39238
+ }
39239
+ async function folderPluginRegistryConfig(folder) {
39240
+ const path = await nearestKennelConfig(folder);
39241
+ if (!path)
39242
+ return defaultPluginRegistryConfig;
39243
+ try {
39244
+ return parsePluginRegistryConfig(await readFile10(path, "utf8"));
39245
+ } catch {
39246
+ return defaultPluginRegistryConfig;
39247
+ }
39248
+ }
38933
39249
  function createFolderServerPlugins() {
38934
39250
  return [createNotesPlugin()];
38935
39251
  }
@@ -38973,7 +39289,7 @@ async function folderServerRegistryAsync() {
38973
39289
  ...server,
38974
39290
  async detect(context) {
38975
39291
  const detection = await server.detect(context);
38976
- if (detection.status === "unavailable" && existsSync4(resolve11(context.folder, entry.trigger))) {
39292
+ if (detection.status === "unavailable" && existsSync4(resolve12(context.folder, entry.trigger))) {
38977
39293
  return { status: "available" };
38978
39294
  }
38979
39295
  return detection;
@@ -39010,15 +39326,19 @@ async function externalApiHandlers() {
39010
39326
  }
39011
39327
  return handlers;
39012
39328
  }
39329
+ var defaultPluginRegistryConfig;
39013
39330
  var init_plugins = __esm(() => {
39014
39331
  init_server3();
39332
+ defaultPluginRegistryConfig = {
39333
+ plugins: { disable: [], order: [] }
39334
+ };
39015
39335
  });
39016
39336
 
39017
39337
  // packages/api/src/server/linked-folder-discovery.ts
39018
- import { execFile as execFile3 } from "node:child_process";
39338
+ import { execFile as execFile4 } from "node:child_process";
39019
39339
  import { existsSync as existsSync5 } from "node:fs";
39020
39340
  import { createRequire as createRequire2 } from "node:module";
39021
- import { dirname as dirname9, join as join4 } from "node:path";
39341
+ import { dirname as dirname10, join as join5 } from "node:path";
39022
39342
  import { fileURLToPath as fileURLToPath2 } from "node:url";
39023
39343
  function resolveLocalModule(from = import.meta.url) {
39024
39344
  const require2 = createRequire2(from);
@@ -39026,19 +39346,19 @@ function resolveLocalModule(from = import.meta.url) {
39026
39346
  try {
39027
39347
  candidates.push(require2.resolve("@raggle-ai/local"));
39028
39348
  } catch {}
39029
- let directory = dirname9(fileURLToPath2(from));
39030
- while (dirname9(directory) !== directory) {
39031
- if (existsSync5(join4(directory, "raggle.json"))) {
39349
+ let directory = dirname10(fileURLToPath2(from));
39350
+ while (dirname10(directory) !== directory) {
39351
+ if (existsSync5(join5(directory, "raggle.json"))) {
39032
39352
  for (const candidate of [
39033
- join4(dirname9(directory), "raggle-ai-scout", "packages", "local"),
39034
- join4(directory, "..", "..", "raggle-ai-scout", "packages", "local")
39353
+ join5(dirname10(directory), "raggle-ai-scout", "packages", "local"),
39354
+ join5(directory, "..", "..", "raggle-ai-scout", "packages", "local")
39035
39355
  ]) {
39036
39356
  if (!candidates.includes(candidate))
39037
39357
  candidates.push(candidate);
39038
39358
  }
39039
39359
  break;
39040
39360
  }
39041
- directory = dirname9(directory);
39361
+ directory = dirname10(directory);
39042
39362
  }
39043
39363
  for (const candidate of candidates) {
39044
39364
  try {
@@ -39047,11 +39367,11 @@ function resolveLocalModule(from = import.meta.url) {
39047
39367
  return candidate;
39048
39368
  } catch {}
39049
39369
  }
39050
- return candidates[0] ?? join4(dirname9(fileURLToPath2(from)), "..", "..", "..", "raggle-ai-scout", "packages", "local");
39370
+ return candidates[0] ?? join5(dirname10(fileURLToPath2(from)), "..", "..", "..", "raggle-ai-scout", "packages", "local");
39051
39371
  }
39052
39372
  function discoverLinkedFolders(folder, signal, modulePath = resolveLocalModule()) {
39053
39373
  return new Promise((resolve, reject) => {
39054
- execFile3(process.execPath, ["-e", script, modulePath, folder], {
39374
+ execFile4(process.execPath, ["-e", script, modulePath, folder], {
39055
39375
  signal,
39056
39376
  timeout: 60000,
39057
39377
  maxBuffer: 8 * 1024 * 1024,
@@ -39147,10 +39467,10 @@ const { stat } = require('node:fs/promises');
39147
39467
  var init_linked_folder_discovery = () => {};
39148
39468
 
39149
39469
  // packages/api/src/server/project-discovery.ts
39150
- import { execFile as execFile4 } from "node:child_process";
39470
+ import { execFile as execFile5 } from "node:child_process";
39151
39471
  function discoverProjects(folder, signal) {
39152
39472
  return new Promise((resolve, reject) => {
39153
- execFile4(process.execPath, ["-e", script2, resolveLocalModule(), folder], {
39473
+ execFile5(process.execPath, ["-e", script2, resolveLocalModule(), folder], {
39154
39474
  signal,
39155
39475
  timeout: 60000,
39156
39476
  maxBuffer: 8 * 1024 * 1024,
@@ -39237,24 +39557,24 @@ var init_project_discovery = __esm(() => {
39237
39557
  // packages/api/src/server/folder-metadata-plugin.ts
39238
39558
  import { createHash as createHash5 } from "node:crypto";
39239
39559
  import { realpathSync as realpathSync2 } from "node:fs";
39240
- import { readdir as readdir5, readFile as readFile9, stat as stat7 } from "node:fs/promises";
39241
- import { homedir as homedir3 } from "node:os";
39560
+ import { readdir as readdir6, readFile as readFile11, realpath as realpath7, stat as stat9 } from "node:fs/promises";
39561
+ import { homedir as homedir4 } from "node:os";
39242
39562
  import {
39243
- basename as basename7,
39244
- dirname as dirname10,
39563
+ basename as basename8,
39564
+ dirname as dirname11,
39245
39565
  extname as extname6,
39246
- isAbsolute as isAbsolute6,
39247
- relative as relative11,
39248
- resolve as resolve12,
39249
- sep as sep7
39566
+ isAbsolute as isAbsolute7,
39567
+ relative as relative12,
39568
+ resolve as resolve13,
39569
+ sep as sep8
39250
39570
  } from "node:path";
39251
- import { execFile as execFile5 } from "node:child_process";
39571
+ import { execFile as execFile6 } from "node:child_process";
39252
39572
  import local from "@raggle-ai/local";
39253
39573
  function metadataFile(path, root) {
39254
- const candidate = relative11(root, path);
39255
- if (!candidate || candidate.startsWith("..") || isAbsolute6(candidate))
39574
+ const candidate = relative12(root, path);
39575
+ if (!candidate || candidate.startsWith("..") || isAbsolute7(candidate))
39256
39576
  return false;
39257
- const parts = candidate.split(sep7);
39577
+ const parts = candidate.split(sep8);
39258
39578
  if (parts.some((part) => part === "node_modules"))
39259
39579
  return false;
39260
39580
  return metadataExtensions.has(extname6(candidate).toLowerCase());
@@ -39265,11 +39585,11 @@ async function configuredAiInputModule(folder) {
39265
39585
  return typeof module === "string" && module.trim() ? module.trim() : null;
39266
39586
  }
39267
39587
  function folderPath(value, root, lockedRoot) {
39268
- const folder = realpathSync2(resolve12(value ?? root));
39588
+ const folder = realpathSync2(resolve13(value ?? root));
39269
39589
  if (!lockedRoot)
39270
39590
  return folder;
39271
- const path = relative11(lockedRoot, folder);
39272
- if (!path || !path.startsWith("..") && !isAbsolute6(path))
39591
+ const path = relative12(lockedRoot, folder);
39592
+ if (!path || !path.startsWith("..") && !isAbsolute7(path))
39273
39593
  return folder;
39274
39594
  throw new Error(`Folder is outside the viewer root: ${folder}`);
39275
39595
  }
@@ -39333,15 +39653,15 @@ function unique(values) {
39333
39653
  return [...new Set(values)];
39334
39654
  }
39335
39655
  function isMachineRoot(folder) {
39336
- return folder === homedir3() || dirname10(folder) === folder;
39656
+ return folder === homedir4() || dirname11(folder) === folder;
39337
39657
  }
39338
39658
  async function shallowMetadata(folder) {
39339
- const entries = await readdir5(folder, { withFileTypes: true });
39659
+ const entries = await readdir6(folder, { withFileTypes: true });
39340
39660
  const visible = entries.filter((entry) => !entry.name.startsWith(".") && entry.name !== "node_modules");
39341
39661
  const files = await Promise.all(visible.filter((entry) => entry.isFile() && [".markdown", ".md"].includes(extname6(entry.name).toLowerCase())).map(async (entry) => {
39342
- const path = resolve12(folder, entry.name);
39343
- const details = await stat7(path);
39344
- const content = await readFile9(path, "utf8").catch(() => "");
39662
+ const path = resolve13(folder, entry.name);
39663
+ const details = await stat9(path);
39664
+ const content = await readFile11(path, "utf8").catch(() => "");
39345
39665
  const title = markdownMetadata(content).title;
39346
39666
  return {
39347
39667
  activityAt: details.mtimeMs,
@@ -39354,10 +39674,10 @@ async function shallowMetadata(folder) {
39354
39674
  };
39355
39675
  }));
39356
39676
  const projects = visible.filter((entry) => entry.isDirectory()).map((entry) => ({
39357
- id: createHash5("sha1").update(resolve12(folder, entry.name)).digest("hex"),
39677
+ id: createHash5("sha1").update(resolve13(folder, entry.name)).digest("hex"),
39358
39678
  name: entry.name,
39359
39679
  relativePath: entry.name,
39360
- worktree: resolve12(folder, entry.name)
39680
+ worktree: resolve13(folder, entry.name)
39361
39681
  })).sort((left, right) => left.name.localeCompare(right.name));
39362
39682
  return {
39363
39683
  changes: { items: [] },
@@ -39383,8 +39703,8 @@ async function loadFolderData(folder) {
39383
39703
  items: projects.map((project) => ({
39384
39704
  id: project.id,
39385
39705
  worktree: project.worktree,
39386
- name: project.name ?? basename7(project.worktree),
39387
- relativePath: relative11(folder, project.worktree)
39706
+ name: project.name ?? basename8(project.worktree),
39707
+ relativePath: relative12(folder, project.worktree)
39388
39708
  }))
39389
39709
  }
39390
39710
  };
@@ -39392,10 +39712,10 @@ async function loadFolderData(folder) {
39392
39712
  async function notesFiles(folder, files, origin) {
39393
39713
  return {
39394
39714
  items: await Promise.all(files.map(async (file) => {
39395
- const path = file.path ?? (file.relativePath ? resolve12(folder, file.relativePath) : undefined);
39715
+ const path = file.path ?? (file.relativePath ? resolve13(folder, file.relativePath) : undefined);
39396
39716
  if (!path)
39397
39717
  return origin ? { ...file, folder: origin } : file;
39398
- const content = [".markdown", ".md"].includes(extname6(path).toLowerCase()) ? await readFile9(path, "utf8").catch(() => "") : "";
39718
+ const content = [".markdown", ".md"].includes(extname6(path).toLowerCase()) ? await readFile11(path, "utf8").catch(() => "") : "";
39399
39719
  const title = content ? markdownMetadata(content).title : undefined;
39400
39720
  return {
39401
39721
  ...file,
@@ -39406,6 +39726,30 @@ async function notesFiles(folder, files, origin) {
39406
39726
  }))
39407
39727
  };
39408
39728
  }
39729
+ async function managedNotesFiles(directory) {
39730
+ const notes = await listManagedNotes(directory);
39731
+ return Promise.all(notes.map(async (note) => {
39732
+ const content = await readFile11(note.path, "utf8").catch(() => "");
39733
+ const title = content ? markdownMetadata(content).title : undefined;
39734
+ return {
39735
+ ...note,
39736
+ folder: directory,
39737
+ name: title ?? note.name,
39738
+ openHref: openPathHref(note.path)
39739
+ };
39740
+ }));
39741
+ }
39742
+ function insideFolder(path, root) {
39743
+ const local = relative12(root, path);
39744
+ return local === "" || !local.startsWith("..") && !isAbsolute7(local);
39745
+ }
39746
+ async function withoutManagedFiles(items, managedRoot) {
39747
+ if (!managedRoot)
39748
+ return items;
39749
+ const root = await canonicalPath(managedRoot);
39750
+ const outside = await Promise.all(items.map(async (item) => !item.path || !insideFolder(await canonicalPath(item.path), root)));
39751
+ return items.filter((_, index) => outside[index]);
39752
+ }
39409
39753
  async function localProjectSearchCache(cache) {
39410
39754
  if (cache && Date.now() - cache.loadedAt < projectSearchCacheMs)
39411
39755
  return cache;
@@ -39428,16 +39772,16 @@ async function localProjectSearchCache(cache) {
39428
39772
  function mediaTarget(folder, file, lockedTarget) {
39429
39773
  if (!file)
39430
39774
  return;
39431
- const rootReal = realpathSync2(resolve12(folder));
39432
- const path = resolve12(folder, file);
39775
+ const rootReal = realpathSync2(resolve13(folder));
39776
+ const path = resolve13(folder, file);
39433
39777
  let pathReal;
39434
39778
  try {
39435
39779
  pathReal = realpathSync2(path);
39436
39780
  } catch {
39437
39781
  return;
39438
39782
  }
39439
- const rel = relative11(rootReal, pathReal);
39440
- if (rel.startsWith("..") || isAbsolute6(rel))
39783
+ const rel = relative12(rootReal, pathReal);
39784
+ if (rel.startsWith("..") || isAbsolute7(rel))
39441
39785
  return;
39442
39786
  if (lockedTarget && pathReal !== lockedTarget)
39443
39787
  return;
@@ -39476,16 +39820,16 @@ function documentError(response) {
39476
39820
  function documentTarget(folder, file, lockedTarget) {
39477
39821
  if (!file)
39478
39822
  return;
39479
- const rootReal = realpathSync2(resolve12(folder));
39480
- const path = resolve12(folder, file);
39823
+ const rootReal = realpathSync2(resolve13(folder));
39824
+ const path = resolve13(folder, file);
39481
39825
  let pathReal;
39482
39826
  try {
39483
39827
  pathReal = realpathSync2(path);
39484
39828
  } catch {
39485
39829
  return;
39486
39830
  }
39487
- const rel = relative11(rootReal, pathReal);
39488
- if (rel.startsWith("..") || isAbsolute6(rel))
39831
+ const rel = relative12(rootReal, pathReal);
39832
+ if (rel.startsWith("..") || isAbsolute7(rel))
39489
39833
  return;
39490
39834
  if (lockedTarget && pathReal !== lockedTarget)
39491
39835
  return;
@@ -39513,41 +39857,6 @@ async function projectSearchItem(project, match) {
39513
39857
  worktree: project.worktree
39514
39858
  };
39515
39859
  }
39516
- function stringArray(value) {
39517
- return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
39518
- }
39519
- function parsePluginRegistryConfig(content) {
39520
- const parsed = JSON.parse(content);
39521
- const config = parsed && typeof parsed === "object" ? parsed : {};
39522
- return {
39523
- plugins: {
39524
- disable: stringArray(config.plugins?.disable),
39525
- order: stringArray(config.plugins?.order)
39526
- }
39527
- };
39528
- }
39529
- async function nearestKennelConfig(folder) {
39530
- for (let current = folder;; current = dirname10(current)) {
39531
- const candidate = resolve12(current, "kennel.json");
39532
- const details = await stat7(candidate).catch(() => {
39533
- return;
39534
- });
39535
- if (details?.isFile())
39536
- return candidate;
39537
- if (dirname10(current) === current)
39538
- return;
39539
- }
39540
- }
39541
- async function folderPluginRegistryConfig(folder) {
39542
- const path = await nearestKennelConfig(folder);
39543
- if (!path)
39544
- return defaultPluginRegistryConfig;
39545
- try {
39546
- return parsePluginRegistryConfig(await readFile9(path, "utf8"));
39547
- } catch {
39548
- return defaultPluginRegistryConfig;
39549
- }
39550
- }
39551
39860
  function pluginConfigWarnings(registry, config) {
39552
39861
  const ids = unique([...config.plugins.disable, ...config.plugins.order]);
39553
39862
  return ids.filter((id) => !registry.has(id)).map((id) => `unknown plugin id "${id}" — not installed in this build`);
@@ -39598,6 +39907,18 @@ function createFolderApi({
39598
39907
  const linkedDiscovery = createLinkedFolderDiscovery(linkedFolderLoader, (folder) => discoveryChanged(folder));
39599
39908
  const linkedState = (folder) => lockedRoot || isMachineRoot(folder) ? { folders: [], status: "ready" } : linkedDiscovery.read(folder);
39600
39909
  const resolveLinkedFolders = (folder) => lockedRoot || isMachineRoot(folder) ? Promise.resolve([]) : linkedDiscovery.folders(folder);
39910
+ const managedNotesFolder = async (folder) => {
39911
+ if (lockedRoot || lockedTarget || isMachineRoot(folder))
39912
+ return;
39913
+ try {
39914
+ if (!(await readRepositoryNotesSettings()).enabled)
39915
+ return;
39916
+ const lookup = await lookupRepositoryNotes(folder);
39917
+ return lookup.status === "registered" ? lookup.notesDirectory : undefined;
39918
+ } catch {
39919
+ return;
39920
+ }
39921
+ };
39601
39922
  const favicon = faviconService();
39602
39923
  return {
39603
39924
  name: "kennel-folder-metadata",
@@ -39640,7 +39961,7 @@ function createFolderApi({
39640
39961
  if (!folder)
39641
39962
  return;
39642
39963
  server.emit({
39643
- data: { path: relative11(folder, path) },
39964
+ data: { path: relative12(folder, path) },
39644
39965
  event: "kennel:metadata-changed",
39645
39966
  type: "custom"
39646
39967
  });
@@ -39723,7 +40044,7 @@ function createFolderApi({
39723
40044
  "Content-Type": asset.mediaType,
39724
40045
  ETag: `"${hash}"`
39725
40046
  });
39726
- response.end(await readFile9(asset.path));
40047
+ response.end(await readFile11(asset.path));
39727
40048
  } catch (error) {
39728
40049
  response.writeHead(404, {
39729
40050
  "Content-Type": "text/plain; charset=utf-8"
@@ -39788,21 +40109,21 @@ function createFolderApi({
39788
40109
  }
39789
40110
  const folder = folderPath(url.searchParams.get("path"), root, lockedRoot);
39790
40111
  const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "explorer.exe" : "xdg-open";
39791
- await new Promise((done, fail) => execFile5(command, [folder], (error) => error ? fail(error) : done()));
40112
+ await new Promise((done, fail) => execFile6(command, [folder], (error) => error ? fail(error) : done()));
39792
40113
  response.writeHead(204);
39793
40114
  response.end();
39794
40115
  return;
39795
40116
  }
39796
40117
  if (url.pathname === "/api/documents") {
39797
40118
  const folder = folderPath(url.searchParams.get("path"), root, lockedRoot);
39798
- const target = lockedTarget ? documentTarget(folder, relative11(folder, lockedTarget), lockedTarget) : undefined;
40119
+ const target = lockedTarget ? documentTarget(folder, relative12(folder, lockedTarget), lockedTarget) : undefined;
39799
40120
  const dataset = lockedTarget ? {
39800
40121
  root: folder,
39801
40122
  documents: target ? [
39802
40123
  {
39803
- path: relative11(folder, target.path),
40124
+ path: relative12(folder, target.path),
39804
40125
  kind: target.kind,
39805
- content: previewTextKind(target.kind) ? await readFile9(target.path, "utf8") : ""
40126
+ content: previewTextKind(target.kind) ? await readFile11(target.path, "utf8") : ""
39806
40127
  }
39807
40128
  ] : []
39808
40129
  } : await readFolder(folder);
@@ -39818,7 +40139,7 @@ function createFolderApi({
39818
40139
  documentError(response);
39819
40140
  return;
39820
40141
  }
39821
- const details = await stat7(target.path);
40142
+ const details = await stat9(target.path);
39822
40143
  if (!details.isFile()) {
39823
40144
  documentError(response);
39824
40145
  return;
@@ -39830,16 +40151,16 @@ function createFolderApi({
39830
40151
  return;
39831
40152
  }
39832
40153
  watch(folder);
39833
- const content = previewTextKind(target.kind) ? await readFile9(target.path, "utf8") : "";
40154
+ const content = previewTextKind(target.kind) ? await readFile11(target.path, "utf8") : "";
39834
40155
  sendContract(response, url, {
39835
40156
  content,
39836
40157
  kind: target.kind,
39837
- path: relative11(target.root, target.path),
40158
+ path: relative12(target.root, target.path),
39838
40159
  root: folder,
39839
40160
  ...previewTextKind(target.kind) ? {} : {
39840
40161
  rawUrl: `/api/raw?${new URLSearchParams({
39841
40162
  path: folder,
39842
- file: relative11(target.root, target.path)
40163
+ file: relative12(target.root, target.path)
39843
40164
  })}`
39844
40165
  }
39845
40166
  }, {
@@ -39859,7 +40180,7 @@ function createFolderApi({
39859
40180
  documentError(response);
39860
40181
  return;
39861
40182
  }
39862
- const details = await stat7(target.path);
40183
+ const details = await stat9(target.path);
39863
40184
  if (!details.isFile()) {
39864
40185
  documentError(response);
39865
40186
  return;
@@ -39880,10 +40201,10 @@ function createFolderApi({
39880
40201
  ETag: etag,
39881
40202
  "X-Content-Type-Options": "nosniff",
39882
40203
  ...download ? {
39883
- "Content-Disposition": `attachment; filename="${basename7(target.path)}"`
40204
+ "Content-Disposition": `attachment; filename="${basename8(target.path)}"`
39884
40205
  } : {}
39885
40206
  });
39886
- response.end(await readFile9(target.path));
40207
+ response.end(await readFile11(target.path));
39887
40208
  } catch (error) {
39888
40209
  documentError(response);
39889
40210
  }
@@ -39897,7 +40218,7 @@ function createFolderApi({
39897
40218
  documentError(response);
39898
40219
  return;
39899
40220
  }
39900
- const details = await stat7(target.path);
40221
+ const details = await stat9(target.path);
39901
40222
  if (!details.isFile()) {
39902
40223
  documentError(response);
39903
40224
  return;
@@ -39909,7 +40230,7 @@ function createFolderApi({
39909
40230
  return;
39910
40231
  }
39911
40232
  watch(folder);
39912
- const data = await readFile9(target.path);
40233
+ const data = await readFile11(target.path);
39913
40234
  const baseHeaders = {
39914
40235
  "Accept-Ranges": "bytes",
39915
40236
  "Cache-Control": "private, max-age=0, must-revalidate",
@@ -39963,9 +40284,13 @@ function createFolderApi({
39963
40284
  if (!lockedRoot && !isMachineRoot(folder) && request.headers["x-kennel-retry-linked"] === "1")
39964
40285
  linkedDiscovery.retry(folder);
39965
40286
  const discovery = linkedState(folder);
40287
+ const managed = await managedNotesFolder(folder);
40288
+ if (managed)
40289
+ watch(managed);
39966
40290
  const result = await plugin.handle({
39967
40291
  folder,
39968
- linkedFolders: discovery.folders
40292
+ linkedFolders: discovery.folders,
40293
+ ...managed ? { managedFolders: [managed] } : {}
39969
40294
  }, subpath);
39970
40295
  if (result.kind === "unavailable") {
39971
40296
  response.writeHead(200, {
@@ -40068,7 +40393,7 @@ function createFolderApi({
40068
40393
  workspace.breadcrumbs = await workspaceBreadcrumbIcons(workspace.breadcrumbs ?? [], assetFiles);
40069
40394
  const files = [
40070
40395
  workspace.configPath,
40071
- resolve12(workspace.root, "raggle.json")
40396
+ resolve13(workspace.root, "raggle.json")
40072
40397
  ].filter((path) => Boolean(path));
40073
40398
  for (const path of files)
40074
40399
  contexts.add(path);
@@ -40092,7 +40417,14 @@ function createFolderApi({
40092
40417
  const machine = isMachineRoot(folder);
40093
40418
  if (part === "notes") {
40094
40419
  const scope = url.searchParams.get("notesScope");
40420
+ const managed = await managedNotesFolder(folder);
40095
40421
  const localFiles = scope === "linked" ? Promise.resolve({ items: [] }) : (machine ? shallowMetadata(folder).then((data) => data.files.items) : listLocalMarkdownFiles({ folder, limit: 200 })).then((files) => notesFiles(folder, files));
40422
+ const managedFiles = async () => {
40423
+ if (scope === "linked" || !managed)
40424
+ return [];
40425
+ watch(managed);
40426
+ return managedNotesFiles(managed);
40427
+ };
40096
40428
  const linkedFiles = async () => {
40097
40429
  if (scope === "local" || machine || lockedRoot)
40098
40430
  return [];
@@ -40103,20 +40435,25 @@ function createFolderApi({
40103
40435
  throw new Error("Linked notes discovery failed. Retry to try again.");
40104
40436
  return Promise.all(linkedFolders.map(async (linkedFolder) => {
40105
40437
  watch(linkedFolder);
40106
- return notesFiles(linkedFolder, await listLocalMarkdownFiles({
40438
+ const group = await notesFiles(linkedFolder, await listLocalMarkdownFiles({
40107
40439
  folder: linkedFolder,
40108
40440
  limit: 200
40109
40441
  }), linkedFolder);
40442
+ return {
40443
+ items: await withoutManagedFiles(group.items, managed)
40444
+ };
40110
40445
  }));
40111
40446
  };
40112
- const [files, linked] = await Promise.all([
40447
+ const [files, managedItems, linked] = await Promise.all([
40113
40448
  localFiles,
40449
+ managedFiles(),
40114
40450
  linkedFiles()
40115
40451
  ]);
40116
40452
  sendContract(response, url, {
40117
40453
  files: {
40118
40454
  items: [
40119
40455
  ...files.items,
40456
+ ...managedItems,
40120
40457
  ...linked.flatMap((group) => group.items)
40121
40458
  ]
40122
40459
  },
@@ -40197,7 +40534,7 @@ function createFolderApi({
40197
40534
  }
40198
40535
  };
40199
40536
  }
40200
- var applyProjectFavorites, buildProjectCatalog, buildProjectSearchIndex, catalogProjectToLocalProject, discoverProjectIcon, discoverProjectsUnderFolder, listLocalMarkdownFiles, projectRepositoryAccessory, projectSubtitle, projectTitle, readRaggleUserSettings, gitRemoteUrl, githubRepositoryPath, repositoryDisplayName, searchProjectIndex, sortProjectList, projectSearchCacheMs = 5000, metadataExtensions, loopbackServerHosts, defaultPluginRegistryConfig;
40537
+ var applyProjectFavorites, buildProjectCatalog, buildProjectSearchIndex, catalogProjectToLocalProject, discoverProjectIcon, discoverProjectsUnderFolder, listLocalMarkdownFiles, projectRepositoryAccessory, projectSubtitle, projectTitle, readRaggleUserSettings, gitRemoteUrl, githubRepositoryPath, repositoryDisplayName, searchProjectIndex, sortProjectList, projectSearchCacheMs = 5000, metadataExtensions, loopbackServerHosts, canonicalPath = (path) => realpath7(path).catch(() => path);
40201
40538
  var init_folder_metadata_plugin = __esm(() => {
40202
40539
  init_contracts();
40203
40540
  init_markdown();
@@ -40207,6 +40544,7 @@ var init_folder_metadata_plugin = __esm(() => {
40207
40544
  init_server();
40208
40545
  init_workspace2();
40209
40546
  init_server2();
40547
+ init_store();
40210
40548
  init_entity_folder();
40211
40549
  init_document_edit();
40212
40550
  init_create_agent_file();
@@ -40254,38 +40592,35 @@ var init_folder_metadata_plugin = __esm(() => {
40254
40592
  undefined,
40255
40593
  false
40256
40594
  ]);
40257
- defaultPluginRegistryConfig = {
40258
- plugins: { disable: [], order: [] }
40259
- };
40260
40595
  });
40261
40596
 
40262
40597
  // packages/api/src/server/index.ts
40263
40598
  import {
40264
40599
  createServer
40265
40600
  } from "node:http";
40266
- import { readFile as readFile10, realpath as realpath5, stat as stat8 } from "node:fs/promises";
40267
- import { extname as extname7, isAbsolute as isAbsolute7, relative as relative12, resolve as resolve13 } from "node:path";
40601
+ import { readFile as readFile12, realpath as realpath8, stat as stat10 } from "node:fs/promises";
40602
+ import { extname as extname7, isAbsolute as isAbsolute8, relative as relative13, resolve as resolve14 } from "node:path";
40268
40603
  async function sendFile(request, response, directory, pathname, fallback) {
40269
- const root = await realpath5(directory);
40604
+ const root = await realpath8(directory);
40270
40605
  const inside = (path) => {
40271
- const local = relative12(root, path);
40272
- return local !== ".." && !local.startsWith("../") && !isAbsolute7(local);
40606
+ const local = relative13(root, path);
40607
+ return local !== ".." && !local.startsWith("../") && !isAbsolute8(local);
40273
40608
  };
40274
- let target = resolve13(root, `.${decodeURIComponent(pathname)}`);
40609
+ let target = resolve14(root, `.${decodeURIComponent(pathname)}`);
40275
40610
  if (!inside(target)) {
40276
40611
  response.writeHead(403);
40277
40612
  response.end();
40278
40613
  return;
40279
40614
  }
40280
- if (!(await stat8(target).catch(() => null))?.isFile()) {
40615
+ if (!(await stat10(target).catch(() => null))?.isFile()) {
40281
40616
  if (!fallback) {
40282
40617
  response.writeHead(404);
40283
40618
  response.end();
40284
40619
  return;
40285
40620
  }
40286
- target = resolve13(root, fallback);
40621
+ target = resolve14(root, fallback);
40287
40622
  }
40288
- target = await realpath5(target);
40623
+ target = await realpath8(target);
40289
40624
  if (!inside(target)) {
40290
40625
  response.writeHead(403);
40291
40626
  response.end();
@@ -40295,7 +40630,7 @@ async function sendFile(request, response, directory, pathname, fallback) {
40295
40630
  "Content-Type": contentTypes[extname7(target)] ?? "application/octet-stream",
40296
40631
  "X-Content-Type-Options": "nosniff"
40297
40632
  });
40298
- response.end(request.method === "HEAD" ? undefined : await readFile10(target));
40633
+ response.end(request.method === "HEAD" ? undefined : await readFile12(target));
40299
40634
  }
40300
40635
  function createKennelApi(options) {
40301
40636
  const remote = options.remoteAccess || !isLoopbackServerHost(options.host);
@@ -40471,7 +40806,7 @@ var init_server4 = __esm(() => {
40471
40806
  import { execFileSync as execFileSync3 } from "node:child_process";
40472
40807
  import { existsSync as existsSync6, realpathSync as realpathSync3, statSync as statSync3 } from "node:fs";
40473
40808
  import { createServer as createServer2 } from "node:net";
40474
- import { dirname as dirname11, isAbsolute as isAbsolute8, relative as relative13, resolve as resolve14 } from "node:path";
40809
+ import { dirname as dirname12, isAbsolute as isAbsolute9, relative as relative14, resolve as resolve15 } from "node:path";
40475
40810
  import { fileURLToPath as fileURLToPath3 } from "node:url";
40476
40811
  function isLoopbackHost(host) {
40477
40812
  return loopbackHosts.has(host) || host.startsWith("127.");
@@ -40550,7 +40885,7 @@ function splitViewerArgs(input) {
40550
40885
  }
40551
40886
  if (arg.startsWith("-"))
40552
40887
  throw new Error(`Unknown viewer option: ${arg}`);
40553
- const candidate = resolve14(arg);
40888
+ const candidate = resolve15(arg);
40554
40889
  const isDirectory = existsSync6(candidate) && statSync3(candidate).isDirectory();
40555
40890
  if (isDirectory) {
40556
40891
  if (hasRoot)
@@ -40561,7 +40896,7 @@ function splitViewerArgs(input) {
40561
40896
  }
40562
40897
  if (hasRoot)
40563
40898
  throw new Error("kennel accepts one folder");
40564
- options.root = dirname11(candidate);
40899
+ options.root = dirname12(candidate);
40565
40900
  options.target = candidate;
40566
40901
  hasRoot = true;
40567
40902
  }
@@ -40602,7 +40937,7 @@ function popUrl(host, port, options) {
40602
40937
  url.pathname = `/folder/${encodeBase64Url2(options.root)}/notes`;
40603
40938
  return url.href;
40604
40939
  }
40605
- url.pathname = `/folder/${encodeBase64Url2(options.root)}/document/${encodeBase64Url2(relative13(options.root, options.target))}`;
40940
+ url.pathname = `/folder/${encodeBase64Url2(options.root)}/document/${encodeBase64Url2(relative14(options.root, options.target))}`;
40606
40941
  if (options.watch)
40607
40942
  url.searchParams.set("watch", "1");
40608
40943
  return url.href;
@@ -40613,8 +40948,8 @@ function viewerPortWasExplicit(input) {
40613
40948
  function isSameOrDescendant(parent, child) {
40614
40949
  const parentReal = realpathSync3(parent);
40615
40950
  const childReal = realpathSync3(child);
40616
- const childRelative = relative13(parentReal, childReal);
40617
- return childRelative === "" || !childRelative.startsWith("..") && !isAbsolute8(childRelative);
40951
+ const childRelative = relative14(parentReal, childReal);
40952
+ return childRelative === "" || !childRelative.startsWith("..") && !isAbsolute9(childRelative);
40618
40953
  }
40619
40954
  async function runningViewer(url) {
40620
40955
  try {
@@ -40675,8 +41010,8 @@ async function viewer(input) {
40675
41010
  await hotViewer(options);
40676
41011
  return;
40677
41012
  }
40678
- const web = resolve14(dirname11(fileURLToPath3(import.meta.url)), "../../web");
40679
- if (!existsSync6(resolve14(web, "index.html"))) {
41013
+ const web = resolve15(dirname12(fileURLToPath3(import.meta.url)), "../../web");
41014
+ if (!existsSync6(resolve15(web, "index.html"))) {
40680
41015
  throw new Error(`Kennel web app not found. Run the CLI build first.`);
40681
41016
  }
40682
41017
  const remote = options.tailscale || !isLoopbackHost(options.host);
@@ -40815,14 +41150,14 @@ async function startProtectedShare(handle) {
40815
41150
  var init_protected_share = () => {};
40816
41151
 
40817
41152
  // packages/preview/src/tailscale-share.ts
40818
- import { execFile as execFile6 } from "node:child_process";
40819
- import { promisify } from "node:util";
41153
+ import { execFile as execFile7 } from "node:child_process";
41154
+ import { promisify as promisify2 } from "node:util";
40820
41155
  async function serveConfig() {
40821
- const { stdout } = await run("tailscale", ["serve", "status", "--json"]);
41156
+ const { stdout } = await run2("tailscale", ["serve", "status", "--json"]);
40822
41157
  return JSON.parse(stdout);
40823
41158
  }
40824
41159
  async function tailscaleHostname() {
40825
- const { stdout } = await run("tailscale", ["status", "--json"]);
41160
+ const { stdout } = await run2("tailscale", ["status", "--json"]);
40826
41161
  const status = JSON.parse(stdout);
40827
41162
  const host = status.Self?.DNSName?.replace(/\.$/, "");
40828
41163
  if (status.BackendState !== "Running" || !host || !host.endsWith(".ts.net")) {
@@ -40841,11 +41176,11 @@ async function startTailscaleProxy(host, port) {
40841
41176
  const current = await serveConfig();
40842
41177
  const handlers = current.Web?.[authority]?.Handlers;
40843
41178
  if (handlers?.["/"]?.Proxy === target && Object.keys(handlers).length === 1) {
40844
- await run("tailscale", ["serve", `--https=${port}`, "off"]);
41179
+ await run2("tailscale", ["serve", `--https=${port}`, "off"]);
40845
41180
  }
40846
41181
  };
40847
41182
  try {
40848
- await run("tailscale", ["serve", "--bg", "--yes", `--https=${port}`, target], { timeout: 30000 });
41183
+ await run2("tailscale", ["serve", "--bg", "--yes", `--https=${port}`, target], { timeout: 30000 });
40849
41184
  const current = await serveConfig();
40850
41185
  if (current.AllowFunnel?.[authority] || current.Web?.[authority]?.Handlers?.["/"]?.Proxy !== target) {
40851
41186
  throw new Error("Tailscale did not create the expected private HTTPS proxy");
@@ -40856,13 +41191,13 @@ async function startTailscaleProxy(host, port) {
40856
41191
  throw error;
40857
41192
  }
40858
41193
  }
40859
- var run;
41194
+ var run2;
40860
41195
  var init_tailscale_share = __esm(() => {
40861
- run = promisify(execFile6);
41196
+ run2 = promisify2(execFile7);
40862
41197
  });
40863
41198
 
40864
41199
  // packages/preview/src/share.ts
40865
- import { dirname as dirname12, resolve as resolve15 } from "node:path";
41200
+ import { dirname as dirname13, resolve as resolve16 } from "node:path";
40866
41201
  import { existsSync as existsSync7, realpathSync as realpathSync4 } from "node:fs";
40867
41202
  import { fileURLToPath as fileURLToPath4 } from "node:url";
40868
41203
  function splitShareArgs(input) {
@@ -40914,7 +41249,7 @@ function splitShareArgs(input) {
40914
41249
  if (folder && !existsSync7(folder)) {
40915
41250
  throw new Error(`Folder not found: ${folder}`);
40916
41251
  }
40917
- options.folder = folder ? resolve15(folder) : undefined;
41252
+ options.folder = folder ? resolve16(folder) : undefined;
40918
41253
  return options;
40919
41254
  }
40920
41255
  async function runningViewerRoot(port = plainViewerPort) {
@@ -40935,8 +41270,8 @@ async function runningViewerRoot(port = plainViewerPort) {
40935
41270
  }
40936
41271
  async function startPreviewShare(target) {
40937
41272
  const root = realpathSync4(target.root);
40938
- const web = resolve15(dirname12(fileURLToPath4(import.meta.url)), "../../web");
40939
- if (!existsSync7(resolve15(web, "index.html"))) {
41273
+ const web = resolve16(dirname13(fileURLToPath4(import.meta.url)), "../../web");
41274
+ if (!existsSync7(resolve16(web, "index.html"))) {
40940
41275
  throw new Error("Kennel web app not found. Run the CLI build first.");
40941
41276
  }
40942
41277
  const host = await tailscaleHostname();