feat(FN-1469): create settings-loadable example plugin

- Add fusion-plugin-settings-demo package with settings schema (string, number, boolean, enum types)
- Add lifecycle hooks (onLoad, onTaskCreated, onTaskCompleted)
- Add tools (suggest_tags, status) with settings-driven behavior
- Add comprehensive tests with 24 test cases
- Add manifest.json for plugin metadata and README with Settings installation flow
- Update PLUGIN_AUTHORING.md with new example and install instructions
- Also includes: FN-1468 plugin wiring, FN-1456 abandon flow, FN-1429 always-green tests, FN-1440 Authentication in Settings, FN-1133 plugin hot-reload support
This commit is contained in:
gsxdsm
2026-04-09 22:00:49 -07:00
parent 0d2a989f07
commit 59674c6037
9 changed files with 1028 additions and 14 deletions

View File

@@ -0,0 +1,138 @@
# Settings Demo Plugin
Example Fusion plugin demonstrating settings schema, hooks, and tools with configurable behavior.
## Features
- **Settings Schema**: Four different setting types (string, number, boolean, enum)
- **Lifecycle Hooks**: `onLoad`, `onTaskCreated`, `onTaskCompleted` that read settings at runtime
- **Plugin Tools**: Two AI-agent-callable tools that expose settings-driven functionality
## Installation via Dashboard Settings
### Method 1: Settings → Plugins (Recommended)
1. Open the Fusion dashboard
2. Navigate to **Settings** (gear icon in header)
3. Click **Plugins** in the sidebar
4. Click the **Install** button
5. Enter the absolute path to this plugin directory:
```
/absolute/path/to/plugins/examples/fusion-plugin-settings-demo
```
6. Click **Install** to register the plugin
7. The plugin will appear in the list with state "installed"
8. Click the toggle to enable the plugin
9. Click the **Settings** (gear) icon to configure the plugin:
- **Greeting Message**: Custom message shown when plugin loads
- **Max Tags**: Maximum tags to suggest per task (1-10)
- **Enable Logging**: Toggle console logging on/off
- **Log Level**: Minimum log level (debug, info, warn, error)
10. Click **Save Settings** to apply configuration
11. The plugin will reload with the new settings
### Method 2: Manual Installation
```bash
# Clone the repository
git clone https://github.com/gsxdsm/fusion.git
cd fusion/plugins/examples/fusion-plugin-settings-demo
```
Then use the dashboard Settings → Plugins UI to install from the local path.
## Settings
| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `greetingMessage` | string | "Hello from Settings Demo!" | Custom greeting shown on load |
| `maxTags` | number | 3 | Maximum tags to suggest per task |
| `enableLogging` | boolean | true | Enable/disable console logging |
| `logLevel` | enum | "info" | Minimum log level: debug, info, warn, error |
## Tools
### `settings_demo_suggest_tags`
Analyze a task description and suggest relevant tags based on keyword matching.
**Parameters:**
- `taskDescription` (string, required): The task description to analyze
**Returns:** Suggested tags separated by commas
### `settings_demo_status`
Get the current plugin configuration status.
**Parameters:** None
**Returns:** Current settings values
## Hooks
| Hook | When | What it does |
|------|------|--------------|
| `onLoad` | Plugin starts | Logs greeting message and configuration |
| `onTaskCreated` | New task created | Suggests tags for tasks with descriptions |
| `onTaskCompleted` | Task reaches "done" | Logs completion message |
## Development
```bash
# Install dependencies
pnpm install
# Run tests
pnpm test
# Build (if needed)
pnpm build
```
## Project Structure
```
fusion-plugin-settings-demo/
├── manifest.json # Plugin metadata and settings schema
├── package.json # Package configuration
├── tsconfig.json # TypeScript configuration
├── vitest.config.ts # Test configuration
├── README.md # This file
└── src/
├── index.ts # Plugin implementation
└── __tests__/
└── index.test.ts # Plugin tests
```
## Testing
The plugin includes unit tests that verify:
- Manifest correctness and metadata consistency
- Plugin export validity
- Settings schema definition
- Hook behavior with different configuration values
- Tool execution with settings-driven output
Run tests:
```bash
pnpm test
```
## Example Usage
After installing and configuring the plugin:
1. Create a new task with description mentioning keywords like "bug", "fix", "performance"
2. The plugin will suggest relevant tags based on the content
3. Check the console logs (if enabled) to see plugin activity
4. Use the `/settings_demo_suggest_tags` tool to get tag suggestions
5. Use the `/settings_demo_status` tool to see current configuration
## Notes
- The plugin uses `src/index.ts` as the entrypoint for local installation
- Settings changes trigger a plugin reload automatically
- Hook errors are isolated and won't crash the host system
- Tools use the current settings values at execution time

