Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,23 @@ jobs:
# the `-c.extraMetadata.main=./...` argument is mis-tokenized and
# electron-builder treats it as a config-file path (ENOENT). bash parses
# it correctly - which is why the Linux/macOS jobs already pass.
#
# The version/appVersion/cliVersion/buildDate metadata mirrors upstream's
# scripts/package.js, but with Arduino Agent's version: it is the version
# in the window title and About dialog, and buildDate is what makes the
# app load its bundled VS Code plugins (arduino-ide-electron-main.js).
shell: bash
run: >
node ../node_modules/electron-builder/cli.js --dir --publish never
-c.electronVersion=30.1.2
-c.extraMetadata.name=arduino-ide
-c.extraMetadata.main=./arduino-ide-electron-main.js
run: |
version=$(node -p "require('../arduino-mcp-extension/package.json').version")
cli_version=$(node -p "require('../arduino-ide-extension/package.json').arduino['arduino-cli'].version")
node ../node_modules/electron-builder/cli.js --dir --publish never \
-c.electronVersion=30.1.2 \
-c.extraMetadata.name=arduino-ide \
-c.extraMetadata.main=./arduino-ide-electron-main.js \
-c.extraMetadata.version="$version" \
-c.extraMetadata.theia.frontend.config.appVersion="$version" \
-c.extraMetadata.theia.frontend.config.cliVersion="$cli_version" \
-c.extraMetadata.theia.frontend.config.buildDate="$(date -u +%Y-%m-%dT%H:%M:%SZ)"

- name: Zip artifact (Windows)
if: runner.os == 'Windows'
Expand Down Expand Up @@ -192,7 +203,8 @@ jobs:
draft: true # publish manually after verifying the artifacts
fail_on_unmatched_files: true
body: |
Unsigned development build of the Arduino IDE with the embedded MCP server.
Unsigned build of Arduino Agent: the Arduino IDE with an embedded MCP server.
Not affiliated with or endorsed by Arduino SA.

Each archive contains the unpacked application for its platform - unzip and run
`Arduino IDE` (`Arduino IDE.exe` on Windows). The MCP server starts on
Expand Down
5 changes: 5 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ Fork of Arduino IDE 2.x (Theia/Electron) with an embedded MCP server.
arduino/arduino-ide by targeted file copies — this repo shares no git history
with upstream); `arduino-mcp-extension/` is ours; `electron-app/` packages both.

