kennel 0.2.12 → 0.2.13

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 (164) hide show
  1. package/dist/cli/src/plugins/external.js +19 -7
  2. package/dist/cli/src/site/server-plugins.d.ts +1 -4
  3. package/dist/core/api-client.js +3 -11
  4. package/dist/core/runtime.js +84 -695
  5. package/dist/package/preview.js +99 -729
  6. package/dist/plugins/external.js +19 -7
  7. package/dist/site/server-plugins.d.ts +1 -4
  8. package/dist/site/server-plugins.js +28 -537
  9. package/kernel/apps/web/package.json +0 -1
  10. package/kernel/apps/web/src/app.css +0 -1
  11. package/kernel/apps/web/src/app.tsx +7 -24
  12. package/kernel/apps/web/src/folder-plugin-view.tsx +6 -3
  13. package/kernel/apps/web/src/plugins.ts +2 -14
  14. package/kernel/apps/web/vite.config.ts +16 -3
  15. package/kernel/packages/api/openapi.json +85 -224
  16. package/kernel/packages/api/package.json +0 -1
  17. package/kernel/packages/api/src/contracts/schemas.ts +1 -9
  18. package/kernel/packages/api/src/server/folder-metadata-plugin.ts +9 -151
  19. package/kernel/packages/api/src/server/plugins.ts +11 -26
  20. package/kernel/packages/api/src/shared/viewer-route.ts +3 -5
  21. package/kernel/packages/api-client/src/generated.d.ts +16 -53
  22. package/kernel/packages/markdown/README.md +2 -2
  23. package/kernel/packages/notes/package.json +1 -0
  24. package/kernel/packages/notes/src/client.tsx +0 -1
  25. package/kernel/packages/notes/src/manifest.ts +7 -0
  26. package/kernel/packages/notes/src/pages/notes-page.tsx +1 -1
  27. package/kernel/packages/notes/src/ui/folder-notes-list.tsx +52 -7
  28. package/kernel/packages/plugins/SKILL.md +3 -2
  29. package/kernel/packages/plugins/package.json +5 -1
  30. package/kernel/packages/plugins/src/hooks.ts +24 -0
  31. package/kernel/packages/plugins/src/index.ts +1 -7
  32. package/kernel/packages/plugins/src/page.ts +0 -7
  33. package/kernel/packages/plugins/src/types.ts +8 -0
  34. package/kernel/packages/plugins/tsconfig.json +1 -1
  35. package/kernel/packages/plugins/tsup.config.ts +1 -0
  36. package/kernel/packages/ui/src/components/folder-activity.tsx +16 -98
  37. package/kernel/packages/ui/src/components/folder-landing.tsx +4 -0
  38. package/kernel/packages/ui/src/components/pagination.tsx +88 -2
  39. package/kernel/packages/ui/src/components/plugin-icon.tsx +19 -0
  40. package/kernel/packages/ui/src/lib/folder-activity.ts +4 -17
  41. package/kernel/packages/ui/src/styles/folder-activity.css +1 -31
  42. package/package.json +2 -2
  43. package/web/assets/{abnfDiagram-VRR7QNED-Brm1vcGG.js → abnfDiagram-VRR7QNED-Dt7QEF5H.js} +1 -1
  44. package/web/assets/app-C1j063QX.js +2 -0
  45. package/web/assets/app-options-98-a6ivv.js +25 -0
  46. package/web/assets/{app-view-header-BSOI-wB6.js → app-view-header-BJ9CWTE1.js} +1 -1
  47. package/web/assets/{arc-zngHsMKV.js → arc-PFB0H7oD.js} +1 -1
  48. package/web/assets/{architectureDiagram-ZJ3FMSHR-BLklLqRP.js → architectureDiagram-ZJ3FMSHR-BHLqEXlZ.js} +1 -1
  49. package/web/assets/{ask-tray-B4SPj-fH.js → ask-tray-C3gBesHJ.js} +1 -1
  50. package/web/assets/{blockDiagram-677ZJIJ3-yhC2fEtu.js → blockDiagram-677ZJIJ3-UbnYoYDg.js} +1 -1
  51. package/web/assets/{c4Diagram-LMCZKHZV-DMcweQme.js → c4Diagram-LMCZKHZV-4OSx1V4W.js} +1 -1
  52. package/web/assets/channel-BuvY5cQN.js +1 -0
  53. package/web/assets/{chunk-2Q5K7J3B-DD-S6n8h.js → chunk-2Q5K7J3B-CgxJ1aJb.js} +1 -1
  54. package/web/assets/{chunk-32BRIVSS-DFIAKRv_.js → chunk-32BRIVSS-Bfwxg2LM.js} +1 -1
  55. package/web/assets/{chunk-5VM5RSS4-B23APb3c.js → chunk-5VM5RSS4-BZN9a2hI.js} +1 -1
  56. package/web/assets/{chunk-EX3LRPZG-BSVEB7db.js → chunk-EX3LRPZG-CrH6hhqq.js} +1 -1
  57. package/web/assets/{chunk-JWPE2WC7-B6qGd0ng.js → chunk-JWPE2WC7-Ddn7ZSp8.js} +1 -1
  58. package/web/assets/{chunk-MOJQB5TN-N4T1-ew1.js → chunk-MOJQB5TN-9lJ7f_hI.js} +1 -1
  59. package/web/assets/{chunk-RYQCIY6F-6oc6i_Xw.js → chunk-RYQCIY6F-QrFo_00s.js} +1 -1
  60. package/web/assets/{chunk-V7JOEXUC-Dftgm9ck.js → chunk-V7JOEXUC-Dn0lQjVf.js} +1 -1
  61. package/web/assets/{chunk-VR4S4FIN-DfjqwEsO.js → chunk-VR4S4FIN-DXVPySkB.js} +1 -1
  62. package/web/assets/{chunk-XXDRQBXY-CsFsQYow.js → chunk-XXDRQBXY-CBiFDOdZ.js} +1 -1
  63. package/web/assets/classDiagram-OUVF2IWQ-DCYNCJhe.js +1 -0
  64. package/web/assets/classDiagram-v2-EOCWNBFH-DCYNCJhe.js +1 -0
  65. package/web/assets/client-DBJY3XDL.js +43 -0
  66. package/web/assets/client-DKJs0w4y.js +6 -0
  67. package/web/assets/{command-D_9V8ejS.js → command-onjTnuXT.js} +1 -1
  68. package/web/assets/{copy-context-CevDWX59.js → copy-context-CciRaLNN.js} +1 -1
  69. package/web/assets/{cose-bilkent-JH36ORCC-DzKsIuDb.js → cose-bilkent-JH36ORCC-CGRic2um.js} +1 -1
  70. package/web/assets/{cynefin-VYW2F7L2-AL45ZHbw.js → cynefin-VYW2F7L2-Blk0Lu0l.js} +1 -1
  71. package/web/assets/{cynefinDiagram-TSTJHNR4-DSbBegxZ.js → cynefinDiagram-TSTJHNR4-DpnnHRDK.js} +1 -1
  72. package/web/assets/{dagre-VKFMJZFB-BaG6MGUE.js → dagre-VKFMJZFB-llzMokKp.js} +1 -1
  73. package/web/assets/{diagram-FQU43EPY-WbDotTX5.js → diagram-FQU43EPY-DC6xHovR.js} +1 -1
  74. package/web/assets/{diagram-G47NLZAW-DMbouYVI.js → diagram-G47NLZAW-B8GVS3EA.js} +1 -1
  75. package/web/assets/{diagram-NH7WQ7WH-DoEuaWd_.js → diagram-NH7WQ7WH-DWUaARfh.js} +1 -1
  76. package/web/assets/{diagram-OA4YK3LP-BjGd-PoH.js → diagram-OA4YK3LP-9Uaes_Yi.js} +1 -1
  77. package/web/assets/{diagram-WEI45ONY-CXHi27jF.js → diagram-WEI45ONY-DPPTBWud.js} +1 -1
  78. package/web/assets/{document-CVJCfWiQ.js → document-D23WzFsW.js} +3 -3
  79. package/web/assets/{ebnfDiagram-CCIWWBDH-B5vMUDOs.js → ebnfDiagram-CCIWWBDH-BPrKneNw.js} +1 -1
  80. package/web/assets/{entities-BTKzWJ-r.js → entities-DPg0B8vd.js} +1 -1
  81. package/web/assets/{erDiagram-Q63AITRT-DhO7ayzF.js → erDiagram-Q63AITRT-CFqYBcnb.js} +1 -1
  82. package/web/assets/{file-viewer-D1HK9J3A.js → file-viewer-Ck73SsLA.js} +1 -1
  83. package/web/assets/{flowDiagram-23GEKE2U-CublTUlC.js → flowDiagram-23GEKE2U-C7RpYqaT.js} +1 -1
  84. package/web/assets/folder-filter-bar-DzejUj87.js +4 -0
  85. package/web/assets/{folder-image-L_AjkRfI.js → folder-image-BjIicmQ6.js} +1 -1
  86. package/web/assets/folder-landing-CCIXZfkp.js +55 -0
  87. package/web/assets/folder-list-TuRwuTss.js +1 -0
  88. package/web/assets/folder-mobile-bar-Bqnfwi-z.js +1 -0
  89. package/web/assets/folder-plugin-view-CrinCX1o.js +2 -0
  90. package/web/assets/{ganttDiagram-NO4QXBWP-BLMJWerM.js → ganttDiagram-NO4QXBWP-Cz2sxU8F.js} +1 -1
  91. package/web/assets/{gitGraphDiagram-IHSO6WYX-Br0E6qyZ.js → gitGraphDiagram-IHSO6WYX-BSuz0yF-.js} +1 -1
  92. package/web/assets/index-DLn_2LPp.js +2 -0
  93. package/web/assets/index-uY9jLiL5.css +1 -0
  94. package/web/assets/{infoDiagram-FWYZ7A6U-Dgz0npJO.js → infoDiagram-FWYZ7A6U-CpeKgqoS.js} +1 -1
  95. package/web/assets/input-Bz_puamS.js +9 -0
  96. package/web/assets/{ishikawaDiagram-FXEZZL3T-rY5pdS3b.js → ishikawaDiagram-FXEZZL3T-5c2Tmknb.js} +1 -1
  97. package/web/assets/{journeyDiagram-5HDEW3XC-CKZALyR6.js → journeyDiagram-5HDEW3XC-Cu-wRkR4.js} +1 -1
  98. package/web/assets/{kanban-definition-HUTT4EX6-EGPY9Vda.js → kanban-definition-HUTT4EX6-BunSqkPe.js} +1 -1
  99. package/web/assets/{linear-DkJW9OwI.js → linear-C8e8bGVv.js} +1 -1
  100. package/web/assets/{mermaid.core-DVEpXYiF.js → mermaid.core-DLgjY1g2.js} +4 -4
  101. package/web/assets/{metadata-Bu_ffLUd.js → metadata-DEfJKlnd.js} +1 -1
  102. package/web/assets/{mindmap-definition-LN4V7U3C-BEOUVnDN.js → mindmap-definition-LN4V7U3C-W9ENYFOD.js} +1 -1
  103. package/web/assets/{pegDiagram-2B236MQR-DThjl3eK.js → pegDiagram-2B236MQR-0vPlHCFp.js} +1 -1
  104. package/web/assets/{pieDiagram-ENE6RG2P-DzmoGgw2.js → pieDiagram-ENE6RG2P-CakaveRV.js} +1 -1
  105. package/web/assets/{project-search-dialog-e2Jmhvfq.js → project-search-dialog-BSKA4peX.js} +1 -1
  106. package/web/assets/{quadrantDiagram-ABIIQ3AL-BqT8U3FS.js → quadrantDiagram-ABIIQ3AL-jxHw_egj.js} +1 -1
  107. package/web/assets/{railroadDiagram-RFXS5EU6-1k1tfPk1.js → railroadDiagram-RFXS5EU6-lIjDXPZt.js} +1 -1
  108. package/web/assets/{requirementDiagram-TGXJPOKE-bM4RTd2J.js → requirementDiagram-TGXJPOKE-C-JiT4bc.js} +1 -1
  109. package/web/assets/{sankeyDiagram-HTMAVEWB-TCgfqDLy.js → sankeyDiagram-HTMAVEWB-SuLPAw9X.js} +1 -1
  110. package/web/assets/{search-DQiFH2up.js → search-CKujW14f.js} +1 -1
  111. package/web/assets/{selection-popup-bqfgVd-L.js → selection-popup-CtxT9SWn.js} +3 -3
  112. package/web/assets/{sequenceDiagram-DBY2YBRQ-BRhGHI0i.js → sequenceDiagram-DBY2YBRQ-o9UwTkZR.js} +1 -1
  113. package/web/assets/{sizeCapture-X5ZJPWSS-EeUWgn0N.js → sizeCapture-X5ZJPWSS-ClbWGybF.js} +1 -1
  114. package/web/assets/{stateDiagram-2N3HPSRC-BSOG758d.js → stateDiagram-2N3HPSRC-Ce4avoWx.js} +1 -1
  115. package/web/assets/stateDiagram-v2-6OUMAXLB-CsrPTZoJ.js +1 -0
  116. package/web/assets/{swimlanes-5IMT3BWC-DpCo4aEm.js → swimlanes-5IMT3BWC-DMZI4XZT.js} +2 -2
  117. package/web/assets/swimlanesDiagram-G3AALYLV-pShSVcTd.js +8 -0
  118. package/web/assets/{timeline-definition-FHXFAJF6-BpaObe6u.js → timeline-definition-FHXFAJF6-CPIpIgPN.js} +1 -1
  119. package/web/assets/{vennDiagram-L72KCM5P-ClvUzW8B.js → vennDiagram-L72KCM5P-_rha8uDZ.js} +1 -1
  120. package/web/assets/view-context-BDtIUHQy.js +229 -0
  121. package/web/assets/{viewer-capabilities-CFBwvpcI.js → viewer-capabilities-YYZqMs56.js} +1 -1
  122. package/web/assets/{wardleyDiagram-EHGQE667-Dg1CX0Fl.js → wardleyDiagram-EHGQE667-D4EAOS35.js} +1 -1
  123. package/web/assets/{xychartDiagram-FW5EYKEG-C0c9zQTr.js → xychartDiagram-FW5EYKEG-DR8PwXpe.js} +1 -1
  124. package/web/index.html +2 -2
  125. package/kernel/packages/agents/eslint.config.js +0 -10
  126. package/kernel/packages/agents/package.json +0 -75
  127. package/kernel/packages/agents/src/agent-card.tsx +0 -334
  128. package/kernel/packages/agents/src/client.tsx +0 -13
  129. package/kernel/packages/agents/src/index.ts +0 -18
  130. package/kernel/packages/agents/src/manifest.ts +0 -7
  131. package/kernel/packages/agents/src/node.ts +0 -447
  132. package/kernel/packages/agents/src/profiles.ts +0 -137
  133. package/kernel/packages/agents/src/resolve.ts +0 -69
  134. package/kernel/packages/agents/src/server.ts +0 -60
  135. package/kernel/packages/agents/src/types.ts +0 -82
  136. package/kernel/packages/agents/src/viewer.tsx +0 -370
  137. package/kernel/packages/agents/test/profiles.test.mjs +0 -88
  138. package/kernel/packages/agents/test/resolve.test.mjs +0 -729
  139. package/kernel/packages/agents/tsconfig.json +0 -17
  140. package/kernel/packages/api/test/agents-loading.test.ts +0 -94
  141. package/kernel/packages/ui/src/components/folder-skill-list.tsx +0 -62
  142. package/web/assets/app-Debw-lUT.js +0 -2
  143. package/web/assets/app-options-UU_JlJQv.js +0 -25
  144. package/web/assets/channel-BgYLn5Cm.js +0 -1
  145. package/web/assets/classDiagram-OUVF2IWQ-Fzhmfbkt.js +0 -1
  146. package/web/assets/classDiagram-v2-EOCWNBFH-Fzhmfbkt.js +0 -1
  147. package/web/assets/client-C1WdY7jQ.js +0 -22
  148. package/web/assets/client-D__L4Hl0.js +0 -17
  149. package/web/assets/client-DoMhF9Tv.js +0 -5
  150. package/web/assets/folder-filter-bar-BHz7s9Vb.js +0 -4
  151. package/web/assets/folder-landing-Byh8W67L.js +0 -54
  152. package/web/assets/folder-list-jMyo-7pV.js +0 -1
  153. package/web/assets/folder-metadata-bCocvxpP.js +0 -2
  154. package/web/assets/folder-mobile-bar-DvvyOS-a.js +0 -1
  155. package/web/assets/folder-plugin-view-sib0Dwfw.js +0 -3
  156. package/web/assets/index-4Xh9UKim.js +0 -1
  157. package/web/assets/index-Bu75S2qS.js +0 -1
  158. package/web/assets/index-CPcMcCUY.js +0 -2
  159. package/web/assets/index-CXxkvzNJ.css +0 -1
  160. package/web/assets/select-CkOwL81C.js +0 -30
  161. package/web/assets/stateDiagram-v2-6OUMAXLB-DUeGDz1r.js +0 -1
  162. package/web/assets/swimlanesDiagram-G3AALYLV-BGOaCfyV.js +0 -8
  163. package/web/assets/use-folder-filter-hA6fa7nm.js +0 -1
  164. package/web/assets/view-context-CVvDw2JM.js +0 -229
