ccdesk 0.2.2 → 0.2.3

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 (130) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +6 -3
  3. package/dist/core/agent-edit.d.ts +6 -0
  4. package/dist/{server → core}/agent-edit.js +15 -21
  5. package/dist/core/backups.d.ts +19 -0
  6. package/dist/{server → core}/backups.js +37 -45
  7. package/dist/core/core.d.ts +96 -0
  8. package/dist/core/core.js +57 -0
  9. package/dist/core/index.d.ts +22 -0
  10. package/dist/core/index.js +38 -0
  11. package/dist/core/mcp-edit.d.ts +25 -0
  12. package/dist/{server → core}/mcp-edit.js +115 -98
  13. package/dist/core/mutations.d.ts +20 -0
  14. package/dist/{server → core}/mutations.js +8 -15
  15. package/dist/core/open.d.ts +31 -0
  16. package/dist/{server → core}/open.js +39 -12
  17. package/dist/core/parse-edit.d.ts +12 -0
  18. package/dist/core/parse-edit.js +64 -0
  19. package/dist/core/reference.d.ts +3 -0
  20. package/dist/core/reference.js +11 -0
  21. package/dist/core/scan/compare.d.ts +7 -0
  22. package/dist/{server → core}/scan/compare.js +2 -2
  23. package/dist/core/scan/context.d.ts +13 -0
  24. package/dist/{server → core}/scan/context.js +55 -32
  25. package/dist/core/scan/data.d.ts +8 -0
  26. package/dist/core/scan/data.js +18 -0
  27. package/dist/core/scan/env.d.ts +26 -0
  28. package/dist/{server → core}/scan/env.js +7 -3
  29. package/dist/core/scan/files.d.ts +68 -0
  30. package/dist/core/scan/files.js +270 -0
  31. package/dist/core/scan/git.d.ts +14 -0
  32. package/dist/core/scan/git.js +98 -0
  33. package/dist/core/scan/index.d.ts +5 -0
  34. package/dist/{server → core}/scan/index.js +115 -81
  35. package/dist/core/scan/instructions.d.ts +54 -0
  36. package/dist/{server → core}/scan/instructions.js +141 -73
  37. package/dist/core/scan/inventory.d.ts +38 -0
  38. package/dist/{server → core}/scan/inventory.js +134 -115
  39. package/dist/core/scan/mcp.d.ts +28 -0
  40. package/dist/{server → core}/scan/mcp.js +151 -94
  41. package/dist/core/scan/memory.d.ts +8 -0
  42. package/dist/{server → core}/scan/memory.js +8 -8
  43. package/dist/core/scan/path-test.d.ts +9 -0
  44. package/dist/core/scan/path-test.js +156 -0
  45. package/dist/core/scan/permissions.d.ts +31 -0
  46. package/dist/{server → core}/scan/permissions.js +5 -8
  47. package/dist/core/scan/plugins.d.ts +39 -0
  48. package/dist/{server → core}/scan/plugins.js +36 -27
  49. package/dist/core/scan/projects.d.ts +9 -0
  50. package/dist/{server → core}/scan/projects.js +5 -4
  51. package/dist/core/scan/sandbox.d.ts +14 -0
  52. package/dist/{server → core}/scan/sandbox.js +87 -59
  53. package/dist/core/scan/settings.d.ts +35 -0
  54. package/dist/{server → core}/scan/settings.js +88 -92
  55. package/dist/core/scan/tools.d.ts +10 -0
  56. package/dist/{server → core}/scan/tools.js +28 -20
  57. package/dist/core/scan/usage.d.ts +25 -0
  58. package/dist/{server → core}/scan/usage.js +117 -95
  59. package/dist/core/schema.d.ts +8 -0
  60. package/dist/core/schema.js +67 -0
  61. package/dist/core/secrets.d.ts +44 -0
  62. package/dist/core/secrets.js +187 -0
  63. package/dist/core/settings-edit.d.ts +9 -0
  64. package/dist/{server → core}/settings-edit.js +91 -112
  65. package/dist/core/watch.d.ts +45 -0
  66. package/dist/{server → core}/watch.js +54 -17
  67. package/dist/core/write.d.ts +119 -0
  68. package/dist/{server → core}/write.js +63 -8
  69. package/dist/server/args.d.ts +32 -0
  70. package/dist/server/args.js +106 -0
  71. package/dist/server/cli.d.ts +2 -0
  72. package/dist/server/cli.js +58 -83
  73. package/dist/server/commands.d.ts +8 -0
  74. package/dist/server/commands.js +39 -0
  75. package/dist/server/server.d.ts +12 -0
  76. package/dist/server/server.js +80 -195
  77. package/dist/server/start.d.ts +13 -0
  78. package/dist/server/start.js +21 -0
  79. package/dist/shared/compare.d.ts +25 -0
  80. package/dist/shared/memory.d.ts +2 -0
  81. package/dist/shared/requests.d.ts +104 -0
  82. package/dist/shared/requests.js +73 -0
  83. package/dist/shared/scopes.d.ts +10 -0
  84. package/dist/shared/settings-catalog.json +2199 -0
  85. package/dist/shared/types.d.ts +530 -0
  86. package/dist/shared/types.js +11 -0
  87. package/dist/ui/assets/index-CH1HoC38.js +60 -0
  88. package/dist/ui/index.html +1 -1
  89. package/dist/view/diff.d.ts +16 -0
  90. package/dist/view/diff.js +85 -0
  91. package/dist/view/format.d.ts +17 -0
  92. package/dist/view/format.js +47 -0
  93. package/dist/view/index.d.ts +11 -0
  94. package/dist/view/index.js +13 -0
  95. package/dist/view/lines.d.ts +9 -0
  96. package/dist/view/lines.js +12 -0
  97. package/dist/view/path-test.d.ts +3 -0
  98. package/dist/view/path-test.js +7 -0
  99. package/dist/view/sections/catalog.d.ts +9 -0
  100. package/dist/view/sections/catalog.js +47 -0
  101. package/dist/view/sections/extensions.d.ts +1 -0
  102. package/dist/view/sections/extensions.js +48 -0
  103. package/dist/view/sections/hooks.d.ts +1 -0
  104. package/dist/view/sections/hooks.js +32 -0
  105. package/dist/view/sections/index.d.ts +6 -0
  106. package/dist/view/sections/index.js +27 -0
  107. package/dist/view/sections/instructions.d.ts +6 -0
  108. package/dist/view/sections/instructions.js +38 -0
  109. package/dist/view/sections/mcp.d.ts +3 -0
  110. package/dist/view/sections/mcp.js +38 -0
  111. package/dist/view/sections/memory.d.ts +1 -0
  112. package/dist/view/sections/memory.js +31 -0
  113. package/dist/view/sections/permissions.d.ts +17 -0
  114. package/dist/view/sections/permissions.js +87 -0
  115. package/dist/view/sections/settings.d.ts +1 -0
  116. package/dist/view/sections/settings.js +33 -0
  117. package/dist/view/sections/tools.d.ts +6 -0
  118. package/dist/view/sections/tools.js +57 -0
  119. package/dist/view/sections/types.d.ts +123 -0
  120. package/dist/view/sections/types.js +10 -0
  121. package/dist/view/settings.d.ts +12 -0
  122. package/dist/view/settings.js +31 -0
  123. package/dist/view/usage.d.ts +8 -0
  124. package/dist/view/usage.js +18 -0
  125. package/package.json +13 -2
  126. package/dist/server/scan/files.js +0 -480
  127. package/dist/server/scan/path-test.js +0 -136
  128. package/dist/server/schema.js +0 -15
  129. package/dist/server/secrets.js +0 -63
  130. package/dist/ui/assets/index-Dg9dxngR.js +0 -60