View File

@@ -0,0 +1,35 @@
{
"id": "fusion-plugin-settings-demo",
"name": "Settings Demo Plugin",
"version": "0.1.0",
"description": "Example plugin demonstrating settings schema, hooks, and tools",
"author": "Fusion Team",
"homepage": "https://github.com/gsxdsm/fusion",
"settingsSchema": {
"greetingMessage": {
"type": "string",
"label": "Greeting Message",
"description": "Custom greeting message shown when the plugin loads",
"defaultValue": "Hello from Settings Demo!"
},
"maxTags": {
"type": "number",
"label": "Max Tags",
"description": "Maximum number of tags to suggest per task",
"defaultValue": 3
},
"enableLogging": {
"type": "boolean",
"label": "Enable Logging",
"description": "Log plugin activity to the console",
"defaultValue": true
},
"logLevel": {
"type": "enum",
"label": "Log Level",
"description": "Minimum log level to output",
"enumValues": ["debug", "info", "warn", "error"],
"defaultValue": "info"
}
}
}

View File

@@ -0,0 +1,27 @@
{
"name": "@fusion-plugin-examples/settings-demo",
"version": "0.1.0",
"type": "module",
"description": "Example Fusion plugin demonstrating settings schema and runtime configuration",
"keywords": [
"fusion-plugin"
],
"exports": {
".": {
"types": "./src/index.ts",
"import": "./src/index.ts"
}
},
"private": true,
"scripts": {
"build": "tsc",
"test": "vitest run"
},
"dependencies": {
"@fusion/plugin-sdk": "workspace:*"
},
"devDependencies": {
"@types/node": "^25.5.2",
"vitest": "^3.2.4"
}
}

View File

