|
| 1 | +// The manifest of levelcode-ai as the browser build ships it. |
| 2 | +// |
| 3 | +// The desktop manifest promises things a browser tab cannot do: an agent that runs commands, MCP |
| 4 | +// servers started as local processes, importing settings from VS Code on this machine, a folder |
| 5 | +// of sessions on disk. A browser build that lists them would offer a command that always fails |
| 6 | +// and a setting that does nothing. So the browser manifest is the desktop one with those removed, |
| 7 | +// and a welcome page that says what is here. Nothing in the desktop manifest changes. |
| 8 | +// |
| 9 | +// Plain CommonJS so the gate can run it (web/test/unit/manifest.test.js) and build.mjs can load it. |
| 10 | +'use strict'; |
| 11 | + |
| 12 | +/** Commands that need a machine, or a part of LevelCode that is not in the browser build. */ |
| 13 | +const REMOVED_COMMANDS = new Set([ |
| 14 | + 'levelcode.customize', // the customization pack is a desktop extension |
| 15 | + 'levelcode.import.vscode', // reads another editor's settings from this machine's disk |
| 16 | + 'levelcode.ai.manageMcp', // MCP servers are local processes |
| 17 | + 'levelcode.ai.sketch', // Agent Sketch has not been run in the browser |
| 18 | +]); |
| 19 | + |
| 20 | +/** Settings that only mean something with a shell, a local disk or a local model server. */ |
| 21 | +const REMOVED_SETTINGS = new Set([ |
| 22 | + 'levelcode.ai.commandTimeout', |
| 23 | + 'levelcode.ai.verify.command', |
| 24 | + 'levelcode.ai.preview.autoOpen', |
| 25 | + 'levelcode.ai.mcp.servers', |
| 26 | + 'levelcode.ai.mcp.toolPolicy', |
| 27 | + 'levelcode.ai.sessions.dir', |
| 28 | + 'levelcode.ai.ollama.url', |
| 29 | + 'levelcode.ai.ollama.model', |
| 30 | +]); |
| 31 | + |
| 32 | +const WELCOME = { |
| 33 | + id: 'welcome', |
| 34 | + title: 'Welcome to LevelCode', |
| 35 | + description: 'Your AI code editor, in a browser tab. Sign in and start.', |
| 36 | + steps: [ |
| 37 | + { |
| 38 | + id: 'ai', |
| 39 | + title: 'Connect your AI', |
| 40 | + description: 'Chat with your files as context, get inline completions as you type, and let the agent read, search, create and edit files in your workspace. **Sign in and you are on your plan**, with no API key to manage. Prefer your own? Bring a key for Claude, OpenAI, Groq, Mistral and more.\n[Sign in to LevelCode](command:levelcode.ai.account)\n[Use your own key (BYOK)](command:levelcode.ai.setApiKey)\n[Pick a model](command:levelcode.ai.pickModel)\n[Open the chat](command:levelcode.ai.focus)', |
| 41 | + media: { svg: 'media/walkthrough/ai.svg', altText: 'Chat, inline completions and an agent built into the editor' }, |
| 42 | + completionEvents: ['onCommand:levelcode.ai.setApiKey', 'onCommand:levelcode.ai.account'], |
| 43 | + }, |
| 44 | + { |
| 45 | + id: 'files', |
| 46 | + title: 'Open your files', |
| 47 | + description: 'Work in the scratch workspace, which is saved in this browser, or open a folder from your computer (Chrome and Edge) and the agent edits it in place.\n[Open a folder from your computer](command:levelcode.web.openLocalFolder)\n[Go to the scratch workspace](command:levelcode.web.openScratch)\n[Choose a theme](command:workbench.action.selectTheme)', |
| 48 | + media: { svg: 'media/walkthrough/look.svg', altText: 'Themes and workspaces' }, |
| 49 | + completionEvents: ['onCommand:levelcode.web.openLocalFolder'], |
| 50 | + }, |
| 51 | + { |
| 52 | + id: 'limits', |
| 53 | + title: 'What stays in the Mac app', |
| 54 | + description: 'The browser edition has no terminal, runs no commands, and starts no local MCP servers. The agent can read, search, create, edit and delete files. For the rest, [get the Mac app](https://levelcode.ai/download).', |
| 55 | + media: { svg: 'media/walkthrough/updates.svg', altText: 'LevelCode for Mac' }, |
| 56 | + }, |
| 57 | + ], |
| 58 | +}; |
| 59 | + |
| 60 | +/** |
| 61 | + * @param {any} pkg the desktop package.json |
| 62 | + * @param {{ entry?: string }} [opts] |
| 63 | + * @returns {any} a new object; `pkg` is not modified |
| 64 | + */ |
| 65 | +function toWebManifest(pkg, opts) { |
| 66 | + const out = JSON.parse(JSON.stringify(pkg)); |
| 67 | + out.browser = (opts && opts.entry) || './extension.web.js'; |
| 68 | + const c = out.contributes || (out.contributes = {}); |
| 69 | + |
| 70 | + if (Array.isArray(c.commands)) { c.commands = c.commands.filter((x) => !REMOVED_COMMANDS.has(x.command)); } |
| 71 | + for (const [where, items] of Object.entries(c.menus || {})) { |
| 72 | + c.menus[where] = items.filter((m) => !REMOVED_COMMANDS.has(m.command)); |
| 73 | + } |
| 74 | + if (Array.isArray(c.keybindings)) { |
| 75 | + c.keybindings = c.keybindings.filter((k) => !REMOVED_COMMANDS.has(k.command)); |
| 76 | + // Windows and Linux browsers keep Ctrl+Shift+I for developer tools, and the page never sees it. |
| 77 | + c.keybindings.push({ command: 'levelcode.ai.focus', key: 'ctrl+alt+i', when: 'isWeb && !isMac' }); |
| 78 | + } |
| 79 | + if (c.configuration) { |
| 80 | + const sections = Array.isArray(c.configuration) ? c.configuration : [c.configuration]; |
| 81 | + for (const s of sections) { |
| 82 | + for (const k of Object.keys(s.properties || {})) { if (REMOVED_SETTINGS.has(k)) { delete s.properties[k]; } } |
| 83 | + } |
| 84 | + } |
| 85 | + c.walkthroughs = [JSON.parse(JSON.stringify(WELCOME))]; |
| 86 | + if (Array.isArray(out.activationEvents)) { |
| 87 | + out.activationEvents = out.activationEvents.filter((e) => !(e.startsWith('onCommand:') && REMOVED_COMMANDS.has(e.slice('onCommand:'.length)))); |
| 88 | + } |
| 89 | + return out; |
| 90 | +} |
| 91 | + |
| 92 | +module.exports = { toWebManifest, REMOVED_COMMANDS, REMOVED_SETTINGS, WELCOME }; |
0 commit comments