Replace commitizen-action with custom workflow that detects which
components have changes based on commit scopes and bumps them
independently.
The workflow:
1. Checks for commits with scope patterns since last tag for each component:
- MCP server: scope=mcp or unscoped, tags=v*
- Helm chart: scope=helm, tags=nextcloud-mcp-server-*
- Astrolabe: scope=astrolabe, tags=astrolabe-v*
2. Runs appropriate bump script for components with changes:
- ./scripts/bump-mcp.sh
- ./scripts/bump-helm.sh
- ./scripts/bump-astrolabe.sh
3. Pushes all created tags at once
4. Provides GitHub Actions summary showing which components were bumped
This ensures each component versions independently based on its
relevant commits, preventing the issue where all components bump
together or some components are missed.
Fixes the issue where PR #418 only bumped MCP server, leaving Helm
chart and Astrolabe at their previous versions despite having changes.
Addresses remaining high-priority code review feedback:
VERSIONING SCHEME FIXES:
- Helm chart: Changed from pep440 to semver (correct for Helm)
- Astrolabe: Changed from pep440 to semver (correct for Nextcloud apps)
- MCP server: Remains pep440 (correct for Python packages)
Helm charts must use semantic versioning per Helm specification.
Nextcloud apps use semantic versioning in info.xml and package.json.
ENHANCED ERROR HANDLING IN BUMP SCRIPTS:
All three bump scripts now include:
- Comprehensive validation checks
* Tool availability (uv)
* Directory structure (must run from repo root)
* Required files exist (Chart.yaml, info.xml, package.json)
- Better error messages
* Stderr output for errors
* Captured commitizen output on failure
* Common failure causes listed
- Success confirmation
* Clear indication of what was updated
* Next steps guidance (git push --follow-tags)
- Robust shell options (set -euo pipefail)
Scripts now provide helpful guidance when:
- No conventional commits found
- No commits with required scope
- Git working directory not clean
- Required dependencies missing
Addresses code review feedback on PR #418:
CRITICAL FIXES:
1. Workflow trigger: Changed from release:published to push:tags
- Enables "tag and publish in one step" workflow as intended
- Automatically creates GitHub release on tag push
- Removed redundant if condition (filtering now via trigger)
- Added prerelease detection based on tag (-alpha, -beta, -rc)
2. Server path: Explicitly pass server_dir to make command
- Fixes path mismatch between local (../../server) and CI
- Uses absolute path: server_dir=${{ github.workspace }}/server
- Prevents signing failures in GitHub Actions
3. Regex validation: Added test script for commitizen patterns
- Validates scope filtering works correctly
- Tests all three components: mcp, helm, astrolabe
- Tests unscoped commits (default to mcp)
- Tests breaking changes and invalid commits
- Location: scripts/test-commitizen-scopes.sh
WORKFLOW IMPROVEMENTS:
- Release creation now automatic on tag push
- Better step naming for clarity
- Consistent prerelease handling across GitHub and App Store
- Explicit server_dir prevents reliance on fragile relative paths
All 16 test cases pass for scope filtering patterns.
CRITICAL FIXES:
- Fix tag parsing in workflow to strip "astrolabe-v" instead of "v"
For tag astrolabe-v0.1.0, now correctly extracts "0.1.0"
- Add workflow filtering to only run on astrolabe-v* tags
Prevents wasting CI resources on MCP/Helm releases
RECOMMENDED IMPROVEMENTS:
- Make Nextcloud server path configurable in Makefile
Can now override: make appstore server_dir=/path/to/server
- Add dependency validation to Makefile
Checks for composer, npm, php before building
- Add signing prerequisite validation
Verifies server/occ, private key, and certificate exist
- Add dependency checks to all bump scripts
Validates uv is installed before running cz bump
These changes improve local build experience and prevent common
errors with clear error messages and installation guidance.
Add complete CI/CD pipeline for automated Astrolabe app releases:
- GitHub Actions workflow for build, sign, and publish
- Makefile for app store package creation
- Version synchronization between info.xml and package.json
- CHANGELOG.md with v0.1.0 release notes
feat: configure commitizen monorepo with independent versioning
Enable independent versioning for three components using scope-based commits:
- MCP Server (feat(mcp) or unscoped): v* tags, updates pyproject.toml + Chart.yaml:appVersion
- Helm Chart (feat(helm)): nextcloud-mcp-server-* tags, updates Chart.yaml:version
- Astrolabe App (feat(astrolabe)): astrolabe-v* tags, updates info.xml + package.json
Changes:
- Add .cz.toml configs for Astrolabe and Helm chart
- Update root pyproject.toml with scope filtering and tag ignores
- Create bump helper scripts (bump-mcp.sh, bump-helm.sh, bump-astrolabe.sh)
- Add CONTRIBUTING.md with version management documentation
- Create component-specific changelogs
- Configure appVersion/version separation for Helm chart
This allows each component to release independently while maintaining
proper version tracking and changelog generation.
This commit fixes two OAuth issues in the Astrolabe app:
1. **Always use PKCE (RFC 9207)**:
- PKCE is now used for all OAuth flows (public and confidential clients)
- Previous code only used PKCE for public clients, causing failures
- Confidential clients now use both PKCE + client_secret (defense in depth)
- Nextcloud OIDC provider requires PKCE, so token exchange was failing
2. **Add token_broker to oauth_context**:
- Token broker is now stored in oauth_context for management API access
- Fixes "Token broker not configured" error when revoking access
- Revoke endpoint needs token_broker to delete refresh tokens and invalidate cache
Changes:
- OAuthController.php: Always generate PKCE verifier/challenge for all clients
- OAuthController.php: Always include code_verifier in token exchange
- app.py: Store token_broker in oauth_context after creation
Fixes:
- Astrolabe OAuth flow now works with Nextcloud OIDC
- Revoke/disconnect functionality now works in Astrolabe settings
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The files_pdfviewer app route is internal to Nextcloud and not a valid
external URL. Reverted to using the standard Files app viewer URL for
all file types.
- Removed PDF-specific handling that used /apps/files_pdfviewer/
- All files now link to /apps/files/files/{id} (standard Files viewer)
- Fixes broken links in chunk modal titles and search results
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add type casts for Starlette app state access
- Add assertions for cipher, card, board, stack after initialization
- Add None checks for XML element text attributes
- Handle __package__ being None in tracing setup
- Fix TokenBrokerService initialization to use storage credentials
Resolves 42 type warnings from ty-check, enabling CI linting to pass.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Move alembic/ directory to nextcloud_mcp_server/alembic/ subpackage
- Update migrations.py to use package location instead of alembic.ini
- Update env.py to set script_location dynamically
- Update alembic.ini for development CLI usage
- Fix Dockerfile typo: .vnev -> .venv
This fixes FileNotFoundError when running in Docker with non-editable
install. The alembic package is now installed with the main package,
making it work in both development and production environments.
Resolves: Docker startup error 'alembic.ini not found at
/app/.venv/lib/python3.12/site-packages/alembic.ini'
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Rewrote Astrolabe README to be user-friendly and release-ready by
incorporating pitch.md content and moving technical details to linked
documentation.
Key changes:
- Incorporated compelling pitch narrative as opening
- Restructured around "What You Can Do" rather than architecture
- Added clear use cases for individuals, teams, and developers
- Simplified installation to 3 steps
- Moved OAuth flow and architecture details to ADR links
- Added emoji sections for visual appeal
- Focused on benefits over implementation
Sections:
- What You Can Do (search, visualization, AI agents)
- Installation (app store + manual)
- Quick Start (3-step setup)
- Features (personal, admin, unified search)
- Use Cases (research, collaboration, RAG workflows)
- Requirements (Nextcloud 30+, MCP server, OAuth)
- Documentation (links to installation, configuration, ADRs)
- Troubleshooting (quick fixes with links to detailed guides)
This README is now suitable for Nextcloud App Store submission.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Updated docs/running.md to use Docker container examples instead of
direct Python commands. This aligns with the CLI change to require
explicit 'run' subcommand while maintaining backward compatibility
for Docker users (ENTRYPOINT includes 'run').
Key changes:
- Quick Start: Use Docker commands instead of uv run
- Running Locally → Running with Docker: All examples use Docker
- Development Mode: Added CLI subcommands documentation (run/db)
- Database Migrations: Documented Alembic integration for developers
- Server Options: Docker port mapping instead of --host/--port flags
- Process Management: Simplified to Docker Compose only (removed systemd)
- Performance Tuning: Production Docker Compose with resource limits
- Troubleshooting: Docker logs and debug commands
Updated Dockerfile ENTRYPOINT:
- Changed from: ["/app/.venv/bin/nextcloud-mcp-server", "--host", "0.0.0.0"]
- Changed to: ["/app/.venv/bin/nextcloud-mcp-server", "run", "--host", "0.0.0.0"]
No breaking changes for Docker/Helm users - container interface unchanged.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implements Alembic for managing token storage database schema versions.
Migrations run automatically on startup with full backward compatibility.
**Changes:**
- Add Alembic dependency (1.14.0+) and SQLAlchemy (auto-installed)
- Create migration infrastructure in alembic/ directory
- Add initial migration (001) capturing current schema
- Modify RefreshTokenStorage.initialize() to run migrations via anyio
- Add CLI commands: db upgrade, current, history, downgrade, migrate
- Add comprehensive migration documentation
**Backward Compatibility:**
- Pre-Alembic databases automatically stamped with revision 001
- No schema changes for existing databases
- Automatic upgrade on first startup after update
**Migration Strategy:**
Three scenarios handled:
1. New database → Run migrations from scratch
2. Pre-Alembic database → Stamp with 001 (no changes)
3. Alembic-managed → Upgrade to latest
**Architecture:**
- Uses anyio.to_thread.run_sync() for structured concurrency
- Alembic env.py runs with anyio.run() in worker thread
- SQLite-friendly migration patterns documented
- No ThreadPoolExecutor needed (anyio handles it)
**CLI Usage:**
```bash
nextcloud-mcp-server db upgrade # Upgrade to latest
nextcloud-mcp-server db current # Show version
nextcloud-mcp-server db history # View changelog
nextcloud-mcp-server db downgrade # Rollback (with confirmation)
nextcloud-mcp-server db migrate "description" # Create migration
```
**Testing:**
- All 13 webhook storage tests pass
- New/pre-Alembic database scenarios validated
- anyio integration tested
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add clickable link to modal title with OpenInNew icon
- Store currentResult to enable document navigation
- Fix deck_card URLs to use metadata.board_id
- Fix news_item URLs to use external article URL from metadata.url
- Add hover styling for title link and icon
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Update the unified search provider to show only chunk/page metadata
in search results, consistent with the chunk visualization result list.
Also fix news item URLs to link directly to the specific item.
Changes to SemanticSearchProvider:
1. Result display improvements:
- Remove excerpt text from search result subline
- Show only chunk/page metadata (e.g., "Chunk 2/5 · Page 3/10")
- Consistent with chunk visualization UI in App.vue
2. News item URL fix:
- Change from generic news index to specific item URL
- Format: /apps/news/item/{id}
- Allows direct navigation to the news article
3. Code cleanup:
- Remove unused $excerpt variable
- Remove unused truncateExcerpt() method
- Simplify transformResult() logic
Benefits:
- Cleaner, more scannable search results
- Consistent UX between unified search and app UI
- Functional links to news items instead of generic news page
- Reduced code complexity
Replace expensive Plotly.restyle() hover handlers with native hoverlabel
styling to indicate clickable points without performance degradation.
Implementation:
- Add hoverlabel configuration to document trace with distinct styling
- Bright blue background (#0082c9) to make hover state obvious
- Larger font size (15px) for better visibility
- White text for contrast against blue background
- Handled natively by Plotly - no JavaScript event handlers needed
Benefits:
- Zero performance impact - no chart re-renders on hover
- Smooth, responsive hover feedback
- Clear visual indication that points are clickable
- Consistent with existing hover tooltip pattern
Removed:
- Expensive handlePlotHover() and handlePlotUnhover() methods
- Plotly.restyle() calls that caused severe lag and freezing
- hover/unhover event listener registrations
The hover tooltip now uses the styled hoverlabel to stand out visually,
providing clear feedback that points are interactive without any
performance cost.
Enable users to click on points in the vector space visualization to
open the chunk viewer modal, providing a more direct interaction
method alongside the existing "Show Chunk" button.
Implementation details:
- Register plotly_click event handler in renderPlot() after chart creation
- Add handlePlotClick() method to process click events
- Use point index mapping to access full result object from this.results
- Add loading guard in viewChunk() to prevent concurrent chunk loading
- Add cursor styling: pointer for result points, default for query point
- Add beforeDestroy() lifecycle hook to cleanup event handlers
Features:
- Both interaction methods work: click chart points OR "Show Chunk" button
- Only result points (trace 0) are clickable, query point (trace 1) ignored
- Pointer cursor on hover indicates clickable points
- Loading state prevents rapid clicks from causing issues
- Memory leak prevention through proper event handler cleanup
Technical approach:
- Uses index mapping (not data duplication) for efficiency
- Results and coordinates arrays have guaranteed 1:1 mapping from API
- Event handler re-registered on each chart re-render
- CSS-based cursor styling (more performant than JS hover handlers)
Testing:
- ESLint validation passed
- Follows Vue 2.7 component property order conventions
- Compatible with existing chunk viewer modal
This commit implements three UI improvements for the chunk viewer:
1. Fixed modal footer with navigation controls
- Moved PDF navigation buttons to a fixed footer
- Footer remains visible while scrolling content
- Three-section layout: fixed header, scrollable body, fixed footer
2. Removed duplicate navigation controls
- Removed previous/next buttons from PDFViewer component
- Controls now only in App.vue modal footer
- Cleaned up unused imports and CSS
3. Markdown rendering for chunk content
- Created MarkdownViewer component using markdown-it
- Renders markdown content aligned with Nextcloud design system
- Removed problematic markdown-it-task-checkbox dependency
- Combines before/chunk/after context with visual separators
4. Cleaned up search results display
- Removed excerpt snippets from results list
- Kept only chunk/page metadata for cleaner UI
The modal structure now has:
- Fixed header (title + close button)
- Scrollable body (PDF canvas or markdown content)
- Fixed footer (page navigation - always visible)
Fixes markdown rendering "require is not defined" error by using
only markdown-it without CommonJS plugins.
Fixes 401 errors after first token refresh when using IdPs that
implement refresh token rotation (Keycloak, modern OAuth providers).
**Root Cause**:
McpTokenStorage::getAccessToken() was discarding the new refresh token
returned by the IdP after successful refresh, always keeping the old one.
This caused:
- First refresh: works (uses original refresh token)
- Second refresh: fails with 401 (old refresh token invalidated by IdP)
**Solution**:
Use new refresh token from IdP response if provided, fall back to old
token for providers that don't rotate refresh tokens.
**Changed**:
- lib/Service/McpTokenStorage.php:184
From: $token['refresh_token'] // Always old token
To: $newTokenData['refresh_token'] ?? $token['refresh_token']
**Verified**:
ApiController already handles rotation correctly using the same pattern.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit includes two improvements to the Astroglobe semantic search UI:
1. **Multi-select Document Types** (App.vue):
- Changed NcCheckboxRadioSwitch binding from v-model to :checked/:update:checked
- Implemented toggleDocType() method to manually manage selectedDocTypes array
- Fixes issue where only single document type could be selected at a time
- Users can now filter search results by multiple doc types simultaneously
2. **PDF Viewer Reactive Rendering** (PDFViewer.vue):
- Refactored canvas rendering to use Vue reactive watcher pattern
- Added watcher on 'loading' state that triggers rendering when canvas available
- Removed imperative renderPage() call from loadPDF() method
- Inspired by files_pdfviewer's promise/event-based initialization approach
- Improves alignment with Vue's reactive data flow
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit addresses 4 critical issues identified in code review:
1. **Token Rotation Race Condition** (token_broker.py)
- Added per-user locking mechanism to prevent concurrent refresh token corruption
- Implemented double-check pattern for cache after acquiring lock
- Users can now safely refresh concurrently without token desync
2. **Hardcoded OAuth Client ID** (PHP files)
- Made client ID configurable via `astroglobe_client_id` in system config
- Updated McpServerClient to provide getClientId() method
- Injected McpServerClient into IdpTokenRefresher and OAuthController
- Updated admin settings UI to display client ID configuration status
- App gracefully handles missing client ID with warnings in admin UI
3. **Missing Cache Invalidation** (management.py:revoke_user_access)
- Added cache.invalidate() call when revoking user access
- Ensures both storage AND cache are cleared atomically
- Prevents stale cached tokens from being used after revocation
4. **Error Message Exposure** (management.py)
- Created _sanitize_error_for_client() helper function
- Updated all error handlers to log detailed errors internally
- Returns generic messages to clients to prevent information leakage
- Protects against exposing database paths, API URLs, tokens, etc.
All changes are backward compatible and preserve existing functionality.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Configure Astroglobe as a confidential OAuth client with client_secret
to support token refresh for long-lived sessions.
Changes:
- Update install-astroglobe-app hook to:
- Create confidential client instead of public
- Add offline_access scope for refresh tokens
- Extract and store client_secret in system config
- Display secret (truncated) for verification
- Update trusted-domains hook (formatting)
Benefits:
- Enables automatic token refresh without re-authentication
- Supports long-lived backend operations
- Better security for server-side OAuth flows
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Improve unified search results with chunk/page metadata and add
webhook management capabilities to McpServerClient.
Changes:
- SemanticSearchProvider improvements:
- Display chunk position (e.g., "Chunk 2/5")
- Display page numbers for PDFs (e.g., "Page 3/10")
- Fix file links to open in Files app correctly
- Fix deck card links to use proper URL format
- Show metadata in subline before excerpt
- Use proper icons and thumbnails for each doc type
- McpServerClient webhook methods:
- listWebhooks() - Get all registered webhooks
- createWebhook() - Register new webhook
- deleteWebhook() - Remove webhook registration
- enableWebhook() / disableWebhook() - Toggle webhook status
- getWebhookLogs() - Retrieve delivery logs
Benefits:
- Better search result context with chunk and page info
- Clickable links that open correct resources
- Full webhook lifecycle management via API
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add admin interface for configuring real-time webhook sync with
pre-configured presets for common scenarios.
Changes:
- Add webhook presets section to admin settings page
- Shows available presets filtered by installed apps
- Enable/disable presets with one click
- Displays current webhook status
- Add client secret configuration status display
- Shows whether confidential client is configured
- Provides setup instructions for optional client secret
- Add adminSettings.js for webhook management
- Load webhook presets via API
- Enable/disable webhook presets
- Handle search settings form submission
- Update vite.config.js to build adminSettings entry point
- Pass clientSecretConfigured flag to template
UI Features:
- Real-time preset status (enabled/disabled)
- One-click enable/disable for webhook bundles
- App-aware filtering (only shows presets for installed apps)
- Clear instructions for requirements and setup
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Changes:
- Add file_path to metadata in semantic and BM25 hybrid search algorithms
for PDF viewer integration (search/semantic.py:161-163, search/bm25_hybrid.py:230-232)
- Include chunk_start_offset, chunk_end_offset, page_number, and page_count
in search results for rich chunk display (api/management.py:981-1004)
- Add point_id field to SearchResult for batch retrieval (models/semantic.py)
- Fix type narrowing for chunk context API parameters (api/management.py:1102-1111)
- Fix None-safety in doc_types discovery (search/algorithms.py:114)
This enables the Astroglobe UI to display PDF pages at the correct
location for matched chunks.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Improve search result display to match Nextcloud's native search providers by using mimetype-specific icons and preview thumbnails.
**File Results:**
- Use preview thumbnails for images/PDFs (core.Preview API)
- Use mimetype-specific icon classes (icon-pdf, icon-text, icon-image, etc.)
- Detect folders and use icon-folder appropriately
**Other Document Types:**
- Notes: icon-notes
- Deck Cards: icon-deck
- Calendar: icon-calendar
- News: icon-rss
- Contacts: icon-contacts
**API Changes:**
- Management API now includes mime_type in search results
- SemanticSearchProvider uses IMimeTypeDetector and IPreview services
This makes Astroglobe search results visually consistent with Files, Notes, and other native providers.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Integrate semantic search into Nextcloud's unified search UI. File results now use fileId parameter to properly open files instead of just navigating to the Files app.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add Plotly.js 3D scatter plot showing search results in PCA space
- Create shared visualization.py module to avoid code duplication
- Pass include_pca parameter through API chain to enable coordinates
- Fix OAuth redirects to use /settings/user/astroglobe
The visualization shows document embeddings projected to 3D via PCA,
with the query point highlighted in red. Uses Viridis colorscale
for score visualization, matching the existing vector-viz page.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update all user-facing text to focus on Astroglobe as a semantic
search service for Nextcloud users:
- info.xml: New description focusing on finding content by meaning
- Settings sections: Renamed from "MCP Server" to "Astroglobe"
- Personal settings: Reframed as content indexing controls
- Admin settings: Reframed as semantic search administration
- OAuth flow: Explains semantic search benefits to users
Key messaging changes:
- "MCP Server" → "Astroglobe"
- "Grant Background Access" → "Enable Semantic Search"
- "Vector Sync" → "Content Indexing"
- Focus on user benefits: natural language search, finding by meaning
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds a native Nextcloud app "Astroglobe" that provides:
- Personal settings: OAuth authorization for background MCP access
- Admin settings: Server status and vector sync monitoring
- API endpoints for MCP server communication
The app uses PKCE OAuth flow to obtain tokens for the MCP server,
enabling features like background vector sync per ADR-018.
Includes:
- PHP app structure (controllers, services, settings)
- Vue.js frontend components
- Docker compose mount configuration
- Installation hook for development testing
- ADR-018 documentation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>