@@ -6,7 +6,7 @@
6
6
  <meta name="color-scheme" content="light dark" />
7
7
  <title>ccdesk</title>
8
8
  <link rel="icon" href="/logo.png" type="image/png" />
9
- <script type="module" crossorigin src="/assets/index-Dg9dxngR.js"></script>
9
+ <script type="module" crossorigin src="/assets/index-CH1HoC38.js"></script>
10
10
  <link rel="stylesheet" crossorigin href="/assets/index-D5XqP5c_.css">
11
11
  </head>
12
12
  <body>
@@ -0,0 +1,16 @@
1
+ type DiffLine = {
2
+ kind: "same" | "add" | "remove";
3
+ text: string;
4
+ };
5
+ /**
6
+ * Line diff: lines shared at the start and end are matched directly, and the changed middle by
7
+ * longest common subsequence, unless it is too large for that.
8
+ */
9
+ export declare function diffLines(before: string, after: string): DiffLine[];
10
+ type DiffRow = DiffLine | {
11
+ kind: "skip";
12
+ count: number;
13
+ };
14
+ /** Keeps `context` unchanged lines around each change and folds longer unchanged runs. */
15
+ export declare function withContext(lines: DiffLine[], context?: number): DiffRow[];
16
+ export {};
@@ -0,0 +1,85 @@
1
+ function splitLines(text) {
2
+ return text === "" ? [] : text.replace(/\n$/, "").split("\n");
3
+ }
4
+ /** Above this many line pairs the changed middle is shown as removed, then added. */
5
+ const MAX_CELLS = 4_000_000;
6
+ /**
7
+ * Line diff: lines shared at the start and end are matched directly, and the changed middle by
8
+ * longest common subsequence, unless it is too large for that.
9
+ */
10
+ export function diffLines(before, after) {
11
+ const a = splitLines(before);
12
+ const b = splitLines(after);
13
+ let start = 0;
14
+ while (start < a.length && start < b.length && a[start] === b[start])
15
+ start++;
16
+ let end = 0;
17
+ while (end < a.length - start &&
18
+ end < b.length - start &&
19
+ a[a.length - 1 - end] === b[b.length - 1 - end])
20
+ end++;
21
+ const same = (text) => ({ kind: "same", text });
22
+ return [
23
+ ...a.slice(0, start).map(same),
24
+ ...middle(a.slice(start, a.length - end), b.slice(start, b.length - end)),
25
+ ...a.slice(a.length - end).map(same),
26
+ ];
27
+ }
28
+ function middle(a, b) {
29
+ if ((a.length + 1) * (b.length + 1) > MAX_CELLS)
30
+ return [
31
+ ...a.map((text) => ({ kind: "remove", text })),
32
+ ...b.map((text) => ({ kind: "add", text })),
33
+ ];
34
+ const width = b.length + 1;
35
+ // lcs[i * width + j] is the common length of a[i..] and b[j..].
36
+ const lcs = new Int32Array((a.length + 1) * width);
37
+ const at = (row, col) => lcs[row * width + col] ?? 0;
38
+ for (let i = a.length - 1; i >= 0; i--) {
39
+ for (let j = b.length - 1; j >= 0; j--) {
40
+ lcs[i * width + j] =
41
+ a[i] === b[j] ? at(i + 1, j + 1) + 1 : Math.max(at(i + 1, j), at(i, j + 1));
42
+ }
43
+ }
44
+ const out = [];
45
+ let i = 0;
46
+ let j = 0;
47
+ while (i < a.length || j < b.length) {
48
+ if (i < a.length && j < b.length && a[i] === b[j]) {
49
+ out.push({ kind: "same", text: a[i] ?? "" });
50
+ i++;
51
+ j++;
52
+ }
53
+ else if (j < b.length && (i === a.length || at(i, j + 1) > at(i + 1, j))) {
54
+ out.push({ kind: "add", text: b[j] ?? "" });
55
+ j++;
56
+ }
57
+ else {
58
+ out.push({ kind: "remove", text: a[i] ?? "" });
59
+ i++;
60
+ }
61
+ }
62
+ return out;
63
+ }
64
+ /** Keeps `context` unchanged lines around each change and folds longer unchanged runs. */
65
+ export function withContext(lines, context = 3) {
66
+ // Distance to the nearest change, in lines, looking both ways.
67
+ const distance = lines.map((l) => (l.kind === "same" ? Number.POSITIVE_INFINITY : 0));
68
+ for (let i = 1; i < lines.length; i++)
69
+ distance[i] = Math.min(distance[i] ?? 0, (distance[i - 1] ?? 0) + 1);
70
+ for (let i = lines.length - 2; i >= 0; i--)
71
+ distance[i] = Math.min(distance[i] ?? 0, (distance[i + 1] ?? 0) + 1);
72
+ const out = [];
73
+ lines.forEach((line, i) => {
74
+ if ((distance[i] ?? 0) <= context) {
75
+ out.push(line);
76
+ return;
77
+ }
78
+ const last = out[out.length - 1];
79
+ if (last?.kind === "skip")
80
+ last.count++;
81
+ else
82
+ out.push({ kind: "skip", count: 1 });
83
+ });
84
+ return out;
85
+ }
@@ -0,0 +1,17 @@
1
+ /** Paths shown the way you read them: under your home folder as `~`. */
2
+ export interface Formatter {
3
+ /** `/home/me/projects/app` → `~/projects/app`. */
4
+ tilde(path: string): string;
5
+ /** Path relative to `base` when inside it, otherwise the tilde form. */
6
+ relativeTo(path: string, base: string): string;
7
+ /** Every home path inside free text (a hook command, an MCP target) in its tilde form. */
8
+ tildeAll(text: string): string;
9
+ /** True for a file reached through a symlink into ~/.agents, which other coding agents read too. */
10
+ sharedWithAgents(realPath: string | undefined): boolean;
11
+ }
12
+ /** A formatter for `home`; with an empty home, paths are shown in full. */
13
+ export declare function formatter(home: string): Formatter;
14
+ export declare function ago(iso: string | null): string;
15
+ /** A one-line value for a table cell: JSON, minus the quotes on plain keys, with spaces to read by. */
16
+ export declare function preview(value: unknown): string;
17
+ export declare function plural(n: number, word: string, many?: string): string;
@@ -0,0 +1,47 @@
1
+ /** True when `path` is `base` or inside it, with / or Windows \\ separators. */
2
+ function under(path, base) {
3
+ return path.startsWith(`${base}/`) || path.startsWith(`${base}\\`);
4
+ }
5
+ /** A formatter for `home`; with an empty home, paths are shown in full. */
6
+ export function formatter(home) {
7
+ const tilde = (path) => home && (path === home || under(path, home)) ? `~${path.slice(home.length)}` : path;
8
+ return {
9
+ tilde,
10
+ relativeTo: (path, base) => (under(path, base) ? path.slice(base.length + 1) : tilde(path)),
11
+ tildeAll: (text) => home ? text.replaceAll(`${home}/`, "~/").replaceAll(`${home}\\`, "~\\") : text,
12
+ sharedWithAgents: (realPath) => realPath !== undefined && /^~[/\\]\.agents[/\\]/.test(tilde(realPath)),
13
+ };
14
+ }
15
+ export function ago(iso) {
16
+ if (!iso)
17
+ return "never";
18
+ const seconds = (Date.now() - new Date(iso).getTime()) / 1000;
19
+ if (seconds < 90)
20
+ return "just now";
21
+ if (seconds < 3600)
22
+ return `${Math.round(seconds / 60)}m ago`;
23
+ if (seconds < 86400)
24
+ return `${Math.round(seconds / 3600)}h ago`;
25
+ if (seconds < 86400 * 60)
26
+ return `${Math.round(seconds / 86400)}d ago`;
27
+ return new Date(iso).toLocaleDateString();
28
+ }
29
+ const PLAIN_KEY = /^[A-Za-z_$][\w$@.-]*$/;
30
+ /** A one-line value for a table cell: JSON, minus the quotes on plain keys, with spaces to read by. */
31
+ export function preview(value) {
32
+ if (typeof value === "string")
33
+ return value;
34
+ return compact(value);
35
+ }
36
+ function compact(value) {
37
+ if (Array.isArray(value))
38
+ return `[${value.map(compact).join(", ")}]`;
39
+ if (value && typeof value === "object") {
40
+ const entries = Object.entries(value).map(([k, v]) => `${PLAIN_KEY.test(k) ? k : JSON.stringify(k)}: ${compact(v)}`);
41
+ return entries.length ? `{ ${entries.join(", ")} }` : "{}";
42
+ }
43
+ return JSON.stringify(value) ?? String(value);
44
+ }
45
+ export function plural(n, word, many = `${word}s`) {
46
+ return `${n} ${n === 1 ? word : many}`;
47
+ }
@@ -0,0 +1,11 @@
1
+ export { diffLines, withContext } from "./diff.ts";
2
+ export { ago, type Formatter, formatter, plural, preview } from "./format.ts";
3
+ export { LINES, type Line, lineName } from "./lines.ts";
4
+ export { valueHere } from "./sections/catalog.ts";
5
+ export { MATCH_KIND } from "./path-test.ts";
6
+ export { buildRows, SECTIONS } from "./sections/index.ts";
7
+ export { isSuggested, unused } from "./sections/permissions.ts";
8
+ export { callsOf, outcome } from "./sections/tools.ts";
9
+ export type { Column, EntryKind, Facet, Row, RowOf, SectionId, ViewContext, ViewSection, } from "./sections/types.ts";
10
+ export { type FieldKind, fieldKind, valuesIn } from "./settings.ts";
11
+ export { sessionsHere, suggestedRules } from "./usage.ts";
@@ -0,0 +1,13 @@
1
+ // What a front end shows from a DirectoryView, without React or the browser: each section's rows,
2
+ // columns, filter text and labels, and the formatting they use. The page in src/ui adds the cells
3
+ // and the inspector; a terminal UI can draw the same rows. See ARCHITECTURE.md.
4
+ export { diffLines, withContext } from "./diff.js";
5
+ export { ago, formatter, plural, preview } from "./format.js";
6
+ export { LINES, lineName } from "./lines.js";
7
+ export { valueHere } from "./sections/catalog.js";
8
+ export { MATCH_KIND } from "./path-test.js";
9
+ export { buildRows, SECTIONS } from "./sections/index.js";
10
+ export { isSuggested, unused } from "./sections/permissions.js";
11
+ export { callsOf, outcome } from "./sections/tools.js";
12
+ export { fieldKind, valuesIn } from "./settings.js";
13
+ export { sessionsHere, suggestedRules } from "./usage.js";
@@ -0,0 +1,9 @@
1
+ import { type Scope } from "../shared/types.ts";
2
+ /** A scope on the route strip. Auto memory and plugins get their own lines. */
3
+ export type Line = Scope | "memory" | "plugin";
4
+ export declare const LINES: Array<{
5
+ line: Line;
6
+ letter: string;
7
+ name: string;
8
+ }>;
9
+ export declare function lineName(line: Line): string;
@@ -0,0 +1,12 @@
1
+ import {} from "../shared/types.js";
2
+ export const LINES = [
3
+ { line: "managed", letter: "M", name: "Managed" },
4
+ { line: "user", letter: "U", name: "User" },
5
+ { line: "project", letter: "P", name: "Project" },
6
+ { line: "local", letter: "L", name: "Local" },
7
+ { line: "memory", letter: "A", name: "Auto memory" },
8
+ { line: "plugin", letter: "X", name: "Plugin" },
9
+ ];
10
+ export function lineName(line) {
11
+ return LINES.find((l) => l.line === line)?.name ?? line;
12
+ }
@@ -0,0 +1,3 @@
1
+ import { type PathMatch } from "../shared/types.ts";
2
+ /** What each kind of path-test match is called, on the page and by `ccdesk explain`. */
3
+ export declare const MATCH_KIND: Record<PathMatch["kind"], string>;
@@ -0,0 +1,7 @@
1
+ import {} from "../shared/types.js";
2
+ /** What each kind of path-test match is called, on the page and by `ccdesk explain`. */
3
+ export const MATCH_KIND = {
4
+ rule: "path rule",
5
+ skill: "skill",
6
+ nested: "folder instructions",
7
+ };
@@ -0,0 +1,9 @@
1
+ import { type DirectoryView, type EffectiveSetting } from "../../shared/types.ts";
2
+ /** The value a key has here: a top-level setting, or a field inside one such as sandbox.network.allowedDomains. */
3
+ export declare function valueHere(view: DirectoryView, key: string): {
4
+ set?: EffectiveSetting;
5
+ value?: unknown;
6
+ };
7
+ /** Docs Markdown kept to one line of plain text, for a table cell. */
8
+ export declare function plain(text: string): string;
9
+ export declare const catalog: import("./types.ts").ViewSection<import("./types.ts").Row>;
@@ -0,0 +1,47 @@
1
+ import {} from "../../shared/types.js";
2
+ import {} from "../lines.js";
3
+ import { defineSection } from "./types.js";
4
+ /** The value a key has here: a top-level setting, or a field inside one such as sandbox.network.allowedDomains. */
5
+ export function valueHere(view, key) {
6
+ const [top = "", ...path] = key.split(".");
7
+ const set = view.effective.find((s) => s.key === top && s.from.length > 0);
8
+ if (!set)
9
+ return {};
10
+ let value = set.value;
11
+ for (const field of path)
12
+ value =
13
+ typeof value === "object" && value !== null
14
+ ? value[field]
15
+ : undefined;
16
+ return value === undefined ? {} : { set, value };
17
+ }
18
+ /** Docs Markdown kept to one line of plain text, for a table cell. */
19
+ export function plain(text) {
20
+ return text.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1").replace(/[`*]/g, "");
21
+ }
22
+ export const catalog = defineSection({
23
+ id: "catalog",
24
+ title: "All settings",
25
+ rows: (view, { reference }) => reference.catalog.map((item) => {
26
+ const { set } = valueHere(view, item.key);
27
+ const line = set?.from.at(-1) ?? null;
28
+ return { id: `cat:${item.key}`, section: "catalog", line, item, ...(set ? { set } : {}) };
29
+ }),
30
+ columns: [
31
+ { label: "", sort: (r) => (r.set ? 0 : 1) },
32
+ { label: "Setting", sort: (r) => r.item.key },
33
+ { label: "What it does" },
34
+ { label: "Files", sort: (r) => -r.item.scopes.length },
35
+ { label: "Here", sort: (r) => (r.set ? 0 : 1) },
36
+ ],
37
+ empty: () => "ccdesk has no list of Claude Code's settings.",
38
+ text: (row) => `${row.item.key} ${plain(row.item.summary)} ${row.item.category}`,
39
+ label: (row) => `setting ${row.item.key} (all settings)`,
40
+ heading: (row) => row.item.key,
41
+ facet: {
42
+ label: "Filter settings by area",
43
+ of: (row) => row.item.category,
44
+ // Categories in the order the settings reference lists them.
45
+ order: ({ reference }) => [...new Set(reference.catalog.map((s) => s.category))],
46
+ },
47
+ });
@@ -0,0 +1 @@
1
+ export declare const extensions: import("./types.ts").ViewSection<import("./types.ts").Row>;
@@ -0,0 +1,48 @@
1
+ import { defineSection } from "./types.js";
2
+ const KINDS = ["agent", "skill", "command", "output style", "plugin"];
3
+ export const extensions = defineSection({
4
+ id: "extensions",
5
+ title: "Agents, skills & plugins",
6
+ short: "Extensions",
7
+ rows: (view) => {
8
+ const inv = view.inventory;
9
+ const entries = [
10
+ ["agent", inv.agents],
11
+ ["skill", inv.skills],
12
+ ["command", inv.commands],
13
+ ["output style", inv.outputStyles],
14
+ ];
15
+ return [
16
+ ...entries.flatMap(([kind, list]) => list.map((item) => ({
17
+ id: `ent:${kind}:${item.path}`,
18
+ section: "extensions",
19
+ line: item.scope,
20
+ item,
21
+ kind,
22
+ }))),
23
+ ...inv.plugins.map((item) => ({
24
+ id: `plug:${item.id}`,
25
+ section: "extensions",
26
+ line: "plugin",
27
+ item,
28
+ kind: "plugin",
29
+ })),
30
+ ];
31
+ },
32
+ columns: [
33
+ { label: "", sort: (r) => r.line },
34
+ { label: "Kind", sort: (r) => KINDS.indexOf(r.kind) },
35
+ { label: "Name", sort: (r) => (r.kind === "plugin" ? r.item.id : (r.item.id ?? r.item.name)) },
36
+ { label: "Description" },
37
+ ],
38
+ empty: () => "No subagents, skills, commands or plugins.",
39
+ dimmed: (row) => row.kind === "plugin"
40
+ ? !row.item.enabled
41
+ : Boolean(row.item.shadowedBy || row.item.skipped) || row.item.override === "off",
42
+ text: (row) => row.kind === "plugin"
43
+ ? `plugin ${row.item.id}`
44
+ : `${row.kind} ${row.item.id ?? row.item.name} ${row.item.description}`,
45
+ label: (row) => row.kind === "plugin" ? `plugin ${row.item.id}` : `${row.kind} ${row.item.id ?? row.item.name}`,
46
+ heading: (row) => (row.kind === "plugin" ? row.item.id : (row.item.id ?? row.item.name)),
47
+ facet: { label: "Filter by kind", of: (row) => row.kind, order: () => KINDS },
48
+ });
@@ -0,0 +1 @@
1
+ export declare const hooks: import("./types.ts").ViewSection<import("./types.ts").Row>;
@@ -0,0 +1,32 @@
1
+ import { defineSection } from "./types.js";
2
+ export const hooks = defineSection({
3
+ id: "hooks",
4
+ title: "Hooks",
5
+ rows: (view) => {
6
+ // Named by what the hook is, so a selection survives hooks added above it.
7
+ const seen = new Map();
8
+ return view.inventory.hooks.map((item) => {
9
+ const key = `hook:${item.sourcePath}:${item.event}:${item.matcher ?? ""}:${item.command}`;
10
+ const count = seen.get(key) ?? 0;
11
+ seen.set(key, count + 1);
12
+ return {
13
+ id: count === 0 ? key : `${key}:${count}`,
14
+ section: "hooks",
15
+ line: item.source,
16
+ item,
17
+ };
18
+ });
19
+ },
20
+ columns: [
21
+ { label: "", sort: (r) => r.item.source },
22
+ { label: "Event", sort: (r) => r.item.event },
23
+ { label: "Matcher", sort: (r) => r.item.matcher ?? "" },
24
+ { label: "Runs" },
25
+ ],
26
+ empty: () => "No hooks run here.",
27
+ dimmed: (row) => !row.item.active,
28
+ text: (row) => `${row.item.event} ${row.item.matcher ?? ""} ${row.item.command}`,
29
+ label: (row) => `${row.item.event} hook: ${row.item.command}`,
30
+ heading: (row) => `${row.item.event} hook`,
31
+ facet: { label: "Filter hooks by event", of: (row) => row.item.event },
32
+ });
@@ -0,0 +1,6 @@
1
+ import { type DirectoryView } from "../../shared/types.ts";
2
+ import { type Row, type ViewContext, type ViewSection } from "./types.ts";
3
+ /** The sections of a directory page, in page order. */
4
+ export declare const SECTIONS: ViewSection<Row>[];
5
+ /** Every section's rows for one directory, in page order. */
6
+ export declare function buildRows(view: DirectoryView, context: ViewContext): Row[];
@@ -0,0 +1,27 @@
1
+ import {} from "../../shared/types.js";
2
+ import { catalog } from "./catalog.js";
3
+ import { extensions } from "./extensions.js";
4
+ import { hooks } from "./hooks.js";
5
+ import { instructions } from "./instructions.js";
6
+ import { mcp } from "./mcp.js";
7
+ import { memory } from "./memory.js";
8
+ import { permissions } from "./permissions.js";
9
+ import { settings } from "./settings.js";
10
+ import { tools } from "./tools.js";
11
+ import {} from "./types.js";
12
+ /** The sections of a directory page, in page order. */
13
+ export const SECTIONS = [
14
+ instructions,
15
+ settings,
16
+ permissions,
17
+ tools,
18
+ memory,
19
+ hooks,
20
+ mcp,
21
+ extensions,
22
+ catalog,
23
+ ];
24
+ /** Every section's rows for one directory, in page order. */
25
+ export function buildRows(view, context) {
26
+ return SECTIONS.flatMap((s) => s.rows(view, context));
27
+ }
@@ -0,0 +1,6 @@
1
+ export declare const STATUS: {
2
+ readonly loaded: "loaded";
3
+ readonly "on-match": "on match";
4
+ readonly skipped: "skipped";
5
+ };
6
+ export declare const instructions: import("./types.ts").ViewSection<import("./types.ts").Row>;
@@ -0,0 +1,38 @@
1
+ import { defineSection } from "./types.js";
2
+ export const STATUS = { loaded: "loaded", "on-match": "on match", skipped: "skipped" };
3
+ export const instructions = defineSection({
4
+ id: "instructions",
5
+ title: "Instructions",
6
+ rows: (view) => view.instructions.map((item, index) => ({
7
+ id: `ins:${item.path}`,
8
+ section: "instructions",
9
+ line: item.scope,
10
+ item,
11
+ order: index + 1,
12
+ })),
13
+ columns: [
14
+ { label: "#", sort: (r) => r.order },
15
+ { label: "" },
16
+ { label: "File", sort: (r) => r.item.path },
17
+ { label: "Status", sort: (r) => r.item.status },
18
+ { label: "Why" },
19
+ { label: "Lines", sort: (r) => r.item.lines },
20
+ { label: "Visibility", sort: (r) => r.item.visibility ?? "" },
21
+ ],
22
+ empty: () => "No CLAUDE.md, AGENTS.md or rules apply here.",
23
+ dimmed: (row) => row.item.status === "skipped",
24
+ text: (row) => `${row.item.path} ${row.item.reason}`,
25
+ label: (row, { format }) => format.tilde(row.item.path),
26
+ // The file and its folder, or the whole ~ path when that is as short: ~/.claude/CLAUDE.md is
27
+ // your user file, while .claude/CLAUDE.md would read as the project's.
28
+ heading: (row, { format }) => {
29
+ const short = format.tilde(row.item.path);
30
+ const parts = short.split(/[/\\]/);
31
+ return parts.length <= 3 ? short : parts.slice(-2).join("/");
32
+ },
33
+ facet: {
34
+ label: "Filter instructions by status",
35
+ of: (row) => STATUS[row.item.status],
36
+ order: () => Object.values(STATUS),
37
+ },
38
+ });
@@ -0,0 +1,3 @@
1
+ import { type McpStatus } from "../../shared/types.ts";
2
+ export declare const STATUS: Record<McpStatus, string>;
3
+ export declare const mcp: import("./types.ts").ViewSection<import("./types.ts").Row>;
@@ -0,0 +1,38 @@
1
+ import {} from "../../shared/types.js";
2
+ import { defineSection } from "./types.js";
3
+ export const STATUS = {
4
+ active: "configured",
5
+ pending: "needs approval",
6
+ shadowed: "overridden",
7
+ disabled: "turned off",
8
+ rejected: "rejected",
9
+ blocked: "blocked",
10
+ invalid: "invalid",
11
+ };
12
+ export const mcp = defineSection({
13
+ id: "mcp",
14
+ title: "MCP servers",
15
+ rows: (view) => view.inventory.mcpServers.map((item) => ({
16
+ id: `mcp:${item.scope}:${item.plugin ?? ""}:${item.name}`,
17
+ section: "mcp",
18
+ line: item.scope,
19
+ item,
20
+ })),
21
+ columns: [
22
+ { label: "", sort: (r) => r.item.scope },
23
+ { label: "Server", sort: (r) => r.item.name },
24
+ { label: "Transport", sort: (r) => r.item.transport },
25
+ { label: "Target" },
26
+ { label: "Status", sort: (r) => Object.keys(STATUS).indexOf(r.item.status) },
27
+ ],
28
+ empty: () => "No MCP servers are configured for this directory.",
29
+ dimmed: (row) => row.item.status !== "active",
30
+ text: (row) => `${row.item.name} ${row.item.target}`,
31
+ label: (row) => `MCP ${row.item.name}`,
32
+ heading: (row) => row.item.name,
33
+ facet: {
34
+ label: "Filter MCP servers by status",
35
+ of: (row) => STATUS[row.item.status],
36
+ order: () => Object.values(STATUS),
37
+ },
38
+ });
@@ -0,0 +1 @@
1
+ export declare const memory: import("./types.ts").ViewSection<import("./types.ts").Row>;
@@ -0,0 +1,31 @@
1
+ import {} from "../../shared/types.js";
2
+ import { defineSection } from "./types.js";
3
+ const MEMORY_TYPES = ["user", "feedback", "project", "reference", "unknown"];
4
+ export const memory = defineSection({
5
+ id: "memory",
6
+ title: "Memory",
7
+ rows: (view) => view.memory.memories.map((item) => ({
8
+ id: `mem:${item.path}`,
9
+ section: "memory",
10
+ line: "memory",
11
+ item,
12
+ })),
13
+ columns: [
14
+ { label: "" },
15
+ { label: "Name", sort: (r) => r.item.name },
16
+ { label: "Type", sort: (r) => MEMORY_TYPES.indexOf(r.item.type) },
17
+ { label: "Description" },
18
+ {
19
+ label: "Checks",
20
+ sort: (r) => Number(!r.item.inIndex) * 10 + r.item.brokenLinks.length,
21
+ },
22
+ { label: "Updated", sort: (r) => r.item.mtime },
23
+ ],
24
+ empty: (view) => view.memory.index
25
+ ? "MEMORY.md is here, but no memory files."
26
+ : "Claude hasn't saved any memory for this directory yet.",
27
+ text: (row) => `${row.item.name} ${row.item.description} ${row.item.type} ${row.item.body}`,
28
+ label: (row) => `memory ${row.item.name}`,
29
+ heading: (row) => row.item.name,
30
+ facet: { label: "Filter memory by type", of: (row) => row.item.type, order: () => MEMORY_TYPES },
31
+ });
@@ -0,0 +1,17 @@
1
+ import { type Permission, type Usage } from "../../shared/types.ts";
2
+ import { type RowOf } from "./types.ts";
3
+ type R = RowOf<"permissions">;
4
+ export type Suggested = Extract<R, {
5
+ suggested: true;
6
+ }>;
7
+ export type Rule = Extract<Permission, {
8
+ kind: "rule";
9
+ }>;
10
+ export declare function isSuggested(row: R): row is Suggested;
11
+ /**
12
+ * An allow rule in effect that covered none of the calls counted here. Ask rules are not flagged:
13
+ * they guard calls, so one that never fired is no reason to remove it.
14
+ */
15
+ export declare function unused(rule: Permission, usage: Usage | null | undefined): rule is Rule;
16
+ export declare const permissions: import("./types.ts").ViewSection<import("./types.ts").Row>;
17
+ export {};