Fork identity (About dialog, Arduino's IDE updater switched off) is overridden
from `arduino-mcp-extension/src/browser/agent-branding.ts` via rebinds, not by
editing vendored files. `arduino-ide-extension` emits no `.d.ts`, so its
classes are `any` in our extension: no `override`, no inherited fields.

## Build

- Yarn 4 via corepack (`corepack enable`). Node 18+ works (engines say <21 but
Expand Down
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,17 @@ Unsigned development builds of the **[latest release](https://github.com/mixelpi
- [**macOS**](https://github.com/mixelpixx/Arduino-Agent/releases/latest/download/arduino-ide-mcp-macos.zip)
- [**Linux x64**](https://github.com/mixelpixx/Arduino-Agent/releases/latest/download/arduino-ide-mcp-linux.zip)

Unzip and run `Arduino IDE` (`Arduino IDE.exe` on Windows). Release notes and
older builds are on the
Unzip and run `Arduino IDE` (`Arduino IDE.exe` on Windows). The window, About
box and icon say **Arduino Agent**; only the executable keeps upstream's name for
now. Arduino Agent doesn't check Arduino's update server; get new versions here.
Release notes and older builds are on the
[Releases](https://github.com/mixelpixx/Arduino-Agent/releases) page.

> **Have the official Arduino IDE installed too?** Both currently share settings
> and a single-instance lock, so starting one while the other is running hands
> off to the one already open. Close one before starting the other. Separating
> them properly is planned for v0.7.0.

> These are unsigned dev builds. On macOS you may need to allow the app under
> **System Settings → Privacy & Security**; on Windows, dismiss SmartScreen with
> **More info → Run anyway**. Prefer to build it yourself? See
Expand Down
113 changes: 113 additions & 0 deletions arduino-mcp-extension/src/browser/agent-branding.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/**
* Arduino Agent identity for the vendored Arduino IDE shell.
*
* arduino-ide-extension is upstream code synced by file copy, so the fork's
* identity lives here as rebinds and contributions rather than as edits there.
*
* - Arduino's IDE updater is switched off. It checks Arduino's official feed,
* so it would offer the official Arduino IDE as an "update" and replace this
* app, MCP server included. Board and library update checks stay on: they
* share the `arduino.checkForUpdates` preference, so that is left alone.
* - The About dialog names Arduino Agent, its own version and the Arduino IDE
* version it is based on, and says it is not an Arduino product.
*
* arduino-ide-extension ships no type declarations, so its classes are `any`
* here: subclasses below cannot use `override` or see inherited fields, and
* inject what they need themselves.
*/

import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
import { ApplicationServer } from '@theia/core/lib/common/application-protocol';
import { CommandContribution, CommandRegistry } from '@theia/core/lib/common/command';
import { MenuContribution, MenuModelRegistry } from '@theia/core/lib/common/menu';
import { isOSX, isWindows } from '@theia/core/lib/common/os';
import { inject, injectable } from '@theia/core/shared/inversify';
import { AppService } from 'arduino-ide-extension/lib/browser/app-service';
import { About } from 'arduino-ide-extension/lib/browser/contributions/about';
import { CheckForIDEUpdates } from 'arduino-ide-extension/lib/browser/contributions/check-for-ide-updates';
import { DialogService } from 'arduino-ide-extension/lib/browser/dialog-service';
import { IDEUpdaterCommands } from 'arduino-ide-extension/lib/browser/ide-updater/ide-updater-commands';

/** Replaces Arduino's startup update check: never contacts the update feed. */
@injectable()
export class DisabledIDEUpdateCheck extends CheckForIDEUpdates {
onStart(): void {
// Updater disabled; see the file header.
}

async onReady(): Promise<void> {
// Updater disabled; see the file header.
}
}

/**
* Removes Help > "Check for Arduino IDE Updates". Contributions run in module
* order and this extension loads after arduino-ide-extension, so the command
* and menu item already exist when these run.
*/
@injectable()
export class RemoveIDEUpdaterCommand implements CommandContribution, MenuContribution {
registerCommands(registry: CommandRegistry): void {
registry.unregisterCommand(IDEUpdaterCommands.CHECK_FOR_UPDATES.id);
}

registerMenus(registry: MenuModelRegistry): void {
registry.unregisterMenuAction(IDEUpdaterCommands.CHECK_FOR_UPDATES.id);
}
}

/** Arduino's About dialog, reworded for the fork. Menu label comes from applicationName. */
@injectable()
export class AgentAbout extends About {
@inject(AppService)
private readonly agentAppService!: AppService;

@inject(ApplicationServer)
private readonly applicationServer!: ApplicationServer;

@inject(ClipboardService)
private readonly agentClipboard!: ClipboardService;

@inject(DialogService)
private readonly agentDialogService!: DialogService;

registerCommands(registry: CommandRegistry): void {
registry.registerCommand(About.Commands.ABOUT_APP, {
execute: () => this.showAgentAbout(),
});
}

private async showAgentAbout(): Promise<void> {
const [{ appVersion, cliVersion, buildDate }, extensions] = await Promise.all([
this.agentAppService.info(),
this.applicationServer.getExtensionsInfos(),
]);
const versionOf = (name: string) => extensions.find((e) => e.name === name)?.version;
const detail = [
`Version: ${versionOf('arduino-mcp-extension') ?? appVersion}`,
`Date: ${buildDate || 'dev build'}`,
`CLI Version: ${cliVersion}`,
`Based on Arduino IDE ${versionOf('arduino-ide-extension') ?? ''}`,
'',
'Arduino Agent is an independent fork of the Arduino IDE. It is not affiliated with or endorsed by Arduino SA.',
`Arduino IDE: Copyright © ${new Date().getFullYear()} Arduino s.r.l. and/or its affiliated companies`,
].join('\n');

const ok = 'OK';
const copy = 'Copy';
const buttons = !isWindows && !isOSX ? [copy, ok] : [ok, copy];
const { response } = await this.agentDialogService.showMessageBox({
message: 'Arduino Agent',
title: 'Arduino Agent',
type: 'info',
detail,
buttons,
noLink: true,
defaultId: buttons.indexOf(ok),
cancelId: buttons.indexOf(ok),
});
if (buttons[response] === copy) {
await this.agentClipboard.writeText(detail);
}
}
}
14 changes: 13 additions & 1 deletion arduino-mcp-extension/src/browser/arduino-mcp-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,26 @@
import { ContainerModule } from '@theia/core/shared/inversify';
import { WebSocketConnectionProvider } from '@theia/core/lib/browser/messaging/ws-connection-provider';
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application-contribution';
import { CommandContribution } from '@theia/core/lib/common/command';
import { MenuContribution } from '@theia/core/lib/common/menu';
import { About } from 'arduino-ide-extension/lib/browser/contributions/about';
import { CheckForIDEUpdates } from 'arduino-ide-extension/lib/browser/contributions/check-for-ide-updates';
import { bindMCPPreferences, MCPPreferences } from './mcp-preferences';
import { MCPService, MCPServicePath } from '../common/mcp-service';
import { MCPFrontendContribution } from './mcp-frontend-contribution';
import { AgentAbout, DisabledIDEUpdateCheck, RemoveIDEUpdaterCommand } from './agent-branding';

export default new ContainerModule((bind, unbind, isBound, rebind) => {
// Bind MCP preferences to add settings to Arduino IDE preferences panel
// Bind MCP preferences to add settings to the IDE preferences panel
bindMCPPreferences(bind);

// Arduino Agent identity: no Arduino IDE updater, fork-specific About dialog
rebind(CheckForIDEUpdates).to(DisabledIDEUpdateCheck).inSingletonScope();
rebind(About).to(AgentAbout).inSingletonScope();
bind(RemoveIDEUpdaterCommand).toSelf().inSingletonScope();
bind(CommandContribution).toService(RemoveIDEUpdaterCommand);
bind(MenuContribution).toService(RemoveIDEUpdaterCommand);

// Bind the MCP service proxy to communicate with backend
bind(MCPService).toDynamicValue(ctx => {
const connection = ctx.container.get(WebSocketConnectionProvider);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export class MCPFrontendContribution
break;
case 'arduino.mcp.requireAuth':
this.messageService.info(
'The MCP authentication setting takes effect after restarting the Arduino IDE.',
'The MCP authentication setting takes effect after restarting Arduino Agent.',
{ timeout: 8000 }
);
break;
Expand Down
4 changes: 2 additions & 2 deletions arduino-mcp-extension/src/browser/mcp-preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const mcpPreferenceSchema: PreferenceSchema = {
type: 'boolean',
description: nls.localize(
'arduino/mcp/preferences.enabled',
'Enable MCP (Model Context Protocol) server integration. When enabled, AI assistants like Claude Code can interact with the Arduino IDE programmatically - compiling, uploading, managing libraries, and more.'
'Enable MCP (Model Context Protocol) server integration. When enabled, AI assistants like Claude Code can interact with Arduino Agent programmatically - compiling, uploading, managing libraries, and more.'
),
default: MCPConfigurationDefaults['arduino.mcp.enabled'],
order: 1,
Expand All @@ -59,7 +59,7 @@ export const mcpPreferenceSchema: PreferenceSchema = {
type: 'boolean',
description: nls.localize(
'arduino/mcp/preferences.autoConnect',
'Automatically start the MCP server when Arduino IDE launches. Disable this if you want to manually control when the MCP server is available.'
'Automatically start the MCP server when Arduino Agent launches. Disable this if you want to manually control when the MCP server is available.'
),
default: MCPConfigurationDefaults['arduino.mcp.autoConnect'],
order: 2,
Expand Down
2 changes: 1 addition & 1 deletion arduino-mcp-extension/src/node/mcp-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,7 @@ export class ArduinoMCPServer {
res.end(
JSON.stringify({
error:
'Unauthorized. Pass the MCP token as an "Authorization: Bearer <token>" header. The token is shown in the Arduino IDE (Preferences > MCP) and stored in ~/.arduinoIDE/mcp-token.',
'Unauthorized. Pass the MCP token as an "Authorization: Bearer <token>" header. The token is shown in Arduino Agent (Preferences > MCP) and stored in ~/.arduinoIDE/mcp-token.',
})
);
return;
Expand Down
6 changes: 3 additions & 3 deletions electron-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"target": "electron",
"frontend": {
"config": {
"applicationName": "Arduino IDE",
"applicationName": "Arduino Agent",
"defaultTheme": {
"light": "arduino-theme",
"dark": "arduino-theme-dark"
Expand Down Expand Up @@ -103,8 +103,8 @@
}
}
},
"author": "Arduino SA",
"description": "Arduino IDE",
"author": "mixelpixx",
"description": "Arduino Agent: the Arduino IDE with a built-in MCP server",
"build": {
"appId": "cc.arduino.IDE2",
"productName": "Arduino IDE",
Expand Down
Binary file modified electron-app/resources/icon.icns
Binary file not shown.
Binary file modified electron-app/resources/icon.ico
Binary file not shown.
Binary file modified electron-app/resources/icons/512x512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
122 changes: 10 additions & 112 deletions electron-app/resources/preload.html
Original file line number Diff line number Diff line change
Expand Up @@ -74,119 +74,17 @@
<body>
<div class="spinner-container">
<div class="custom-spinner">
<svg
id="spinner"
xmlns="http://www.w3.org/2000/svg"
width="2499"
height="2500"
viewBox="0 0 1372.201 1372.684"
>
<path
fill="#00979D"
stroke="#81C9CB"
stroke-width=".932"
stroke-miterlimit="10"
d="M1371.701 686.024c0 378.658-306.972 685.605-685.549 685.605C307.451 1371.629.5 1064.682.5 686.024.5 307.455 307.451.483 686.152.483c378.594.001 685.549 306.972 685.549 685.541z"
/>
<linearGradient
id="a"
gradientUnits="userSpaceOnUse"
x1="-16.3"
y1="16.071"
x2="1354.901"
y2="16.071"
gradientTransform="matrix(1 0 0 -1 16.8 702.696)"
>
<stop offset=".117" stop-color="#fff" stop-opacity="0" />
<stop
offset=".252"
stop-color="#c0d1d3"
stop-opacity=".153"
/>
<stop
offset=".387"
stop-color="#91b3b7"
stop-opacity=".306"
/>
<stop
offset=".52"
stop-color="#6d9fa3"
stop-opacity=".457"
/>
<stop
offset=".65"
stop-color="#4d9195"
stop-opacity=".604"
/>
<stop
offset=".776"
stop-color="#30888b"
stop-opacity=".746"
/>
<stop
offset=".895"
stop-color="#148386"
stop-opacity=".881"
/>
<stop offset="1" stop-color="#008184" />
<svg id="spinner" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="1024" height="1024">
<defs>
<linearGradient id="agentBg" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="rgb(79, 70, 229)"/>
<stop offset="1" stop-color="rgb(49, 46, 129)"/>
</linearGradient>
<linearGradient
id="b"
gradientUnits="userSpaceOnUse"
x1="-16.8"
y1="16.071"
x2="1355.401"
y2="16.071"
gradientTransform="matrix(1 0 0 -1 16.8 702.696)"
>
<stop offset="0" stop-color="#fff" stop-opacity="0" />
<stop
offset=".153"
stop-color="#c0d1d3"
stop-opacity=".153"
/>
<stop
offset=".306"
stop-color="#91b3b7"
stop-opacity=".306"
/>
<stop
offset=".457"
stop-color="#6d9fa3"
stop-opacity=".457"
/>
<stop
offset=".604"
stop-color="#4d9195"
stop-opacity=".604"
/>
<stop
offset=".746"
stop-color="#30888b"
stop-opacity=".746"
/>
<stop
offset=".881"
stop-color="#148386"
stop-opacity=".881"
/>
<stop offset="1" stop-color="#008184" />
</linearGradient>
<path
opacity=".5"
fill="url(#a)"
stroke="url(#b)"
stroke-miterlimit="10"
d="M1371.701 686.595c0 378.65-306.972 685.606-685.549 685.606C307.451 1372.201.5 1065.23.5 686.595.5 308.019 307.451 1.048 686.152 1.048c378.594.016 685.549 306.97 685.549 685.547z"
/>
<g fill="#FFF">
<path
d="M947.959 931.196c-12.909 0-26.127-.929-39.127-2.864-108.978-15.554-181.848-93.822-222.665-153.989-40.946 60.166-113.811 138.512-222.74 154.045a275.864 275.864 0 0 1-39.133 2.785c-67.753 0-131.358-25.217-179.201-71.003-48.299-46.165-74.951-108.114-74.951-174.171 0-66.14 26.651-128.004 75.021-174.253 47.797-45.793 111.449-70.936 179.231-70.936 12.918 0 26.067.928 39.023 2.783 108.932 15.535 181.794 93.813 222.743 153.99 40.825-60.177 113.689-138.432 222.658-153.99 13-1.863 26.148-2.783 39.066-2.783 67.753 0 131.401 25.208 179.197 70.936 48.345 46.249 74.937 108.113 74.937 174.253 0 66.057-26.524 128.006-74.868 174.171-47.881 45.785-111.434 71.026-179.191 71.026M734.42 686.024c21.283 40.534 84.067 141.676 186.692 156.375 8.984 1.236 18.028 1.923 26.839 1.923 92.185 0 167.225-71.002 167.225-158.322s-75.023-158.321-167.291-158.321c-8.812 0-17.853.629-26.753 1.921-102.644 14.664-165.428 115.806-186.712 156.424M424.393 527.702c-92.308 0-167.36 70.998-167.36 158.321 0 87.305 75.021 158.322 167.245 158.322 8.852 0 17.897-.688 26.879-1.922 102.629-14.697 165.394-115.783 186.689-156.375-21.237-40.535-84.061-141.761-186.689-156.376-8.877-1.341-17.945-1.97-26.764-1.97"
/>
<path
d="M354.37 662.051h152.625v49.181H354.37zM1016.484 662.051h-51.671v-51.747h-49.348v51.747h-51.648v49.181h51.648v51.737h49.348v-51.737h51.671z"
/>
</g>
</defs>
<rect x="48" y="48" width="928" height="928" rx="220" fill="url(#agentBg)"/>
<polygon points="432,250 592,250 774,790 610,790 512,499 414,790 250,790" fill="#FFFFFF"/>
<polygon points="470,600 554,600 584,688 440,688" fill="#FFFFFF"/>
<polygon points="792,192 819.04,268.96 896,296 819.04,323.04 792,400 764.96,323.04 688,296 764.96,268.96" fill="rgb(251, 191, 36)"/>
</svg>
</div>
</div>
Expand Down
Loading