bluekiwi 1.1.2 → 1.2.0

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 (127) hide show
  1. package/README.md +59 -0
  2. package/dist/assets/app-runtime/.next/BUILD_ID +1 -1
  3. package/dist/assets/app-runtime/.next/app-path-routes-manifest.json +5 -5
  4. package/dist/assets/app-runtime/.next/build-manifest.json +2 -2
  5. package/dist/assets/app-runtime/.next/prerender-manifest.json +3 -3
  6. package/dist/assets/app-runtime/.next/server/app/(app)/credentials/page_client-reference-manifest.js +1 -1
  7. package/dist/assets/app-runtime/.next/server/app/(app)/docs/page_client-reference-manifest.js +1 -1
  8. package/dist/assets/app-runtime/.next/server/app/(app)/instructions/page_client-reference-manifest.js +1 -1
  9. package/dist/assets/app-runtime/.next/server/app/(app)/page_client-reference-manifest.js +1 -1
  10. package/dist/assets/app-runtime/.next/server/app/(app)/settings/groups/page_client-reference-manifest.js +1 -1
  11. package/dist/assets/app-runtime/.next/server/app/(app)/settings/page_client-reference-manifest.js +1 -1
  12. package/dist/assets/app-runtime/.next/server/app/(app)/tasks/[id]/page.js +1 -1
  13. package/dist/assets/app-runtime/.next/server/app/(app)/tasks/[id]/page_client-reference-manifest.js +1 -1
  14. package/dist/assets/app-runtime/.next/server/app/(app)/tasks/page_client-reference-manifest.js +1 -1
  15. package/dist/assets/app-runtime/.next/server/app/(app)/tutorial/page_client-reference-manifest.js +1 -1
  16. package/dist/assets/app-runtime/.next/server/app/(app)/workflows/[id]/edit/page_client-reference-manifest.js +1 -1
  17. package/dist/assets/app-runtime/.next/server/app/(app)/workflows/[id]/page_client-reference-manifest.js +1 -1
  18. package/dist/assets/app-runtime/.next/server/app/(app)/workflows/new/page_client-reference-manifest.js +1 -1
  19. package/dist/assets/app-runtime/.next/server/app/(app)/workflows/page_client-reference-manifest.js +1 -1
  20. package/dist/assets/app-runtime/.next/server/app/(auth)/change-password/page_client-reference-manifest.js +1 -1
  21. package/dist/assets/app-runtime/.next/server/app/(auth)/invite/[token]/page_client-reference-manifest.js +1 -1
  22. package/dist/assets/app-runtime/.next/server/app/(auth)/login/page_client-reference-manifest.js +1 -1
  23. package/dist/assets/app-runtime/.next/server/app/(auth)/setup/page_client-reference-manifest.js +1 -1
  24. package/dist/assets/app-runtime/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  25. package/dist/assets/app-runtime/.next/server/app/_global-error.html +1 -1
  26. package/dist/assets/app-runtime/.next/server/app/_global-error.rsc +1 -1
  27. package/dist/assets/app-runtime/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  28. package/dist/assets/app-runtime/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  29. package/dist/assets/app-runtime/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  30. package/dist/assets/app-runtime/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  31. package/dist/assets/app-runtime/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  32. package/dist/assets/app-runtime/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  33. package/dist/assets/app-runtime/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  34. package/dist/assets/app-runtime/.next/server/app/_not-found.html +1 -1
  35. package/dist/assets/app-runtime/.next/server/app/_not-found.rsc +1 -1
  36. package/dist/assets/app-runtime/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  37. package/dist/assets/app-runtime/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  38. package/dist/assets/app-runtime/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  39. package/dist/assets/app-runtime/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  40. package/dist/assets/app-runtime/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  41. package/dist/assets/app-runtime/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  42. package/dist/assets/app-runtime/.next/server/app/api/tasks/start/route.js +1 -1
  43. package/dist/assets/app-runtime/.next/server/app/api/version/route.js +1 -1
  44. package/dist/assets/app-runtime/.next/server/app/api/workflows/[id]/activate/route.js +5 -5
  45. package/dist/assets/app-runtime/.next/server/app/api/workflows/[id]/route.js +5 -5
  46. package/dist/assets/app-runtime/.next/server/app/api/workflows/[id]/transfer/route.js +5 -5
  47. package/dist/assets/app-runtime/.next/server/app/api/workflows/import/route.js +1 -1
  48. package/dist/assets/app-runtime/.next/server/app/api/workflows/route.js +5 -5
  49. package/dist/assets/app-runtime/.next/server/app/change-password.html +1 -1
  50. package/dist/assets/app-runtime/.next/server/app/change-password.rsc +1 -1
  51. package/dist/assets/app-runtime/.next/server/app/change-password.segments/!KGF1dGgp/change-password/__PAGE__.segment.rsc +1 -1
  52. package/dist/assets/app-runtime/.next/server/app/change-password.segments/!KGF1dGgp/change-password.segment.rsc +1 -1
  53. package/dist/assets/app-runtime/.next/server/app/change-password.segments/!KGF1dGgp.segment.rsc +1 -1
  54. package/dist/assets/app-runtime/.next/server/app/change-password.segments/_full.segment.rsc +1 -1
  55. package/dist/assets/app-runtime/.next/server/app/change-password.segments/_head.segment.rsc +1 -1
  56. package/dist/assets/app-runtime/.next/server/app/change-password.segments/_index.segment.rsc +1 -1
  57. package/dist/assets/app-runtime/.next/server/app/change-password.segments/_tree.segment.rsc +1 -1
  58. package/dist/assets/app-runtime/.next/server/app/login.html +1 -1
  59. package/dist/assets/app-runtime/.next/server/app/login.rsc +1 -1
  60. package/dist/assets/app-runtime/.next/server/app/login.segments/!KGF1dGgp/login/__PAGE__.segment.rsc +1 -1
  61. package/dist/assets/app-runtime/.next/server/app/login.segments/!KGF1dGgp/login.segment.rsc +1 -1
  62. package/dist/assets/app-runtime/.next/server/app/login.segments/!KGF1dGgp.segment.rsc +1 -1
  63. package/dist/assets/app-runtime/.next/server/app/login.segments/_full.segment.rsc +1 -1
  64. package/dist/assets/app-runtime/.next/server/app/login.segments/_head.segment.rsc +1 -1
  65. package/dist/assets/app-runtime/.next/server/app/login.segments/_index.segment.rsc +1 -1
  66. package/dist/assets/app-runtime/.next/server/app/login.segments/_tree.segment.rsc +1 -1
  67. package/dist/assets/app-runtime/.next/server/app/setup.html +1 -1
  68. package/dist/assets/app-runtime/.next/server/app/setup.rsc +1 -1
  69. package/dist/assets/app-runtime/.next/server/app/setup.segments/!KGF1dGgp/setup/__PAGE__.segment.rsc +1 -1
  70. package/dist/assets/app-runtime/.next/server/app/setup.segments/!KGF1dGgp/setup.segment.rsc +1 -1
  71. package/dist/assets/app-runtime/.next/server/app/setup.segments/!KGF1dGgp.segment.rsc +1 -1
  72. package/dist/assets/app-runtime/.next/server/app/setup.segments/_full.segment.rsc +1 -1
  73. package/dist/assets/app-runtime/.next/server/app/setup.segments/_head.segment.rsc +1 -1
  74. package/dist/assets/app-runtime/.next/server/app/setup.segments/_index.segment.rsc +1 -1
  75. package/dist/assets/app-runtime/.next/server/app/setup.segments/_tree.segment.rsc +1 -1
  76. package/dist/assets/app-runtime/.next/server/app-paths-manifest.json +5 -5
  77. package/dist/assets/app-runtime/.next/server/chunks/2624.js +1 -1
  78. package/dist/assets/app-runtime/.next/server/chunks/4024.js +1 -1
  79. package/dist/assets/app-runtime/.next/server/middleware-build-manifest.js +1 -1
  80. package/dist/assets/app-runtime/.next/server/pages/404.html +1 -1
  81. package/dist/assets/app-runtime/.next/server/pages/500.html +1 -1
  82. package/dist/assets/app-runtime/.next/server/server-reference-manifest.json +1 -1
  83. package/dist/assets/app-runtime/.next/static/chunks/{7552-67667f5f15d4f961.js → 7552-aa220264909ca9d9.js} +1 -1
  84. package/dist/assets/app-runtime/.next/static/chunks/{9243-34655a593a6aa471.js → 9243-9c99cc0df75c9121.js} +1 -1
  85. package/dist/assets/app-runtime/.next/static/chunks/app/(app)/instructions/{page-0aa5fcbf056c7144.js → page-dd25654a8191a638.js} +1 -1
  86. package/dist/assets/app-runtime/.next/static/chunks/app/(app)/{layout-64cb9b84079366f9.js → layout-d7f54b544bb8d2df.js} +1 -1
  87. package/dist/assets/app-runtime/.next/static/chunks/app/(app)/tasks/[id]/{page-29349afdbe6e318f.js → page-e88075977b43a9d5.js} +1 -1
  88. package/dist/assets/app-runtime/node_modules/@img/sharp-libvips-linux-arm64/lib/index.js +1 -0
  89. package/dist/assets/app-runtime/node_modules/@img/sharp-libvips-linux-arm64/lib/libvips-cpp.so.8.17.3 +0 -0
  90. package/dist/assets/app-runtime/node_modules/@img/sharp-libvips-linux-arm64/package.json +42 -0
  91. package/dist/assets/app-runtime/node_modules/@img/sharp-libvips-linux-arm64/versions.json +30 -0
  92. package/dist/assets/app-runtime/node_modules/@img/sharp-libvips-linuxmusl-arm64/lib/index.js +1 -0
  93. package/dist/assets/app-runtime/node_modules/@img/sharp-libvips-linuxmusl-arm64/lib/libvips-cpp.so.8.17.3 +0 -0
  94. package/dist/assets/app-runtime/node_modules/@img/sharp-libvips-linuxmusl-arm64/package.json +42 -0
  95. package/dist/assets/app-runtime/node_modules/@img/sharp-libvips-linuxmusl-arm64/versions.json +30 -0
  96. package/dist/assets/app-runtime/node_modules/@img/sharp-linux-arm64/LICENSE +191 -0
  97. package/dist/assets/app-runtime/node_modules/@img/sharp-linux-arm64/lib/sharp-linux-arm64.node +0 -0
  98. package/dist/assets/app-runtime/node_modules/@img/sharp-linux-arm64/package.json +46 -0
  99. package/dist/assets/app-runtime/node_modules/@img/sharp-linuxmusl-arm64/LICENSE +191 -0
  100. package/dist/assets/app-runtime/node_modules/@img/sharp-linuxmusl-arm64/lib/sharp-linuxmusl-arm64.node +0 -0
  101. package/dist/assets/app-runtime/node_modules/@img/sharp-linuxmusl-arm64/package.json +46 -0
  102. package/dist/assets/app-runtime/node_modules/better-sqlite3/build/Release/better_sqlite3.node +0 -0
  103. package/dist/assets/app-runtime/package.json +1 -1
  104. package/dist/runtimes/antigravity.js +14 -0
  105. package/dist/runtimes/claude-code.js +10 -62
  106. package/dist/runtimes/claude-desktop.js +16 -0
  107. package/dist/runtimes/cline.js +16 -0
  108. package/dist/runtimes/codex.js +8 -24
  109. package/dist/runtimes/continue.js +68 -0
  110. package/dist/runtimes/cursor.js +14 -0
  111. package/dist/runtimes/detect.js +24 -0
  112. package/dist/runtimes/gemini-cli.js +9 -61
  113. package/dist/runtimes/goose.js +109 -0
  114. package/dist/runtimes/jetbrains.js +50 -0
  115. package/dist/runtimes/json-mcp.js +93 -0
  116. package/dist/runtimes/openclaw.js +9 -61
  117. package/dist/runtimes/opencode.js +9 -61
  118. package/dist/runtimes/platform-paths.js +57 -0
  119. package/dist/runtimes/roo-code.js +15 -0
  120. package/dist/runtimes/skills-helper.js +31 -0
  121. package/dist/runtimes/trae.js +16 -0
  122. package/dist/runtimes/vscode.js +19 -0
  123. package/dist/runtimes/windsurf.js +15 -0
  124. package/dist/runtimes/zed.js +16 -0
  125. package/package.json +6 -3
  126. /package/dist/assets/app-runtime/.next/static/{hqMZcC_as2uhicKR6YANm → UVzMOjly0ACJRK3vNndFP}/_buildManifest.js +0 -0
  127. /package/dist/assets/app-runtime/.next/static/{hqMZcC_as2uhicKR6YANm → UVzMOjly0ACJRK3vNndFP}/_ssgManifest.js +0 -0
