@takara-ai/miru-code 1.5.3 → 1.7.2
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/README.md +102 -25
- package/docs/miru-workflow-diagram.svg +47 -0
- package/docs/self-hosted-sagemaker.md +1 -1
- package/package.json +3 -1
- package/scripts/install-git-hooks.ts +29 -0
- package/src/auth/device.ts +306 -0
- package/src/auth/errors.ts +24 -0
- package/src/auth/providers.ts +115 -0
- package/src/auth/types.ts +65 -0
- package/src/cli.ts +59 -8
- package/src/credentials.ts +173 -65
- package/src/embeddings/openai.ts +3 -2
- package/src/env.ts +6 -2
- package/src/help.ts +29 -13
- package/src/installer/agents.ts +118 -16
- package/src/installer/caveman-shared.ts +51 -0
- package/src/installer/config.ts +73 -0
- package/src/installer/copilot-family.ts +8 -0
- package/src/installer/hooks/install.ts +3 -14
- package/src/installer/installer.ts +332 -11
- package/src/installer/prompt.ts +15 -8
- package/src/installer/search-policy.ts +3 -2
- package/src/installer/shared-skill.ts +200 -0
- package/src/installer/style-packs/caveman.ts +118 -0
- package/src/installer/style-packs/ste/skill.ts +218 -0
- package/src/mcp/auth-tool.ts +161 -0
- package/src/mcp/index-cache.ts +2 -1
- package/src/mcp/server.ts +7 -4
- package/src/prompt.ts +8 -5
- package/src/setup.ts +100 -69
- package/src/spinner.ts +33 -6
- package/tokenizer/tokenizer.json +1 -1
package/src/help.ts
CHANGED
|
@@ -37,24 +37,23 @@ export function printMainHelp(): void {
|
|
|
37
37
|
commandRow("locate", "Exact substring location in the index");
|
|
38
38
|
commandRow("expand", "Adjacent chunks in the same file as a hit");
|
|
39
39
|
commandRow("find-related", "Find chunks related to a file:line");
|
|
40
|
-
commandRow("setup", "
|
|
40
|
+
commandRow("setup", "Authenticate with Takara");
|
|
41
41
|
commandRow("install", "Configure miru across coding agents");
|
|
42
42
|
commandRow("uninstall", "Remove miru agent configuration");
|
|
43
43
|
commandRow("benchmark", "Turn MCP benchmark mode on or off");
|
|
44
44
|
commandRow("init", "Write a project-local sub-agent file");
|
|
45
45
|
commandRow("clear", "Remove cached index for a path");
|
|
46
|
-
commandRow("help", "Show help for a command");
|
|
47
46
|
divider();
|
|
48
47
|
|
|
49
48
|
section("Quick start");
|
|
50
49
|
writeStdout(" miru setup && miru install");
|
|
51
50
|
writeStdout(' miru search "auth middleware" ./src');
|
|
52
51
|
writeStdout("");
|
|
53
|
-
hint("miru
|
|
52
|
+
hint("miru <command> -h · miru -v for version");
|
|
54
53
|
writeStdout("");
|
|
55
54
|
}
|
|
56
55
|
|
|
57
|
-
export function
|
|
56
|
+
export function printEnvironmentHelp(): void {
|
|
58
57
|
section("Environment");
|
|
59
58
|
writeStdout(` ${TAKARA_API_KEY_ENV}`);
|
|
60
59
|
writeStdout(" Takara bearer token for embeddings");
|
|
@@ -67,6 +66,9 @@ export function printEnvHelp(): void {
|
|
|
67
66
|
writeStdout(" MIRU_TOKENIZER_JSON");
|
|
68
67
|
writeStdout(" Path to tokenizer.json (default: <package>/tokenizer/tokenizer.json)");
|
|
69
68
|
writeStdout("");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function printSageMakerHelp(): void {
|
|
70
72
|
section("Self-hosted (AWS SageMaker)");
|
|
71
73
|
writeStdout(" MIRU_SAGEMAKER_ENDPOINT_ARN");
|
|
72
74
|
writeStdout(" arn:aws:sagemaker:<region>:<account-id>:endpoint/<name> — set this to");
|
|
@@ -90,13 +92,17 @@ export function printEnvHelp(): void {
|
|
|
90
92
|
writeStdout("");
|
|
91
93
|
}
|
|
92
94
|
|
|
93
|
-
export function
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
export function printEnvHelp(): void {
|
|
96
|
+
printEnvironmentHelp();
|
|
97
|
+
printSageMakerHelp();
|
|
96
98
|
}
|
|
97
99
|
|
|
98
100
|
export function printCommandHelp(command: string): void {
|
|
99
101
|
switch (command) {
|
|
102
|
+
case "env":
|
|
103
|
+
case "environment":
|
|
104
|
+
printEnvironmentHelp();
|
|
105
|
+
return;
|
|
100
106
|
case "search":
|
|
101
107
|
commandHeader("search", "Hybrid semantic + keyword search.");
|
|
102
108
|
section("Usage");
|
|
@@ -147,13 +153,14 @@ export function printCommandHelp(command: string): void {
|
|
|
147
153
|
writeStdout("");
|
|
148
154
|
return;
|
|
149
155
|
case "setup":
|
|
150
|
-
commandHeader("setup", "
|
|
156
|
+
commandHeader("setup", "Authenticate with Takara or self-hosted SageMaker credentials.");
|
|
151
157
|
section("Usage");
|
|
152
|
-
writeStdout(" miru setup [--key TOKEN] [--force] [--clear]");
|
|
158
|
+
writeStdout(" miru setup [--device] [--key TOKEN] [--force] [--clear]");
|
|
153
159
|
writeStdout(" miru setup --sagemaker --arn ENDPOINT_ARN --profile NAME");
|
|
154
160
|
section("Options (Takara)");
|
|
155
|
-
writeStdout(" --
|
|
156
|
-
writeStdout(" --
|
|
161
|
+
writeStdout(" --device Start device-code login (default when interactive)");
|
|
162
|
+
writeStdout(" --key, -k TOKEN Non-interactive: store a bearer token directly");
|
|
163
|
+
writeStdout(" --force Replace existing stored credentials");
|
|
157
164
|
writeStdout(" --clear Remove stored credentials");
|
|
158
165
|
section("Options (SageMaker)");
|
|
159
166
|
writeStdout(" --sagemaker Switch setup to self-hosted SageMaker mode");
|
|
@@ -170,15 +177,24 @@ export function printCommandHelp(command: string): void {
|
|
|
170
177
|
return;
|
|
171
178
|
case "install":
|
|
172
179
|
commandHeader("install", "Interactive global agent setup.");
|
|
173
|
-
writeStdout("Configures MCP server, instructions,
|
|
180
|
+
writeStdout("Configures MCP server, instructions, sub-agent files, and optional");
|
|
181
|
+
writeStdout("experimental integrations (search hooks, Caveman, STE writing) under");
|
|
174
182
|
writeStdout("your user config (~/.claude, ~/.cursor, etc.).");
|
|
175
183
|
writeStdout("");
|
|
184
|
+
writeStdout(
|
|
185
|
+
"Caveman = chat compression (/caveman). STE = clear docs (/ste). Off by default.",
|
|
186
|
+
);
|
|
187
|
+
writeStdout("Caveman is supported on all Miru installer IDEs (shared path for most IDEs).");
|
|
188
|
+
writeStdout(
|
|
189
|
+
"STE is supported on all Miru installer IDEs (same shared-path ownership model).",
|
|
190
|
+
);
|
|
176
191
|
writeStdout("Run miru setup first, or set TAKARA_API_KEY for MCP env expansion.");
|
|
177
192
|
writeStdout("");
|
|
178
193
|
return;
|
|
179
194
|
case "uninstall":
|
|
180
195
|
commandHeader("uninstall", "Remove miru configuration from agents.");
|
|
181
|
-
writeStdout("Removes MCP entries, marked instruction blocks,
|
|
196
|
+
writeStdout("Removes MCP entries, marked instruction blocks, global sub-agents,");
|
|
197
|
+
writeStdout("and selected optional integrations (hooks, Caveman, STE skills).");
|
|
182
198
|
writeStdout("Also deletes the global benchmark report from Miru's state directory.");
|
|
183
199
|
writeStdout("");
|
|
184
200
|
return;
|
package/src/installer/agents.ts
CHANGED
|
@@ -9,6 +9,52 @@ function copilotHooksPath(home: string): string {
|
|
|
9
9
|
return join(home, ".copilot", "hooks", "miru-search.json");
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
/** Shared Copilot / VS Code / Visual Studio config root (MCP, hooks, agents). */
|
|
13
|
+
export function copilotHomeDir(home: string): string {
|
|
14
|
+
return join(home, ".copilot");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Vendors that do not scan `~/.agents/skills/` and need a native skill dir
|
|
19
|
+
* (Caveman + STE).
|
|
20
|
+
*/
|
|
21
|
+
export type NativeSkillVendor = "claude" | "kiro";
|
|
22
|
+
|
|
23
|
+
/** @deprecated Use NativeSkillVendor. */
|
|
24
|
+
export type NativeCavemanVendor = NativeSkillVendor;
|
|
25
|
+
|
|
26
|
+
function skillMd(home: string, rootDir: string, skillName: string): string {
|
|
27
|
+
return join(home, rootDir, "skills", skillName, "SKILL.md");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function skillDir(home: string, rootDir: string, skillName: string): string {
|
|
31
|
+
return join(home, rootDir, "skills", skillName);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Cross-agent Agent Skills root (`~/.agents/skills/…`).
|
|
36
|
+
* Used by Cursor, Gemini, OpenCode, Codex, Windsurf, and Copilot-family IDEs.
|
|
37
|
+
* Claude Code and Kiro still use vendor-native skill dirs.
|
|
38
|
+
*/
|
|
39
|
+
export function agentsCavemanSkillPath(home: string): string {
|
|
40
|
+
return skillMd(home, ".agents", "caveman");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Claude Code / Kiro native Caveman skill path. */
|
|
44
|
+
export function nativeCavemanSkillPath(home: string, vendor: NativeSkillVendor): string {
|
|
45
|
+
return skillMd(home, `.${vendor}`, "caveman");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Shared STE skill directory under `~/.agents/skills/ste`. */
|
|
49
|
+
export function agentsSteSkillDir(home: string): string {
|
|
50
|
+
return skillDir(home, ".agents", "ste");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Claude Code / Kiro native STE skill directory. */
|
|
54
|
+
export function nativeSteSkillDir(home: string, vendor: NativeSkillVendor): string {
|
|
55
|
+
return skillDir(home, `.${vendor}`, "ste");
|
|
56
|
+
}
|
|
57
|
+
|
|
12
58
|
function kiroHooksPath(home: string): string {
|
|
13
59
|
return join(home, ".kiro", "settings", "hooks.json");
|
|
14
60
|
}
|
|
@@ -17,10 +63,14 @@ function windsurfHooksPath(home: string): string {
|
|
|
17
63
|
return join(home, ".codeium", "windsurf", "hooks.json");
|
|
18
64
|
}
|
|
19
65
|
|
|
20
|
-
|
|
66
|
+
/** OpenCode config root (`$XDG_CONFIG_HOME/opencode` or `~/.config/opencode`). */
|
|
67
|
+
export function opencodeConfigDir(home: string): string {
|
|
21
68
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
22
|
-
|
|
23
|
-
|
|
69
|
+
return xdg ? join(xdg, "opencode") : join(home, ".config", "opencode");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function opencodePluginPath(home: string): string {
|
|
73
|
+
return join(opencodeConfigDir(home), "plugins", "miru-search-guard.ts");
|
|
24
74
|
}
|
|
25
75
|
|
|
26
76
|
export type InstallAction =
|
|
@@ -92,11 +142,19 @@ export interface AgentTarget {
|
|
|
92
142
|
hooksFormat: HooksFormat | null;
|
|
93
143
|
subagentPath: string | null;
|
|
94
144
|
subagentId: AgentId | null;
|
|
145
|
+
/**
|
|
146
|
+
* On-demand Caveman Agent Skill (`…/skills/caveman/SKILL.md`), or null if
|
|
147
|
+
* unsupported.
|
|
148
|
+
*/
|
|
149
|
+
cavemanSkillPath: string | null;
|
|
150
|
+
/**
|
|
151
|
+
* On-demand STE skill directory (`…/skills/ste/`), or null if unsupported.
|
|
152
|
+
*/
|
|
153
|
+
steSkillDir: string | null;
|
|
95
154
|
}
|
|
96
155
|
|
|
97
156
|
export function opencodeMcpPath(): string {
|
|
98
|
-
const
|
|
99
|
-
const base = xdg ? join(xdg, "opencode") : join(HOME, ".config", "opencode");
|
|
157
|
+
const base = opencodeConfigDir(HOME);
|
|
100
158
|
const jsonc = join(base, "opencode.jsonc");
|
|
101
159
|
const json = join(base, "opencode.json");
|
|
102
160
|
if (existsSync(jsonc)) {
|
|
@@ -163,6 +221,9 @@ function jsonMcp(path: string, key: string, entry: Record<string, unknown>): Mcp
|
|
|
163
221
|
return { path, key, memberKey: "miru", entry, format: "json" };
|
|
164
222
|
}
|
|
165
223
|
|
|
224
|
+
const SHARED_CAVEMAN_SKILL = agentsCavemanSkillPath(HOME);
|
|
225
|
+
const SHARED_STE_SKILL_DIR = agentsSteSkillDir(HOME);
|
|
226
|
+
|
|
166
227
|
export const AGENT_TARGETS: AgentTarget[] = [
|
|
167
228
|
{
|
|
168
229
|
id: "claude",
|
|
@@ -176,6 +237,8 @@ export const AGENT_TARGETS: AgentTarget[] = [
|
|
|
176
237
|
hooksFormat: "claude",
|
|
177
238
|
subagentPath: join(HOME, ".claude", "agents", "miru-code.md"),
|
|
178
239
|
subagentId: "claude",
|
|
240
|
+
cavemanSkillPath: nativeCavemanSkillPath(HOME, "claude"),
|
|
241
|
+
steSkillDir: nativeSteSkillDir(HOME, "claude"),
|
|
179
242
|
},
|
|
180
243
|
{
|
|
181
244
|
id: "cursor",
|
|
@@ -189,6 +252,8 @@ export const AGENT_TARGETS: AgentTarget[] = [
|
|
|
189
252
|
hooksFormat: "cursor",
|
|
190
253
|
subagentPath: join(HOME, ".cursor", "agents", "miru-code.md"),
|
|
191
254
|
subagentId: "cursor",
|
|
255
|
+
cavemanSkillPath: SHARED_CAVEMAN_SKILL,
|
|
256
|
+
steSkillDir: SHARED_STE_SKILL_DIR,
|
|
192
257
|
},
|
|
193
258
|
{
|
|
194
259
|
id: "gemini",
|
|
@@ -202,50 +267,57 @@ export const AGENT_TARGETS: AgentTarget[] = [
|
|
|
202
267
|
hooksFormat: "gemini",
|
|
203
268
|
subagentPath: join(HOME, ".gemini", "agents", "miru-code.md"),
|
|
204
269
|
subagentId: "gemini",
|
|
270
|
+
cavemanSkillPath: SHARED_CAVEMAN_SKILL,
|
|
271
|
+
steSkillDir: SHARED_STE_SKILL_DIR,
|
|
205
272
|
},
|
|
206
273
|
{
|
|
207
274
|
id: "kiro",
|
|
208
275
|
displayName: "Kiro",
|
|
209
276
|
binary: "kiro",
|
|
210
277
|
configDir: join(HOME, ".kiro"),
|
|
211
|
-
mcp: jsonMcp(join(HOME, ".kiro", "settings", "mcp.json"), "mcpServers",
|
|
212
|
-
...STDIO_SERVER_CONFIG,
|
|
213
|
-
env: {
|
|
214
|
-
PATH: "/usr/local/bin:/usr/bin:/bin",
|
|
215
|
-
},
|
|
216
|
-
}),
|
|
278
|
+
mcp: jsonMcp(join(HOME, ".kiro", "settings", "mcp.json"), "mcpServers", STDIO_SERVER_CONFIG),
|
|
217
279
|
instructionsPath: join(HOME, ".kiro", "steering", "miru.md"),
|
|
218
280
|
cursorRulesPath: null,
|
|
219
281
|
hooksPath: kiroHooksPath(HOME),
|
|
220
282
|
hooksFormat: "kiro",
|
|
221
283
|
subagentPath: join(HOME, ".kiro", "agents", "miru-code.md"),
|
|
222
284
|
subagentId: "kiro",
|
|
285
|
+
cavemanSkillPath: nativeCavemanSkillPath(HOME, "kiro"),
|
|
286
|
+
steSkillDir: nativeSteSkillDir(HOME, "kiro"),
|
|
223
287
|
},
|
|
224
288
|
{
|
|
225
289
|
id: "opencode",
|
|
226
290
|
displayName: "OpenCode",
|
|
227
291
|
binary: "opencode",
|
|
228
|
-
configDir:
|
|
292
|
+
configDir: opencodeConfigDir(HOME),
|
|
229
293
|
mcp: jsonMcp(opencodeMcpPath(), "mcp", OPENCODE_SERVER_CONFIG),
|
|
230
|
-
instructionsPath: join(HOME, "
|
|
294
|
+
instructionsPath: join(opencodeConfigDir(HOME), "AGENTS.md"),
|
|
231
295
|
cursorRulesPath: null,
|
|
232
296
|
hooksPath: opencodePluginPath(HOME),
|
|
233
297
|
hooksFormat: "opencode",
|
|
234
|
-
subagentPath: join(HOME, "
|
|
298
|
+
subagentPath: join(opencodeConfigDir(HOME), "agents", "miru-code.md"),
|
|
235
299
|
subagentId: "opencode",
|
|
300
|
+
cavemanSkillPath: SHARED_CAVEMAN_SKILL,
|
|
301
|
+
steSkillDir: SHARED_STE_SKILL_DIR,
|
|
236
302
|
},
|
|
237
303
|
{
|
|
238
304
|
id: "copilot",
|
|
239
305
|
displayName: "GitHub Copilot",
|
|
240
306
|
binary: null,
|
|
241
307
|
configDir: join(HOME, ".config", "github-copilot"),
|
|
242
|
-
mcp: jsonMcp(
|
|
308
|
+
mcp: jsonMcp(
|
|
309
|
+
join(copilotHomeDir(HOME), "mcp-config.json"),
|
|
310
|
+
"mcpServers",
|
|
311
|
+
BARE_STDIO_SERVER_CONFIG,
|
|
312
|
+
),
|
|
243
313
|
instructionsPath: null,
|
|
244
314
|
cursorRulesPath: null,
|
|
245
315
|
hooksPath: copilotHooksPath(HOME),
|
|
246
316
|
hooksFormat: "vscode",
|
|
247
|
-
subagentPath: join(HOME, "
|
|
317
|
+
subagentPath: join(copilotHomeDir(HOME), "agents", "miru-code.agent.md"),
|
|
248
318
|
subagentId: "copilot",
|
|
319
|
+
cavemanSkillPath: SHARED_CAVEMAN_SKILL,
|
|
320
|
+
steSkillDir: SHARED_STE_SKILL_DIR,
|
|
249
321
|
},
|
|
250
322
|
{
|
|
251
323
|
id: "codex",
|
|
@@ -265,6 +337,8 @@ export const AGENT_TARGETS: AgentTarget[] = [
|
|
|
265
337
|
hooksFormat: "claude",
|
|
266
338
|
subagentPath: null,
|
|
267
339
|
subagentId: null,
|
|
340
|
+
cavemanSkillPath: SHARED_CAVEMAN_SKILL,
|
|
341
|
+
steSkillDir: SHARED_STE_SKILL_DIR,
|
|
268
342
|
},
|
|
269
343
|
{
|
|
270
344
|
id: "vscode",
|
|
@@ -278,6 +352,8 @@ export const AGENT_TARGETS: AgentTarget[] = [
|
|
|
278
352
|
hooksFormat: "vscode",
|
|
279
353
|
subagentPath: null,
|
|
280
354
|
subagentId: null,
|
|
355
|
+
cavemanSkillPath: SHARED_CAVEMAN_SKILL,
|
|
356
|
+
steSkillDir: SHARED_STE_SKILL_DIR,
|
|
281
357
|
},
|
|
282
358
|
{
|
|
283
359
|
id: "windsurf",
|
|
@@ -291,6 +367,8 @@ export const AGENT_TARGETS: AgentTarget[] = [
|
|
|
291
367
|
hooksFormat: "windsurf",
|
|
292
368
|
subagentPath: null,
|
|
293
369
|
subagentId: null,
|
|
370
|
+
cavemanSkillPath: SHARED_CAVEMAN_SKILL,
|
|
371
|
+
steSkillDir: SHARED_STE_SKILL_DIR,
|
|
294
372
|
},
|
|
295
373
|
{
|
|
296
374
|
id: "visualstudio",
|
|
@@ -304,6 +382,8 @@ export const AGENT_TARGETS: AgentTarget[] = [
|
|
|
304
382
|
hooksFormat: "vscode",
|
|
305
383
|
subagentPath: null,
|
|
306
384
|
subagentId: null,
|
|
385
|
+
cavemanSkillPath: SHARED_CAVEMAN_SKILL,
|
|
386
|
+
steSkillDir: SHARED_STE_SKILL_DIR,
|
|
307
387
|
},
|
|
308
388
|
];
|
|
309
389
|
|
|
@@ -330,6 +410,10 @@ export async function isAgentDetected(agent: AgentTarget): Promise<boolean> {
|
|
|
330
410
|
}
|
|
331
411
|
return false;
|
|
332
412
|
}
|
|
413
|
+
// Copilot-specific artifacts only — ~/.copilot alone is shared with VS Code / VS.
|
|
414
|
+
if (agent.id === "copilot") {
|
|
415
|
+
return isCopilotInstalled(HOME);
|
|
416
|
+
}
|
|
333
417
|
if (agent.binary && (await commandOnPath(agent.binary))) {
|
|
334
418
|
return true;
|
|
335
419
|
}
|
|
@@ -338,3 +422,21 @@ export async function isAgentDetected(agent: AgentTarget): Promise<boolean> {
|
|
|
338
422
|
}
|
|
339
423
|
return false;
|
|
340
424
|
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* True when Copilot-specific config exists. Does not treat shared
|
|
428
|
+
* `~/.copilot` hooks (also used by VS Code / Visual Studio) as enough.
|
|
429
|
+
*/
|
|
430
|
+
export function isCopilotInstalled(home: string = HOME): boolean {
|
|
431
|
+
if (existsSync(join(home, ".config", "github-copilot"))) {
|
|
432
|
+
return true;
|
|
433
|
+
}
|
|
434
|
+
const copilot = copilotHomeDir(home);
|
|
435
|
+
if (existsSync(join(copilot, "mcp-config.json"))) {
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
if (existsSync(join(copilot, "agents"))) {
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { AgentTarget, InstallAction } from "./agents.ts";
|
|
2
|
+
import {
|
|
3
|
+
ensureSharedSkillOwnersOnInstall,
|
|
4
|
+
isSharedSkillPath,
|
|
5
|
+
removeSkillMdAndOwners,
|
|
6
|
+
resolveSharedSkillUninstall,
|
|
7
|
+
type SharedSkillCtx,
|
|
8
|
+
type SharedSkillUninstallDecision,
|
|
9
|
+
SKILL_OWNERS_FILE,
|
|
10
|
+
type SkillPathOf,
|
|
11
|
+
} from "./shared-skill.ts";
|
|
12
|
+
|
|
13
|
+
/** @deprecated Prefer SKILL_OWNERS_FILE — same sidecar name for all shared skills. */
|
|
14
|
+
export const CAVEMAN_OWNERS_FILE = SKILL_OWNERS_FILE;
|
|
15
|
+
|
|
16
|
+
/** Context for shared `~/.agents/skills` Caveman install/uninstall. */
|
|
17
|
+
export type CavemanSharedCtx = SharedSkillCtx;
|
|
18
|
+
|
|
19
|
+
export type SharedCavemanUninstallDecision = SharedSkillUninstallDecision;
|
|
20
|
+
|
|
21
|
+
const cavemanPathOf: SkillPathOf = (agent) => agent.cavemanSkillPath;
|
|
22
|
+
|
|
23
|
+
export function isSharedCavemanPath(path: string, allAgents: AgentTarget[]): boolean {
|
|
24
|
+
return isSharedSkillPath(path, allAgents, cavemanPathOf);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function ensureSharedCavemanOwnersOnInstall(
|
|
28
|
+
skillDir: string,
|
|
29
|
+
skillPath: string,
|
|
30
|
+
agent: AgentTarget,
|
|
31
|
+
ctx: CavemanSharedCtx,
|
|
32
|
+
): Promise<void> {
|
|
33
|
+
return ensureSharedSkillOwnersOnInstall(skillDir, skillPath, agent, ctx, cavemanPathOf);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function resolveSharedCavemanUninstall(
|
|
37
|
+
path: string,
|
|
38
|
+
skillDir: string,
|
|
39
|
+
agent: AgentTarget,
|
|
40
|
+
ctx: CavemanSharedCtx,
|
|
41
|
+
): Promise<SharedCavemanUninstallDecision> {
|
|
42
|
+
return resolveSharedSkillUninstall(path, skillDir, agent, ctx, cavemanPathOf);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Remove SKILL.md and owners sidecar; leave sibling skill folders untouched. */
|
|
46
|
+
export async function removeCavemanSkillFiles(
|
|
47
|
+
path: string,
|
|
48
|
+
skillDir: string,
|
|
49
|
+
): Promise<Extract<InstallAction, "removed" | "not-found">> {
|
|
50
|
+
return removeSkillMdAndOwners(path, skillDir);
|
|
51
|
+
}
|
package/src/installer/config.ts
CHANGED
|
@@ -616,3 +616,76 @@ export async function removeTomlBlock(path: string): Promise<InstallAction> {
|
|
|
616
616
|
await Bun.write(path, `${remaining}\n`);
|
|
617
617
|
return "removed";
|
|
618
618
|
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Ensure Codex loads Agent Skills: `[features] skills = true` in config.toml.
|
|
622
|
+
* Does not disable the flag on uninstall (other skills may still need it).
|
|
623
|
+
*/
|
|
624
|
+
export function codexSkillsFeatureEnabled(text: string): boolean {
|
|
625
|
+
const lines = text.split("\n");
|
|
626
|
+
let inFeatures = false;
|
|
627
|
+
for (const line of lines) {
|
|
628
|
+
const tableKey = line.split("#")[0]?.trim() ?? "";
|
|
629
|
+
if (tableKey.startsWith("[") && tableKey.endsWith("]")) {
|
|
630
|
+
inFeatures = tableKey === "[features]";
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
if (!inFeatures) {
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
const value = line.split("#")[0]?.match(/^\s*skills\s*=\s*(true|false)\s*$/i)?.[1];
|
|
637
|
+
if (value?.toLowerCase() === "true") {
|
|
638
|
+
return true;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return false;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
export async function ensureCodexSkillsFeature(path: string): Promise<InstallAction> {
|
|
645
|
+
const existed = await Bun.file(path).exists();
|
|
646
|
+
const existing = existed ? await Bun.file(path).text() : "";
|
|
647
|
+
if (codexSkillsFeatureEnabled(existing)) {
|
|
648
|
+
return "unchanged";
|
|
649
|
+
}
|
|
650
|
+
const lines = existing.length > 0 ? existing.split("\n") : [];
|
|
651
|
+
|
|
652
|
+
let featuresIdx = -1;
|
|
653
|
+
let skillsIdx = -1;
|
|
654
|
+
let inFeatures = false;
|
|
655
|
+
|
|
656
|
+
for (let i = 0; i < lines.length; i++) {
|
|
657
|
+
const tableKey = (lines[i] ?? "").split("#")[0]?.trim() ?? "";
|
|
658
|
+
if (tableKey.startsWith("[") && tableKey.endsWith("]")) {
|
|
659
|
+
if (tableKey === "[features]") {
|
|
660
|
+
inFeatures = true;
|
|
661
|
+
featuresIdx = i;
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
inFeatures = false;
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
if (inFeatures && /^\s*skills\s*=/.test(lines[i] ?? "")) {
|
|
668
|
+
skillsIdx = i;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
if (featuresIdx >= 0 && skillsIdx >= 0) {
|
|
673
|
+
lines[skillsIdx] = "skills = true";
|
|
674
|
+
const next = `${lines.join("\n").replace(/\n+$/, "")}\n`;
|
|
675
|
+
await Bun.write(path, next);
|
|
676
|
+
return "updated";
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (featuresIdx >= 0) {
|
|
680
|
+
lines.splice(featuresIdx + 1, 0, "skills = true");
|
|
681
|
+
const next = `${lines.join("\n").replace(/\n+$/, "")}\n`;
|
|
682
|
+
await Bun.write(path, next);
|
|
683
|
+
return "updated";
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const block = "[features]\nskills = true\n";
|
|
687
|
+
const base = existing.replace(/\n+$/, "");
|
|
688
|
+
const next = base.length > 0 ? `${base}\n\n${block}` : block;
|
|
689
|
+
await Bun.write(path, next.endsWith("\n") ? next : `${next}\n`);
|
|
690
|
+
return existed ? "updated" : "created";
|
|
691
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Agents that share `~/.copilot` hooks. Caveman uses `~/.agents/skills`. */
|
|
2
|
+
export const COPILOT_FAMILY_IDS = ["copilot", "vscode", "visualstudio"] as const;
|
|
3
|
+
|
|
4
|
+
export type CopilotFamilyId = (typeof COPILOT_FAMILY_IDS)[number];
|
|
5
|
+
|
|
6
|
+
export function isCopilotFamilyId(id: string): id is CopilotFamilyId {
|
|
7
|
+
return (COPILOT_FAMILY_IDS as readonly string[]).includes(id);
|
|
8
|
+
}
|
|
@@ -2,11 +2,11 @@ import { unlink } from "node:fs/promises";
|
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import type { HooksFormat, InstallAction } from "../agents.ts";
|
|
4
4
|
import { stripJsonComments } from "../config.ts";
|
|
5
|
+
import { isCopilotFamilyId } from "../copilot-family.ts";
|
|
5
6
|
|
|
6
7
|
const HOOK_GUARD_MARKER = "hook-guard";
|
|
7
8
|
const MIRU_HOOK_MARKER = "miru-search";
|
|
8
9
|
const VSCODE_OWNERS_KEY = "miruOwners";
|
|
9
|
-
const SHARED_VSCODE_HOOK_OWNERS = ["copilot", "vscode", "visualstudio"] as const;
|
|
10
10
|
|
|
11
11
|
const MATCHERS = {
|
|
12
12
|
cursor: "Grep|Glob|Shell|SemanticSearch",
|
|
@@ -346,22 +346,11 @@ export async function mergeVscodeHooks(path: string, owner?: string): Promise<In
|
|
|
346
346
|
|
|
347
347
|
const currentOwners = Array.isArray(parsed[VSCODE_OWNERS_KEY])
|
|
348
348
|
? (parsed[VSCODE_OWNERS_KEY] as unknown[]).filter(
|
|
349
|
-
(item): item is string =>
|
|
350
|
-
typeof item === "string" &&
|
|
351
|
-
SHARED_VSCODE_HOOK_OWNERS.includes(item as (typeof SHARED_VSCODE_HOOK_OWNERS)[number]),
|
|
349
|
+
(item): item is string => typeof item === "string" && isCopilotFamilyId(item),
|
|
352
350
|
)
|
|
353
351
|
: [];
|
|
354
352
|
const nextOwners = owner
|
|
355
|
-
? Array.from(
|
|
356
|
-
new Set([
|
|
357
|
-
...currentOwners,
|
|
358
|
-
...(SHARED_VSCODE_HOOK_OWNERS.includes(
|
|
359
|
-
owner as (typeof SHARED_VSCODE_HOOK_OWNERS)[number],
|
|
360
|
-
)
|
|
361
|
-
? [owner]
|
|
362
|
-
: []),
|
|
363
|
-
]),
|
|
364
|
-
)
|
|
353
|
+
? Array.from(new Set([...currentOwners, ...(isCopilotFamilyId(owner) ? [owner] : [])]))
|
|
365
354
|
: currentOwners;
|
|
366
355
|
|
|
367
356
|
const next = {
|