Skip to content

Commit da1f5c9

Browse files
earayu梅西
authored andcommitted
feat(desktop): bundle ApeMind MCP placeholder + seed default recipes on first launch (#19)
* feat(desktop): bundle ApeMind MCP placeholder + seed default recipes on first launch Three pieces for the ApeCloud distribution layer: 1. forge.config.ts: add `default-recipes` to extraResource so the directory ships inside the packaged app's Resources path. 2. main.ts seedDefaultRecipes(): on app startup, read recipe YAMLs from the bundled `default-recipes/` directory (packaged: process.resourcesPath; dev: relative to __dirname) and copy them to ~/.config/goose/recipes/. Same-name files are skipped (never overwrite user edits). This intentionally uses goose's existing user-recipe directory so the reader logic in goose-rs and the Desktop UI need no changes — upstream-merge-friendly. 3. bundled-extensions.json: add an `apemind` entry of type streamable_http, enabled=false, uri="" — appears in the extensions list with description prompting the user to configure URL + token before enabling. URL/token intentionally not hardcoded. Recipe YAML content authored separately in #18 (placed at ui/desktop/default-recipes/). 5-cat compliance: category 3 (默认配置 / bundled extension config) + category 5 (打包分发 / forge extraResource + first-copy logic). Zero changes to Rust crates or recipe reader logic. Signed-off-by: earayu <earayu@163.com> * fix(desktop): wire Authorization header for bundled streamable_http ApeMind MCP Per earayu2 #鹅岛 msg 792dda6a: - ApeMind MCP placeholder URL: `https://your-apemind.example.com/mcp` - Auth header placeholder: `Authorization: Bearer your-api-key-here` Two pieces: 1. `bundled-extensions.json` apemind entry: add `uri` + `headers.Authorization` placeholder values so the user only has to swap `your-api-key-here` (and adjust the URL if needed) when enabling. 2. `bundled-extensions.ts` BundledExtension type + streamable_http loader case: extend to propagate `headers` (and `envs`/`env_keys`) into the actual extension config. Without this, the JSON `headers` field is silently dropped during sync, so the auth would never reach the runtime. 5-cat compliance: still category 3 (默认配置 / bundled extension config). The loader extension is a 1-line TS plumbing pass-through, not a change to the goose-rs extension runtime — minimal rebase surface upstream-side. Signed-off-by: earayu <earayu@163.com> --------- Signed-off-by: earayu <earayu@163.com>
1 parent ddafeb4 commit da1f5c9

4 files changed

Lines changed: 44 additions & 1 deletion

File tree

ui/desktop/forge.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const isLinuxVulkanBuild = process.env.GOOSE_DESKTOP_LINUX_VARIANT === 'vulkan';
77
let cfg = {
88
asar: true,
99
executableName: 'Goose',
10-
extraResource: ['src/bin', 'src/images'],
10+
extraResource: ['src/bin', 'src/images', 'default-recipes'],
1111
icon: 'src/images/icon',
1212
// Windows specific configuration
1313
win32: {

ui/desktop/src/components/settings/extensions/bundled-extensions.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,19 @@
5252
"type": "builtin",
5353
"env_keys": [],
5454
"bundled": true
55+
},
56+
{
57+
"id": "apemind",
58+
"name": "apemind",
59+
"display_name": "ApeMind",
60+
"description": "ApeMind 知识图谱 / RAG 服务(启用前请在扩展设置里替换 URL 与 Authorization Bearer token)",
61+
"enabled": false,
62+
"type": "streamable_http",
63+
"uri": "https://your-apemind.example.com/mcp",
64+
"headers": {
65+
"Authorization": "Bearer your-api-key-here"
66+
},
67+
"timeout": 300,
68+
"bundled": true
5569
}
5670
]

ui/desktop/src/components/settings/extensions/bundled-extensions.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type BundledExtension = {
1717
uri?: string;
1818
envs?: { [key: string]: string };
1919
env_keys?: Array<string>;
20+
headers?: { [key: string]: string };
2021
timeout?: number;
2122
allow_configure?: boolean;
2223
};
@@ -116,6 +117,9 @@ export async function syncBundledExtensions(
116117
description: bundledExt.description,
117118
timeout: bundledExt.timeout,
118119
uri: bundledExt.uri || '',
120+
envs: bundledExt.envs,
121+
env_keys: bundledExt.env_keys || [],
122+
headers: bundledExt.headers,
119123
bundled: true,
120124
};
121125
}

ui/desktop/src/main.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,25 @@ function translateMenuLabels(items: MenuItem[]): void {
175175
const SETTINGS_FILE = path.join(app.getPath('userData'), 'settings.json');
176176
const STARTUP_LOGS_DIR = path.join(app.getPath('userData'), 'logs', 'startup');
177177

178+
async function seedDefaultRecipes(): Promise<void> {
179+
const sourceRoot = app.isPackaged
180+
? path.join(process.resourcesPath, 'default-recipes')
181+
: path.join(__dirname, '..', 'default-recipes');
182+
const destDir = path.join(os.homedir(), '.config', 'goose', 'recipes');
183+
184+
if (!fsSync.existsSync(sourceRoot)) return;
185+
186+
await fs.mkdir(destDir, { recursive: true });
187+
const entries = await fs.readdir(sourceRoot);
188+
for (const entry of entries) {
189+
if (!entry.endsWith('.yaml') && !entry.endsWith('.yml')) continue;
190+
const destPath = path.join(destDir, entry);
191+
if (fsSync.existsSync(destPath)) continue;
192+
await fs.copyFile(path.join(sourceRoot, entry), destPath);
193+
log.info(`[seedDefaultRecipes] seeded ${entry}${destPath}`);
194+
}
195+
}
196+
178197
function getSettings(): Settings {
179198
if (fsSync.existsSync(SETTINGS_FILE)) {
180199
let stored: Partial<Settings>;
@@ -2079,6 +2098,12 @@ const registerGlobalShortcuts = () => {
20792098
async function appMain() {
20802099
await configureProxy();
20812100

2101+
try {
2102+
await seedDefaultRecipes();
2103+
} catch (err) {
2104+
log.warn('[seedDefaultRecipes] failed:', err);
2105+
}
2106+
20822107
// Ensure Windows shims are available before any MCP processes are spawned
20832108
await ensureWinShims();
20842109

0 commit comments

Comments
 (0)