@@ -1,9 +1,11 @@
1
- import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, rmSync, } from "fs";
1
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
2
2
  import { homedir } from "os";
3
3
  import { join } from "path";
4
+ import { installSkills, pruneSkills, uninstallSkills, } from "./skills-helper.js";
4
5
  const BASE = join(homedir(), ".codex");
5
6
  const SKILLS_DIR = join(BASE, "skills");
6
7
  const MCP_CONFIG = join(BASE, "config.toml");
8
+ const SECTION_REGEX = /\n?\[mcp_servers\.bluekiwi[\s\S]*?(?=\n\[(?!mcp_servers\.bluekiwi)|$)/g;
7
9
  export class CodexAdapter {
8
10
  name = "codex";
9
11
  displayName = "Codex CLI";
@@ -17,21 +19,10 @@ export class CodexAdapter {
17
19
  return MCP_CONFIG;
18
20
  }
19
21
  installSkills(skills) {
20
- mkdirSync(SKILLS_DIR, { recursive: true });
21
- for (const skill of skills) {
22
- const dir = join(SKILLS_DIR, skill.name);
23
- mkdirSync(dir, { recursive: true });
24
- writeFileSync(join(dir, "SKILL.md"), skill.content);
25
- }
22
+ installSkills(SKILLS_DIR, skills);
26
23
  }
27
24
  pruneSkills(keep) {
28
- if (!existsSync(SKILLS_DIR))
29
- return;
30
- for (const entry of readdirSync(SKILLS_DIR)) {
31
- if (entry.startsWith("bk-") && !keep.has(entry)) {
32
- rmSync(join(SKILLS_DIR, entry), { recursive: true, force: true });
33
- }
34
- }
25
+ pruneSkills(SKILLS_DIR, keep);
35
26
  }
36
27
  installMcp(config) {
37
28
  mkdirSync(BASE, { recursive: true });
@@ -45,20 +36,13 @@ export class CodexAdapter {
45
36
  const existing = existsSync(MCP_CONFIG)
46
37
  ? readFileSync(MCP_CONFIG, "utf8")
47
38
  : "";
48
- const stripped = existing.replace(/\n?\[mcp_servers\.bluekiwi[\s\S]*?(?=\n\[(?!mcp_servers\.bluekiwi)|$)/g, "");
49
- writeFileSync(MCP_CONFIG, stripped + snippet);
39
+ writeFileSync(MCP_CONFIG, existing.replace(SECTION_REGEX, "") + snippet);
50
40
  }
51
41
  uninstall() {
52
42
  if (existsSync(MCP_CONFIG)) {
53
43
  const existing = readFileSync(MCP_CONFIG, "utf8");
54
- writeFileSync(MCP_CONFIG, existing.replace(/\n?\[mcp_servers\.bluekiwi[\s\S]*?(?=\n\[(?!mcp_servers\.bluekiwi)|$)/g, ""));
55
- }
56
- if (existsSync(SKILLS_DIR)) {
57
- for (const entry of readdirSync(SKILLS_DIR)) {
58
- if (entry.startsWith("bk-")) {
59
- rmSync(join(SKILLS_DIR, entry), { recursive: true, force: true });
60
- }
61
- }
44
+ writeFileSync(MCP_CONFIG, existing.replace(SECTION_REGEX, ""));
62
45
  }
46
+ uninstallSkills(SKILLS_DIR);
63
47
  }
64
48
  }
@@ -0,0 +1,68 @@
1
+ import { existsSync, mkdirSync, writeFileSync, rmSync } from "fs";
2
+ import { homedir } from "os";
3
+ import { join } from "path";
4
+ import { installSkills, pruneSkills, uninstallSkills, } from "./skills-helper.js";
5
+ // Continue.dev loads MCP server "blocks" from ~/.continue/mcpServers/.
6
+ // We install a BlueKiwi-owned YAML file so uninstall is a single unlink and
7
+ // the user's other MCP servers stay untouched. YAML 1.2 is a superset of
8
+ // JSON, so we emit JSON-flavored values to avoid needing a YAML parser.
9
+ const BASE = join(homedir(), ".continue");
10
+ const SKILLS_DIR = join(BASE, "skills");
11
+ const MCP_DIR = join(BASE, "mcpServers");
12
+ const MCP_CONFIG = join(MCP_DIR, "bluekiwi.yaml");
13
+ function quote(value) {
14
+ return JSON.stringify(value);
15
+ }
16
+ function buildYaml(config) {
17
+ const argsLines = config.args.length === 0
18
+ ? [" args: []"]
19
+ : [" args:", ...config.args.map((a) => ` - ${quote(a)}`)];
20
+ const envEntries = Object.entries(config.env);
21
+ const envLines = envEntries.length === 0
22
+ ? [" env: {}"]
23
+ : [
24
+ " env:",
25
+ ...envEntries.map(([k, v]) => ` ${quote(k)}: ${quote(v)}`),
26
+ ];
27
+ return [
28
+ "# Managed by BlueKiwi CLI — do not edit manually.",
29
+ "name: bluekiwi",
30
+ "version: 0.0.1",
31
+ "schema: v1",
32
+ "mcpServers:",
33
+ " - name: bluekiwi",
34
+ ` command: ${quote(config.command)}`,
35
+ ...argsLines,
36
+ ...envLines,
37
+ "",
38
+ ].join("\n");
39
+ }
40
+ export class ContinueAdapter {
41
+ name = "continue";
42
+ displayName = "Continue.dev";
43
+ isInstalled() {
44
+ return existsSync(BASE);
45
+ }
46
+ getSkillsDir() {
47
+ return SKILLS_DIR;
48
+ }
49
+ getMcpConfigPath() {
50
+ return MCP_CONFIG;
51
+ }
52
+ installSkills(skills) {
53
+ installSkills(SKILLS_DIR, skills);
54
+ }
55
+ pruneSkills(keep) {
56
+ pruneSkills(SKILLS_DIR, keep);
57
+ }
58
+ installMcp(config) {
59
+ mkdirSync(MCP_DIR, { recursive: true });
60
+ writeFileSync(MCP_CONFIG, buildYaml(config));
61
+ }
62
+ uninstall() {
63
+ uninstallSkills(SKILLS_DIR);
64
+ if (existsSync(MCP_CONFIG)) {
65
+ rmSync(MCP_CONFIG, { force: true });
66
+ }
67
+ }
68
+ }
@@ -0,0 +1,14 @@
1
+ import { homedir } from "os";
2
+ import { join } from "path";
3
+ import { JsonMcpAdapter } from "./json-mcp.js";
4
+ const BASE = join(homedir(), ".cursor");
5
+ export class CursorAdapter extends JsonMcpAdapter {
6
+ constructor() {
7
+ super({
8
+ name: "cursor",
9
+ displayName: "Cursor",
10
+ baseDir: BASE,
11
+ mcpConfigPath: join(BASE, "mcp.json"),
12
+ });
13
+ }
14
+ }
@@ -1,15 +1,39 @@
1
+ import { AntigravityAdapter } from "./antigravity.js";
1
2
  import { ClaudeCodeAdapter } from "./claude-code.js";
3
+ import { ClaudeDesktopAdapter } from "./claude-desktop.js";
4
+ import { ClineAdapter } from "./cline.js";
2
5
  import { CodexAdapter } from "./codex.js";
6
+ import { ContinueAdapter } from "./continue.js";
7
+ import { CursorAdapter } from "./cursor.js";
3
8
  import { GeminiCliAdapter } from "./gemini-cli.js";
9
+ import { GooseAdapter } from "./goose.js";
10
+ import { JetbrainsAdapter } from "./jetbrains.js";
4
11
  import { OpenCodeAdapter } from "./opencode.js";
5
12
  import { OpenClawAdapter } from "./openclaw.js";
13
+ import { RooCodeAdapter } from "./roo-code.js";
14
+ import { TraeAdapter } from "./trae.js";
15
+ import { VscodeAdapter } from "./vscode.js";
16
+ import { WindsurfAdapter } from "./windsurf.js";
17
+ import { ZedAdapter } from "./zed.js";
6
18
  export function getAllAdapters() {
7
19
  return [
8
20
  new ClaudeCodeAdapter(),
21
+ new ClaudeDesktopAdapter(),
9
22
  new CodexAdapter(),
10
23
  new GeminiCliAdapter(),
11
24
  new OpenCodeAdapter(),
12
25
  new OpenClawAdapter(),
26
+ new CursorAdapter(),
27
+ new AntigravityAdapter(),
28
+ new WindsurfAdapter(),
29
+ new ClineAdapter(),
30
+ new RooCodeAdapter(),
31
+ new VscodeAdapter(),
32
+ new ContinueAdapter(),
33
+ new ZedAdapter(),
34
+ new GooseAdapter(),
35
+ new JetbrainsAdapter(),
36
+ new TraeAdapter(),
13
37
  ];
14
38
  }
15
39
  export function detectInstalledAdapters() {
@@ -1,66 +1,14 @@
1
- import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, rmSync, } from "fs";
2
1
  import { homedir } from "os";
3
2
  import { join } from "path";
3
+ import { JsonMcpAdapter } from "./json-mcp.js";
4
4
  const BASE = join(homedir(), ".gemini");
5
- const SKILLS_DIR = join(BASE, "skills");
6
- const MCP_CONFIG = join(BASE, "settings.json");
7
- export class GeminiCliAdapter {
8
- name = "gemini-cli";
9
- displayName = "Gemini CLI";
10
- isInstalled() {
11
- return existsSync(BASE);
12
- }
13
- getSkillsDir() {
14
- return SKILLS_DIR;
15
- }
16
- getMcpConfigPath() {
17
- return MCP_CONFIG;
18
- }
19
- installSkills(skills) {
20
- mkdirSync(SKILLS_DIR, { recursive: true });
21
- for (const skill of skills) {
22
- const dir = join(SKILLS_DIR, skill.name);
23
- mkdirSync(dir, { recursive: true });
24
- writeFileSync(join(dir, "SKILL.md"), skill.content);
25
- }
26
- }
27
- pruneSkills(keep) {
28
- if (!existsSync(SKILLS_DIR))
29
- return;
30
- for (const entry of readdirSync(SKILLS_DIR)) {
31
- if (entry.startsWith("bk-") && !keep.has(entry)) {
32
- rmSync(join(SKILLS_DIR, entry), { recursive: true, force: true });
33
- }
34
- }
35
- }
36
- installMcp(config) {
37
- mkdirSync(BASE, { recursive: true });
38
- let existing = {};
39
- if (existsSync(MCP_CONFIG)) {
40
- try {
41
- existing = JSON.parse(readFileSync(MCP_CONFIG, "utf8"));
42
- }
43
- catch { }
44
- }
45
- existing.mcpServers = existing.mcpServers ?? {};
46
- existing.mcpServers.bluekiwi = config;
47
- writeFileSync(MCP_CONFIG, JSON.stringify(existing, null, 2));
48
- }
49
- uninstall() {
50
- if (existsSync(SKILLS_DIR)) {
51
- for (const entry of readdirSync(SKILLS_DIR)) {
52
- if (entry.startsWith("bk-")) {
53
- rmSync(join(SKILLS_DIR, entry), { recursive: true, force: true });
54
- }
55
- }
56
- }
57
- if (existsSync(MCP_CONFIG)) {
58
- try {
59
- const config = JSON.parse(readFileSync(MCP_CONFIG, "utf8"));
60
- delete config.mcpServers?.bluekiwi;
61
- writeFileSync(MCP_CONFIG, JSON.stringify(config, null, 2));
62
- }
63
- catch { }
64
- }
5
+ export class GeminiCliAdapter extends JsonMcpAdapter {
6
+ constructor() {
7
+ super({
8
+ name: "gemini-cli",
9
+ displayName: "Gemini CLI",
10
+ baseDir: BASE,
11
+ mcpConfigPath: join(BASE, "settings.json"),
12
+ });
65
13
  }
66
14
  }
@@ -0,0 +1,109 @@
1
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
2
+ import { homedir } from "os";
3
+ import { join } from "path";
4
+ import { installSkills, pruneSkills, uninstallSkills, } from "./skills-helper.js";
5
+ // Goose keeps all config in a single ~/.config/goose/config.yaml file.
6
+ // We merge into the `extensions:` mapping using sentinel-marker blocks so
7
+ // install/uninstall are idempotent and leave the user's other extensions
8
+ // untouched (mirrors the Codex TOML strategy).
9
+ const BASE = join(homedir(), ".config", "goose");
10
+ const SKILLS_DIR = join(BASE, "skills");
11
+ const MCP_CONFIG = join(BASE, "config.yaml");
12
+ const SENTINEL_BEGIN = " # bluekiwi:begin — managed by BlueKiwi CLI";
13
+ const SENTINEL_END = " # bluekiwi:end";
14
+ const SENTINEL_REGEX = /\n?[ \t]*# bluekiwi:begin[^\n]*\n[\s\S]*?[ \t]*# bluekiwi:end[^\n]*\n?/;
15
+ function quote(value) {
16
+ return JSON.stringify(value);
17
+ }
18
+ function buildBlock(config) {
19
+ const argsLines = config.args.length === 0
20
+ ? [" args: []"]
21
+ : [" args:", ...config.args.map((a) => ` - ${quote(a)}`)];
22
+ const envEntries = Object.entries(config.env);
23
+ const envLines = envEntries.length === 0
24
+ ? [" envs: {}"]
25
+ : [
26
+ " envs:",
27
+ ...envEntries.map(([k, v]) => ` ${quote(k)}: ${quote(v)}`),
28
+ ];
29
+ return [
30
+ SENTINEL_BEGIN,
31
+ " bluekiwi:",
32
+ " enabled: true",
33
+ " name: bluekiwi",
34
+ " type: stdio",
35
+ ` cmd: ${quote(config.command)}`,
36
+ ...argsLines,
37
+ ...envLines,
38
+ " timeout: 300",
39
+ " bundled: false",
40
+ " description: BlueKiwi MCP client",
41
+ SENTINEL_END,
42
+ "",
43
+ ].join("\n");
44
+ }
45
+ // Block-style `extensions:` header: "extensions:" alone on a line, optionally
46
+ // followed by whitespace and/or a trailing `# comment`. Children may appear
47
+ // on subsequent lines — this only matches the header itself.
48
+ const BLOCK_HEADER_REGEX = /^extensions:[ \t]*(?:#[^\n]*)?$/m;
49
+ // Flow-style header: `extensions: {...}` or `extensions: [...]`. We cannot
50
+ // merge into these safely without a YAML parser.
51
+ const FLOW_HEADER_REGEX = /^extensions:[ \t]*[[{]/m;
52
+ // Catch-all: any top-of-line `extensions:`, used to detect headers our
53
+ // block-style regex missed so we fail closed instead of appending a
54
+ // duplicate top-level key.
55
+ const ANY_HEADER_REGEX = /^extensions:/m;
56
+ function injectBlock(existing, block) {
57
+ const stripped = existing.replace(SENTINEL_REGEX, "\n");
58
+ const blockMatch = stripped.match(BLOCK_HEADER_REGEX);
59
+ if (blockMatch && blockMatch.index !== undefined) {
60
+ // Insert our sentinel block as the first child so existing entries below
61
+ // stay intact. Preserving the trailing comment (if any) means the line
62
+ // stays byte-for-byte as the user wrote it.
63
+ const insertAt = blockMatch.index + blockMatch[0].length;
64
+ return (stripped.slice(0, insertAt) +
65
+ "\n" +
66
+ block.replace(/\n$/, "") +
67
+ stripped.slice(insertAt));
68
+ }
69
+ if (FLOW_HEADER_REGEX.test(stripped) || ANY_HEADER_REGEX.test(stripped)) {
70
+ throw new Error("Goose config has an `extensions:` mapping that BlueKiwi cannot merge safely. " +
71
+ "Convert it to block style (e.g. `extensions:` on its own line) or remove " +
72
+ "it before running this command.");
73
+ }
74
+ const separator = stripped.length > 0 && !stripped.endsWith("\n") ? "\n" : "";
75
+ return `${stripped}${separator}extensions:\n${block}`;
76
+ }
77
+ export class GooseAdapter {
78
+ name = "goose";
79
+ displayName = "Goose";
80
+ isInstalled() {
81
+ return existsSync(BASE);
82
+ }
83
+ getSkillsDir() {
84
+ return SKILLS_DIR;
85
+ }
86
+ getMcpConfigPath() {
87
+ return MCP_CONFIG;
88
+ }
89
+ installSkills(skills) {
90
+ installSkills(SKILLS_DIR, skills);
91
+ }
92
+ pruneSkills(keep) {
93
+ pruneSkills(SKILLS_DIR, keep);
94
+ }
95
+ installMcp(config) {
96
+ mkdirSync(BASE, { recursive: true });
97
+ const existing = existsSync(MCP_CONFIG)
98
+ ? readFileSync(MCP_CONFIG, "utf8")
99
+ : "";
100
+ writeFileSync(MCP_CONFIG, injectBlock(existing, buildBlock(config)));
101
+ }
102
+ uninstall() {
103
+ uninstallSkills(SKILLS_DIR);
104
+ if (existsSync(MCP_CONFIG)) {
105
+ const existing = readFileSync(MCP_CONFIG, "utf8");
106
+ writeFileSync(MCP_CONFIG, existing.replace(SENTINEL_REGEX, "\n"));
107
+ }
108
+ }
109
+ }
@@ -0,0 +1,50 @@
1
+ import { existsSync, readdirSync } from "fs";
2
+ import { join } from "path";
3
+ import { JsonMcpAdapter } from "./json-mcp.js";
4
+ import { jetbrainsConfigDir } from "./platform-paths.js";
5
+ // JetBrains stores per-IDE config under ~/Library/Application Support/JetBrains/
6
+ // (mac) and equivalents on Linux/Windows. Each IDE × version is its own
7
+ // directory (e.g. IntelliJIdea2025.2, PyCharm2023.3). MCP config goes in
8
+ // `mcp.json` inside that dir, so this adapter fans out across all installed
9
+ // JetBrains IDE config dirs.
10
+ const BASE = jetbrainsConfigDir();
11
+ const KNOWN_IDE_PREFIXES = [
12
+ "IntelliJ",
13
+ "PyCharm",
14
+ "WebStorm",
15
+ "PhpStorm",
16
+ "RubyMine",
17
+ "GoLand",
18
+ "CLion",
19
+ "Rider",
20
+ "DataGrip",
21
+ "DataSpell",
22
+ "AppCode",
23
+ "Aqua",
24
+ "RustRover",
25
+ "MPS",
26
+ "Junie",
27
+ ];
28
+ function discoverIdeDirs() {
29
+ if (!existsSync(BASE))
30
+ return [];
31
+ return readdirSync(BASE, { withFileTypes: true })
32
+ .filter((entry) => entry.isDirectory())
33
+ .filter((entry) => KNOWN_IDE_PREFIXES.some((prefix) => entry.name.startsWith(prefix)))
34
+ .map((entry) => join(BASE, entry.name));
35
+ }
36
+ export class JetbrainsAdapter extends JsonMcpAdapter {
37
+ constructor() {
38
+ super({
39
+ name: "jetbrains",
40
+ displayName: "JetBrains AI",
41
+ baseDir: BASE,
42
+ // Skills sit under the parent dir rather than duplicating across each
43
+ // IDE; JetBrains does not natively load them either way.
44
+ skillsDir: join(BASE, "skills"),
45
+ mcpConfigPath: () => discoverIdeDirs().map((dir) => join(dir, "mcp.json")),
46
+ displayPath: join(BASE, "<each-ide>/mcp.json"),
47
+ isInstalled: () => discoverIdeDirs().length > 0,
48
+ });
49
+ }
50
+ }
@@ -0,0 +1,93 @@
1
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
2
+ import { dirname, join } from "path";
3
+ import { installSkills, pruneSkills, uninstallSkills, } from "./skills-helper.js";
4
+ export class JsonMcpAdapter {
5
+ name;
6
+ displayName;
7
+ baseDir;
8
+ skillsDir;
9
+ resolvePaths;
10
+ displayPath;
11
+ serversKey;
12
+ entryKey;
13
+ buildEntry;
14
+ isInstalledFn;
15
+ constructor(opts) {
16
+ this.name = opts.name;
17
+ this.displayName = opts.displayName;
18
+ this.baseDir = opts.baseDir;
19
+ this.skillsDir = opts.skillsDir ?? join(opts.baseDir, "skills");
20
+ if (typeof opts.mcpConfigPath === "function") {
21
+ const fn = opts.mcpConfigPath;
22
+ this.resolvePaths = fn;
23
+ this.displayPath = opts.displayPath ?? "<multiple>";
24
+ }
25
+ else {
26
+ const path = opts.mcpConfigPath;
27
+ this.resolvePaths = () => [path];
28
+ this.displayPath = path;
29
+ }
30
+ this.serversKey = opts.serversKey ?? "mcpServers";
31
+ this.entryKey = opts.entryKey ?? "bluekiwi";
32
+ this.buildEntry = opts.buildEntry ?? ((c) => c);
33
+ this.isInstalledFn = opts.isInstalled ?? (() => existsSync(this.baseDir));
34
+ }
35
+ isInstalled() {
36
+ return this.isInstalledFn();
37
+ }
38
+ getSkillsDir() {
39
+ return this.skillsDir;
40
+ }
41
+ getMcpConfigPath() {
42
+ return this.displayPath;
43
+ }
44
+ installSkills(skills) {
45
+ installSkills(this.skillsDir, skills);
46
+ }
47
+ pruneSkills(keep) {
48
+ pruneSkills(this.skillsDir, keep);
49
+ }
50
+ installMcp(config) {
51
+ const entry = this.buildEntry(config);
52
+ for (const path of this.resolvePaths()) {
53
+ mkdirSync(dirname(path), { recursive: true });
54
+ const existing = this.readJson(path);
55
+ const servers = existing[this.serversKey] ??
56
+ {};
57
+ servers[this.entryKey] = entry;
58
+ existing[this.serversKey] = servers;
59
+ writeFileSync(path, JSON.stringify(existing, null, 2));
60
+ }
61
+ }
62
+ uninstall() {
63
+ uninstallSkills(this.skillsDir);
64
+ for (const path of this.resolvePaths()) {
65
+ if (!existsSync(path))
66
+ continue;
67
+ const existing = this.readJson(path);
68
+ const servers = existing[this.serversKey];
69
+ if (servers)
70
+ delete servers[this.entryKey];
71
+ writeFileSync(path, JSON.stringify(existing, null, 2));
72
+ }
73
+ }
74
+ // Reads the target config file as JSON. If the file is missing, returns
75
+ // an empty object so install/uninstall can create it. If the file exists
76
+ // but cannot be parsed, fails closed by throwing — otherwise install
77
+ // would rewrite `{}` back and silently erase the user's unrelated
78
+ // settings (theme, other MCP servers, editor preferences, etc.).
79
+ readJson(path) {
80
+ if (!existsSync(path))
81
+ return {};
82
+ const raw = readFileSync(path, "utf8");
83
+ try {
84
+ return JSON.parse(raw);
85
+ }
86
+ catch (err) {
87
+ const message = err instanceof Error ? err.message : String(err);
88
+ throw new Error(`BlueKiwi CLI cannot parse existing MCP config at ${path}: ${message}. ` +
89
+ `The file has been left untouched. Fix the JSON manually or remove the file ` +
90
+ `and retry.`);
91
+ }
92
+ }
93
+ }
@@ -1,66 +1,14 @@
1
- import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, rmSync, } from "fs";
2
1
  import { homedir } from "os";
3
2
  import { join } from "path";
3
+ import { JsonMcpAdapter } from "./json-mcp.js";
4
4
  const BASE = join(homedir(), ".openclaw");
5
- const SKILLS_DIR = join(BASE, "skills");
6
- const MCP_CONFIG = join(BASE, "mcp.json");
7
- export class OpenClawAdapter {
8
- name = "openclaw";
9
- displayName = "OpenClaw";
10
- isInstalled() {
11
- return existsSync(BASE);
12
- }
13
- getSkillsDir() {
14
- return SKILLS_DIR;
15
- }
16
- getMcpConfigPath() {
17
- return MCP_CONFIG;
18
- }
19
- installSkills(skills) {
20
- mkdirSync(SKILLS_DIR, { recursive: true });
21
- for (const skill of skills) {
22
- const dir = join(SKILLS_DIR, skill.name);
23
- mkdirSync(dir, { recursive: true });
24
- writeFileSync(join(dir, "SKILL.md"), skill.content);
25
- }
26
- }
27
- pruneSkills(keep) {
28
- if (!existsSync(SKILLS_DIR))
29
- return;
30
- for (const entry of readdirSync(SKILLS_DIR)) {
31
- if (entry.startsWith("bk-") && !keep.has(entry)) {
32
- rmSync(join(SKILLS_DIR, entry), { recursive: true, force: true });
33
- }
34
- }
35
- }
36
- installMcp(config) {
37
- mkdirSync(BASE, { recursive: true });
38
- let existing = {};
39
- if (existsSync(MCP_CONFIG)) {
40
- try {
41
- existing = JSON.parse(readFileSync(MCP_CONFIG, "utf8"));
42
- }
43
- catch { }
44
- }
45
- existing.mcpServers = existing.mcpServers ?? {};
46
- existing.mcpServers.bluekiwi = config;
47
- writeFileSync(MCP_CONFIG, JSON.stringify(existing, null, 2));
48
- }
49
- uninstall() {
50
- if (existsSync(SKILLS_DIR)) {
51
- for (const entry of readdirSync(SKILLS_DIR)) {
52
- if (entry.startsWith("bk-")) {
53
- rmSync(join(SKILLS_DIR, entry), { recursive: true, force: true });
54
- }
55
- }
56
- }
57
- if (existsSync(MCP_CONFIG)) {
58
- try {
59
- const config = JSON.parse(readFileSync(MCP_CONFIG, "utf8"));
60
- delete config.mcpServers?.bluekiwi;
61
- writeFileSync(MCP_CONFIG, JSON.stringify(config, null, 2));
62
- }
63
- catch { }
64
- }
5
+ export class OpenClawAdapter extends JsonMcpAdapter {
6
+ constructor() {
7
+ super({
8
+ name: "openclaw",
9
+ displayName: "OpenClaw",
10
+ baseDir: BASE,
11
+ mcpConfigPath: join(BASE, "mcp.json"),
12
+ });
65
13
  }
66
14
  }
@@ -1,66 +1,14 @@
1
- import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, rmSync, } from "fs";
2
1
  import { homedir } from "os";
3
2
  import { join } from "path";
3
+ import { JsonMcpAdapter } from "./json-mcp.js";
4
4
  const BASE = join(homedir(), ".opencode");
5
- const SKILLS_DIR = join(BASE, "skills");
6
- const MCP_CONFIG = join(BASE, "mcp.json");
7
- export class OpenCodeAdapter {
8
- name = "opencode";
9
- displayName = "OpenCode";
10
- isInstalled() {
11
- return existsSync(BASE);
12
- }
13
- getSkillsDir() {
14
- return SKILLS_DIR;
15
- }
16
- getMcpConfigPath() {
17
- return MCP_CONFIG;
18
- }
19
- installSkills(skills) {
20
- mkdirSync(SKILLS_DIR, { recursive: true });
21
- for (const skill of skills) {
22
- const dir = join(SKILLS_DIR, skill.name);
23
- mkdirSync(dir, { recursive: true });
24
- writeFileSync(join(dir, "SKILL.md"), skill.content);
25
- }
26
- }
27
- pruneSkills(keep) {
28
- if (!existsSync(SKILLS_DIR))
29
- return;
30
- for (const entry of readdirSync(SKILLS_DIR)) {
31
- if (entry.startsWith("bk-") && !keep.has(entry)) {
32
- rmSync(join(SKILLS_DIR, entry), { recursive: true, force: true });
33
- }
34
- }
35
- }
36
- installMcp(config) {
37
- mkdirSync(BASE, { recursive: true });
38
- let existing = {};
39
- if (existsSync(MCP_CONFIG)) {
40
- try {
41
- existing = JSON.parse(readFileSync(MCP_CONFIG, "utf8"));
42
- }
43
- catch { }
44
- }
45
- existing.mcpServers = existing.mcpServers ?? {};
46
- existing.mcpServers.bluekiwi = config;
47
- writeFileSync(MCP_CONFIG, JSON.stringify(existing, null, 2));
48
- }
49
- uninstall() {
50
- if (existsSync(SKILLS_DIR)) {
51
- for (const entry of readdirSync(SKILLS_DIR)) {
52
- if (entry.startsWith("bk-")) {
53
- rmSync(join(SKILLS_DIR, entry), { recursive: true, force: true });
54
- }
55
- }
56
- }
57
- if (existsSync(MCP_CONFIG)) {
58
- try {
59
- const config = JSON.parse(readFileSync(MCP_CONFIG, "utf8"));
60
- delete config.mcpServers?.bluekiwi;
61
- writeFileSync(MCP_CONFIG, JSON.stringify(config, null, 2));
62
- }
63
- catch { }
64
- }
5
+ export class OpenCodeAdapter extends JsonMcpAdapter {
6
+ constructor() {
7
+ super({
8
+ name: "opencode",
9
+ displayName: "OpenCode",
10
+ baseDir: BASE,
11
+ mcpConfigPath: join(BASE, "mcp.json"),
12
+ });
65
13
  }
66
14
  }