@rimori/client 2.0.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +140 -1122
  2. package/dist/{rimori-client/src/cli → cli}/scripts/init/main.js +0 -0
  3. package/dist/{rimori-client/src/cli → cli}/scripts/release/release.js +0 -0
  4. package/dist/{rimori-client/src/index.d.ts → index.d.ts} +7 -1
  5. package/dist/{rimori-client/src/index.js → index.js} +1 -0
  6. package/dist/{rimori-client/src/plugin → plugin}/CommunicationHandler.d.ts +1 -0
  7. package/dist/{rimori-client/src/plugin → plugin}/CommunicationHandler.js +19 -2
  8. package/dist/{rimori-client/src/plugin → plugin}/RimoriClient.d.ts +1 -1
  9. package/dist/{rimori-client/src/worker → worker}/WorkerSetup.js +1 -1
  10. package/package.json +2 -1
  11. package/src/index.ts +7 -1
  12. package/src/plugin/CommunicationHandler.ts +22 -6
  13. package/src/plugin/RimoriClient.ts +5 -3
  14. package/src/worker/WorkerSetup.ts +1 -1
  15. package/tsconfig.json +1 -0
  16. package/dist/react-client/plugin/ThemeSetter.d.ts +0 -2
  17. package/dist/react-client/plugin/ThemeSetter.js +0 -19
  18. package/dist/react-client/src/plugin/ThemeSetter.d.ts +0 -2
  19. package/dist/react-client/src/plugin/ThemeSetter.js +0 -19
  20. package/dist/react-client/src/utils/FullscreenUtils.d.ts +0 -2
  21. package/dist/react-client/src/utils/FullscreenUtils.js +0 -23
  22. package/dist/react-client/src/utils/PluginUtils.d.ts +0 -2
  23. package/dist/react-client/src/utils/PluginUtils.js +0 -23
  24. /package/dist/{rimori-client/src/cli → cli}/scripts/init/dev-registration.d.ts +0 -0
  25. /package/dist/{rimori-client/src/cli → cli}/scripts/init/dev-registration.js +0 -0
  26. /package/dist/{rimori-client/src/cli → cli}/scripts/init/env-setup.d.ts +0 -0
  27. /package/dist/{rimori-client/src/cli → cli}/scripts/init/env-setup.js +0 -0
  28. /package/dist/{rimori-client/src/cli → cli}/scripts/init/file-operations.d.ts +0 -0
  29. /package/dist/{rimori-client/src/cli → cli}/scripts/init/file-operations.js +0 -0
  30. /package/dist/{rimori-client/src/cli → cli}/scripts/init/html-cleaner.d.ts +0 -0
  31. /package/dist/{rimori-client/src/cli → cli}/scripts/init/html-cleaner.js +0 -0
  32. /package/dist/{rimori-client/src/cli → cli}/scripts/init/main.d.ts +0 -0
  33. /package/dist/{rimori-client/src/cli → cli}/scripts/init/package-setup.d.ts +0 -0
  34. /package/dist/{rimori-client/src/cli → cli}/scripts/init/package-setup.js +0 -0
  35. /package/dist/{rimori-client/src/cli → cli}/scripts/init/router-transformer.d.ts +0 -0
  36. /package/dist/{rimori-client/src/cli → cli}/scripts/init/router-transformer.js +0 -0
  37. /package/dist/{rimori-client/src/cli → cli}/scripts/init/tailwind-config.d.ts +0 -0
  38. /package/dist/{rimori-client/src/cli → cli}/scripts/init/tailwind-config.js +0 -0
  39. /package/dist/{rimori-client/src/cli → cli}/scripts/init/vite-config.d.ts +0 -0
  40. /package/dist/{rimori-client/src/cli → cli}/scripts/init/vite-config.js +0 -0
  41. /package/dist/{rimori-client/src/cli → cli}/scripts/release/detect-translation-languages.d.ts +0 -0
  42. /package/dist/{rimori-client/src/cli → cli}/scripts/release/detect-translation-languages.js +0 -0
  43. /package/dist/{rimori-client/src/cli → cli}/scripts/release/release-config-upload.d.ts +0 -0
  44. /package/dist/{rimori-client/src/cli → cli}/scripts/release/release-config-upload.js +0 -0
  45. /package/dist/{rimori-client/src/cli → cli}/scripts/release/release-db-update.d.ts +0 -0
  46. /package/dist/{rimori-client/src/cli → cli}/scripts/release/release-db-update.js +0 -0
  47. /package/dist/{rimori-client/src/cli → cli}/scripts/release/release-file-upload.d.ts +0 -0
  48. /package/dist/{rimori-client/src/cli → cli}/scripts/release/release-file-upload.js +0 -0
  49. /package/dist/{rimori-client/src/cli → cli}/scripts/release/release.d.ts +0 -0
  50. /package/dist/{rimori-client/src/cli → cli}/types/DatabaseTypes.d.ts +0 -0
  51. /package/dist/{rimori-client/src/cli → cli}/types/DatabaseTypes.js +0 -0
  52. /package/dist/{rimori-client/src/controller → controller}/AIController.d.ts +0 -0
  53. /package/dist/{rimori-client/src/controller → controller}/AIController.js +0 -0
  54. /package/dist/{rimori-client/src/controller → controller}/AccomplishmentController.d.ts +0 -0
  55. /package/dist/{rimori-client/src/controller → controller}/AccomplishmentController.js +0 -0
  56. /package/dist/{rimori-client/src/controller → controller}/AudioController.d.ts +0 -0
  57. /package/dist/{rimori-client/src/controller → controller}/AudioController.js +0 -0
  58. /package/dist/{rimori-client/src/controller → controller}/ExerciseController.d.ts +0 -0
  59. /package/dist/{rimori-client/src/controller → controller}/ExerciseController.js +0 -0
  60. /package/dist/{rimori-client/src/controller → controller}/ObjectController.d.ts +0 -0
  61. /package/dist/{rimori-client/src/controller → controller}/ObjectController.js +0 -0
  62. /package/dist/{rimori-client/src/controller → controller}/SettingsController.d.ts +0 -0
  63. /package/dist/{rimori-client/src/controller → controller}/SettingsController.js +0 -0
  64. /package/dist/{rimori-client/src/controller → controller}/SharedContentController.d.ts +0 -0
  65. /package/dist/{rimori-client/src/controller → controller}/SharedContentController.js +0 -0
  66. /package/dist/{rimori-client/src/controller → controller}/TranslationController.d.ts +0 -0
  67. /package/dist/{rimori-client/src/controller → controller}/TranslationController.js +0 -0
  68. /package/dist/{rimori-client/src/controller → controller}/VoiceController.d.ts +0 -0
  69. /package/dist/{rimori-client/src/controller → controller}/VoiceController.js +0 -0
  70. /package/dist/{rimori-client/src/fromRimori → fromRimori}/EventBus.d.ts +0 -0
  71. /package/dist/{rimori-client/src/fromRimori → fromRimori}/EventBus.js +0 -0
  72. /package/dist/{rimori-client/src/fromRimori → fromRimori}/PluginTypes.d.ts +0 -0
  73. /package/dist/{rimori-client/src/fromRimori → fromRimori}/PluginTypes.js +0 -0
  74. /package/dist/{rimori-client/src/plugin → plugin}/Logger.d.ts +0 -0
  75. /package/dist/{rimori-client/src/plugin → plugin}/Logger.js +0 -0
  76. /package/dist/{rimori-client/src/plugin → plugin}/RimoriClient.js +0 -0
  77. /package/dist/{rimori-client/src/plugin → plugin}/StandaloneClient.d.ts +0 -0
  78. /package/dist/{rimori-client/src/plugin → plugin}/StandaloneClient.js +0 -0
  79. /package/dist/{rimori-client/src/utils → utils}/difficultyConverter.d.ts +0 -0
  80. /package/dist/{rimori-client/src/utils → utils}/difficultyConverter.js +0 -0
  81. /package/dist/{rimori-client/src/utils → utils}/endpoint.d.ts +0 -0
  82. /package/dist/{rimori-client/src/utils → utils}/endpoint.js +0 -0
  83. /package/dist/{rimori-client/src/worker → worker}/WorkerSetup.d.ts +0 -0