@@ -0,0 +1,486 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import plugin from "../index.js";
// ── Types for mocking ─────────────────────────────────────────────────────────
interface MockLogger {
info: ReturnType<typeof vi.fn>;
warn: ReturnType<typeof vi.fn>;
error: ReturnType<typeof vi.fn>;
debug: ReturnType<typeof vi.fn>;
}
interface MockContext {
pluginId: string;
settings: Record<string, unknown>;
logger: MockLogger;
emitEvent: ReturnType<typeof vi.fn>;
taskStore: {
getTask: ReturnType<typeof vi.fn>;
};
}
function createMockContext(overrides: Partial<MockContext> = {}): MockContext {
return {
pluginId: "fusion-plugin-settings-demo",
settings: {
greetingMessage: "Hello from Settings Demo!",
maxTags: 3,
enableLogging: true,
logLevel: "info",
},
logger: {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
},
emitEvent: vi.fn(),
taskStore: {
getTask: vi.fn(),
},
...overrides,
};
}
// ── Mock Task ─────────────────────────────────────────────────────────────────
const mockTask = {
id: "FN-001",
title: "Test Task",
description: "Fix the bug in the performance module",
column: "todo" as const,
dependencies: [],
steps: [],
currentStep: 0,
size: "M" as const,
reviewLevel: "full" as const,
createdAt: "2024-01-01T00:00:00.000Z",
updatedAt: "2024-01-01T00:00:00.000Z",
};
// ── Test Suite ─────────────────────────────────────────────────────────────────
describe("settings demo plugin", () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe("plugin export", () => {
it("should export a valid FusionPlugin with correct manifest fields", () => {
expect(plugin.manifest.id).toBe("fusion-plugin-settings-demo");
expect(plugin.manifest.name).toBe("Settings Demo Plugin");
expect(plugin.manifest.version).toBe("0.1.0");
expect(plugin.manifest.description).toBe(
"Example plugin demonstrating settings schema, hooks, and tools",
);
expect(plugin.state).toBe("installed");
expect(plugin.hooks).toBeDefined();
expect(plugin.tools).toBeDefined();
});
it("should have manifest matching manifest.json", () => {
// Verify consistency between code and manifest.json
expect(plugin.manifest.id).toBe("fusion-plugin-settings-demo");
expect(plugin.manifest.name).toBe("Settings Demo Plugin");
expect(plugin.manifest.version).toBe("0.1.0");
});
it("should have settings schema defined with all four types", () => {
expect(plugin.manifest.settingsSchema).toBeDefined();
const schema = plugin.manifest.settingsSchema!;
// String type
expect(schema.greetingMessage).toBeDefined();
expect(schema.greetingMessage.type).toBe("string");
expect(schema.greetingMessage.label).toBe("Greeting Message");
// Number type
expect(schema.maxTags).toBeDefined();
expect(schema.maxTags.type).toBe("number");
expect(schema.maxTags.label).toBe("Max Tags");
// Boolean type
expect(schema.enableLogging).toBeDefined();
expect(schema.enableLogging.type).toBe("boolean");
expect(schema.enableLogging.label).toBe("Enable Logging");
// Enum type
expect(schema.logLevel).toBeDefined();
expect(schema.logLevel.type).toBe("enum");
expect(schema.logLevel.enumValues).toEqual(["debug", "info", "warn", "error"]);
});
it("should have default values in settings schema", () => {
const schema = plugin.manifest.settingsSchema!;
expect(schema.greetingMessage.defaultValue).toBe("Hello from Settings Demo!");
expect(schema.maxTags.defaultValue).toBe(3);
expect(schema.enableLogging.defaultValue).toBe(true);
expect(schema.logLevel.defaultValue).toBe("info");
});
it("should have tools defined", () => {
expect(plugin.tools).toBeDefined();
expect(plugin.tools!.length).toBe(2);
const toolNames = plugin.tools!.map((t) => t.name);
expect(toolNames).toContain("settings_demo_suggest_tags");
expect(toolNames).toContain("settings_demo_status");
});
it("should have all required hooks defined", () => {
expect(plugin.hooks.onLoad).toBeDefined();
expect(plugin.hooks.onTaskCreated).toBeDefined();
expect(plugin.hooks.onTaskCompleted).toBeDefined();
});
});
describe("hooks.onLoad", () => {
it("should log greeting message when logging is enabled", async () => {
const ctx = createMockContext({
settings: {
greetingMessage: "Custom Greeting",
maxTags: 3,
enableLogging: true,
logLevel: "info",
},
});
await plugin.hooks.onLoad?.(ctx as any);
expect(ctx.logger.info).toHaveBeenCalledWith("Custom Greeting");
expect(ctx.logger.info).toHaveBeenCalledWith(
expect.stringContaining("maxTags: 3"),
);
});
it("should not log when logging is disabled", async () => {
const ctx = createMockContext({
settings: {
greetingMessage: "Custom Greeting",
maxTags: 3,
enableLogging: false,
logLevel: "info",
},
});
await plugin.hooks.onLoad?.(ctx as any);
expect(ctx.logger.info).not.toHaveBeenCalled();
});
it("should use default greeting when not configured", async () => {
const ctx = createMockContext({
settings: {
greetingMessage: "",
maxTags: 3,
enableLogging: true,
logLevel: "info",
},
});
await plugin.hooks.onLoad?.(ctx as any);
expect(ctx.logger.info).toHaveBeenCalledWith("Hello from Settings Demo!");
});
});
describe("hooks.onTaskCreated", () => {
it("should log when task is created with logging enabled", async () => {
const ctx = createMockContext({
settings: {
greetingMessage: "Hello",
maxTags: 3,
enableLogging: true,
logLevel: "debug",
},
});
await plugin.hooks.onTaskCreated?.(mockTask as any, ctx as any);
expect(ctx.logger.debug).toHaveBeenCalledWith(
expect.stringContaining("FN-001"),
);
});
it("should suggest tags for task with description", async () => {
const ctx = createMockContext({
settings: {
greetingMessage: "Hello",
maxTags: 3,
enableLogging: true,
logLevel: "debug",
},
});
await plugin.hooks.onTaskCreated?.(mockTask as any, ctx as any);
// "performance" keyword matches performance tag
expect(ctx.logger.debug).toHaveBeenCalledWith(
expect.stringContaining("Suggested tags"),
);
expect(ctx.logger.debug).toHaveBeenCalledWith(
expect.stringContaining("performance"),
);
});
it("should not log when logging is disabled", async () => {
const ctx = createMockContext({
settings: {
greetingMessage: "Hello",
maxTags: 3,
enableLogging: false,
logLevel: "info",
},
});
await plugin.hooks.onTaskCreated?.(mockTask as any, ctx as any);
expect(ctx.logger.debug).not.toHaveBeenCalled();
});
it("should handle task without description gracefully", async () => {
const ctx = createMockContext({
settings: {
greetingMessage: "Hello",
maxTags: 3,
enableLogging: true,
logLevel: "debug",
},
});
const taskNoDesc = { ...mockTask, description: undefined };
await plugin.hooks.onTaskCreated?.(taskNoDesc as any, ctx as any);
// Should log task creation but not tag suggestions
expect(ctx.logger.debug).toHaveBeenCalledWith(
expect.stringContaining("FN-001"),
);
});
});
describe("hooks.onTaskCompleted", () => {
it("should log when task is completed with logging enabled", async () => {
const ctx = createMockContext({
settings: {
greetingMessage: "Hello",
maxTags: 3,
enableLogging: true,
logLevel: "info",
},
});
await plugin.hooks.onTaskCompleted?.(mockTask as any, ctx as any);
expect(ctx.logger.info).toHaveBeenCalledWith(
expect.stringContaining("Task completed"),
);
expect(ctx.logger.info).toHaveBeenCalledWith(
expect.stringContaining("FN-001"),
);
});
it("should not log when logging is disabled", async () => {
const ctx = createMockContext({
settings: {
greetingMessage: "Hello",
maxTags: 3,
enableLogging: false,
logLevel: "info",
},
});
await plugin.hooks.onTaskCompleted?.(mockTask as any, ctx as any);
expect(ctx.logger.info).not.toHaveBeenCalled();
});
});
describe("tools.suggest_tags", () => {
it("should suggest tags based on description keywords", async () => {
const ctx = createMockContext({
settings: {
greetingMessage: "Hello",
maxTags: 3,
enableLogging: false,
logLevel: "info",
},
});
const tool = plugin.tools!.find((t) => t.name === "settings_demo_suggest_tags")!;
const result = await tool.execute(
{ taskDescription: "Fix the bug in the performance module" },
ctx as any,
);
expect(result.content[0].text).toContain("Suggested tags:");
expect(result.content[0].text).toContain("performance");
});
it("should respect maxTags setting", async () => {
const ctx = createMockContext({
settings: {
greetingMessage: "Hello",
maxTags: 1,
enableLogging: false,
logLevel: "info",
},
});
const tool = plugin.tools!.find((t) => t.name === "settings_demo_suggest_tags")!;
const result = await tool.execute(
{ taskDescription: "Fix bug fix fix" },
ctx as any,
);
// Should only return 1 tag
expect(result.details!.count).toBe(1);
});
it("should return no tags for generic description", async () => {
const ctx = createMockContext({
settings: {
greetingMessage: "Hello",
maxTags: 3,
enableLogging: false,
logLevel: "info",
},
});
const tool = plugin.tools!.find((t) => t.name === "settings_demo_suggest_tags")!;
const result = await tool.execute(
{ taskDescription: "Do something" },
ctx as any,
);
expect(result.content[0].text).toContain("No tags could be suggested");
});
it("should handle empty description", async () => {
const ctx = createMockContext({
settings: {
greetingMessage: "Hello",
maxTags: 3,
enableLogging: false,
logLevel: "info",
},
});
const tool = plugin.tools!.find((t) => t.name === "settings_demo_suggest_tags")!;
const result = await tool.execute({ taskDescription: "" }, ctx as any);
expect(result.content[0].text).toContain("No tags could be suggested");
});
});
describe("tools.status", () => {
it("should return current configuration status", async () => {
const ctx = createMockContext({
settings: {
greetingMessage: "Custom Greeting",
maxTags: 5,
enableLogging: true,
logLevel: "debug",
},
});
const tool = plugin.tools!.find((t) => t.name === "settings_demo_status")!;
const result = await tool.execute({}, ctx as any);
expect(result.content[0].text).toContain("Settings Demo Plugin Status:");
expect(result.content[0].text).toContain("Custom Greeting");
expect(result.content[0].text).toContain("Max Tags: 5");
expect(result.content[0].text).toContain("Logging: enabled");
expect(result.content[0].text).toContain("Log Level: debug");
expect(result.details!.greetingMessage).toBe("Custom Greeting");
expect(result.details!.maxTags).toBe(5);
expect(result.details!.enableLogging).toBe(true);
expect(result.details!.logLevel).toBe("debug");
});
it("should return raw settings values including undefined", async () => {
const ctx = createMockContext({
settings: {
greetingMessage: "",
maxTags: undefined,
enableLogging: undefined,
logLevel: undefined,
},
});
const tool = plugin.tools!.find((t) => t.name === "settings_demo_status")!;
const result = await tool.execute({}, ctx as any);
// Details returns raw values (not resolved defaults)
expect(result.details!.greetingMessage).toBe("");
expect(result.details!.maxTags).toBeUndefined();
expect(result.details!.enableLogging).toBeUndefined();
expect(result.details!.logLevel).toBeUndefined();
// But display text uses resolved defaults
expect(result.content[0].text).toContain("Not configured");
expect(result.content[0].text).toContain("Max Tags: 3");
expect(result.content[0].text).toContain("Logging: enabled");
expect(result.content[0].text).toContain("Log Level: info");
});
});
describe("settings-driven behavior", () => {
it("should log debug messages only when logLevel allows", async () => {
const ctx = createMockContext({
settings: {
greetingMessage: "Hello",
maxTags: 3,
enableLogging: true,
logLevel: "error", // Only error level
},
});
await plugin.hooks.onLoad?.(ctx as any);
await plugin.hooks.onTaskCreated?.(mockTask as any, ctx as any);
// Debug and info should be filtered out
expect(ctx.logger.debug).not.toHaveBeenCalled();
expect(ctx.logger.info).not.toHaveBeenCalled();
});
it("should log all levels when logLevel is debug", async () => {
const ctx = createMockContext({
settings: {
greetingMessage: "Hello",
maxTags: 3,
enableLogging: true,
logLevel: "debug",
},
});
await plugin.hooks.onLoad?.(ctx as any);
// All log levels should work
expect(ctx.logger.info).toHaveBeenCalled();
});
it("should handle maxTags edge case of 0", async () => {
const ctx = createMockContext({
settings: {
greetingMessage: "Hello",
maxTags: 0, // Edge case: 0 tags
enableLogging: false,
logLevel: "info",
},
});
const tool = plugin.tools!.find((t) => t.name === "settings_demo_suggest_tags")!;
const result = await tool.execute(
{ taskDescription: "Fix bug performance ui" },
ctx as any,
);
// When maxTags is 0, no tags should be returned
expect(result.details!.tags).toHaveLength(0);
});
});
});