@@ -1,82 +0,0 @@
1
- export type AgentClientId = "generic" | "codex" | "claude" | "pi"
2
-
3
- export type ClientFilter = AgentClientId | "all"
4
-
5
- export type SourceLevel =
6
- "project" | "parent" | "repository" | "linked" | "global"
7
-
8
- export type AgentSourceStatus = "loaded" | "unreadable" | "unsafe"
9
-
10
- export type AgentSource = {
11
- /** Physical file identity (resolved realpath), used for deduplication. */
12
- id: string
13
- /** Canonical physical path shown next to the source text. */
14
- path: string
15
- /** Literal file paths that resolved to this physical file (e.g. symlinks). */
16
- sourcePaths: string[]
17
- /** Leaf file name, e.g. AGENTS.md or CLAUDE.local.md. */
18
- filename: string
19
- /** Physical containing directory. */
20
- directory: string
21
- /** Where the source sits in the project → parents → global order. */
22
- level: SourceLevel
23
- /** Read result. Unreadable sources stay visible instead of hidden. */
24
- status: AgentSourceStatus
25
- /** Full instruction text (empty when the status is not `loaded`). */
26
- text: string
27
- /** Byte length of the physical file. */
28
- size: number
29
- /** Clients whose discovery includes this physical file. */
30
- clientIds: AgentClientId[]
31
- /** True when this file is a per-directory override (AGENTS.override.md). */
32
- override: boolean
33
- /** All listed sources apply to the selected working folder. */
34
- applies: true
35
- /** Human reason for an unreadable source. */
36
- reason?: string
37
- }
38
-
39
- export type AgentWarning =
40
- | {
41
- kind: "missing"
42
- /** Directory or path that was expected but contained no instruction file. */
43
- path: string
44
- /** Clients that expected a file here. */
45
- clientIds: AgentClientId[]
46
- reason: string
47
- }
48
- | {
49
- kind: "unsupported"
50
- /** Clients whose configuration is not applied by this preview. */
51
- clientIds: AgentClientId[]
52
- reason: string
53
- }
54
-
55
- /**
56
- * One directory in the ancestor walk: the working folder first, then every
57
- * parent up to the selected client's boundary. Directories without any
58
- * instruction file stay visible so the full chain going up is always shown.
59
- */
60
- export type AgentChainEntry = {
61
- /** Physical directory, ordered from the working folder up to the boundary. */
62
- directory: string
63
- /** Where the directory sits in the working-folder → boundary order. */
64
- level: SourceLevel
65
- /** Instruction files found in this directory for the selected client. */
66
- filenames: string[]
67
- }
68
-
69
- export type AgentsResolution = {
70
- /** Canonical working folder. */
71
- folder: string
72
- /** Nearest ancestor containing `.git`, or null when none is found. */
73
- repositoryRoot: string | null
74
- /** Client filter used for this resolution. */
75
- client: ClientFilter
76
- /** Ordered, deduplicated sources: project, parents, then global. */
77
- sources: AgentSource[]
78
- /** Non-fatal warnings (missing files, unapplied configuration). */
79
- warnings: AgentWarning[]
80
- /** The full ancestor walk: working folder up to the client's boundary. */
81
- chain: AgentChainEntry[]
82
- }
@@ -1,370 +0,0 @@
1
- import { useMemo, useRef, useState } from "react"
2
- import { useQuery } from "@tanstack/react-query"
3
- import { ChevronDown, Info, TriangleAlert } from "lucide-react"
4
-
5
- import type { FolderPluginViewProps } from "kennel-plugins/client"
6
- import {
7
- Collapsible,
8
- CollapsibleContent,
9
- CollapsibleTrigger,
10
- } from "@raggle-ai/ui/components/collapsible"
11
- import { FolderFilterBar } from "@raggle-ai/ui/components/folder-filter-bar"
12
- import {
13
- Select,
14
- SelectContent,
15
- SelectItem,
16
- SelectTrigger,
17
- SelectValue,
18
- } from "@raggle-ai/ui/components/select"
19
- import { useFolderFilter } from "@raggle-ai/ui/hooks/use-folder-filter"
20
- import { cn } from "@raggle-ai/ui/lib/utils"
21
-
22
- import type { AgentsResolution, AgentWarning } from "./types.ts"
23
- import {
24
- AgentCard,
25
- agentCardClass,
26
- CurrentFolderBox,
27
- FolderRow,
28
- } from "./agent-card.tsx"
29
- import {
30
- clientLabel,
31
- FILTERS,
32
- parseClientFilter,
33
- PROFILES,
34
- } from "./profiles.ts"
35
-
36
- export type AgentsPluginData = Pick<
37
- AgentsResolution,
38
- "chain" | "client" | "sources"
39
- > & {
40
- folder: string
41
- repositoryRoot: string | null
42
- status: "available"
43
- warnings: AgentWarning[]
44
- linkedStatus?: "pending" | "ready" | "error"
45
- }
46
-
47
- function ClientSelect({
48
- client,
49
- href,
50
- }: {
51
- client: string
52
- href: (subpath: string) => string
53
- }) {
54
- return (
55
- <div className="flex items-center gap-2">
56
- <label
57
- className="text-[0.78rem] font-medium tracking-wide text-[#6c7a6d]
58
- uppercase"
59
- >
60
- AI client
61
- </label>
62
- <Select
63
- onValueChange={(value) => {
64
- window.location.href = href(value)
65
- }}
66
- value={client}
67
- >
68
- <SelectTrigger aria-label="AI client" size="sm" className="w-44">
69
- <SelectValue placeholder="Select client" />
70
- </SelectTrigger>
71
- <SelectContent>
72
- {FILTERS.map((option) => (
73
- <SelectItem key={option.id} value={option.id}>
74
- {option.label}
75
- </SelectItem>
76
- ))}
77
- </SelectContent>
78
- </Select>
79
- </div>
80
- )
81
- }
82
-
83
- export function AgentsPluginView({
84
- folder,
85
- folderId,
86
- hostActions,
87
- href,
88
- request,
89
- subpath,
90
- }: FolderPluginViewProps) {
91
- const client = parseClientFilter(subpath || null)
92
- const retryLinked = useRef(false)
93
- const query = useQuery({
94
- enabled: Boolean(folder),
95
- gcTime: 5 * 60_000,
96
- queryFn: ({ signal }) => {
97
- const headers = retryLinked.current
98
- ? { "x-kennel-retry-linked": "1" }
99
- : undefined
100
- retryLinked.current = false
101
- return request(subpath, { signal, headers }) as Promise<AgentsPluginData>
102
- },
103
- queryKey: ["folder", folderId, "agents", subpath ?? ""],
104
- staleTime: 15_000,
105
- refetchInterval: (query) =>
106
- query.state.data?.linkedStatus === "pending" ? 1_000 : false,
107
- })
108
- const data = query.data as AgentsPluginData | undefined
109
- const sources = useMemo(() => data?.sources ?? [], [data])
110
- const warnings = useMemo(() => data?.warnings ?? [], [data])
111
- // Shared folder filter over each source's containing directory. Source
112
- // directories can be the project folder, ancestors, or the global
113
- // directory — `allowOutside` surfaces all of them as chips.
114
- const filter = useFolderFilter(sources, {
115
- allowOutside: true,
116
- getFolder: (source) => source.directory,
117
- rootFolder: folder,
118
- })
119
- const profile = PROFILES[client]
120
- const [detailsOpen, setDetailsOpen] = useState(false)
121
- const [warningsOpen, setWarningsOpen] = useState(false)
122
- const currentFolder = data?.folder ?? folder
123
- const hasOwnInstructions = sources.some(
124
- (source) => source.level === "project",
125
- )
126
- // The full ancestor walk (working folder first, then parents up to the
127
- // client's boundary). The current-folder box covers the first entry, so
128
- // the chain list below shows every directory going up from it.
129
- const parentChain = useMemo(() => (data?.chain ?? []).slice(1), [data])
130
- const projectSources = useMemo(
131
- () => filter.visibleItems.filter((source) => source.level === "project"),
132
- [filter.visibleItems],
133
- )
134
- const globalSources = useMemo(
135
- () => filter.visibleItems.filter((source) => source.level === "global"),
136
- [filter.visibleItems],
137
- )
138
- const sourcesIn = (directory: string) =>
139
- filter.visibleItems.filter((source) => source.directory === directory)
140
- const sourceKey = (source: (typeof sources)[number]) =>
141
- `${source.id}:${source.clientIds.join(",")}`
142
-
143
- return (
144
- <div
145
- className="mx-auto grid w-full max-w-5xl min-w-0 grid-cols-1 gap-4 px-4
146
- py-5"
147
- >
148
- <Collapsible onOpenChange={setDetailsOpen} open={detailsOpen}>
149
- <header className="flex flex-wrap items-center justify-between gap-3">
150
- <h1 className="m-0 text-2xl font-semibold text-[#233025]">Agents</h1>
151
- <CollapsibleTrigger asChild>
152
- <button
153
- aria-expanded={detailsOpen}
154
- aria-label={
155
- detailsOpen ? "Hide client details" : "Show client details"
156
- }
157
- className="inline-flex items-center gap-1.5 rounded-md border
158
- border-[#e7ebe7] bg-white px-2.5 py-1 text-[0.78rem] font-medium
159
- text-[#4a554b] hover:bg-[#f3f5f2] focus-visible:outline-2
160
- focus-visible:outline-[#56725e]"
161
- type="button"
162
- >
163
- <Info className="size-4 text-[#6c7a6d]" />
164
- Details
165
- <ChevronDown
166
- className={cn(
167
- "size-3.5 text-[#6c7a6d] transition-transform",
168
- detailsOpen && "rotate-180",
169
- )}
170
- />
171
- </button>
172
- </CollapsibleTrigger>
173
- </header>
174
- <CollapsibleContent
175
- className="data-[state=closed]:animate-out
176
- data-[state=open]:animate-in"
177
- >
178
- <p
179
- className="m-0 mt-3 flex items-start gap-2 text-[0.72rem]
180
- text-[#6c7a6d]"
181
- >
182
- <TriangleAlert className="mt-0.5 size-3.5 shrink-0" />
183
- <span>
184
- {profile.note} {profile.limits}
185
- </span>
186
- </p>
187
- </CollapsibleContent>
188
- </Collapsible>
189
-
190
- <div className="flex flex-wrap items-center gap-x-4 gap-y-2">
191
- <ClientSelect client={client} href={href} />
192
- {filter.choices.length > 1 ? (
193
- <FolderFilterBar
194
- ariaLabel="Filter by instruction folder"
195
- choices={filter.choices}
196
- onSelect={filter.setSelected}
197
- selected={filter.effectiveSelected}
198
- total={filter.total}
199
- />
200
- ) : null}
201
- </div>
202
-
203
- {data ? (
204
- <section
205
- className={agentCardClass}
206
- aria-label="Current folder instructions"
207
- >
208
- {!projectSources.length ? (
209
- <CurrentFolderBox
210
- createAction={hostActions?.createAgentFile}
211
- folder={currentFolder}
212
- hasOwn={hasOwnInstructions}
213
- onCreated={() => void query.refetch()}
214
- />
215
- ) : null}
216
-
217
- {projectSources.length ? (
218
- <div className="grid grid-cols-1">
219
- {projectSources.map((source) => (
220
- <AgentCard
221
- hostActions={hostActions}
222
- key={sourceKey(source)}
223
- source={source}
224
- embedded
225
- />
226
- ))}
227
- </div>
228
- ) : null}
229
- </section>
230
- ) : null}
231
-
232
- {data?.linkedStatus === "pending" ? (
233
- <p role="status" className="m-0 text-sm text-[#6c7a6d]">
234
- Local instructions loaded. Looking for linked project instructions…
235
- </p>
236
- ) : null}
237
- {data?.linkedStatus === "error" ? (
238
- <p role="status" className="m-0 text-sm text-[#9c3a3a]">
239
- Linked project discovery is unavailable. Local instructions are shown.
240
- <button
241
- onClick={() => {
242
- retryLinked.current = true
243
- void query.refetch()
244
- }}
245
- >
246
- Retry linked instructions
247
- </button>
248
- </p>
249
- ) : null}
250
- {query.isPending ? (
251
- <p className="m-0 text-sm text-[#6c7a6d]">Loading…</p>
252
- ) : null}
253
- {query.error ? (
254
- <p role="alert" className="m-0 text-sm text-[#9c3a3a]">
255
- {String(query.error)}{" "}
256
- <button onClick={() => void query.refetch()}>
257
- Retry instructions
258
- </button>
259
- </p>
260
- ) : null}
261
-
262
- {warnings.length ? (
263
- <Collapsible onOpenChange={setWarningsOpen} open={warningsOpen}>
264
- <CollapsibleTrigger asChild>
265
- <button
266
- aria-expanded={warningsOpen}
267
- aria-label={warningsOpen ? "Hide warnings" : "Show warnings"}
268
- className="inline-flex items-center gap-1.5 rounded-md border
269
- border-[#e7ebe7] bg-white px-2.5 py-1 text-[0.78rem] font-medium
270
- text-[#4a554b] hover:bg-[#f3f5f2] focus-visible:outline-2
271
- focus-visible:outline-[#56725e]"
272
- type="button"
273
- >
274
- <TriangleAlert className="size-4 text-[#6c7a6d]" />
275
- Warnings
276
- <span
277
- className="rounded-full bg-[#f3f5f2] px-1.5 py-0.5
278
- text-[0.68rem] text-[#6c7a6d]"
279
- >
280
- {warnings.length}
281
- </span>
282
- <ChevronDown
283
- className={cn(
284
- "size-3.5 text-[#6c7a6d] transition-transform",
285
- warningsOpen && "rotate-180",
286
- )}
287
- />
288
- </button>
289
- </CollapsibleTrigger>
290
- <CollapsibleContent
291
- className="data-[state=closed]:animate-out
292
- data-[state=open]:animate-in"
293
- >
294
- <ul className="m-0 mt-3 list-none space-y-1 p-0">
295
- {warnings.map((warning) => (
296
- <li
297
- className="text-[0.72rem] text-[#6c7a6d]"
298
- key={`${warning.kind}:${"path" in warning ? warning.path : ""}:${warning.clientIds.join(",")}`}
299
- >
300
- {"path" in warning ? `${warning.path} — ` : ""}
301
- {warning.reason} (
302
- {warning.clientIds.map((id) => clientLabel(id)).join(", ")})
303
- </li>
304
- ))}
305
- </ul>
306
- </CollapsibleContent>
307
- </Collapsible>
308
- ) : null}
309
-
310
- {parentChain.length ? (
311
- <div className="grid grid-cols-1 gap-4">
312
- {parentChain.map((entry) => {
313
- const entrySources = sourcesIn(entry.directory)
314
- return (
315
- <section
316
- className={cn(agentCardClass, "scroll-mt-6")}
317
- aria-label={`Instructions for ${entry.directory}`}
318
- id={`agents-folder-${entry.directory}`}
319
- key={entry.directory}
320
- >
321
- {!entrySources.length ? (
322
- <FolderRow
323
- createAction={hostActions?.createAgentFile}
324
- entry={entry}
325
- onCreated={() => void query.refetch()}
326
- />
327
- ) : null}
328
- {entrySources.length ? (
329
- <div className="grid grid-cols-1">
330
- {entrySources.map((source) => (
331
- <AgentCard
332
- hostActions={hostActions}
333
- key={sourceKey(source)}
334
- source={source}
335
- embedded
336
- />
337
- ))}
338
- </div>
339
- ) : null}
340
- </section>
341
- )
342
- })}
343
- </div>
344
- ) : null}
345
-
346
- {globalSources.length ? (
347
- <section className="scroll-mt-6">
348
- <div className="flex items-baseline gap-2">
349
- <h3
350
- className="m-0 text-[0.82rem] font-semibold"
351
- style={{ color: "#315c3b" }}
352
- >
353
- Global instructions
354
- </h3>
355
- <span className="text-[0.7rem] text-[#6c7a6d]">Global</span>
356
- </div>
357
- <div className="mt-1 grid grid-cols-1 gap-3">
358
- {globalSources.map((source) => (
359
- <AgentCard
360
- hostActions={hostActions}
361
- key={sourceKey(source)}
362
- source={source}
363
- />
364
- ))}
365
- </div>
366
- </section>
367
- ) : null}
368
- </div>
369
- )
370
- }
@@ -1,88 +0,0 @@
1
- import { describe, expect, test } from "bun:test"
2
-
3
- import {
4
- CLIENT_ORDER,
5
- clientLabel,
6
- FILTERS,
7
- parseClientFilter,
8
- PROFILES,
9
- } from "../src/profiles.ts"
10
-
11
- describe("client profiles", () => {
12
- test("the filter list is exactly the four clients", () => {
13
- expect(FILTERS.map((option) => option.id)).toEqual([
14
- "generic",
15
- "codex",
16
- "claude",
17
- "pi",
18
- ])
19
- expect(FILTERS.some((option) => option.id.includes("repository"))).toBe(
20
- false,
21
- )
22
- })
23
-
24
- test("unknown filter values resolve to pi by default", () => {
25
- expect(parseClientFilter(null)).toBe("pi")
26
- expect(parseClientFilter(undefined)).toBe("pi")
27
- expect(parseClientFilter("")).toBe("pi")
28
- expect(parseClientFilter("bogus")).toBe("pi")
29
- expect(parseClientFilter("all")).toBe("pi")
30
- expect(parseClientFilter("generic")).toBe("generic")
31
- expect(parseClientFilter("codex")).toBe("codex")
32
- expect(parseClientFilter("claude")).toBe("claude")
33
- expect(parseClientFilter("pi")).toBe("pi")
34
- })
35
-
36
- test("generic is a root-walking reference view with no verified claim", () => {
37
- expect(PROFILES.generic.scope).toBe("root")
38
- expect(PROFILES.generic.files).toEqual(["AGENTS.md"])
39
- expect(PROFILES.generic.verified).toBe(false)
40
- })
41
-
42
- test("codex stops at git, prefers AGENTS.override.md, and honors CODEX_HOME", () => {
43
- expect(PROFILES.codex.scope).toBe("git")
44
- expect(PROFILES.codex.files).toEqual(["AGENTS.override.md", "AGENTS.md"])
45
- expect(PROFILES.codex.filesMode).toBe("first")
46
- expect(PROFILES.codex.globalEnv).toBe("CODEX_HOME")
47
- expect(PROFILES.codex.globalFiles).toEqual([
48
- "AGENTS.override.md",
49
- "AGENTS.md",
50
- ])
51
- })
52
-
53
- test("claude loads the three documented ancestor files and walks to root", () => {
54
- expect(PROFILES.claude.scope).toBe("root")
55
- expect(PROFILES.claude.files).toEqual([
56
- "CLAUDE.md",
57
- ".claude/CLAUDE.md",
58
- "CLAUDE.local.md",
59
- ])
60
- expect(PROFILES.claude.filesMode).toBe("all")
61
- expect(PROFILES.claude.globalDir).toBe(".claude")
62
- })
63
-
64
- test("pi uses the documented first-match candidate list and honors PI_CODING_AGENT_DIR", () => {
65
- expect(PROFILES.pi.scope).toBe("root")
66
- expect(PROFILES.pi.files).toEqual([
67
- "AGENTS.override.md",
68
- "AGENTS.md",
69
- "AGENTS.MD",
70
- "CLAUDE.md",
71
- "CLAUDE.MD",
72
- ])
73
- expect(PROFILES.pi.filesMode).toBe("first")
74
- expect(PROFILES.pi.globalEnv).toBe("PI_CODING_AGENT_DIR")
75
- expect(PROFILES.pi.globalFiles).toEqual(PROFILES.pi.files)
76
- })
77
-
78
- test("client order keeps the stable inventory order", () => {
79
- expect(CLIENT_ORDER).toEqual(["generic", "codex", "claude", "pi"])
80
- })
81
-
82
- test("labels map back to the client ids", () => {
83
- expect(clientLabel("generic")).toBe("Generic")
84
- expect(clientLabel("codex")).toBe("Codex")
85
- expect(clientLabel("claude")).toBe("Claude Code")
86
- expect(clientLabel("pi")).toBe("Pi")
87
- })
88
- })