package/README.md CHANGED
@@ -1,23 +1,35 @@
1
1
  # Rimori Client Package
2
2
 
3
- The **@rimori/client** package is a comprehensive React library that enables plugins to seamlessly integrate with the Rimori learning platform. It provides database access, AI/LLM integration, inter-plugin communication, community features, and pre-built UI components.
3
+ The `@rimori/client` package is the framework-agnostic runtime and CLI that powers Rimori plugins. Use it inside plugin iframes, workers, and build scripts to access Rimori platform features such as database access, AI, shared content, and the event bus. All React-specific helpers and UI components are now published separately in `@rimori/react-client`.
4
4
 
5
5
  ## Table of Contents
6
-
6
+ - [Overview](#overview)
7
7
  - [Installation](#installation)
8
- - [Quick Start Plugin Development](#quick-start-plugin-development)
9
- - [Releasing Your Plugin to Rimori](#releasing-your-plugin-to-rimori)
8
+ - [Relationship to @rimori/react-client](#relationship-to-rimori-react-client)
10
9
  - [Quick Start](#quick-start)
11
- - [Core API - usePlugin Hook](#core-api---useplugin-hook)
12
- - [Database Integration](#database-integration)
13
- - [LLM Integration](#llm-integration)
14
- - [Event System](#event-system)
15
- - [Community Features](#community-features)
16
- - [Components](#components)
17
- - [Hooks](#hooks)
10
+ - [CLI Tooling](#cli-tooling)
11
+ - [Runtime API](#runtime-api)
12
+ - [Bootstrapping](#bootstrapping)
13
+ - [Plugin Interface](#plugin-interface)
14
+ - [Database Access](#database-access)
15
+ - [AI & Voice](#ai--voice)
16
+ - [Event Bus & Actions](#event-bus--actions)
17
+ - [Community Content](#community-content)
18
+ - [Workers & Standalone Development](#workers--standalone-development)
18
19
  - [Utilities](#utilities)
19
20
  - [TypeScript Support](#typescript-support)
20
- - [Examples](#examples)
21
+ - [Example Integration](#example-integration)
22
+ - [Troubleshooting](#troubleshooting)
23
+
24
+ ## Overview
25
+
26
+ `@rimori/client` gives you direct, typed access to the Rimori platform:
27
+ - Bootstrap authenticated plugin sessions and fetch Rimori context.
28
+ - Run Supabase queries against your plugin's dedicated schema.
29
+ - Call AI services for text, structured data, or voice.
30
+ - Communicate with Rimori and other plugins through the event bus.
31
+ - Share content with the community and emit accomplishments.
32
+ - Ship and upgrade plugins by using the bundled CLI.
21
33
 
22
34
  ## Installation
23
35
 
@@ -27,1190 +39,196 @@ npm install @rimori/client
27
39
  yarn add @rimori/client
28
40
  ```
29
41
 
30
- ## Quick Start Plugin Development
31
-
32
- The Rimori Client package includes powerful CLI tools to eliminate the tedious setup process and get you building your plugin fast. The initialization script handles authentication, plugin registration, environment setup, and all necessary boilerplate configuration.
33
-
34
- ### Prerequisites
35
-
36
- Before initializing your plugin, ensure you have:
37
-
38
- 1. **Node.js and yarn/npm installed**
39
- 2. **A Rimori account** - You'll need to login during initialization to receive your access token
42
+ ## Relationship to @rimori/react-client
40
43
 
41
- ### Initializing a New Plugin
42
-
43
- Open Lovable and vibe code the look of your plugin.
44
-
45
- Then connect it to your Github account.
46
-
47
- Clone the git repository:
44
+ If you are building a React-based plugin UI, install the companion package:
48
45
 
49
46
  ```bash
50
- git clone ...
51
- cd my-awesome-plugin
52
-
53
- # Initialize with Rimori Client (this handles everything!)
54
- npx @rimori/client rimori-init
47
+ npm install @rimori/react-client
55
48
  ```
56
49
 
57
- ### What the Init Script Does
58
-
59
- The `rimori-init` command automates the entire plugin setup process:
60
-
61
- 1. **🔐 Authentication**: Prompts for your Rimori credentials and authenticates with the platform
62
- 2. **🚀 Plugin Registration**: Automatically registers your plugin and generates a unique plugin ID
63
- 3. **🔑 Access Token**: Provides you with an access token for future plugin releases
64
- 4. **📦 Package Configuration**: Updates `package.json` with plugin-specific settings
65
- 5. **⚙️ Environment Setup**: Creates `.env` files with your credentials
66
- 6. **📁 File Structure**: Copies all necessary boilerplate files and examples
67
- 7. **🎨 Configuration**: Sets up Vite, Tailwind, and router configurations
68
- 8. **📖 Documentation**: Provides example documentation and getting started guides
50
+ `@rimori/react-client` wraps this core runtime with React context, hooks, and prebuilt UI components. Use it for UI concerns (`PluginProvider`, `useRimori`, `useChat`, widgets). Keep importing non-UI functionality such as `RimoriClient`, `StandaloneClient`, `setupWorker`, or the CLI directly from `@rimori/client`.
69
51
 
70
- ### Upgrade Mode
52
+ ## Quick Start
71
53
 
72
- If you need to upgrade an existing plugin's configuration without changing the plugin ID:
54
+ Instantiate the client once in your application entry point and reuse it everywhere:
73
55
 
74
- ```bash
75
- yarn rimori-init --upgrade
76
- ```
56
+ ```ts
57
+ import { RimoriClient } from "@rimori/client";
77
58
 
78
- ### Development Setup
59
+ async function bootstrap() {
60
+ const client = await RimoriClient.getInstance("your-plugin-id");
79
61
 
80
- After initialization, start developing immediately:
62
+ const user = client.plugin.getUserInfo();
63
+ const { data } = await client.db
64
+ .from("notes")
65
+ .select("*")
66
+ .eq("user_id", user.profile_id);
81
67
 
82
- ```bash
83
- # Start development server
84
- yarn dev
68
+ console.log("Loaded notes", data);
69
+ }
85
70
 
86
- # Your plugin will be available at:
87
- # http://localhost:3000 (or your chosen port)
71
+ bootstrap().catch(console.error);
88
72
  ```
89
73
 
90
- Rimori client comes pre-configured with:
74
+ The instance exposes high-level controllers grouped under properties such as `plugin`, `db`, `ai`, `event`, `community`, `runtime`, and `navigation`.
91
75
 
92
- - **Hot reload** for instant development feedback
93
- - ✅ **TypeScript support** with full type safety
94
- - ✅ **TailwindCSS** for modern styling
95
- - ✅ **React Router** for navigation
96
- - ✅ **Example components** and documentation
76
+ ## CLI Tooling
97
77
 
98
- ## Releasing Your Plugin to Rimori
78
+ Two CLI commands ship with the package (also available through `npx`):
99
79
 
100
- Publishing your plugin to the Rimori platform is streamlined through the built-in `rimori-release` CLI tool. The release process handles database updates, file uploads, and plugin activation automatically.
80
+ ### `rimori-init`
101
81
 
102
- ### Prerequisites
82
+ - Authenticates against Rimori using your developer credentials.
83
+ - Registers the plugin and writes the plugin ID (`r_id`) into `package.json`.
84
+ - Provisions environment files, Vite/Tailwind scaffolding, worker configuration, and sample assets.
103
85
 
104
- 1. **Plugin must be initialized** - Use `rimori-init` first to set up your plugin
105
- 2. **Build your plugin** - Ensure your plugin is built and the output is in the `dist/` directory
106
- 3. **Environment configured** - Your `.env` file should contain `RIMORI_TOKEN` (set during initialization)
107
-
108
- ### Quick Release (Recommended)
109
-
110
- During plugin initialization, convenient release scripts are automatically added to your `package.json`. These scripts handle building and releasing in one command:
86
+ Usage:
111
87
 
112
88
  ```bash
113
- # Quick release commands (build + release)
114
- yarn release:alpha # Build and release to alpha channel
115
- yarn release:beta # Build and release to beta channel
116
- yarn release:stable # Build and release to stable channel
89
+ npx @rimori/client rimori-init
90
+ npx @rimori/client rimori-init --upgrade # refresh config without changing the plugin ID
117
91
  ```
118
92
 
119
- ### Manual Release Process
93
+ ### `rimori-release`
120
94
 
121
- If you prefer more control or need to understand the underlying process, you can use the `rimori-release` command directly:
95
+ - Builds (optionally) and uploads the plugin bundle to Rimori.
96
+ - Updates release metadata, database migrations, and activates the chosen channel (`alpha`, `beta`, `stable`).
97
+
98
+ Usage:
122
99
 
123
100
  ```bash
124
- # Build your plugin first
125
101
  yarn build
126
-
127
- # Then release to different channels
128
- yarn rimori-release alpha # For alpha testing
129
- yarn rimori-release beta # For beta releases
130
- yarn rimori-release stable # For production releases
102
+ npx @rimori/client rimori-release alpha
131
103
  ```
132
104
 
133
- The plugin version is automatically read from your `package.json`, and the plugin ID is retrieved from the `r_id` field (set during initialization).
134
-
135
- ### What the Release Script Does
136
-
137
- The `rimori-release` command performs a complete release workflow:
138
-
139
- 1. **📋 Configuration Upload**: Sends plugin metadata and configuration to the platform
140
- 2. **🗄️ Database Updates**: Updates plugin information and release records
141
- 3. **📁 File Upload**: Uploads all files from your `dist/` directory to the platform
142
- 4. **🚀 Plugin Activation**: Activates the new version on the specified release channel
143
-
144
- ### Release Channels
145
-
146
- - **`alpha`**: Early development releases for internal testing
147
- - **`beta`**: Pre-release versions for beta testers
148
- - **`stable`**: Production-ready releases for all users
105
+ During initialization, convenience scripts (`release:alpha`, `release:beta`, `release:stable`) are added to your project automatically.
149
106
 
150
- ### Automatic Configuration
107
+ ## Runtime API
151
108
 
152
- During plugin initialization, the following are automatically configured:
109
+ ### Bootstrapping
153
110
 
154
- - `RIMORI_TOKEN`: Your authentication token (stored in `.env`)
155
- - `r_id`: Your unique plugin ID (stored in `package.json`)
156
- - **Release scripts**: `yarn release:alpha`, `yarn release:beta`, `yarn release:stable`
157
- - **Build configuration**: TypeScript checking, Vite setup, and worker builds
158
-
159
- ### Troubleshooting
160
-
161
- If you encounter release issues:
162
-
163
- 1. **Missing token**: Ensure `RIMORI_TOKEN` is in your `.env` file
164
- 2. **No plugin ID**: Verify `r_id` exists in your `package.json`
165
- 3. **Build errors**: Run `yarn build` successfully before releasing
166
- 4. **Authentication**: Your token may have expired - re-run `rimori-init` if needed
167
-
168
- #### Worker Process Errors
169
-
170
- If you encounter errors like:
171
-
172
- ```
173
- ReferenceError: process is not defined
174
- ```
175
-
176
- **Root Cause**: Your plugin is importing a library that tries to access `process` or `process.env` in the worker context. Web workers don't have access to Node.js globals like `process`.
177
-
178
- **Debugging Steps**:
179
-
180
- 1. **Check your imports**: Look through the files used in the worker which might import libraries that might access `process.env`:
181
- - React libraries (React Router, React Query, etc.)
182
- - UI component libraries (Material-UI, Ant Design, etc.)
183
- - Utility libraries that check for environment variables
184
-
185
- 2. **Verify Rimori Client imports**: Ensure you're importing from `@rimori/client/core` and not from `@rimori/client`:
186
-
187
- ```typescript
188
- // ✅ Correct - import from rimori client
189
- import { RimoriClient } from '@rimori/client/core';
190
-
191
- // ❌ Incorrect - direct imports that might access process.env directly or through their dependencies
192
- import { Avatar } from '@rimori/client';
193
- import { useQuery } from '@tanstack/react-query';
194
- ```
195
-
196
- 3. **Check your dependencies**: Look at your `package.json` for libraries that might be bundled into the worker:
197
- - UI frameworks (React, Vue, etc.)
198
- - State management libraries
199
- - Routing libraries
200
- - Any library that checks `process.env.NODE_ENV`
201
-
202
- **Solution**: Use only `@rimori/client/core` exports in your worker code. The Rimori client provides all necessary functionality without requiring Node.js globals.
203
-
204
- **Note**: The worker bundling order is determined by the build system and cannot be controlled. Libraries that access `process.env` will always cause this error in the worker context.
205
-
206
- ````
207
-
208
- ## Core API - usePlugin Hook
209
-
210
- The `useRimori()` hook is the main interface for accessing Rimori platform features:
211
-
212
- ```typescript
213
- import { useRimori } from "@rimori/client";
214
-
215
- const MyComponent = () => {
216
- const client = useRimori();
217
-
218
- // Access all client features
219
- const { db, llm, event, community, plugin } = client;
220
-
221
- return <div>My Plugin Content</div>;
222
- };
223
- ````
111
+ - `RimoriClient.getInstance(pluginId)` connect the sandboxed iframe to Rimori.
112
+ - `StandaloneClient` authenticate when developing a plugin outside Rimori.
113
+ - `setupWorker()` – register worker scripts that need the Rimori runtime.
224
114
 
225
115
  ### Plugin Interface
226
116
 
227
- ```typescript
228
- const { plugin } = useRimori();
117
+ Access metadata and settings through `client.plugin`:
229
118
 
230
- // Plugin information and settings
231
- plugin.pluginId: string // Current plugin ID
232
- plugin.getSettings<T>(defaultSettings: T): Promise<T> // Get plugin settings
233
- plugin.setSettings(settings: any): Promise<void> // Update plugin settings
234
- plugin.getInstalled(): Promise<Plugin[]> // Get all installed plugins
235
- plugin.getUserInfo(): Promise<UserInfo> // Get current user information
236
- ```
119
+ - `plugin.pluginId` current plugin identifier.
120
+ - `plugin.getSettings(defaults)` / `plugin.setSettings(settings)` – persist configuration.
121
+ - `plugin.getPluginInfo()` read active/installed plugin information.
122
+ - `plugin.getUserInfo()` obtain user profile details (language, name, guild, etc.).
123
+ - `plugin.getTranslator()` lazily initialize the translator for manual i18n.
237
124
 
238
- **Example: Managing Flashcard Plugin Settings**
125
+ ### Database Access
239
126
 
240
- ```typescript
241
- interface FlashcardSettings {
242
- dailyGoal: number;
243
- reviewInterval: 'easy' | 'medium' | 'hard';
244
- showAnswerDelay: number;
245
- enableAudioPronunciation: boolean;
246
- difficultyAlgorithm: 'spaced-repetition' | 'random' | 'progressive';
247
- }
127
+ `client.db` wraps the Supabase client that is scoped to your plugin tables:
248
128
 
249
- const FlashcardSettingsComponent = () => {
250
- const { plugin } = useRimori();
251
- const [settings, setSettings] = useState<FlashcardSettings>();
252
-
253
- useEffect(() => {
254
- const loadSettings = async () => {
255
- const defaultSettings: FlashcardSettings = {
256
- dailyGoal: 20,
257
- reviewInterval: 'medium',
258
- showAnswerDelay: 3,
259
- enableAudioPronunciation: true,
260
- difficultyAlgorithm: 'spaced-repetition'
261
- };
262
-
263
- const currentSettings = await plugin.getSettings(defaultSettings);
264
- setSettings(currentSettings);
265
- };
266
-
267
- loadSettings();
268
- }, []);
269
-
270
- const updateSettings = async (newSettings: Partial<FlashcardSettings>) => {
271
- const updated = { ...settings, ...newSettings };
272
- await plugin.setSettings(updated);
273
- setSettings(updated);
274
- };
275
-
276
- return (
277
- <div className="flashcard-settings">
278
- <label>
279
- Daily Goal: {settings?.dailyGoal} cards
280
- <input
281
- type="range"
282
- min="5"
283
- max="100"
284
- value={settings?.dailyGoal}
285
- onChange={(e) => updateSettings({ dailyGoal: parseInt(e.target.value) })}
286
- />
287
- </label>
288
-
289
- <label>
290
- Review Interval:
291
- <select
292
- value={settings?.reviewInterval}
293
- onChange={(e) => updateSettings({ reviewInterval: e.target.value as any })}
294
- >
295
- <option value="easy">Easy (longer intervals)</option>
296
- <option value="medium">Medium</option>
297
- <option value="hard">Hard (shorter intervals)</option>
298
- </select>
299
- </label>
300
-
301
- <label>
302
- <input
303
- type="checkbox"
304
- checked={settings?.enableAudioPronunciation}
305
- onChange={(e) => updateSettings({ enableAudioPronunciation: e.target.checked })}
306
- />
307
- Enable Audio Pronunciation
308
- </label>
309
- </div>
310
- );
311
- };
312
- ```
313
-
314
- ## Database Integration
315
-
316
- Access your plugin's dedicated database tables with full TypeScript support:
317
-
318
- ```typescript
319
- const { db } = useRimori();
320
-
321
- // Database interface
322
- db.from(tableName) // Query builder for tables/views - supports ALL Supabase operations
323
- db.storage // File storage access
324
- db.tablePrefix: string // Your plugin's table prefix
325
- db.getTableName(table: string): string // Get full table name with prefix
326
- ```
327
-
328
- The `db.from()` method provides access to the complete Supabase PostgREST API, supporting all database operations including:
329
-
330
- - **CRUD Operations**: `insert()`, `select()`, `update()`, `delete()`, `upsert()`
331
- - **Filtering**: `eq()`, `neq()`, `gt()`, `gte()`, `lt()`, `lte()`, `like()`, `ilike()`, `is()`, `in()`, `contains()`, `containedBy()`, `rangeLt()`, `rangeGt()`, `rangeGte()`, `rangeLte()`, `rangeAdjacent()`, `overlaps()`, `textSearch()`, `match()`, `not()`, `or()`, `filter()`
332
- - **Modifiers**: `order()`, `limit()`, `range()`, `single()`, `maybe_single()`, `csv()`, `geojson()`, `explain()`
333
- - **Aggregations**: `count()`, `sum()`, `avg()`, `min()`, `max()`
334
- - **Advanced Features**: Row Level Security (RLS), real-time subscriptions, stored procedures, and custom functions
335
-
336
- All operations automatically use your plugin's table prefix for security and isolation.
337
-
338
- **Column deprecation and removal (short policy)**
339
-
340
- - To remove a column, use a two-step process:
341
- 1. Mark the column with `deprecated: true` in your `rimori/db.config.ts` and release. The backend renames the column to `<name>_old`.
342
- 2. In a subsequent release, remove the column from the config. The backend drops `<name>_old`.
343
-
344
- **Example: CRUD Operations**
345
-
346
- ```typescript
347
- interface StudySession {
348
- id?: string;
349
- user_id: string;
350
- topic: string;
351
- duration: number;
352
- completed_at: string;
353
- }
354
-
355
- const StudySessionManager = () => {
356
- const { db } = useRimori();
357
-
358
- // Create a new study session
359
- const createSession = async (session: Omit<StudySession, 'id'>) => {
360
- const { data, error } = await db
361
- .from('study_sessions') // Automatically prefixed
362
- .insert(session)
363
- .select()
364
- .single();
365
-
366
- if (error) throw error;
367
- return data;
368
- };
369
-
370
- // Get user's study sessions
371
- const getUserSessions = async (userId: string) => {
372
- const { data, error } = await db
373
- .from('study_sessions')
374
- .select('*')
375
- .eq('user_id', userId)
376
- .order('completed_at', { ascending: false });
377
-
378
- if (error) throw error;
379
- return data;
380
- };
381
-
382
- // Update session
383
- const updateSession = async (id: string, updates: Partial<StudySession>) => {
384
- const { data, error } = await db
385
- .from('study_sessions')
386
- .update(updates)
387
- .eq('id', id)
388
- .select()
389
- .single();
390
-
391
- if (error) throw error;
392
- return data;
393
- };
394
-
395
- return (
396
- <div>
397
- {/* Your component UI */}
398
- </div>
399
- );
400
- };
401
- ```
402
-
403
- **File Storage Example**
404
-
405
- ```typescript
406
- const FileManager = () => {
407
- const { db } = useRimori();
408
-
409
- const uploadFile = async (file: File) => {
410
- const fileName = `uploads/${Date.now()}-${file.name}`;
411
-
412
- const { data, error } = await db.storage
413
- .from('plugin-files')
414
- .upload(fileName, file);
415
-
416
- if (error) throw error;
417
- return data;
418
- };
419
-
420
- const downloadFile = async (filePath: string) => {
421
- const { data, error } = await db.storage
422
- .from('plugin-files')
423
- .download(filePath);
424
-
425
- if (error) throw error;
426
- return data;
427
- };
428
-
429
- return <div>File Manager UI</div>;
430
- };
129
+ ```ts
130
+ const { data, error } = await client.db
131
+ .from("study_sessions")
132
+ .select("*")
133
+ .order("completed_at", { ascending: false });
431
134
  ```
432
135
 
433
- ## LLM Integration
434
-
435
- Powerful AI/Language Model capabilities built-in:
436
-
437
- ```typescript
438
- const { llm } = useRimori();
439
-
440
- // Text generation
441
- llm.getText(messages: Message[], tools?: Tool[]): Promise<string>
442
-
443
- // Streaming text generation
444
- llm.getSteamedText(messages: Message[], onMessage: OnLLMResponse, tools?: Tool[]): void
136
+ Helpers:
445
137
 
446
- // Structured object generation
447
- llm.getObject(request: ObjectRequest): Promise<any>
138
+ - `db.tablePrefix` plugin-specific prefix applied to all tables.
139
+ - `db.getTableName("notes")` – resolve the fully qualified table name.
140
+ - Supabase query builder methods (`insert`, `update`, `delete`, `eq`, `limit`, etc.) are available out of the box.
448
141
 
449
- // Text-to-speech
450
- llm.getVoice(text: string, voice?: string, speed?: number, language?: string): Promise<Blob>
142
+ ### AI & Voice
451
143
 
452
- // Speech-to-text
453
- llm.getTextFromVoice(file: Blob): Promise<string>
454
- ```
455
-
456
- **Example: AI Chat Assistant**
144
+ The `client.ai` controller surfaces AI capabilities:
457
145
 
458
- ```typescript
459
- import { useChat } from "@rimori/client";
146
+ - `getText(messages, tools?)` – chat completion (string result).
147
+ - `getSteamedText(messages, onMessage, tools?)` streamed responses.
148
+ - `getObject(request)` – structured JSON generation.
149
+ - `getVoice(text, voice?, speed?, language?)` – text-to-speech (returns `Blob`).
150
+ - `getTextFromVoice(file)` – speech-to-text transcription.
460
151
 
461
- const ChatAssistant = () => {
462
- const { messages, append, isLoading } = useChat();
463
- const [input, setInput] = useState('');
152
+ Use `client.runtime.fetchBackend` for authenticated calls to Rimori-managed HTTP endpoints.
464
153
 
465
- const sendMessage = () => {
466
- if (!input.trim()) return;
154
+ ### Event Bus & Actions
467
155
 
468
- append([{
469
- role: 'user',
470
- content: input
471
- }]);
156
+ `client.event` lets you collaborate with Rimori and other plugins:
472
157
 
473
- setInput('');
474
- };
158
+ - `emit(topic, data?)`, `request(topic, data?)` – publish and request data.
159
+ - `on(topic, handler)` / `once(topic, handler)` / `respond(topic, handler)` – subscribe and reply (each call returns an object with `off()` for cleanup).
160
+ - `emitAccomplishment(payload)` / `onAccomplishment(topic, handler)` – report learning milestones.
161
+ - `emitSidebarAction(pluginId, actionKey, text?)` – trigger sidebar plugins.
162
+ - `onMainPanelAction(handler, actionsToListen?)` – react to dashboard actions.
163
+ - `client.navigation.toDashboard()` – navigate the user back to Rimori.
475
164
 
476
- return (
477
- <div className="chat-container">
478
- <div className="messages">
479
- {messages.map((message, index) => (
480
- <div key={index} className={`message ${message.role}`}>
481
- {message.content}
482
- </div>
483
- ))}
484
- {isLoading && <div className="message assistant">Thinking...</div>}
485
- </div>
486
-
487
- <div className="input-area">
488
- <input
489
- value={input}
490
- onChange={(e) => setInput(e.target.value)}
491
- onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
492
- placeholder="Ask anything..."
493
- />
494
- <button onClick={sendMessage} disabled={isLoading}>
495
- Send
496
- </button>
497
- </div>
498
- </div>
499
- );
500
- };
501
- ```
502
-
503
- **Example: Structured Data Generation**
504
-
505
- ```typescript
506
- const QuizGenerator = () => {
507
- const { llm } = useRimori();
508
-
509
- const generateQuiz = async (topic: string) => {
510
- const quiz = await llm.getObject({
511
- schema: {
512
- type: "object",
513
- properties: {
514
- title: { type: "string" },
515
- questions: {
516
- type: "array",
517
- items: {
518
- type: "object",
519
- properties: {
520
- question: { type: "string" },
521
- options: { type: "array", items: { type: "string" } },
522
- correctAnswer: { type: "number" },
523
- explanation: { type: "string" }
524
- }
525
- }
526
- }
527
- }
528
- },
529
- prompt: `Create a quiz about ${topic} with 5 multiple choice questions.`
530
- });
531
-
532
- return quiz;
533
- };
534
-
535
- return <div>Quiz Generator UI</div>;
536
- };
537
- ```
165
+ ### Community Content
538
166
 
539
- **Example: Voice Integration**
167
+ `client.community.sharedContent` exposes helpers to share or consume content:
540
168
 
541
- ```typescript
542
- const VoiceAssistant = () => {
543
- const { llm } = useRimori();
169
+ - `get(contentType, id)`
170
+ - `getList(contentType, filter?, limit?)`
171
+ - `getNew(contentType, instructions, filter?, options?)`
172
+ - `create(payload)`
173
+ - `update(id, payload)`
174
+ - `remove(id)`
175
+ - `complete(contentType, assignmentId)`
544
176
 
545
- const speakText = async (text: string) => {
546
- const audioBlob = await llm.getVoice(text, "alloy", 1, "en");
547
- const audioUrl = URL.createObjectURL(audioBlob);
548
- const audio = new Audio(audioUrl);
549
- audio.play();
550
- };
177
+ The controller handles topic generation, metadata, and completion tracking automatically.
551
178
 
552
- const transcribeAudio = async (audioFile: File) => {
553
- const transcript = await llm.getTextFromVoice(audioFile);
554
- return transcript;
555
- };
179
+ ### Workers & Standalone Development
556
180
 
557
- return <div>Voice Assistant UI</div>;
558
- };
559
- ```
560
-
561
- ## Event System
562
-
563
- Robust inter-plugin communication and platform integration:
564
-
565
- ```typescript
566
- const { event } = useRimori();
567
-
568
- // Event methods
569
- event.emit(topic: string, data?: any, eventId?: number): void
570
- event.request<T>(topic: string, data?: any): Promise<EventBusMessage<T>>
571
- event.on<T>(topic: string | string[], callback: EventHandler<T>): string[]
572
- event.once<T>(topic: string, callback: EventHandler<T>): void
573
- event.respond<T>(topic: string, data: EventPayload | Function): void
574
-
575
- // Accomplishments
576
- event.emitAccomplishment(payload: AccomplishmentPayload): void
577
- event.onAccomplishment(topic: string, callback: Function): void
578
-
579
- // Sidebar actions
580
- event.emitSidebarAction(pluginId: string, actionKey: string, text?: string): void
581
- ```
582
-
583
- **Example: Plugin Communication**
584
-
585
- ```typescript
586
- const PluginCommunicator = () => {
587
- const { event } = useRimori();
588
-
589
- useEffect(() => {
590
- // Listen for messages from other plugins
591
- const unsubscribe = event.on('flashcards.newCard', (message) => {
592
- console.log('New flashcard created:', message.data);
593
- });
594
-
595
- // Listen for global events
596
- event.on('global.userProgress', (message) => {
597
- console.log('User progress updated:', message.data);
598
- });
599
-
600
- return () => {
601
- // Cleanup subscriptions
602
- unsubscribe.forEach(id => event.off(id));
603
- };
604
- }, []);
605
-
606
- const shareData = () => {
607
- // Emit data to other plugins
608
- event.emit('studyplan.dataUpdate', {
609
- type: 'session_completed',
610
- sessionId: '123',
611
- score: 85
612
- });
613
- };
614
-
615
- const requestData = async () => {
616
- // Request data from another plugin
617
- const response = await event.request('flashcards.getStats', {
618
- timeframe: 'week'
619
- });
620
-
621
- console.log('Flashcard stats:', response.data);
622
- };
623
-
624
- return (
625
- <div>
626
- <button onClick={shareData}>Share Progress</button>
627
- <button onClick={requestData}>Get Flashcard Stats</button>
628
- </div>
629
- );
630
- };
631
- ```
632
-
633
- **Example: Accomplishment System**
634
-
635
- ```typescript
636
- const AccomplishmentTracker = () => {
637
- const { event } = useRimori();
638
-
639
- const trackAccomplishment = () => {
640
- event.emitAccomplishment({
641
- type: 'study_milestone',
642
- title: 'Study Streak',
643
- description: 'Completed 7 days of studying',
644
- points: 100,
645
- metadata: {
646
- streakDays: 7,
647
- subject: 'Spanish'
648
- }
649
- });
650
- };
651
-
652
- useEffect(() => {
653
- // Listen for accomplishments from this plugin
654
- event.onAccomplishment('study_milestone', (accomplishment) => {
655
- console.log('New accomplishment:', accomplishment);
656
- // Show notification, update UI, etc.
657
- });
658
- }, []);
659
-
660
- return <div>Accomplishment Tracker UI</div>;
661
- };
662
- ```
663
-
664
- **Example: Sidebar Integration**
665
-
666
- ```typescript
667
- const SidebarIntegration = () => {
668
- const { event } = useRimori();
669
-
670
- const openTranslator = (text: string) => {
671
- // Trigger translator plugin in sidebar
672
- event.emitSidebarAction('translator', 'translate', text);
673
- };
674
-
675
- const openFlashcards = () => {
676
- // Open flashcards plugin
677
- event.emitSidebarAction('flashcards', 'review');
678
- };
679
-
680
- return (
681
- <div>
682
- <button onClick={() => openTranslator('Hello world')}>
683
- Translate "Hello world"
684
- </button>
685
- <button onClick={openFlashcards}>
686
- Review Flashcards
687
- </button>
688
- </div>
689
- );
690
- };
691
- ```
692
-
693
- ## Community Features
694
-
695
- Share and discover content created by other users:
696
-
697
- ```typescript
698
- const { community } = useRimori();
699
-
700
- // Shared content methods
701
- community.sharedContent.get<T>(contentType: string, id: string): Promise<BasicAssignment<T>>
702
- community.sharedContent.getList<T>(contentType: string, filter?: SharedContentFilter, limit?: number): Promise<BasicAssignment<T>[]>
703
- community.sharedContent.getNew<T>(contentType: string, instructions: SharedContentObjectRequest, filter?: SharedContentFilter, privateTopic?: boolean): Promise<BasicAssignment<T>>
704
- community.sharedContent.create<T>(content: SharedContent<T>): Promise<BasicAssignment<T>>
705
- community.sharedContent.update<T>(id: string, content: Partial<SharedContent<T>>): Promise<BasicAssignment<T>>
706
- community.sharedContent.remove(id: string): Promise<BasicAssignment<any>>
707
- community.sharedContent.complete(contentType: string, assignmentId: string): Promise<void>
708
- ```
709
-
710
- **Example: Exercise Sharing Platform**
711
-
712
- ```typescript
713
- interface Exercise {
714
- title: string;
715
- description: string;
716
- difficulty: 'beginner' | 'intermediate' | 'advanced';
717
- questions: Array<{
718
- question: string;
719
- answer: string;
720
- hints?: string[];
721
- }>;
722
- }
723
-
724
- const ExerciseManager = () => {
725
- const { community } = useRimori();
726
- const [exercises, setExercises] = useState<BasicAssignment<Exercise>[]>([]);
727
-
728
- // Load community exercises
729
- const loadExercises = async () => {
730
- const exerciseList = await community.sharedContent.getList<Exercise>(
731
- 'grammar_exercises',
732
- { column: 'difficulty', value: 'beginner' },
733
- 10
734
- );
735
- setExercises(exerciseList);
736
- };
737
-
738
- // Create new exercise
739
- const createExercise = async (exercise: Exercise) => {
740
- const newExercise = await community.sharedContent.create({
741
- content_type: 'grammar_exercises',
742
- content: exercise,
743
- metadata: {
744
- difficulty: exercise.difficulty,
745
- questionCount: exercise.questions.length
746
- }
747
- });
748
-
749
- return newExercise;
750
- };
751
-
752
- // Generate AI exercise
753
- const generateExercise = async (topic: string) => {
754
- const aiExercise = await community.sharedContent.getNew<Exercise>(
755
- 'grammar_exercises',
756
- {
757
- prompt: `Create a grammar exercise about ${topic}`,
758
- schema: {
759
- type: "object",
760
- properties: {
761
- title: { type: "string" },
762
- description: { type: "string" },
763
- difficulty: { type: "string", enum: ["beginner", "intermediate", "advanced"] },
764
- questions: {
765
- type: "array",
766
- items: {
767
- type: "object",
768
- properties: {
769
- question: { type: "string" },
770
- answer: { type: "string" },
771
- hints: { type: "array", items: { type: "string" } }
772
- }
773
- }
774
- }
775
- }
776
- }
777
- },
778
- { column: 'difficulty', value: 'beginner' }
779
- );
780
-
781
- return aiExercise;
782
- };
783
-
784
- // Complete exercise
785
- const completeExercise = async (exerciseId: string) => {
786
- await community.sharedContent.complete('grammar_exercises', exerciseId);
787
- // Exercise is now marked as completed for the user
788
- };
789
-
790
- return (
791
- <div>
792
- <button onClick={loadExercises}>Load Exercises</button>
793
- <button onClick={() => generateExercise('present tense')}>
794
- Generate Present Tense Exercise
795
- </button>
796
- {/* Exercise list UI */}
797
- </div>
798
- );
799
- };
800
- ```
801
-
802
- ## Components
803
-
804
- Pre-built React components for common functionality:
805
-
806
- ### MarkdownEditor
807
-
808
- Rich text editor with markdown support:
809
-
810
- ```typescript
811
- import { MarkdownEditor } from "@rimori/client";
812
-
813
- const EditorExample = () => {
814
- const [content, setContent] = useState('');
815
-
816
- return (
817
- <MarkdownEditor
818
- value={content}
819
- onChange={setContent}
820
- placeholder="Start writing..."
821
- />
822
- );
823
- };
824
- ```
825
-
826
- ### PlayButton
827
-
828
- Audio playback component:
829
-
830
- ```typescript
831
- import { PlayButton } from "@rimori/client";
832
-
833
- const AudioPlayer = () => {
834
- return (
835
- <PlayButton
836
- audioUrl="https://example.com/audio.mp3"
837
- onPlay={() => console.log('Audio playing')}
838
- onPause={() => console.log('Audio paused')}
839
- />
840
- );
841
- };
842
- ```
843
-
844
- ### AI Components
845
-
846
- ```typescript
847
- import { Avatar, Assistant } from "@rimori/client";
848
-
849
- const AIInterface = () => {
850
- return (
851
- <div>
852
- <Avatar
853
- name="AI Assistant"
854
- status="online"
855
- size="large"
856
- />
857
-
858
- <Assistant
859
- onMessage={(message) => console.log('AI message:', message)}
860
- placeholder="Ask the AI assistant..."
861
- />
862
- </div>
863
- );
864
- };
865
- ```
866
-
867
- ## Hooks
868
-
869
- ### useChat
870
-
871
- Manage AI chat conversations:
872
-
873
- ```typescript
874
- import { useChat } from "@rimori/client";
875
-
876
- const ChatExample = () => {
877
- const { messages, append, isLoading, setMessages } = useChat([
878
- // Optional tools for the AI
879
- {
880
- name: "get_weather",
881
- description: "Get current weather",
882
- parameters: {
883
- type: "object",
884
- properties: {
885
- location: { type: "string" }
886
- }
887
- }
888
- }
889
- ]);
890
-
891
- const sendMessage = (content: string) => {
892
- append([{ role: 'user', content }]);
893
- };
894
-
895
- return (
896
- <div>
897
- {messages.map((msg, index) => (
898
- <div key={index}>{msg.content}</div>
899
- ))}
900
- {isLoading && <div>AI is typing...</div>}
901
- </div>
902
- );
903
- };
904
- ```
905
-
906
- ### useTranslation
907
-
908
- Internationalization (i18n) support built on i18next:
909
-
910
- ```typescript
911
- import { useTranslation } from "@rimori/client";
912
-
913
- const TranslatedComponent = () => {
914
- const { t, ready } = useTranslation();
915
-
916
- if (!ready) {
917
- return <div>Loading translations...</div>;
918
- }
919
-
920
- return (
921
- <div>
922
- <h1>{t('discussion.title')}</h1>
923
- <p>{t('discussion.whatToTalkAbout')}</p>
924
- </div>
925
- );
926
- };
927
- ```
928
-
929
- ## Translation Feature
930
-
931
- Rimori includes a comprehensive internationalization (i18n) system built on i18next that allows plugins to support multiple languages with minimal developer effort.
932
-
933
- ### How it works
934
-
935
- - **Developer Focus**: Developers only need to ensure their interface works in English
936
- - **Automatic Translations**: With every release, translations for all other languages are generated automatically
937
- - **Local Testing**: For local development, you can test translations by:
938
- 1. Setting your user language to a non-English locale (e.g., German)
939
- 2. Creating a local translation file with "local-" prefix (e.g., `local-de.json`) in the `public/locales/` directory
940
- 3. The translator will automatically use the local translation file in development mode
941
- - **Manual Translations**: If developers want to manually translate files, they should place the language file manually in the `public/locales/` folder with the language code as filename (e.g., `de.json`, `fr.json`)
942
-
943
- ### Usage
944
-
945
- #### Using the Hook (Recommended)
946
-
947
- ```typescript
948
- import { useTranslation } from "@rimori/client";
949
-
950
- function MyComponent() {
951
- const { t, ready } = useTranslation();
952
-
953
- if (!ready) {
954
- return <div>Loading translations...</div>;
955
- }
956
-
957
- return (
958
- <div>
959
- <h1>{t('discussion.title')}</h1>
960
- <p>{t('discussion.whatToTalkAbout')}</p>
961
- </div>
962
- );
963
- }
964
- ```
965
-
966
- #### Using the Translator Instance
967
-
968
- ```typescript
969
- import { useRimori } from "@rimori/client";
970
-
971
- const { plugin } = useRimori();
972
- const translator = await plugin.getTranslator()
973
-
974
- const translatedText = translator.t("discussion.title");
975
- ```
976
-
977
- ### Translation File Structure
978
-
979
- - **Location**: `public/locales/`
980
- - **Production Files**: Must be named `{language}.json` (e.g., `en.json`, `de.json`, `fr.json`)
981
- - **Local Development Files**: Must be named `local-{language}.json` (e.g., `local-de.json`, `local-fr.json`)
982
- - **Format**: Standard JSON with nested objects for organization
983
- - **English Requirement**: `en.json` is required as the base language
984
- - **Release Process**: Files starting with "local-" are ignored during the release process
985
-
986
- Example translation file structure:
987
-
988
- ```json
989
- {
990
- "discussion": {
991
- "title": "Discussion",
992
- "whatToTalkAbout": "What do you want to talk about?",
993
- "topics": {
994
- "everyday": {
995
- "title": "Everyday Conversations",
996
- "description": "Ordering coffee, asking for directions, etc."
997
- }
998
- }
999
- }
1000
- }
1001
- ```
1002
-
1003
- ### Features
1004
-
1005
- - **I18next Support**: All i18next features work with these translations including:
1006
- - Variable interpolation: `{{name}}`
1007
- - Pluralization
1008
- - Fallback mechanisms
1009
- - **Automatic Fallback**: If a translation is missing, it falls back to English
1010
- - **Development Mode**: Local translation files are prioritized in development
1011
- - **Production Ready**: Automatic translation generation for production releases
1012
-
1013
- ### Limitations
1014
-
1015
- - Only one translation file per language is allowed
1016
- - Namespaces are not supported
1017
- - Production translation files must be named `{language}.json` and placed in `public/locales/`
1018
- - Local development files must be named `local-{language}.json` and placed in `public/locales/`
1019
- - English (`en.json`) is required as the base language
1020
- - Local files (prefixed with "local-") are ignored during the release process
181
+ - `setupWorker()` wires the Rimori event bus into worker contexts.
182
+ - `StandaloneClient.getInstance()` signs in against Rimori when your plugin runs outside the platform (e.g., local development).
183
+ - `client.getQueryParam(key)` reads values provided by Rimori through the sandbox handshake (such as `applicationMode` or theme information).
1021
184
 
1022
185
  ## Utilities
1023
186
 
1024
- ### difficultyConverter
1025
-
1026
- Convert between different difficulty representations:
1027
-
1028
- ```typescript
1029
- import { difficultyConverter } from '@rimori/client';
1030
-
1031
- const difficulty = difficultyConverter.toNumber('intermediate'); // Returns: 2
1032
- const difficultyText = difficultyConverter.toString(3); // Returns: 'advanced'
1033
- ```
1034
-
1035
- ### PluginUtils
1036
-
1037
- Various utility functions:
1038
-
1039
- ```typescript
1040
- import { PluginUtils } from '@rimori/client';
1041
-
1042
- // Utility functions for common plugin operations
1043
- const utils = PluginUtils.getInstance();
1044
- // Access various helper methods
1045
- ```
187
+ Import additional helpers as needed:
1046
188
 
1047
- ### Language Utilities
1048
-
1049
- Language detection and processing:
1050
-
1051
- ```typescript
1052
- import { Language } from '@rimori/client';
1053
-
1054
- // Language-related utility functions
1055
- const languageCode = Language.detectLanguage(text);
1056
- const isSupported = Language.isSupported('es');
1057
- ```
189
+ - `AudioController` – high-level audio playback/recording utilities for non-React environments.
190
+ - `Translator` – encapsulated i18next integration for manual translation flows.
191
+ - `difficultyConverter` – convert between textual and numeric difficulty levels.
192
+ - Type definitions for AI messages, shared content, triggers, accomplishments, and more.
1058
193
 
1059
194
  ## TypeScript Support
1060
195
 
1061
- The package is fully typed with comprehensive TypeScript definitions:
1062
-
1063
- ```typescript
1064
- import type {
1065
- MainPanelAction,
1066
- Message,
1067
- Tool,
1068
- EventPayload,
1069
- AccomplishmentPayload,
1070
- SharedContent,
1071
- BasicAssignment,
1072
- UserInfo,
1073
- } from '@rimori/client';
1074
-
1075
- // All interfaces and types are exported for use in your plugin
1076
- interface MyPluginData extends SharedContent<any> {
1077
- // Your custom properties
1078
- }
1079
- ```
1080
-
1081
- The SharedContent has this type definition:
196
+ All exports are fully typed. You can import the type definitions directly:
1082
197
 
198
+ ```ts
199
+ import type { Message, Tool, SharedContent, MacroAccomplishmentPayload } from "@rimori/client";
1083
200
  ```
1084
- export interface SharedContent<T> {
1085
- /** The type/category of the content (e.g. 'grammar_exercises', 'flashcards', etc.) */
1086
- contentType: string;
1087
201
 
1088
- /** The human readable title/topic of the content */
1089
- topic: string;
202
+ The generated declaration files cover every controller and helper to keep plugins strictly typed.
1090
203
 
1091
- /** Array of keywords/tags associated with the content for search and categorization */
1092
- keywords: string[];
204
+ ## Example Integration
1093
205
 
1094
- /** The actual content data of type T */
1095
- data: T;
206
+ React users should install `@rimori/react-client` and wrap their app:
1096
207
 
1097
- /** Whether this content should only be visible to the creator. Defaults to false if not specified */
1098
- privateTopic?: boolean;
1099
- }
1100
- ```
208
+ ```tsx
209
+ import { PluginProvider, useRimori, useChat } from "@rimori/react-client";
1101
210
 
1102
- ## Examples
1103
-
1104
- ### Complete Plugin Example
1105
-
1106
- ```typescript
1107
- import React, { useState, useEffect } from 'react';
1108
- import {
1109
- PluginProvider,
1110
- usePlugin,
1111
- MarkdownEditor,
1112
- Spinner,
1113
- useChat
1114
- } from '@rimori/client';
1115
- import { HashRouter, Route, Routes } from 'react-router-dom';
1116
-
1117
- const StudyNotesPlugin = () => {
1118
- const { db, llm, plugin, community } = useRimori();
1119
- const [notes, setNotes] = useState([]);
1120
- const [isLoading, setIsLoading] = useState(true);
211
+ function Dashboard() {
212
+ const client = useRimori();
1121
213
  const { messages, append } = useChat();
1122
214
 
1123
- useEffect(() => {
1124
- loadNotes();
1125
- }, []);
1126
-
1127
- const loadNotes = async () => {
1128
- try {
1129
- const { data } = await db.from('notes').select('*').order('created_at', { ascending: false });
1130
- setNotes(data || []);
1131
- } catch (error) {
1132
- console.error('Error loading notes:', error);
1133
- } finally {
1134
- setIsLoading(false);
1135
- }
1136
- };
1137
-
1138
- const saveNote = async (content: string) => {
1139
- const { data } = await db.from('notes').insert({
1140
- content,
1141
- created_at: new Date().toISOString()
1142
- }).select().single();
1143
-
1144
- setNotes([data, ...notes]);
1145
-
1146
- // Share with community
1147
- await community.sharedContent.create({
1148
- content_type: 'study_notes',
1149
- content: { text: content },
1150
- metadata: { wordCount: content.length }
1151
- });
1152
- };
1153
-
1154
- const generateSummary = async (noteContent: string) => {
1155
- const summary = await llm.getText([
1156
- { role: 'user', content: `Summarize this study note: ${noteContent}` }
1157
- ]);
1158
-
1159
- return summary;
1160
- };
1161
-
1162
- if (isLoading) return <Spinner size="large" />;
215
+ // interact with the core API through the client instance
216
+ // e.g. client.db.from("notes")...
217
+ }
1163
218
 
219
+ export function App() {
1164
220
  return (
1165
- <div className="study-notes-plugin">
1166
- <h1>Study Notes</h1>
1167
-
1168
- <div className="notes-grid">
1169
- {notes.map(note => (
1170
- <div key={note.id} className="note-card">
1171
- <MarkdownEditor
1172
- value={note.content}
1173
- onChange={(content) => {/* Update logic */}}
1174
- />
1175
- <button onClick={() => generateSummary(note.content)}>
1176
- Generate Summary
1177
- </button>
1178
- </div>
1179
- ))}
1180
- </div>
1181
-
1182
- <div className="ai-chat">
1183
- <h2>Study Assistant</h2>
1184
- {messages.map((msg, index) => (
1185
- <div key={index} className={`message ${msg.role}`}>
1186
- {msg.content}
1187
- </div>
1188
- ))}
1189
- <button onClick={() => append([{ role: 'user', content: 'Help me study' }])}>
1190
- Get Study Help
1191
- </button>
1192
- </div>
1193
- </div>
221
+ <PluginProvider pluginId="your-plugin-id">
222
+ <Dashboard />
223
+ </PluginProvider>
1194
224
  );
1195
- };
1196
-
1197
- const App = () => (
1198
- <PluginProvider pluginId="study-notes-plugin">
1199
- <HashRouter>
1200
- <Routes>
1201
- <Route path="/" element={<StudyNotesPlugin />} />
1202
- </Routes>
1203
- </HashRouter>
1204
- </PluginProvider>
1205
- );
1206
-
1207
- export default App;
225
+ }
1208
226
  ```
1209
227
 
1210
- ## Best Practices
228
+ Non-React projects can interact with the same client instance directly via the examples in the sections above.
229
+
230
+ ## Troubleshooting
1211
231
 
1212
- 1. **Performance**: Use lazy loading for pages and implement proper loading states
1213
- 2. **State Management**: Leverage React hooks and context when needed
1214
- 3. **Type Safety**: Use TypeScript interfaces for all data structures
1215
- 4. **Event Cleanup**: Always unsubscribe from events in useEffect cleanup
1216
- 5. **Responsive Design**: Use TailwindCSS classes for responsive layouts
232
+ - **`ReferenceError: process is not defined` in workers** ensure worker bundles only import from `@rimori/client`. Packages that reference `process.env` are not compatible with Rimori workers.
233
+ - **Missing plugin ID or token** – re-run `rimori-init` to regenerate configuration and authentication secrets.
234
+ - **Event bus listeners firing twice** store the listener returned by `event.on` and call `listener.off()` during cleanup (React users get this cleanup inside the hooks provided by `@rimori/react-client`).