View File

@@ -0,0 +1,277 @@
/**
* Settings Demo Plugin
*
* Example Fusion plugin that demonstrates:
* - Settings schema with multiple field types (string, number, boolean, enum)
* - Hooks that read configuration at runtime
* - Tools that expose settings-driven functionality
*
* This plugin suggests tags for tasks based on configurable keywords
* and provides a status endpoint for checking configuration.
*/
import { definePlugin } from "@fusion/plugin-sdk";
import type {
FusionPlugin,
PluginContext,
PluginSettingSchema,
PluginToolDefinition,
PluginToolResult,
} from "@fusion/plugin-sdk";
// ── Settings Schema ─────────────────────────────────────────────────────────────
/**
* Settings schema for the plugin.
* Users can configure these values via the dashboard Settings → Plugins UI.
*/
const settingsSchema: Record<string, PluginSettingSchema> = {
greetingMessage: {
type: "string",
label: "Greeting Message",
description: "Custom greeting message shown when the plugin loads",
defaultValue: "Hello from Settings Demo!",
},
maxTags: {
type: "number",
label: "Max Tags",
description: "Maximum number of tags to suggest per task",
defaultValue: 3,
},
enableLogging: {
type: "boolean",
label: "Enable Logging",
description: "Log plugin activity to the console",
defaultValue: true,
},
logLevel: {
type: "enum",
label: "Log Level",
description: "Minimum log level to output",
enumValues: ["debug", "info", "warn", "error"],
defaultValue: "info",
},
};
// ── Tag Keywords Configuration ─────────────────────────────────────────────────
/**
* Keyword-to-tag mappings for automatic tag suggestion.
* In a real plugin, this would be user-configurable.
*/
const TAG_KEYWORDS: Record<string, string[]> = {
bug: ["fix", "bug", "error", "crash", "broken", "issue"],
feature: ["add", "implement", "create", "new", "feature"],
refactor: ["refactor", "cleanup", "improve", "optimize", "restructure"],
docs: ["docs", "documentation", "readme", "comment"],
test: ["test", "testing", "spec", "coverage"],
security: ["security", "vulnerability", "auth", "permission"],
performance: ["performance", "speed", "optimize", "fast"],
ui: ["ui", "interface", "design", "visual", "frontend"],
backend: ["api", "backend", "server", "database"],
};
// ── Helper Functions ───────────────────────────────────────────────────────────
/**
* Extract suggested tags from task description based on keywords.
*/
function suggestTags(
description: string,
maxTags: number,
): string[] {
if (!description) return [];
const lowerDesc = description.toLowerCase();
const suggestions: { tag: string; count: number }[] = [];
for (const [tag, keywords] of Object.entries(TAG_KEYWORDS)) {
const matchCount = keywords.filter((kw) => lowerDesc.includes(kw)).length;
if (matchCount > 0) {
suggestions.push({ tag, count: matchCount });
}
}
// Sort by match count (most matches first) and take up to maxTags
return suggestions
.sort((a, b) => b.count - a.count)
.slice(0, maxTags)
.map((s) => s.tag);
}
/**
* Check if a log level should be output based on configured minimum level.
*/
function shouldLog(
configuredLevel: string,
messageLevel: string,
): boolean {
const levels = ["debug", "info", "warn", "error"];
const configuredIdx = levels.indexOf(configuredLevel);
const messageIdx = levels.indexOf(messageLevel);
return messageIdx >= configuredIdx;
}
// ── Plugin Tool ───────────────────────────────────────────────────────────────
/**
* Tool for getting tag suggestions for a task.
* Demonstrates how tools can read and use plugin settings.
*/
const suggestTagsTool: PluginToolDefinition = {
name: "settings_demo_suggest_tags",
description: "Suggest tags for a task based on its description using keyword matching. Returns up to the configured max tags.",
parameters: {
type: "object",
properties: {
taskDescription: {
type: "string",
description: "The task description to analyze for tag suggestions",
},
},
required: ["taskDescription"],
},
execute: async (
params: Record<string, unknown>,
ctx: PluginContext,
): Promise<PluginToolResult> => {
const description = params.taskDescription as string;
const maxTagsSetting = ctx.settings.maxTags as number | undefined;
// Use 3 as default only if maxTags is not explicitly set (undefined or NaN)
// Allow maxTags=0 to return no tags
const maxTags = maxTagsSetting !== undefined && !isNaN(maxTagsSetting) ? maxTagsSetting : 3;
// Log tool usage if enabled
if (ctx.settings.enableLogging) {
ctx.logger.info(`Suggesting tags for description: ${description.slice(0, 50)}...`);
}
const tags = suggestTags(description, maxTags);
const result: PluginToolResult = {
content: [
{
type: "text",
text: tags.length === 0
? "No tags could be suggested based on the task description."
: `Suggested tags: ${tags.join(", ")}`,
},
],
details: {
tags,
count: tags.length,
},
};
return result;
},
};
/**
* Tool for getting plugin configuration status.
* Demonstrates how tools expose current settings.
*/
const statusTool: PluginToolDefinition = {
name: "settings_demo_status",
description: "Get the current configuration status of the Settings Demo plugin",
parameters: {
type: "object",
properties: {},
required: [],
},
execute: async (
_params: Record<string, unknown>,
ctx: PluginContext,
): Promise<PluginToolResult> => {
// Return raw settings values for status display
const greetingMessage = ctx.settings.greetingMessage as string | undefined;
const maxTags = ctx.settings.maxTags as number | undefined;
const enableLogging = ctx.settings.enableLogging as boolean | undefined;
const logLevel = ctx.settings.logLevel as string | undefined;
// Use resolved values for display text
const greeting = greetingMessage || "Not configured";
const displayMaxTags = maxTags ?? 3;
const displayEnableLogging = enableLogging ?? true;
const displayLogLevel = logLevel || "info";
return {
content: [
{
type: "text",
text: [
"Settings Demo Plugin Status:",
`- Greeting: ${greeting}`,
`- Max Tags: ${displayMaxTags}`,
`- Logging: ${displayEnableLogging ? "enabled" : "disabled"}`,
`- Log Level: ${displayLogLevel}`,
].join("\n"),
},
],
details: {
greetingMessage,
maxTags,
enableLogging,
logLevel,
},
};
},
};
// ── Plugin Definition ───────────────────────────────────────────────────────────
const plugin: FusionPlugin = definePlugin({
manifest: {
id: "fusion-plugin-settings-demo",
name: "Settings Demo Plugin",
version: "0.1.0",
description: "Example plugin demonstrating settings schema, hooks, and tools",
settingsSchema,
},
state: "installed",
tools: [suggestTagsTool, statusTool],
hooks: {
onLoad: (ctx: PluginContext) => {
const greeting =
(ctx.settings.greetingMessage as string) || "Hello from Settings Demo!";
const enableLogging = (ctx.settings.enableLogging as boolean) ?? true;
const logLevel = (ctx.settings.logLevel as string) || "info";
if (enableLogging && shouldLog(logLevel, "info")) {
ctx.logger.info(greeting);
ctx.logger.info(`Plugin configured with maxTags: ${ctx.settings.maxTags || 3}`);
}
},
onTaskCreated: async (task: { id: string; title?: string; description?: string }, ctx: PluginContext) => {
const enableLogging = (ctx.settings.enableLogging as boolean) ?? true;
const logLevel = (ctx.settings.logLevel as string) || "info";
if (!enableLogging || !shouldLog(logLevel, "debug")) {
return;
}
ctx.logger.debug(`Task created: ${task.id} - ${task.title || "untitled"}`);
// Auto-suggest tags for new tasks
if (task.description) {
const maxTags = (ctx.settings.maxTags as number) || 3;
const tags = suggestTags(task.description, maxTags);
if (tags.length > 0) {
ctx.logger.debug(`Suggested tags for ${task.id}: ${tags.join(", ")}`);
}
}
},
onTaskCompleted: async (task: { id: string; title?: string }, ctx: PluginContext) => {
const enableLogging = (ctx.settings.enableLogging as boolean) ?? true;
const logLevel = (ctx.settings.logLevel as string) || "info";
if (enableLogging && shouldLog(logLevel, "info")) {
ctx.logger.info(`Task completed: ${task.id} - ${task.title || "untitled"}`);
}
},
},
});
export default plugin;

View File

@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"types": ["node", "vitest/globals"]
},
"include": ["src/**/*"]
}

View File

@@ -0,0 +1,8 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
include: ["src/**/*.test.ts"],
pool: "threads",
},
});