ccdesk 0.2.1 → 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.
- package/CHANGELOG.md +54 -0
- package/README.md +9 -5
- package/dist/core/agent-edit.d.ts +6 -0
- package/dist/{server → core}/agent-edit.js +21 -22
- package/dist/core/backups.d.ts +19 -0
- package/dist/{server → core}/backups.js +37 -45
- package/dist/core/core.d.ts +96 -0
- package/dist/core/core.js +57 -0
- package/dist/core/index.d.ts +22 -0
- package/dist/core/index.js +38 -0
- package/dist/core/mcp-edit.d.ts +25 -0
- package/dist/{server → core}/mcp-edit.js +116 -95
- package/dist/core/mutations.d.ts +20 -0
- package/dist/{server → core}/mutations.js +8 -15
- package/dist/core/open.d.ts +31 -0
- package/dist/{server → core}/open.js +39 -12
- package/dist/core/parse-edit.d.ts +12 -0
- package/dist/core/parse-edit.js +64 -0
- package/dist/core/reference.d.ts +3 -0
- package/dist/core/reference.js +11 -0
- package/dist/core/scan/compare.d.ts +7 -0
- package/dist/{server → core}/scan/compare.js +5 -3
- package/dist/core/scan/context.d.ts +13 -0
- package/dist/{server → core}/scan/context.js +58 -32
- package/dist/core/scan/data.d.ts +8 -0
- package/dist/core/scan/data.js +18 -0
- package/dist/core/scan/env.d.ts +26 -0
- package/dist/{server → core}/scan/env.js +10 -3
- package/dist/core/scan/files.d.ts +68 -0
- package/dist/core/scan/files.js +270 -0
- package/dist/core/scan/git.d.ts +14 -0
- package/dist/core/scan/git.js +98 -0
- package/dist/core/scan/index.d.ts +5 -0
- package/dist/{server → core}/scan/index.js +115 -81
- package/dist/core/scan/instructions.d.ts +54 -0
- package/dist/{server → core}/scan/instructions.js +141 -73
- package/dist/core/scan/inventory.d.ts +38 -0
- package/dist/{server → core}/scan/inventory.js +134 -115
- package/dist/core/scan/mcp.d.ts +28 -0
- package/dist/{server → core}/scan/mcp.js +151 -94
- package/dist/core/scan/memory.d.ts +8 -0
- package/dist/{server → core}/scan/memory.js +8 -8
- package/dist/core/scan/path-test.d.ts +9 -0
- package/dist/core/scan/path-test.js +156 -0
- package/dist/core/scan/permissions.d.ts +31 -0
- package/dist/{server → core}/scan/permissions.js +6 -9
- package/dist/core/scan/plugins.d.ts +39 -0
- package/dist/{server → core}/scan/plugins.js +36 -27
- package/dist/core/scan/projects.d.ts +9 -0
- package/dist/{server → core}/scan/projects.js +5 -4
- package/dist/core/scan/sandbox.d.ts +14 -0
- package/dist/{server → core}/scan/sandbox.js +87 -59
- package/dist/core/scan/settings.d.ts +35 -0
- package/dist/{server → core}/scan/settings.js +88 -92
- package/dist/core/scan/tools.d.ts +10 -0
- package/dist/{server → core}/scan/tools.js +28 -20
- package/dist/core/scan/usage.d.ts +25 -0
- package/dist/{server → core}/scan/usage.js +117 -92
- package/dist/core/schema.d.ts +8 -0
- package/dist/core/schema.js +67 -0
- package/dist/core/secrets.d.ts +44 -0
- package/dist/core/secrets.js +187 -0
- package/dist/core/settings-edit.d.ts +9 -0
- package/dist/core/settings-edit.js +216 -0
- package/dist/core/watch.d.ts +45 -0
- package/dist/{server → core}/watch.js +129 -20
- package/dist/core/write.d.ts +119 -0
- package/dist/{server → core}/write.js +63 -8
- package/dist/server/args.d.ts +32 -0
- package/dist/server/args.js +106 -0
- package/dist/server/cli.d.ts +2 -0
- package/dist/server/cli.js +58 -83
- package/dist/server/commands.d.ts +8 -0
- package/dist/server/commands.js +39 -0
- package/dist/server/server.d.ts +12 -0
- package/dist/server/server.js +80 -195
- package/dist/server/start.d.ts +13 -0
- package/dist/server/start.js +21 -0
- package/dist/shared/compare.d.ts +25 -0
- package/dist/shared/memory.d.ts +2 -0
- package/dist/shared/requests.d.ts +104 -0
- package/dist/shared/requests.js +73 -0
- package/dist/shared/scopes.d.ts +10 -0
- package/dist/shared/settings-catalog.json +2199 -0
- package/dist/shared/types.d.ts +530 -0
- package/dist/shared/types.js +11 -0
- package/dist/ui/assets/index-CH1HoC38.js +60 -0
- package/dist/ui/index.html +1 -1
- package/dist/view/diff.d.ts +16 -0
- package/dist/view/diff.js +85 -0
- package/dist/view/format.d.ts +17 -0
- package/dist/view/format.js +47 -0
- package/dist/view/index.d.ts +11 -0
- package/dist/view/index.js +13 -0
- package/dist/view/lines.d.ts +9 -0
- package/dist/view/lines.js +12 -0
- package/dist/view/path-test.d.ts +3 -0
- package/dist/view/path-test.js +7 -0
- package/dist/view/sections/catalog.d.ts +9 -0
- package/dist/view/sections/catalog.js +47 -0
- package/dist/view/sections/extensions.d.ts +1 -0
- package/dist/view/sections/extensions.js +48 -0
- package/dist/view/sections/hooks.d.ts +1 -0
- package/dist/view/sections/hooks.js +32 -0
- package/dist/view/sections/index.d.ts +6 -0
- package/dist/view/sections/index.js +27 -0
- package/dist/view/sections/instructions.d.ts +6 -0
- package/dist/view/sections/instructions.js +38 -0
- package/dist/view/sections/mcp.d.ts +3 -0
- package/dist/view/sections/mcp.js +38 -0
- package/dist/view/sections/memory.d.ts +1 -0
- package/dist/view/sections/memory.js +31 -0
- package/dist/view/sections/permissions.d.ts +17 -0
- package/dist/view/sections/permissions.js +87 -0
- package/dist/view/sections/settings.d.ts +1 -0
- package/dist/view/sections/settings.js +33 -0
- package/dist/view/sections/tools.d.ts +6 -0
- package/dist/view/sections/tools.js +57 -0
- package/dist/view/sections/types.d.ts +123 -0
- package/dist/view/sections/types.js +10 -0
- package/dist/view/settings.d.ts +12 -0
- package/dist/view/settings.js +31 -0
- package/dist/view/usage.d.ts +8 -0
- package/dist/view/usage.js +18 -0
- package/package.json +13 -2
- package/dist/server/scan/files.js +0 -478
- package/dist/server/scan/path-test.js +0 -121
- package/dist/server/schema.js +0 -15
- package/dist/server/secrets.js +0 -63
- package/dist/server/settings-edit.js +0 -221
- package/dist/ui/assets/index-XqJwI3k9.js +0 -60
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type EffectiveSetting, type Entry, type Inventory, type Problem, type Scope, type SessionProfile, type SettingsLayer, type Trust } from "../../shared/types.ts";
|
|
2
|
+
import { type ScanEnv } from "./env.ts";
|
|
3
|
+
type Source = Scope | "plugin";
|
|
4
|
+
interface ReadOptions {
|
|
5
|
+
plugin?: string | undefined;
|
|
6
|
+
/** Joins the folders of a file in a subfolder into its name; commands use ":". */
|
|
7
|
+
separator?: string;
|
|
8
|
+
/** Receives files that are there but cannot be read. */
|
|
9
|
+
problems?: Problem[] | undefined;
|
|
10
|
+
}
|
|
11
|
+
export declare function readSkill(env: ScanEnv, folder: string, scope: Source, { plugin, problems }?: ReadOptions): Promise<Entry | null>;
|
|
12
|
+
interface InventoryInput {
|
|
13
|
+
env: ScanEnv;
|
|
14
|
+
projectDir: string;
|
|
15
|
+
/** The repository (or worktree) root, where the search for project skills stops. */
|
|
16
|
+
gitRoot: string | null;
|
|
17
|
+
layers: SettingsLayer[];
|
|
18
|
+
effective: EffectiveSetting[];
|
|
19
|
+
/** Managed settings as written, nothing hidden: for checks that must see header values. */
|
|
20
|
+
managed: Record<string, unknown> | null;
|
|
21
|
+
claudeJson: Record<string, unknown> | null;
|
|
22
|
+
trust: Trust;
|
|
23
|
+
profile: SessionProfile;
|
|
24
|
+
/** Receives files that are there but cannot be read or parsed. */
|
|
25
|
+
problems: Problem[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* What Claude Code requires of a subagent file; it skips one that falls short, and treats one
|
|
29
|
+
* without a name as documentation (docs: sub-agents#subagent-files-claude-code-skips).
|
|
30
|
+
*/
|
|
31
|
+
export declare function agentProblems(text: string): string[];
|
|
32
|
+
/**
|
|
33
|
+
* Applies skillOverrides to your skills and command files; plugin skills are not affected
|
|
34
|
+
* (docs: skills#override-skill-visibility-from-settings).
|
|
35
|
+
*/
|
|
36
|
+
export declare function applySkillOverrides(entries: Entry[], overrides: unknown): void;
|
|
37
|
+
export declare function readInventory(input: InventoryInput): Promise<Inventory>;
|
|
38
|
+
export {};
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { basename, dirname, join, relative } from "node:path";
|
|
2
2
|
import { HIGHEST_FIRST } from "../../shared/scopes.js";
|
|
3
3
|
import {} from "../../shared/types.js";
|
|
4
|
+
import { redactSecrets, shownUrl } from "../secrets.js";
|
|
4
5
|
import {} from "./env.js";
|
|
5
|
-
import { isDir, isFile,
|
|
6
|
+
import { isDir, isFile, isWithin, listDir, readJsonOrReport, readTextFile, resolvedPath, stringField, toStringList, } from "./files.js";
|
|
7
|
+
import { isRecord, parseFrontmatter } from "./data.js";
|
|
6
8
|
import { ancestorsRootFirst } from "./instructions.js";
|
|
7
9
|
import { readMcpServers } from "./mcp.js";
|
|
8
10
|
import { manifestCommandMap, manifestConfigs, manifestPaths, pluginNamespace, readPlugins, } from "./plugins.js";
|
|
9
|
-
import { effectiveSettings } from "./settings.js";
|
|
10
11
|
function isTrue(value) {
|
|
11
12
|
return value === true || (typeof value === "string" && /^(true|yes|on|1)$/i.test(value));
|
|
12
13
|
}
|
|
@@ -14,8 +15,8 @@ function isTrue(value) {
|
|
|
14
15
|
* A `.md` file as an agent, command or output style. `fileName` is its name from its path, which a
|
|
15
16
|
* frontmatter `name` replaces, except for a command (docs: skills#how-a-skill-gets-its-command-name).
|
|
16
17
|
*/
|
|
17
|
-
async function readEntry(env, path, fileName, scope, { plugin, isCommand = false } = {}) {
|
|
18
|
-
const file = await readTextFile(env, path);
|
|
18
|
+
async function readEntry(env, path, fileName, scope, { plugin, isCommand = false, problems } = {}) {
|
|
19
|
+
const file = await readTextFile(env, path, problems);
|
|
19
20
|
if (!file)
|
|
20
21
|
return null;
|
|
21
22
|
const { data } = parseFrontmatter(file.text);
|
|
@@ -40,7 +41,7 @@ async function readEntry(env, path, fileName, scope, { plugin, isCommand = false
|
|
|
40
41
|
* `*.md` files in a folder and its subfolders. A file in a subfolder is named by its path, joined
|
|
41
42
|
* with `separator`: commands use ":" (`frontend/component.md` is `/frontend:component`).
|
|
42
43
|
*/
|
|
43
|
-
async function readFileEntries(env, dir, scope,
|
|
44
|
+
async function readFileEntries(env, dir, scope, options = {}, base = dir, depth = 0) {
|
|
44
45
|
const out = [];
|
|
45
46
|
for (const name of await listDir(dir)) {
|
|
46
47
|
if (name.startsWith("."))
|
|
@@ -48,16 +49,18 @@ async function readFileEntries(env, dir, scope, { plugin, separator = "/" } = {}
|
|
|
48
49
|
const path = join(dir, name);
|
|
49
50
|
if (await isDir(path)) {
|
|
50
51
|
if (depth < 4)
|
|
51
|
-
out.push(...(await readFileEntries(env, path, scope,
|
|
52
|
+
out.push(...(await readFileEntries(env, path, scope, options, base, depth + 1)));
|
|
52
53
|
continue;
|
|
53
54
|
}
|
|
54
55
|
if (!name.endsWith(".md"))
|
|
55
56
|
continue;
|
|
57
|
+
const separator = options.separator ?? "/";
|
|
56
58
|
const fileName = relative(base, path).replace(/\.md$/, "").split(/[/\\]/).join(separator);
|
|
57
59
|
// Commands are the entries joined with ":".
|
|
58
60
|
const entry = await readEntry(env, path, fileName, scope, {
|
|
59
|
-
plugin,
|
|
61
|
+
plugin: options.plugin,
|
|
60
62
|
isCommand: separator === ":",
|
|
63
|
+
problems: options.problems,
|
|
61
64
|
});
|
|
62
65
|
if (entry)
|
|
63
66
|
out.push(entry);
|
|
@@ -74,6 +77,7 @@ async function readPaths(env, paths, scope, options) {
|
|
|
74
77
|
const entry = await readEntry(env, path, basename(path, ".md"), scope, {
|
|
75
78
|
plugin: options.plugin,
|
|
76
79
|
isCommand: options.separator === ":",
|
|
80
|
+
problems: options.problems,
|
|
77
81
|
});
|
|
78
82
|
if (entry)
|
|
79
83
|
out.push(entry);
|
|
@@ -81,9 +85,9 @@ async function readPaths(env, paths, scope, options) {
|
|
|
81
85
|
}
|
|
82
86
|
return out;
|
|
83
87
|
}
|
|
84
|
-
export async function readSkill(env, folder, scope, plugin) {
|
|
88
|
+
export async function readSkill(env, folder, scope, { plugin, problems } = {}) {
|
|
85
89
|
const path = join(folder, "SKILL.md");
|
|
86
|
-
const file = await readTextFile(env, path);
|
|
90
|
+
const file = await readTextFile(env, path, problems);
|
|
87
91
|
if (!file)
|
|
88
92
|
return null;
|
|
89
93
|
const { data, body } = parseFrontmatter(file.text);
|
|
@@ -115,87 +119,104 @@ export async function readSkill(env, folder, scope, plugin) {
|
|
|
115
119
|
return entry;
|
|
116
120
|
}
|
|
117
121
|
/** `<name>/SKILL.md` folders, or one folder holding SKILL.md itself. */
|
|
118
|
-
async function readSkills(env, dir, scope,
|
|
119
|
-
if (plugin && (await isFile(join(dir, "SKILL.md")))) {
|
|
120
|
-
const skill = await readSkill(env, dir, scope,
|
|
122
|
+
async function readSkills(env, dir, scope, options) {
|
|
123
|
+
if (options.plugin && (await isFile(join(dir, "SKILL.md")))) {
|
|
124
|
+
const skill = await readSkill(env, dir, scope, options);
|
|
121
125
|
return skill ? [skill] : [];
|
|
122
126
|
}
|
|
123
127
|
const out = [];
|
|
124
128
|
for (const name of await listDir(dir)) {
|
|
125
129
|
if (name.startsWith(".") || (scope === "user" && name === "synced"))
|
|
126
130
|
continue;
|
|
127
|
-
const skill = await readSkill(env, join(dir, name), scope,
|
|
131
|
+
const skill = await readSkill(env, join(dir, name), scope, options);
|
|
128
132
|
if (skill)
|
|
129
133
|
out.push(skill);
|
|
130
134
|
}
|
|
131
135
|
return out;
|
|
132
136
|
}
|
|
133
137
|
/** A plugin's skills, agents, commands and output styles, from its folders and its manifest. */
|
|
134
|
-
async function pluginEntries(env,
|
|
135
|
-
const root = plugin.installPath;
|
|
136
|
-
const options = { plugin: plugin.id };
|
|
138
|
+
async function pluginEntries(env, loaded, problems) {
|
|
139
|
+
const root = loaded.plugin.installPath;
|
|
140
|
+
const options = { plugin: loaded.plugin.id, problems };
|
|
141
|
+
const namespace = pluginNamespace(loaded);
|
|
142
|
+
const listed = async (key, folder, separator = "/") => readPaths(env, (await manifestPaths(loaded, key)) ?? [join(root, folder)], "plugin", {
|
|
143
|
+
...options,
|
|
144
|
+
separator,
|
|
145
|
+
});
|
|
146
|
+
const skills = await pluginSkills(env, loaded, problems);
|
|
147
|
+
const commands = [
|
|
148
|
+
...(await listed("commands", "commands", ":")),
|
|
149
|
+
...(await manifestCommands(env, loaded, problems)),
|
|
150
|
+
];
|
|
151
|
+
// A plugin's skills and commands are called as plugin-name:name; a name that already has the
|
|
152
|
+
// prefix keeps it once (docs: skills#how-a-skill-gets-its-command-name).
|
|
153
|
+
for (const entry of [...skills, ...commands])
|
|
154
|
+
entry.id = entry.name.startsWith(`${namespace}:`) ? entry.name : `${namespace}:${entry.name}`;
|
|
155
|
+
return {
|
|
156
|
+
skills,
|
|
157
|
+
agents: await pluginAgents(env, loaded, problems),
|
|
158
|
+
commands,
|
|
159
|
+
outputStyles: await listed("outputStyles", "output-styles"),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/** A plugin's skills: its skills folder, the manifest's extra ones, or a SKILL.md at its root. */
|
|
163
|
+
async function pluginSkills(env, loaded, problems) {
|
|
164
|
+
const root = loaded.plugin.installPath;
|
|
165
|
+
const options = { plugin: loaded.plugin.id, problems };
|
|
137
166
|
// `skills` adds to the default folder; the other keys replace theirs (docs: plugins-reference).
|
|
138
|
-
const extraSkills = await manifestPaths(
|
|
167
|
+
const extraSkills = await manifestPaths(loaded, "skills");
|
|
139
168
|
const skillDirs = [join(root, "skills"), ...(extraSkills ?? [])];
|
|
140
169
|
const rootSkill = extraSkills === null && !(await isDir(join(root, "skills")));
|
|
141
|
-
const namespace = await pluginNamespace(env, plugin);
|
|
142
170
|
const skills = [];
|
|
143
171
|
if (rootSkill)
|
|
144
|
-
for (const skill of await readSkills(env, root, "plugin",
|
|
172
|
+
for (const skill of await readSkills(env, root, "plugin", options)) {
|
|
145
173
|
// Without a name, a root SKILL.md is named after the plugin (docs: skills#how-a-skill-gets-its-command-name).
|
|
146
174
|
if (skill.folder === root && !stringField(parseFrontmatter(skill.text).data, "name"))
|
|
147
|
-
skill.name =
|
|
175
|
+
skill.name = pluginNamespace(loaded);
|
|
148
176
|
skills.push(skill);
|
|
149
177
|
}
|
|
150
178
|
for (const dir of skillDirs)
|
|
151
|
-
skills.push(...(await readSkills(env, dir, "plugin",
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
// A plugin's skills and commands are called as plugin-name:name; a name that already has the
|
|
179
|
-
// prefix keeps it once (docs: skills#how-a-skill-gets-its-command-name).
|
|
180
|
-
for (const entry of [...skills, ...commands])
|
|
181
|
-
entry.id = entry.name.startsWith(`${namespace}:`) ? entry.name : `${namespace}:${entry.name}`;
|
|
182
|
-
// A plugin subagent is named with the plugin and its subfolders (docs: sub-agents#choose-the-subagent-scope).
|
|
179
|
+
skills.push(...(await readSkills(env, dir, "plugin", options)));
|
|
180
|
+
return skills;
|
|
181
|
+
}
|
|
182
|
+
/** The commands a plugin's manifest names with their own file or text. */
|
|
183
|
+
async function manifestCommands(env, loaded, problems) {
|
|
184
|
+
const plugin = loaded.plugin.id;
|
|
185
|
+
const commands = [];
|
|
186
|
+
for (const c of await manifestCommandMap(loaded)) {
|
|
187
|
+
const listed = {
|
|
188
|
+
name: c.name,
|
|
189
|
+
description: c.description,
|
|
190
|
+
path: c.path,
|
|
191
|
+
scope: "plugin",
|
|
192
|
+
plugin,
|
|
193
|
+
text: c.text ?? "",
|
|
194
|
+
};
|
|
195
|
+
const read = c.text === null
|
|
196
|
+
? await readEntry(env, c.path, c.name, "plugin", { plugin, problems, isCommand: true })
|
|
197
|
+
: null;
|
|
198
|
+
commands.push(read ?? listed);
|
|
199
|
+
}
|
|
200
|
+
return commands;
|
|
201
|
+
}
|
|
202
|
+
/** A plugin's subagents, named with the plugin and its subfolders (docs: sub-agents#choose-the-subagent-scope). */
|
|
203
|
+
async function pluginAgents(env, loaded, problems) {
|
|
204
|
+
const root = loaded.plugin.installPath;
|
|
205
|
+
const namespace = pluginNamespace(loaded);
|
|
183
206
|
const agents = [];
|
|
184
|
-
for (const path of (await manifestPaths(
|
|
207
|
+
for (const path of (await manifestPaths(loaded, "agents")) ?? [join(root, "agents")]) {
|
|
185
208
|
const folder = (await isDir(path)) ? path : null;
|
|
186
|
-
for (const agent of await readPaths(env, [path], "plugin",
|
|
209
|
+
for (const agent of await readPaths(env, [path], "plugin", {
|
|
210
|
+
plugin: loaded.plugin.id,
|
|
211
|
+
problems,
|
|
212
|
+
})) {
|
|
187
213
|
const subfolders = folder ? relative(folder, dirname(agent.path)).split(/[/\\]/) : [];
|
|
188
214
|
const own = agent.name.split("/").at(-1) ?? agent.name;
|
|
189
215
|
agent.id = [namespace, ...subfolders.filter(Boolean), own].join(":");
|
|
190
216
|
agents.push(agent);
|
|
191
217
|
}
|
|
192
218
|
}
|
|
193
|
-
return
|
|
194
|
-
skills,
|
|
195
|
-
agents,
|
|
196
|
-
commands,
|
|
197
|
-
outputStyles: await listed("outputStyles", "output-styles"),
|
|
198
|
-
};
|
|
219
|
+
return agents;
|
|
199
220
|
}
|
|
200
221
|
/**
|
|
201
222
|
* The `.claude/<sub>` folders of the start directory and every parent up to the repository root
|
|
@@ -229,7 +250,7 @@ export function agentProblems(text) {
|
|
|
229
250
|
* yours. A name defined again lower down is marked as shadowed (docs: sub-agents#choose-the-subagent-scope).
|
|
230
251
|
* A file Claude Code skips takes no part in that.
|
|
231
252
|
*/
|
|
232
|
-
async function readAgents(env, projectDir, gitRoot) {
|
|
253
|
+
async function readAgents(env, projectDir, gitRoot, problems) {
|
|
233
254
|
const sources = [
|
|
234
255
|
[join(env.managedDir, ".claude", "agents"), "managed"],
|
|
235
256
|
...projectFolders(env, projectDir, gitRoot, "agents")
|
|
@@ -240,7 +261,7 @@ async function readAgents(env, projectDir, gitRoot) {
|
|
|
240
261
|
const agents = [];
|
|
241
262
|
const winners = new Map();
|
|
242
263
|
for (const [dir, scope] of sources)
|
|
243
|
-
for (const agent of await readFileEntries(env, dir, scope)) {
|
|
264
|
+
for (const agent of await readFileEntries(env, dir, scope, { problems })) {
|
|
244
265
|
const [problem] = agentProblems(agent.text);
|
|
245
266
|
if (problem) {
|
|
246
267
|
agent.skipped = problem;
|
|
@@ -261,7 +282,7 @@ async function readAgents(env, projectDir, gitRoot) {
|
|
|
261
282
|
* repository root (the closest wins) and yours (docs: output-styles#create-a-custom-output-style).
|
|
262
283
|
* The docs order only the project folders; managed, project, then yours follows subagents.
|
|
263
284
|
*/
|
|
264
|
-
async function readOutputStyles(env, projectDir, gitRoot) {
|
|
285
|
+
async function readOutputStyles(env, projectDir, gitRoot, problems) {
|
|
265
286
|
const sources = [
|
|
266
287
|
[join(env.managedDir, ".claude", "output-styles"), "managed"],
|
|
267
288
|
...projectFolders(env, projectDir, gitRoot, "output-styles")
|
|
@@ -271,7 +292,7 @@ async function readOutputStyles(env, projectDir, gitRoot) {
|
|
|
271
292
|
];
|
|
272
293
|
const styles = [];
|
|
273
294
|
for (const [dir, scope] of sources)
|
|
274
|
-
styles.push(...(await readFileEntries(env, dir, scope)));
|
|
295
|
+
styles.push(...(await readFileEntries(env, dir, scope, { problems })));
|
|
275
296
|
return styles;
|
|
276
297
|
}
|
|
277
298
|
/**
|
|
@@ -293,16 +314,16 @@ export function applySkillOverrides(entries, overrides) {
|
|
|
293
314
|
}
|
|
294
315
|
}
|
|
295
316
|
export async function readInventory(input) {
|
|
296
|
-
const { env, projectDir, gitRoot, layers } = input;
|
|
297
|
-
const plugins = await readPlugins(env, layers, projectDir, gitRoot);
|
|
298
|
-
const active = plugins.filter((p) => p.enabled);
|
|
299
|
-
const fromPlugins = await Promise.all(active.map((plugin) => pluginEntries(env, plugin)));
|
|
317
|
+
const { env, projectDir, gitRoot, layers, problems } = input;
|
|
318
|
+
const plugins = await readPlugins(env, layers, projectDir, gitRoot, problems);
|
|
319
|
+
const active = plugins.filter((p) => p.plugin.enabled);
|
|
320
|
+
const fromPlugins = await Promise.all(active.map((plugin) => pluginEntries(env, plugin, problems)));
|
|
300
321
|
const projectClaude = join(projectDir, ".claude");
|
|
301
322
|
const hasProjectDir = projectClaude !== env.claudeDir;
|
|
302
323
|
const files = async (sub, separator = "/") => [
|
|
303
|
-
...(await readFileEntries(env, join(env.claudeDir, sub), "user", { separator })),
|
|
324
|
+
...(await readFileEntries(env, join(env.claudeDir, sub), "user", { separator, problems })),
|
|
304
325
|
...(hasProjectDir
|
|
305
|
-
? await readFileEntries(env, join(projectClaude, sub), "project", { separator })
|
|
326
|
+
? await readFileEntries(env, join(projectClaude, sub), "project", { separator, problems })
|
|
306
327
|
: []),
|
|
307
328
|
];
|
|
308
329
|
// Project skills come from the start directory and every parent up to the repository root.
|
|
@@ -323,7 +344,7 @@ export async function readInventory(input) {
|
|
|
323
344
|
// (docs: skills#resolve-skills-that-share-a-name).
|
|
324
345
|
const winners = new Map();
|
|
325
346
|
for (const [dir, scope] of skillDirs) {
|
|
326
|
-
for (const skill of await readSkills(env, dir, scope)) {
|
|
347
|
+
for (const skill of await readSkills(env, dir, scope, { problems })) {
|
|
327
348
|
// A folder that several locations link to loads once.
|
|
328
349
|
const real = skill.realPath ?? skill.folder ?? skill.path;
|
|
329
350
|
if (seenFolders.has(real))
|
|
@@ -347,36 +368,38 @@ export async function readInventory(input) {
|
|
|
347
368
|
if (skill)
|
|
348
369
|
command.shadowedBy = skill.path;
|
|
349
370
|
}
|
|
350
|
-
const overrides =
|
|
371
|
+
const overrides = input.effective.find((s) => s.key === "skillOverrides")?.value;
|
|
351
372
|
applySkillOverrides([...skills, ...commands], overrides);
|
|
352
373
|
return {
|
|
353
374
|
agents: [
|
|
354
|
-
...(await readAgents(env, projectDir, gitRoot)),
|
|
375
|
+
...(await readAgents(env, projectDir, gitRoot, problems)),
|
|
355
376
|
...fromPlugins.flatMap((p) => p.agents),
|
|
356
377
|
],
|
|
357
378
|
skills: [...skills, ...fromPlugins.flatMap((p) => p.skills)],
|
|
358
379
|
commands: [...commands, ...fromPlugins.flatMap((p) => p.commands)],
|
|
359
380
|
outputStyles: [
|
|
360
|
-
...(await readOutputStyles(env, projectDir, gitRoot)),
|
|
381
|
+
...(await readOutputStyles(env, projectDir, gitRoot, problems)),
|
|
361
382
|
...fromPlugins.flatMap((p) => p.outputStyles),
|
|
362
383
|
],
|
|
363
|
-
hooks: await readHooks(
|
|
384
|
+
hooks: await readHooks(input, active),
|
|
364
385
|
mcpServers: await readMcpServers(input, active),
|
|
365
|
-
plugins,
|
|
386
|
+
plugins: plugins.map((p) => p.plugin),
|
|
366
387
|
};
|
|
367
388
|
}
|
|
368
|
-
async function readHooks(
|
|
389
|
+
async function readHooks(input, plugins) {
|
|
390
|
+
const { env, layers, trust, profile, problems } = input;
|
|
369
391
|
const out = [];
|
|
370
392
|
for (const layer of layers) {
|
|
371
393
|
if (layer.data)
|
|
372
394
|
out.push(...hooksFrom(layer.data.hooks, layer.scope, layer.path));
|
|
373
395
|
}
|
|
374
|
-
for (const
|
|
396
|
+
for (const loaded of plugins) {
|
|
397
|
+
const { plugin } = loaded;
|
|
375
398
|
const path = join(plugin.installPath, "hooks", "hooks.json");
|
|
376
|
-
const file = await
|
|
377
|
-
if (file
|
|
378
|
-
out.push(...hooksFrom(file.
|
|
379
|
-
for (const part of await manifestConfigs(env,
|
|
399
|
+
const file = await readJsonOrReport(env, path, problems);
|
|
400
|
+
if (file)
|
|
401
|
+
out.push(...hooksFrom(file.hooks, "plugin", path, plugin));
|
|
402
|
+
for (const part of await manifestConfigs(env, loaded, "hooks", problems))
|
|
380
403
|
out.push(...hooksFrom(part.value, "plugin", part.path, plugin));
|
|
381
404
|
}
|
|
382
405
|
// docs: hooks#disable-or-remove-hooks, settings-reference#allowmanagedhooksonly
|
|
@@ -387,7 +410,7 @@ async function readHooks(env, layers, plugins, trust, profile) {
|
|
|
387
410
|
.flatMap((scope) => layers.filter((l) => l.scope === scope))
|
|
388
411
|
.find((l) => typeof l.data?.disableAllHooks === "boolean")?.data?.disableAllHooks === true;
|
|
389
412
|
const managedOnly = managed?.allowManagedHooksOnly === true;
|
|
390
|
-
const forcedByManaged = new Set(plugins.filter((p) => p.enabledIn === "managed").map((p) => p.id));
|
|
413
|
+
const forcedByManaged = new Set(plugins.filter((p) => p.plugin.enabledIn === "managed").map((p) => p.plugin.id));
|
|
391
414
|
// An interactive session holds back hooks from every settings file, managed and yours included,
|
|
392
415
|
// until the folder or a parent covering it is trusted; -p and the SDK do not (docs:
|
|
393
416
|
// hooks#workspace-trust, permissions#what-runs-before-you-trust-a-folder). The docs say nothing
|
|
@@ -417,36 +440,32 @@ async function readHooks(env, layers, plugins, trust, profile) {
|
|
|
417
440
|
function hooksFrom(config, source, sourcePath, plugin) {
|
|
418
441
|
if (!isRecord(config))
|
|
419
442
|
return [];
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
return out;
|
|
443
|
+
return Object.entries(config).flatMap(([event, groups]) => Array.isArray(groups)
|
|
444
|
+
? groups.flatMap((group, g) => groupHooks(event, g, group, source, sourcePath, plugin))
|
|
445
|
+
: []);
|
|
446
|
+
}
|
|
447
|
+
/** The hooks of one matcher group, each with where it sits so an edit can find it again. */
|
|
448
|
+
function groupHooks(event, g, group, source, sourcePath, plugin) {
|
|
449
|
+
if (!isRecord(group) || !Array.isArray(group.hooks))
|
|
450
|
+
return [];
|
|
451
|
+
const matcher = stringField(group, "matcher");
|
|
452
|
+
return group.hooks.flatMap((h, index) => {
|
|
453
|
+
if (!isRecord(h))
|
|
454
|
+
return [];
|
|
455
|
+
const entry = {
|
|
456
|
+
event,
|
|
457
|
+
type: stringField(h, "type") || "command",
|
|
458
|
+
command: stringField(h, "command") || stringField(h, "prompt") || shownUrl(stringField(h, "url")),
|
|
459
|
+
source,
|
|
460
|
+
sourcePath,
|
|
461
|
+
active: true,
|
|
462
|
+
at: { group: g, index },
|
|
463
|
+
definition: redactSecrets(h),
|
|
464
|
+
};
|
|
465
|
+
if (matcher)
|
|
466
|
+
entry.matcher = matcher;
|
|
467
|
+
if (plugin)
|
|
468
|
+
entry.plugin = plugin.id;
|
|
469
|
+
return [entry];
|
|
470
|
+
});
|
|
452
471
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type McpServer, type Problem, type SessionProfile, type SettingsLayer, type Trust } from "../../shared/types.ts";
|
|
2
|
+
import { type ScanEnv } from "./env.ts";
|
|
3
|
+
import { type LoadedPlugin } from "./plugins.ts";
|
|
4
|
+
/**
|
|
5
|
+
* What makes a server definition one Claude Code cannot start: a missing command or url, a type it
|
|
6
|
+
* does not know, or a field the docs define holding the wrong kind of value (docs: mcp). It checks
|
|
7
|
+
* the rest when it starts the server.
|
|
8
|
+
*/
|
|
9
|
+
export declare function serverProblems(definition: unknown): string[];
|
|
10
|
+
interface Input {
|
|
11
|
+
env: ScanEnv;
|
|
12
|
+
projectDir: string;
|
|
13
|
+
layers: SettingsLayer[];
|
|
14
|
+
/** Managed settings as written, nothing hidden. */
|
|
15
|
+
managed: Record<string, unknown> | null;
|
|
16
|
+
claudeJson: Record<string, unknown> | null;
|
|
17
|
+
trust: Trust;
|
|
18
|
+
profile: SessionProfile;
|
|
19
|
+
/** Receives MCP files that are there but do not parse. */
|
|
20
|
+
problems: Problem[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Every MCP server definition, each marked with whether Claude Code would start it here
|
|
24
|
+
* (docs: mcp#scope-hierarchy-and-precedence, managed-mcp). ccdesk never connects, so an active
|
|
25
|
+
* server is one that is configured and allowed, not one that is known to work.
|
|
26
|
+
*/
|
|
27
|
+
export declare function readMcpServers(input: Input, plugins: LoadedPlugin[]): Promise<McpServer[]>;
|
|
28
|
+
export {};
|