feat(api): one API for curl and MCP, REST reads with keys, origin badge with key name
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m16s

API keys reach tasks and lists over plain REST too (GET /task-lists, GET /tasks with
status/due filters, PATCH/DELETE /thoughts/{id}, GET /lists, list items), guarded by a route
table; notes and settings stay closed. Rights hold for every access alike (webhook:task also
allows create_task over MCP, tasks:write also task delivery).

Tasks, thoughts and list items record which key created them and which one changed them last
(migration 13 replaces agent_mark; origin stays after the user's own edits and survives the
triage into a list item). OriginBadge shows the key name with a webhook or robot icon, details
on hover and tap, and replaces the Webhook and KI badges everywhere, now also on list items.

Settings: category "API" with rights grouped by tasks, lists and inbox and two tabs
(curl / smart home with a Home Assistant sensor, AI agents over MCP). Docs, help, README and
changelog updated; v0.30.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-11-01 10:26:44 +01:00
1 parent 0ca715ba64
commit b883315914
36 files changed
+1119 -563

No files matched your search

@@ -21,28 +21,32 @@ import { ApiKeysPanel } from '../ApiKeysPanel';
describe('ApiKeysPanel', () => {
beforeEach(() => createAPIKey.mockClear());
it('shows the scopes of existing keys as badges', async () => {
it('shows the rights of existing keys as badges', async () => {
render(<ApiKeysPanel />);
expect(await screen.findByText('Rauchmelder')).toBeTruthy();
expect(screen.getAllByText('📋 Aufgaben anlegen').length).toBeGreaterThan(1); // switch label + badge
expect(screen.getByText('Aufgaben anlegen')).toBeTruthy();
expect(screen.getByText('Inbox')).toBeTruthy();
});
it('creates a key with the chosen scopes and never without one', async () => {
render(<ApiKeysPanel />);
const triage = screen.getByRole('switch', { name: /Triage/ });
const triage = screen.getByRole('switch', { name: 'Inbox' });
fireEvent.click(triage); // last remaining scope cannot be switched off
expect(triage.getAttribute('aria-checked')).toBe('true');
fireEvent.click(screen.getByRole('switch', { name: /Listeneinträge anlegen/ }));
fireEvent.click(screen.getByRole('switch', { name: 'Listeneinträge hinzufügen' }));
await act(async () => {
fireEvent.click(screen.getByRole('button', { name: /API-Key erzeugen/ }));
});
expect(createAPIKey).toHaveBeenCalledWith('Home Assistant', ['webhook:triage', 'webhook:list']);
});
it('offers agent scopes and shows how to connect an MCP client', async () => {
it('shows curl for scripts and, in the second tab, how to connect an MCP client', async () => {
render(<ApiKeysPanel />);
fireEvent.click(screen.getByRole('switch', { name: /Aufgaben ändern/ }));
expect(screen.getByRole('switch', { name: /Aufgaben ändern/ }).getAttribute('aria-checked')).toBe('true');
fireEvent.click(screen.getByRole('switch', { name: 'Aufgaben ändern' }));
expect(screen.getByRole('switch', { name: 'Aufgaben ändern' }).getAttribute('aria-checked')).toBe('true');
expect(screen.getAllByText(/tasks\?due=today&status=open/).length).toBeGreaterThan(0);
expect(screen.queryByText(/claude mcp add/)).toBeNull();
fireEvent.click(screen.getByRole('tab', { name: /KI-Agenten/ }));
expect(screen.getByText(/claude mcp add --transport http mindsift/)).toBeTruthy();
fireEvent.click(screen.getByRole('radio', { name: 'OpenAI Codex' }));
expect(screen.getByText(/\[mcp_servers\.mindsift\]/)).toBeTruthy();