diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a71708f --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# Go +/go/bin/ +/go/pkg/ +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.test +*.out +/vendor/ +dist/ + +# Mattermost plugin +*.tar.gz +plugin.tar.gz +/server/plugin +/server/dist + +# IDE / editors +.idea/ +.vscode/ +*.swp +*.swo +*~ +.DS_Store + +# OS +Thumbs.db +Desktop.ini + +# Build artifacts +/tmp/ +/build/ diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..ad3ace7 --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,7 @@ +# Changes + +## v0.1.0 — 2026-06-16 + +- Project scaffolded: DESIGN.md, PROJECT.md, CHANGES.md, DECISIONS.md, + README.md, .gitignore +- No functional code yet — this is the pre-implementation skeleton diff --git a/DECISIONS.md b/DECISIONS.md new file mode 100644 index 0000000..8735d31 --- /dev/null +++ b/DECISIONS.md @@ -0,0 +1,74 @@ +# Design Decisions + +This file records architectural choices and the alternatives considered. +New entries are added when a design decision is resolved (not when options +are brainstormed). Unresolved questions live in DESIGN.md §10. + +--- + +## Why Mattermost Plugin vs Bot Account + +**Context:** AI assistants in Mattermost can be implemented as plugins +(running inside the Mattermost server process) or as bot accounts +(independent services using the REST API and WebSocket). + +**Alternatives considered:** +1. **Plugin** — hooks into slash commands, message hooks, KV store, System + Console config UI, and the plugin lifecycle. No separate process to + manage. Deployment is a single `.tar.gz` upload. +2. **Bot account** — separate service, more language freedom, but requires + managing OAuth tokens, WebSocket reconnection, a separate process with + its own lifecycle, and the bot account itself in Mattermost. + +**Outcome:** Plugin chosen for v0.1.0 because: +- Single deployment artifact +- Built-in config UI via System Console +- No bot account to create and maintain +- KV store for persistence without a database +- Simpler for the target audience (self-hosted teams) + +**Trade-off:** Plugin runs inside the Mattermost process — bugs can affect +server stability. Mitigated by standard isolation practices (panic recovery, +goroutine lifecycle management). + +--- + +## Why Ollama + +**Context:** Backend LLM provider for the plugin. + +**Alternatives considered:** +1. **Ollama** — Single binary, simple REST API, streaming, model management, + self-hosted (data never leaves the infrastructure). Community Edition + friendly. +2. **OpenAI / Anthropic API** — SaaS, API key required, data leaves the + network, ongoing API costs. Not aligned with self-hosted Mattermost + ethos. +3. **Local inference (llama.cpp, etc.)** — More complex to set up and + manage. Ollama wraps this with a clean API. + +**Outcome:** Ollama. It matches the self-hosted, no-data-leaves paradigm of +Mattermost Community Edition and provides the simplest API surface for a +plugin to consume. + +--- + +## Why Per-Channel Per-User Context (Proposed) + +**Context:** When a user sends `/ai `, should the plugin remember +previous exchanges? + +**Alternatives considered:** +1. **Per-user global** — Context follows the user across channels. Simple + but confusing: context from a #general question leaks into #dev. +2. **Per-channel per-user** — Each user gets separate context in each + channel. Natural mapping: different channels are different topics. +3. **No context** — Stateless. Simplest but every prompt is isolated; + users can't have a conversation. + +**Outcome (proposed):** Per-channel per-user. Marked as D2 in DESIGN.md — +not final until v0.1.0 implementation. + +--- + +*This file follows the template from the `project-docs` skill.* diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..8aec200 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,506 @@ +# Design: Mattermore — Mattermost AI Chat Agent for Ollama + +> **Status:** Draft / Pre-implementation +> **Last updated:** 2026-06-16 +> **Assumed direction:** AI chat agent plugin connecting to a remote Ollama instance. +> **Open questions are marked** `TODO(design):` throughout. This is a living template — change +> assumptions as the product direction firms up. + +--- + +## 1. Purpose & Scope + +### 1.1 What It Is + +Mattermore is a Mattermost (Community Edition) plugin that lets users chat +with remote LLMs through an Ollama backend. Users invoke the plugin via a +slash command (e.g. `/ai`) and receive responses — either ephemeral or as +thread replies — streamed directly into the channel. + +### 1.2 What It Is Not (Explicit Out-of-Scope) + +| Out of scope | Rationale | +|---|---| +| Hosting its own LLM inference | Ollama is the sole backend; the plugin is a thin proxy | +| Multi-backend abstraction (OpenAI, Anthropic, etc.) | If needed later, abstract behind an `LLMProvider` interface — not now | +| Training / fine-tuning | Ollama handles that | +| Native webapp UI beyond slash commands | `TODO(design):` decide if a channel header button or RHS panel adds value | +| Bot accounts | Pure plugin hooks — no separate bot user to manage | + +### 1.3 Target Audience + +- Teams self-hosting Mattermost who want AI assistant access without data + leaving their infrastructure +- Users comfortable with slash commands + +--- + +## 2. Architecture Overview + +### 2.1 Component Diagram (text) + +``` +┌─────────────────────────────────────────────────────────┐ +│ Mattermost Server (Community Edition) │ +│ │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ Mattermore Plugin │ │ +│ │ │ │ +│ │ ┌─────────────┐ ┌─────────────────────────┐ │ │ +│ │ │ SlashCommand │──▶│ Plugin (Go) │ │ │ +│ │ │ /ai │ │ │ │ │ +│ │ └─────────────┘ │ ┌───────────────────┐ │ │ │ +│ │ │ │ OllamaClient │──┼───┼───┼──▶ Ollama API +│ │ ┌─────────────┐ │ └───────────────────┘ │ │ │ (remote) +│ │ │ Config (KV) │ │ ┌───────────────────┐ │ │ │ +│ │ └─────────────┘ │ │ ConversationStore │ │ │ │ +│ │ │ └───────────────────┘ │ │ │ +│ │ ┌─────────────┐ │ ┌───────────────────┐ │ │ │ +│ │ │ Webapp (?) │ │ │ RateLimiter │ │ │ │ +│ │ └─────────────┘ │ └───────────────────┘ │ │ │ +│ └──────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────┘ +``` + +### 2.2 Request Flow (happy path) + +``` +User types "/ai write a release note for v2.1" + │ + ▼ +Mattermost parses slash command → routes to Mattermore plugin + │ + ▼ +Plugin.OnConfigurationChange() → reads config (Ollama URL, model, system prompt) + │ + ▼ +Plugin.ExecuteCommand() → parses args, strips command prefix + │ + ▼ +RateLimiter.Allow(userId) → 200 or 429 + │ + ▼ +ConversationStore.GetSession(userId, channelId) → previous messages for context + │ + ▼ +OllamaClient.ChatCompletion(messages, stream=true, model=defaultModel) + │ + ▼ +Ollama returns ndjson stream → plugin writes ephemeral post with live updates + │ + ▼ +Post created → user sees response in channel (ephemeral or thread reply) + │ + ▼ +ConversationStore.Append(userId, channelId, userMsg, assistantMsg) +``` + +### 2.3 Error Flow + +| Failure point | Behaviour | +|---|---| +| Ollama unreachable | Ephemeral post: "Ollama at unreachable. Check configuration." | +| Rate limit hit | Ephemeral post: "Rate limit exceeded. Try again in N seconds." | +| Invalid model name | Ephemeral post: "Model 'xyz' not found on Ollama server." | +| Timeout (>30 s) | Abort stream, post partial response + "Response truncated (timeout)." | +| Input too long | Reject with context window limit message; suggest `/ai new` to reset. | + +--- + +## 3. Plugin Manifest & Identity + +### 3.1 `plugin.json` + +```json +{ + "id": "com.forkless.mattermore", + "name": "Mattermore", + "description": "AI chat agent powered by Ollama.", + "version": "0.1.0", + "server": { + "executables": { + "linux-amd64": "server/dist/plugin-linux-amd64", + "linux-arm64": "server/dist/plugin-linux-arm64", + "darwin-amd64": "server/dist/plugin-darwin-amd64" + } + }, + "webapp": {}, + "settings_schema": { + "header": "Configure the connection to your Ollama instance.", + "footer": "", + "settings": [ + { + "key": "OllamaURL", + "display_name": "Ollama Server URL", + "type": "text", + "help_text": "Base URL of the Ollama API, e.g. http://10.0.0.5:11434", + "placeholder": "http://localhost:11434", + "default": "http://localhost:11434" + }, + { + "key": "DefaultModel", + "display_name": "Default Model", + "type": "text", + "help_text": "Model name to use when none is specified in the command.", + "placeholder": "llama3.2", + "default": "llama3.2" + }, + { + "key": "SystemPrompt", + "display_name": "System Prompt", + "type": "text", + "help_text": "System prompt prepended to every conversation.", + "placeholder": "You are a helpful assistant.", + "default": "You are a helpful assistant." + }, + { + "key": "MaxTokens", + "display_name": "Max Response Tokens", + "type": "number", + "help_text": "Maximum tokens per response (0 = model default).", + "default": 2048 + }, + { + "key": "RateLimitPerMinute", + "display_name": "Rate Limit (requests/minute/user)", + "type": "number", + "help_text": "Maximum requests per minute per user. 0 = unlimited.", + "default": 10 + }, + { + "key": "AllowedUserIDs", + "display_name": "Allowed User IDs (comma-separated)", + "type": "text", + "help_text": "Restrict to specific user IDs. Empty = all users.", + "default": "" + } + ] + } +} +``` + +`TODO(design):` Should `AllowedUserIDs` be a team/channel whitelist instead? +For now, keep user-level granularity — easy to widen later. + +### 3.2 Versioning + +Follow `release-workflow` skill: `v0.1.0`, `v0.2.0`, etc. Bump PATCH for +bugs, MINOR for features. MAJOR after 1.0 for breaking config schema changes. + +--- + +## 4. Server-Side Components + +### 4.1 Plugin Lifecycle Hooks + +| Hook | Purpose | +|---|---| +| `OnConfigurationChange()` | Re-read System Console settings, validate OllamaURL, rebuild clients | +| `ExecuteCommand()` | Intercept `/ai` and subcommands (`/ai new`, `/ai model ...`) | +| `OnActivate()` | Register slash command in `OnConfigurationChange()` — idempotent | +| `OnDeactivate()` | Clean up goroutines, close idle HTTP connections | + +### 4.2 Slash Command: `/ai` + +**Syntax:** + +``` +/ai + — Send a prompt using the default model (current session context). +/ai model + — Send a prompt using a specific model. +/ai new + — Reset conversation context for the current user+channel. +/ai model list + — List available models from the Ollama server (ephemeral). +/ai help + — Show usage help. +``` + +`TODO(design):` Should `/ai` reply ephemerally (only the caller sees it) or +as a thread post (visible to the channel)? **Proposed:** ephemeral for +privacy, with an opt-in `--public` flag to post to thread. + +### 4.3 OllamaClient + +Interface (in `server/ollama/client.go`): + +```go +type Client interface { + ChatCompletion(ctx context.Context, req *ChatRequest) (<-chan ChatStreamEvent, error) + ListModels(ctx context.Context) ([]Model, error) + Ping(ctx context.Context) error +} + +type ChatRequest struct { + Model string `json:"model"` + Messages []ChatMessage `json:"messages"` + Stream bool `json:"stream"` + Options map[string]any `json:"options,omitempty"` +} + +type ChatStreamEvent struct { + Token string // delta content + Done bool + Error error +} +``` + +`TODO(design):` Should we use Ollama's `/api/chat` (chat format) or +`/api/generate` (raw prompt)? `/api/chat` is preferred — it maps cleanly to +the conversational UX and supports the messages array natively. + +### 4.4 ConversationStore + +Store session context in Mattermost's KV store (plugin's `API.KVSet` / +`KVGet`). + +| Key pattern | Value | TTL | +|---|---|---| +| `ctx_{userID}_{channelID}` | `[]Message` (last N turns) | 1 hour | +| `ctx_{userID}_{channelID}_model` | Model name override | 1 hour | + +`TODO(design):` +- How many turns to keep? **Proposed:** last 10 messages (5 user + 5 assistant). +- TTL of 1 hour — resets on activity. Configurable? +- Should context be channel-scoped or user-global? Channel-scoped means a + user gets different context in different channels. + +### 4.5 RateLimiter + +Token-bucket per user. Configurable requests/minute from System Console. +Resets on config change. Stored in memory, not KV (ephemeral state). + +--- + +## 5. Ollama Integration + +### 5.1 API Mapping + +| Plugin Action | Ollama Endpoint | Method | +|---|---|---| +| Chat completion (streaming) | `/api/chat` | POST | +| List models | `/api/tags` | GET | +| Health check | `/api/tags` (or HEAD `/`) | GET | +| Pull model | `/api/pull` | POST | + +`TODO(design):` Should the plugin auto-pull a model if it's not present? +Risk: user mistypes name → long pull. **Proposed:** return error with list of +available models instead. + +### 5.2 Streaming Strategy + +1. Send POST to `/api/chat` with `"stream": true`. +2. Read ndjson response body line by line (each line is `{"message":{"role":"assistant","content":"..."},"done":false}`). +3. On each `done:false` event, update an ephemeral post via + `API.UpdateEphemeralPost()` with accumulated content. +4. On `done:true`, create the final ephemeral (or thread) post with full + content, usage stats (token count, duration). + +`TODO(design):` Post-update on every token may be too chatty. **Proposed:** +batch updates every ~200 ms or every 3 tokens, whichever comes first. + +### 5.3 Context Window Management + +- Token counting: use Ollama's `num_ctx` parameter or a Go tokenizer + (tiktoken-go). +- If input exceeds configured limit (default: 4096 tokens), trim oldest + messages until within limit, or reject if a single message overflows. +- Display a warning: "Context window trimmed (oldest messages removed)." + +`TODO(design):` Should the user be able to set `num_ctx` per-request? +`/ai --context 8192 ...` + +### 5.4 Model Selection Precedence + +1. Per-request: `/ai model gemma3:12b write a poem` +2. Session override: `/ai model gemma3:12b` (stored in KV) +3. Default from System Console: `DefaultModel` + +--- + +## 6. Configuration + +### 6.1 System Console Settings + +Defined in `plugin.json` `settings_schema` (see §3.1). The System Console +provides the UI; the plugin reads via `OnConfigurationChange()`. + +### 6.2 Dynamic Config Reload + +`OnConfigurationChange()` is called on every save. The plugin must: +1. Re-read the config struct from `API.GetConfig()` / `API.GetPluginConfig()`. +2. Validate `OllamaURL` (parse as URL, reject non-HTTP(S) schemes). +3. If URL changed, create a new `http.Client` (with configurable timeout). +4. If `RateLimitPerMinute` changed, rebuild the token buckets. +5. Log the change at debug level. + +--- + +## 7. Data Flow — Detailed + +### 7.1 Full Lifecycle + +``` +Step User / System Component +──── ────────────────────────────── ────────────────── + 1 User types "/ai explain TCP" Mattermost webapp + 2 Mattermost routes to plugin mm-server + 3 Plugin.ExecuteCommand() parses args Mattermore plugin + 3a If "/ai help" → show help text + 3b If "/ai new" → KVStore.Delete(ctx) + 3c If "/ai model …" → override model KVStore.Set(model) + 3d If "/ai model list" → OllamaClient.ListModels() + 4 RateLimiter.Allow(userId) RateLimiter + 5 ConversationStore.GetSession(...) ConversationStore + 6 Build []ChatMessage (system + history + new prompt) + 7 OllamaClient.ChatCompletion(...) OllamaClient + 8 Ollama streams ndjson tokens Ollama (remote) + 9 Plugin accumulates tokens, updates Mattermore plugin + ephemeral post every ~200ms + 10 On done:true → create final post Mattermore plugin + 11 ConversationStore.Append(...) ConversationStore +``` + +### 7.2 Thread Participation + +`TODO(design):` If the user replies in a thread where the bot posted, should +the plugin pick it up? This requires a `MessageHasBeenPosted` hook and +thread detection — adds complexity. **Scope:** defer to v0.2+. + +--- + +## 8. Security Model + +### 8.1 Input Sanitization + +- Strip control characters / zero-width Unicode from prompts before sending + to Ollama. +- Limit prompt length to 4096 characters (configurable via System Console). +- `TODO(design):` Should we block prompts that look like prompt injections? + ("Ignore previous instructions...") Hard to do reliably — document risk + instead. + +### 8.2 Output Safety + +- Cap response tokens in the Ollama request (`options.num_predict`). +- `TODO(design):` Add a "Report this response" feedback mechanism? Defer. + +### 8.3 Network Security + +- `OllamaURL` must start with `http://` or `https://` — validate at config + load. +- Reject internal IP ranges unless explicitly enabled by a + `AllowPrivateNetworks` toggle (default: off for production). +- TLS verify enabled by default; optional `SkipTLSVerify` toggle (logged as + a warning). + +### 8.4 Authorization + +- If `AllowedUserIDs` is non-empty, reject requests from other users with a + "not authorized" ephemeral message. +- All plugin API calls are already scoped to authenticated Mattermost users. + +### 8.5 Rate Limiting + +- Per-user token bucket, replenish rate set via System Console. +- Default: 10 requests/minute. 0 = unlimited (logged as a warning). +- Burst: 1 (enforce smooth spacing). + +--- + +## 9. Observability + +### 9.1 Logging + +| Level | When | +|---|---| +| `ERROR` | Ollama unreachable, config validation failure, KV store error | +| `WARN` | Rate limit hit, context window trimmed, TLS skipped | +| `INFO` | Plugin activated/deactivated, config changed, first request | +| `DEBUG` | Every request/response round-trip (with userID, model, token count, latency) | + +### 9.2 Metrics (future) + +`TODO(design):` Expose Prometheus-style counters via a `/metrics` endpoint +if the Mattermost server has plugin metrics support: +- `mattermore_requests_total{user,model,status}` +- `mattermore_latency_seconds` +- `mattermore_tokens_total{direction="input|output"}` +- `mattermore_rate_limit_hits_total` + +### 9.3 Error Reporting + +Ephemeral posts with user-facing messages (not raw Go errors). Internal +errors logged at `ERROR` with stack trace. + +--- + +## 10. Open Design Decisions + +These are the unresolved questions that need answers before v0.1.0 ships. + +| # | Question | Proposed | Alternatives | Decided? | +|---|---|---|---|---| +| D1 | Reply style | Ephemeral by default, `--public` flag for thread | Always ephemeral, always thread, always channel | ❌ | +| D2 | Context scope | Per-channel per-user | Per-user global, per-channel only, no context | ❌ | +| D3 | Context turns | Last 5 exchanges (10 messages) | Last N tokens, everything in TTL, sliding window | ❌ | +| D4 | Webapp UI | None (slash commands only) | Channel header button, RHS panel, settings page | ❌ | +| D5 | Model governance | All models available | Admin-restricted model list, least-privilege Ollama API key | ❌ | +| D6 | Thread replies | Defer to v0.2 | Auto-reply in thread when user replies to bot post | ❌ | +| D7 | `num_ctx` per request | Via `--context N` flag | System Console default only | ❌ | +| D8 | Auto-pull models | No (error + list available) | Yes (convenient but slow) | ❌ | +| D9 | Prompt injection guard | Defer (document risk) | Regex blocklist, LLM-as-judge pre-filter | ❌ | +| D10 | Metrics endpoint | Defer | `/metrics` via plugin HTTP handler | ❌ | + +Each decision should be recorded in `DECISIONS.md` once resolved. + +--- + +## 11. Deployment & Lifecycle + +### 11.1 Build + +```bash +make dist +``` + +Produces `dist/com.forkless.mattermore-0.1.0.tar.gz` containing the server +binaries and `plugin.json`. + +### 11.2 Upload + +Via Mattermost System Console → Plugins → Upload Plugin, or via REST API: + +```bash +curl -X POST $MM_URL/api/v4/plugins \ + -H "Authorization: Bearer $MM_TOKEN" \ + -F "plugin=@dist/com.forkless.mattermore-0.1.0.tar.gz" +``` + +### 11.3 Upgrade + +1. Upload new `.tar.gz` — Mattermost replaces the plugin binaries. +2. Plugin's `OnConfigurationChange()` fires (config preserved). +3. If the KV schema changed, a migration in `OnActivate()` handles it. + +### 11.4 Rollback + +Re-upload the previous version's `.tar.gz`. No data migration needed if the +KV key schema hasn't changed. If it has, restore from backup or +`DECISIONS.md` records the migration path. + +--- + +## 12. Future Considerations (v0.2+) + +- **Multi-modal inputs** — Ollama supports images via `/api/chat` with + `images` field. Could allow `/ai` with attached image. +- **Multi-model routing** — Different models for different channel categories + (e.g. code-review channel uses codellama). +- **Tool calling** — Ollama supports tool definitions. Could let the + plugin fetch data from external APIs (weather, Jira, etc.). +- **Bot account mode** — A separate `mattermost-bot` skill / mode for + always-on presence in channels vs. on-demand slash commands. +- **Streaming into message attachments** — Render structured data (tables, + JSON, code blocks) with better formatting. diff --git a/PROJECT.md b/PROJECT.md new file mode 100644 index 0000000..74d83d8 --- /dev/null +++ b/PROJECT.md @@ -0,0 +1,70 @@ +# Project: Mattermore + +## Purpose + +Mattermore is a Mattermost (Community Edition) plugin that connects chat to +remote LLMs via Ollama. Users invoke it with a `/ai` slash command and +receive streamed AI responses directly in the channel. + +## Structure + +``` +mattermore/ +├── DESIGN.md — Functional design document +├── DECISIONS.md — Design rationale log +├── PROJECT.md — This file +├── CHANGES.md — Per-release changelog +├── README.md — User-facing docs +├── plugin.json — Mattermost plugin manifest +├── Makefile — Build, lint, dist targets +├── .gitignore — Go + plugin ignores +├── server/ +│ ├── main.go — Plugin entry point (OnActivate, ExecuteCommand) +│ ├── plugin.go — Core plugin struct and hooks +│ ├── configuration.go — Config reading and validation +│ ├── command.go — Slash command parsing and dispatch +│ ├── ollama/ +│ │ ├── client.go — Ollama API client (ChatCompletion, ListModels, Ping) +│ │ └── client_test.go — Unit tests (mocked HTTP server) +│ ├── store/ +│ │ ├── conversation.go — KV-backed conversation storage +│ │ └── conversation_test.go +│ └── rate/ +│ └── limiter.go — Token-bucket rate limiter +└── webapp/ (future — optional) + └── .placeholder +``` + +## Key Files + +| Path | Purpose | +|---|---| +| `plugin.json` | Manifest: id, name, version, settings schema | +| `server/main.go` | `OnActivate()`, `OnDeactivate()`, `ExecuteCommand()` | +| `server/ollama/client.go` | HTTP client for Ollama REST API | +| `server/store/conversation.go` | Context persistence via Mattermost KV store | +| `DESIGN.md` | Full functional design with open decisions | +| `DECISIONS.md` | Record of architectural decisions and their rationale | + +## External Dependencies + +- **Go ≥ 1.21** (toolchain) +- **Mattermost Server ≥ v8.x** (plugin API) +- **Ollama** (remote inference server) +- Go modules (see `go.mod`): + - `github.com/mattermost/mattermost-server/v6` — Plugin SDK + - Standard library only for Ollama HTTP client (no external AI SDKs) + +## Release Cadence + +- Tags: `v0.x.y` +- CI builds on tag (see `.gitea/workflows/ci.yml`) +- Plugin bundle published as Gitea release artifact +- Follows `release-workflow` skill: draft → user test → publish + +## Documentation + +- `DESIGN.md` — functional design (living document) +- `DECISIONS.md` — why key decisions were made +- `CHANGES.md` — user-facing changelog +- `README.md` — quick start, config reference, build instructions diff --git a/README.md b/README.md index e69de29..10cedec 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,79 @@ +# Mattermore + +**AI chat agent for Mattermost — powered by Ollama.** + +Ask questions, draft content, and get AI assistance directly in your +Mattermost channels using `/ai` slash commands. All inference runs on your +own Ollama server — no data leaves your infrastructure. + +> **Status:** Pre-implementation. Design is in `DESIGN.md`. + +--- + +## Features + +- `/ai ` — Chat with the default model +- `/ai model ` — Use a specific model +- `/ai new` — Reset conversation context +- `/ai model list` — List available models +- `/ai help` — Show usage help +- Configurable Ollama endpoint, model, system prompt, rate limits +- Streaming responses updated live in ephemeral posts +- Per-channel per-user conversation context (configurable retention) + +## Prerequisites + +- **Mattermost Server** v8.x (Community Edition) +- **Ollama** running and reachable from the Mattermost server +- **Go** ≥ 1.21 (for building) + +## Quick Start + +```bash +# Clone the repository +git clone https://gitea.forkless.com/forkless/mattermore.git +cd mattermore + +# Build the plugin +make dist + +# Upload via Mattermost System Console → Plugins → Upload Plugin +# Or via API: +curl -X POST $MM_URL/api/v4/plugins \ + -H "Authorization: Bearer $MM_TOKEN" \ + -F "plugin=@dist/com.forkless.mattermore-0.1.0.tar.gz" +``` + +## Configuration + +Configure via Mattermost **System Console → Plugins → Mattermore**: + +| Setting | Description | Default | +|---|---|---| +| Ollama Server URL | Base URL of the Ollama API | `http://localhost:11434` | +| Default Model | Model used when none specified | `llama3.2` | +| System Prompt | Prepended to every conversation | `You are a helpful assistant.` | +| Max Response Tokens | Max tokens per response (0 = model default) | `2048` | +| Rate Limit | Requests/minute/user (0 = unlimited) | `10` | +| Allowed User IDs | Comma-separated user IDs (empty = all users) | (empty) | + +## Build Targets + +```bash +make dist # Build all platform binaries and package plugin +make check # Lint + test +make clean # Remove build artifacts +``` + +## Documentation + +| File | What it covers | +|---|---| +| `DESIGN.md` | Functional design, architecture, data flow, open decisions | +| `PROJECT.md` | Project structure, key files, dependencies | +| `DECISIONS.md` | Rationale behind architectural choices | +| `CHANGES.md` | Per-release changelog | + +## License + +MIT (to be confirmed — see `LICENSE` once added).