First implementation of Plimi

This commit is contained in:
achraf
2026-06-02 19:32:51 +02:00
commit be635b1828
136 changed files with 13663 additions and 0 deletions

215
README.md Normal file
View File

@@ -0,0 +1,215 @@
# Plimi: Privacy-First Browser Toolbox
Plimi is a modern, modular, zero-backend utility suite that executes all tools directly inside your browser. No data ever leaves your device.
## Features
- **Privacy-First Design**: Completely offline-capable. No data is sent to external servers.
- **Plugin Architecture**: Modular, strictly-typed plugin ecosystem supporting custom UI or generated UI based on schema.
- **Web Workers & WASM**: Supports off-loading expensive operations (e.g. PDF manipulation, media processing) into separate threads.
- **High Performance**: Built with React, Vite, and Tailwind CSS v4.
---
## Getting Started
### Prerequisites
- Node.js 18+
- npm or pnpm
### Installation & Run Dev Server
```bash
npm install
npm run dev
```
### Run Unit Tests
```bash
npm test
```
### Production Build
```bash
npm run build
```
---
## How to Create a Tool (Plugin)
Adding a new tool to Plimi is seamless thanks to its generic rendering architecture. By default, Plimi generates the tool's interface based on its options schema and input/output definitions.
### Step 1: Create the Plugin Definition
Create a new directory in `src/tools/` (e.g., `src/tools/my-tool/index.ts`):
```typescript
import type { PlimiPlugin } from '../../core/plugins/plugin-types';
import { runMyTool } from './run';
export interface MyToolOptions {
uppercase: boolean;
prefix: string;
}
export const myToolPlugin: PlimiPlugin<MyToolOptions> = {
manifest: {
id: 'my-tool',
name: 'My Custom Tool',
description: 'Does something awesome entirely in the browser.',
category: 'developer',
version: '1.0.0',
tags: ['custom', 'developer', 'text'],
// 1. Defining Inputs (see Input Options below)
input: {
type: 'text',
multiline: false, // Renders a single-line input field instead of a textarea
placeholder: 'Type something...',
example: 'Hello Plimi'
},
// 2. Defining Outputs (text, json, table, files)
output: { type: 'text' },
offlineReady: true,
},
// 3. User options generated in the right panel automatically
optionsSchema: {
fields: [
{
type: 'boolean',
key: 'uppercase',
label: 'Uppercase Output',
defaultValue: false
},
{
type: 'text',
key: 'prefix',
label: 'Output Prefix',
defaultValue: 'Result: ',
placeholder: 'e.g. Result: '
}
]
},
// Optional: override or add user-facing examples.
// If omitted, Plimi automatically builds a "Try example" action from
// manifest.example for single-input tools or field.example for grouped inputs.
examples: [
{
label: 'Try greeting',
input: { text: 'Hello Plimi' },
options: { uppercase: true, prefix: 'Result: ' }
}
],
capabilities: {
cancelable: false,
worker: false,
},
run: runMyTool,
};
```
---
### Step 2: Implement the Runner Logic
Create the runner file (e.g., `src/tools/my-tool/run.ts`):
```typescript
import { getTextInput, type ToolInput } from '../../core/io/input-types';
import type { ToolResult } from '../../core/io/output-types';
import type { ToolContext } from '../../core/plugins/plugin-types';
import type { MyToolOptions } from './index';
export async function runMyTool(
input: ToolInput,
options: MyToolOptions,
context: ToolContext
): Promise<ToolResult> {
// Use getTextInput helper for safe retrieval
const text = getTextInput(input);
if (!text) {
throw new Error("No text provided.");
}
// Report progress back to the UI progress bar (if needed)
context.reportProgress({ percentage: 50, message: "Processing text..." });
let result = text;
if (options.uppercase) {
result = result.toUpperCase();
}
return {
type: 'text',
value: `${options.prefix}${result}`,
};
}
```
---
### Step 3: Register the Plugin
Import your new plugin into `src/core/plugins/plugin-registry.ts` and append it to the `pluginRegistry` array:
```typescript
import { myToolPlugin } from "../../tools/my-tool";
export const pluginRegistry: PlimiPlugin<any>[] = [
// ... existing plugins
myToolPlugin,
];
```
The application will automatically register the tool, list it in the directory, enable search, and construct its input and options UI!
---
## UI Abstraction Details
### Input Schema Configuration
The input property in the manifest supports three structures:
1. **`type: "none"`**: No input field is rendered (useful for generators, e.g., Lorem Ipsum).
2. **Single Field**: Renders a single input field.
- `type: "text"`: Renders a text entry box.
- Specify `multiline: false` to make it a compact single-line input field (default is a textarea).
- Specify `rows: number` to customize multiline text area height.
- `type: "files"`: Renders a drag-and-drop file uploader area.
3. **`type: "group"`**: Renders a group of input fields stacked vertically. Each field requires a unique `key`.
- Plimi will automatically expose a shared **"Try example"** action when fields contain example data, allowing the user to populate the form with one click.
- Access group values inside the runner using `getTextInput(input, "field_key")`.
### Output Visualizers
Plimi automatically formats results based on the returned `ToolResult.type`:
- **`type: "text"`**: Renders a code block or plain text area with a global copy button.
- **`type: "files"`**: Renders a lists of downloadable files with size comparisons.
- **`type: "table"`**: Renders a tabular data format with column headers, row styles, and a **"Copy CSV"** button.
- **`type: "json"`**: Renders a flat grid of cards for each key.
- Return a key named `"primary"` in your JSON output to showcase it prominently in a large card at the top of the results panel.
- Each key in the grid renders with its own individual, hoverable **"Copy"** button for a premium experience.
---
### Writing Custom UIs
For complex interfaces (like Canvas-based image manipulation or interactive dropzones), set `customUi: true` under `capabilities` in the manifest and supply a React component to the `customUi` key:
```typescript
import { lazy } from "react";
const CustomUiComponent = lazy(() => import("./CustomUiComponent"));
export const myPlugin = {
// ...
capabilities: { customUi: true },
customUi: CustomUiComponent
};
```
Your component will receive the plugin definition as `plugin`, the current `dark` theme boolean, and normalized `examples` as props. Custom UIs can import `ExampleActionButton` from `src/components/tool/ExampleActionButton` to render the same "Try example" action style used by generated tools.
---
### Writing Tests
Every tool should be tested. Create a `run.test.ts` alongside your tool and run:
```bash
npm test
```
See `src/tools/regex-tester/run.test.ts` for an example of testing both key-based inputs and fallback single-string values.