Skip to content

Commit 69973d7

Browse files
refactor: enhance plugin registration and API options handling (#102)
* refactor: enhance plugin registration and API options handling - Updated the API to include a `registerPlugin` method, allowing for more flexible plugin management. - Refactored existing code to utilize the new plugin system, improving extensibility and maintainability. - Adjusted type definitions and interfaces to support the new plugin architecture, ensuring compatibility with existing functionality. * docs: update README for plugin system enhancements and usage examples - Revised sections on plugin registration and lifecycle hooks to reflect recent API changes. - Added detailed examples for new plugins, including analytics and context menu actions. - Improved clarity in usage instructions and added a section on unregistering plugins. * fix * Update packages/grab/README.md Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * Update README.md Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * feat: enhance file opening functionality with plugin hooks - Added `onOpenFile` hook to allow plugins to handle file opening events, providing flexibility for custom behaviors. - Updated context menu handling to utilize the new `onOpenFile` hook, improving integration with plugins. - Refactored related code to ensure consistent handling of file opening across the application. * refactor: streamline agent capabilities handling in core index - Updated the logic for setting agent capabilities to use a captured provider, improving clarity and reducing redundancy. - Enhanced connection checking for the agent provider, ensuring accurate updates to the agent's connection status. - Refactored related code for better maintainability and consistency in handling agent options. * fix * refactor: update plugin contribution interface and related types - Renamed `PluginContribution` to `PluginConfig` to better reflect its purpose. - Adjusted type definitions in the core and types files to ensure consistency with the new naming convention. - Updated imports and references throughout the codebase to align with the refactored interface. * refactor: improve options handling in plugin registry - Enhanced the `setOptions` function to streamline the updating of plugin options, ensuring undefined values are ignored. - Updated the logic for merging options to include direct overrides, improving the clarity and maintainability of the code. - Refactored related code to ensure consistent handling of options within the plugin registry. --------- Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
1 parent efcf3f7 commit 69973d7

File tree

23 files changed

+870
-482
lines changed

23 files changed

+870
-482
lines changed

README.md

Lines changed: 81 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -529,37 +529,98 @@ export default function RootLayout({ children }) {
529529
530530
## Extending React Grab
531531
532-
React Grab provides an public customization API. Check out the [type definitions](https://github.com/aidenybai/react-grab/blob/main/packages/react-grab/src/types.ts) to see all available options for extending React Grab.
532+
React Grab uses a plugin system to extend functionality. Check out the [type definitions](https://github.com/aidenybai/react-grab/blob/main/packages/react-grab/src/types.ts) to see all available options.
533+
534+
#### Basic Usage
533535
534536
```typescript
535537
import { init } from "react-grab/core";
536538
537-
const api = init({
538-
theme: {
539-
enabled: true, // disable all UI by setting to false
540-
hue: 180, // shift colors by 180 degrees (pink → cyan/turquoise)
541-
crosshair: {
542-
enabled: false, // disable crosshair
539+
const api = init();
540+
541+
api.activate();
542+
api.copyElement(document.querySelector(".my-element"));
543+
console.log(api.getState());
544+
```
545+
546+
#### Lifecycle Hooks Plugin
547+
548+
Track element selections with analytics:
549+
550+
```typescript
551+
api.registerPlugin({
552+
name: "analytics",
553+
hooks: {
554+
onElementSelect: (element) => {
555+
analytics.track("element_selected", { tagName: element.tagName });
556+
},
557+
onDragEnd: (elements, bounds) => {
558+
analytics.track("drag_end", { count: elements.length, bounds });
543559
},
544-
elementLabel: {
545-
enabled: false, // disable element label
560+
onCopySuccess: (elements, content) => {
561+
analytics.track("copy", { count: elements.length });
546562
},
547563
},
564+
});
565+
```
548566
549-
onElementSelect: (element) => {
550-
console.log("Selected:", element);
551-
},
552-
onCopySuccess: (elements, content) => {
553-
console.log("Copied to clipboard:", content);
554-
},
555-
onStateChange: (state) => {
556-
console.log("Active:", state.isActive);
567+
#### Context Menu Plugin
568+
569+
Add custom actions to the right-click menu:
570+
571+
```typescript
572+
api.registerPlugin({
573+
name: "custom-actions",
574+
contextMenuActions: [
575+
{
576+
label: "Log to Console",
577+
handler: ({ elements }) => console.dir(elements[0]),
578+
},
579+
],
580+
});
581+
```
582+
583+
#### Theme Plugin
584+
585+
Customize the UI appearance:
586+
587+
```typescript
588+
api.registerPlugin({
589+
name: "theme",
590+
theme: {
591+
hue: 180, // shift colors (pink → cyan)
592+
crosshair: { enabled: false },
593+
elementLabel: { enabled: false },
557594
},
558595
});
596+
```
559597
560-
api.activate();
561-
api.copyElement(document.querySelector(".my-element"));
562-
console.log(api.getState());
598+
#### Agent Plugin
599+
600+
Create a custom agent that processes selected elements:
601+
602+
```typescript
603+
api.registerPlugin({
604+
name: "my-custom-agent",
605+
agent: {
606+
provider: {
607+
async *send({ prompt, elements, content }) {
608+
yield "Analyzing element...";
609+
610+
const response = await fetch("/api/ai", {
611+
method: "POST",
612+
headers: { "Content-Type": "application/json" },
613+
body: JSON.stringify({ prompt, content }),
614+
});
615+
616+
yield "Processing response...";
617+
618+
const result = await response.json();
619+
yield `Done: ${result.message}`;
620+
},
621+
},
622+
},
623+
});
563624
```
564625
565626
## Resources & Contributing Back

packages/grab/README.md

Lines changed: 81 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -529,37 +529,98 @@ export default function RootLayout({ children }) {
529529
530530
## Extending React Grab
531531
532-
React Grab provides an public customization API. Check out the [type definitions](https://github.com/aidenybai/react-grab/blob/main/packages/react-grab/src/types.ts) to see all available options for extending React Grab.
532+
React Grab uses a plugin system to extend functionality. Check out the [type definitions](https://github.com/aidenybai/react-grab/blob/main/packages/react-grab/src/types.ts) to see all available options.
533+
534+
#### Basic Usage
533535
534536
```typescript
535537
import { init } from "grab/core";
536538
537-
const api = init({
538-
theme: {
539-
enabled: true, // disable all UI by setting to false
540-
hue: 180, // shift colors by 180 degrees (pink → cyan/turquoise)
541-
crosshair: {
542-
enabled: false, // disable crosshair
539+
const api = init();
540+
541+
api.activate();
542+
api.copyElement(document.querySelector(".my-element"));
543+
console.log(api.getState());
544+
```
545+
546+
#### Lifecycle Hooks Plugin
547+
548+
Track element selections with analytics:
549+
550+
```typescript
551+
api.registerPlugin({
552+
name: "analytics",
553+
hooks: {
554+
onElementSelect: (element) => {
555+
analytics.track("element_selected", { tagName: element.tagName });
556+
},
557+
onDragEnd: (elements, bounds) => {
558+
analytics.track("drag_end", { count: elements.length, bounds });
543559
},
544-
elementLabel: {
545-
enabled: false, // disable element label
560+
onCopySuccess: (elements, content) => {
561+
analytics.track("copy", { count: elements.length });
546562
},
547563
},
564+
});
565+
```
548566
549-
onElementSelect: (element) => {
550-
console.log("Selected:", element);
551-
},
552-
onCopySuccess: (elements, content) => {
553-
console.log("Copied to clipboard:", content);
554-
},
555-
onStateChange: (state) => {
556-
console.log("Active:", state.isActive);
567+
#### Context Menu Plugin
568+
569+
Add custom actions to the right-click menu:
570+
571+
```typescript
572+
api.registerPlugin({
573+
name: "custom-actions",
574+
contextMenuActions: [
575+
{
576+
label: "Log to Console",
577+
handler: ({ elements }) => console.dir(elements[0]),
578+
},
579+
],
580+
});
581+
```
582+
583+
#### Theme Plugin
584+
585+
Customize the UI appearance:
586+
587+
```typescript
588+
api.registerPlugin({
589+
name: "theme",
590+
theme: {
591+
hue: 180, // shift colors (pink → cyan)
592+
crosshair: { enabled: false },
593+
elementLabel: { enabled: false },
557594
},
558595
});
596+
```
559597
560-
api.activate();
561-
api.copyElement(document.querySelector(".my-element"));
562-
console.log(api.getState());
598+
#### Agent Plugin
599+
600+
Create a custom agent that processes selected elements:
601+
602+
```typescript
603+
api.registerPlugin({
604+
name: "my-custom-agent",
605+
agent: {
606+
provider: {
607+
async *send({ prompt, elements, content }) {
608+
yield "Analyzing element...";
609+
610+
const response = await fetch("/api/ai", {
611+
method: "POST",
612+
headers: { "Content-Type": "application/json" },
613+
body: JSON.stringify({ prompt, content }),
614+
});
615+
616+
yield "Processing response...";
617+
618+
const result = await response.json();
619+
yield `Done: ${result.message}`;
620+
},
621+
},
622+
},
623+
});
563624
```
564625
565626
## Resources & Contributing Back

packages/provider-ami/src/client.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ const runAgent = async (
250250
};
251251

252252
const isReactGrabApi = (value: unknown): value is ReactGrabAPI =>
253-
typeof value === "object" && value !== null && "setOptions" in value;
253+
typeof value === "object" && value !== null && "registerPlugin" in value;
254254

255255
interface SessionData {
256256
messages: AmiUIMessage[];
@@ -550,7 +550,10 @@ export const attachAgent = async () => {
550550
const provider = createAmiAgentProvider();
551551

552552
const attach = (api: ReactGrabAPI) => {
553-
api.setOptions({ agent: { provider, storage: sessionStorage } });
553+
api.registerPlugin({
554+
name: "ami-agent",
555+
agent: { provider, storage: sessionStorage },
556+
});
554557
};
555558

556559
const existingApi = window.__REACT_GRAB__;

packages/provider-amp/src/client.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ interface AmpAgentProviderOptions {
2929
}
3030

3131
const isReactGrabApi = (value: unknown): value is ReactGrabAPI =>
32-
typeof value === "object" && value !== null && "setOptions" in value;
32+
typeof value === "object" && value !== null && "registerPlugin" in value;
3333

3434
export const createAmpAgentProvider = (
3535
providerOptions: AmpAgentProviderOptions = {},
@@ -115,7 +115,10 @@ export const attachAgent = async () => {
115115
const provider = createAmpAgentProvider();
116116

117117
const attach = (api: ReactGrabAPI) => {
118-
api.setOptions({ agent: { provider, storage: sessionStorage } });
118+
api.registerPlugin({
119+
name: "amp-agent",
120+
agent: { provider, storage: sessionStorage },
121+
});
119122
};
120123

121124
const existingApi = window.__REACT_GRAB__;

packages/provider-claude-code/src/client.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ interface ClaudeAgentProviderOptions {
3939
}
4040

4141
const isReactGrabApi = (value: unknown): value is ReactGrabAPI =>
42-
typeof value === "object" && value !== null && "setOptions" in value;
42+
typeof value === "object" && value !== null && "registerPlugin" in value;
4343

4444
export const createClaudeAgentProvider = (
4545
providerOptions: ClaudeAgentProviderOptions = {},
@@ -120,7 +120,10 @@ export const attachAgent = async () => {
120120
const provider = createClaudeAgentProvider();
121121

122122
const attach = (api: ReactGrabAPI) => {
123-
api.setOptions({ agent: { provider, storage: sessionStorage } });
123+
api.registerPlugin({
124+
name: "claude-code-agent",
125+
agent: { provider, storage: sessionStorage },
126+
});
124127
};
125128

126129
const existingApi = window.__REACT_GRAB__;

packages/provider-codex/src/client.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ interface CodexAgentProviderOptions {
3030
}
3131

3232
const isReactGrabApi = (value: unknown): value is ReactGrabAPI =>
33-
typeof value === "object" && value !== null && "setOptions" in value;
33+
typeof value === "object" && value !== null && "registerPlugin" in value;
3434

3535
export const createCodexAgentProvider = (
3636
options: CodexAgentProviderOptions = {},
@@ -112,7 +112,10 @@ export const attachAgent = async () => {
112112
const provider = createCodexAgentProvider();
113113

114114
const attach = (api: ReactGrabAPI) => {
115-
api.setOptions({ agent: { provider, storage: sessionStorage } });
115+
api.registerPlugin({
116+
name: "codex-agent",
117+
agent: { provider, storage: sessionStorage },
118+
});
116119
};
117120

118121
const existingApi = window.__REACT_GRAB__;

packages/provider-cursor/src/client.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ interface CursorAgentProviderOptions {
3131
}
3232

3333
const isReactGrabApi = (value: unknown): value is ReactGrabAPI =>
34-
typeof value === "object" && value !== null && "setOptions" in value;
34+
typeof value === "object" && value !== null && "registerPlugin" in value;
3535

3636
export const createCursorAgentProvider = (
3737
providerOptions: CursorAgentProviderOptions = {},
@@ -119,7 +119,10 @@ export const attachAgent = async () => {
119119
const provider = createCursorAgentProvider();
120120

121121
const attach = (api: ReactGrabAPI) => {
122-
api.setOptions({ agent: { provider, storage: sessionStorage } });
122+
api.registerPlugin({
123+
name: "cursor-agent",
124+
agent: { provider, storage: sessionStorage },
125+
});
123126
};
124127

125128
const existingApi = window.__REACT_GRAB__;

packages/provider-droid/src/client.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ interface DroidAgentProviderOptions {
3636
}
3737

3838
const isReactGrabApi = (value: unknown): value is ReactGrabAPI =>
39-
typeof value === "object" && value !== null && "setOptions" in value;
39+
typeof value === "object" && value !== null && "registerPlugin" in value;
4040

4141
export const createDroidAgentProvider = (
4242
providerOptions: DroidAgentProviderOptions = {},
@@ -119,7 +119,10 @@ export const attachAgent = async () => {
119119
const provider = createDroidAgentProvider();
120120

121121
const attach = (api: ReactGrabAPI) => {
122-
api.setOptions({ agent: { provider, storage: sessionStorage } });
122+
api.registerPlugin({
123+
name: "droid-agent",
124+
agent: { provider, storage: sessionStorage },
125+
});
123126
};
124127

125128
const existingApi = window.__REACT_GRAB__;

packages/provider-gemini/src/client.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ interface GeminiAgentProviderOptions {
3030
}
3131

3232
const isReactGrabApi = (value: unknown): value is ReactGrabAPI =>
33-
typeof value === "object" && value !== null && "setOptions" in value;
33+
typeof value === "object" && value !== null && "registerPlugin" in value;
3434

3535
export const createGeminiAgentProvider = (
3636
providerOptions: GeminiAgentProviderOptions = {},
@@ -118,7 +118,10 @@ export const attachAgent = async () => {
118118
const provider = createGeminiAgentProvider();
119119

120120
const attach = (api: ReactGrabAPI) => {
121-
api.setOptions({ agent: { provider, storage: sessionStorage } });
121+
api.registerPlugin({
122+
name: "gemini-agent",
123+
agent: { provider, storage: sessionStorage },
124+
});
122125
};
123126

124127
const existingApi = window.__REACT_GRAB__;

0 commit comments

Comments
 (0)