Adds complete app password provisioning workflow for multi-user BasicAuth
deployments, allowing users to independently enable background sync by
generating and storing Nextcloud app passwords.
**New Components:**
Backend (PHP):
- CredentialsController: Validates and stores app passwords
* Validates app password format and authenticity via OCS API
* Stores encrypted passwords in oc_preferences
* Provides status and credential management endpoints
- AstrolabeAdminSettings: Admin configuration page for MCP server URL
- AstrolabeAdminSettingsListener: Event listener for admin section
- Updated McpTokenStorage: Added background sync credential methods
Frontend:
- personalSettings.js: Form handling for app password entry
* AJAX submission with error handling
* Shows success/error notifications
* Triggers page reload after successful save
- settings.css: Styling for settings pages
- Updated personal.php template: Two-option UI
* Option 1: OAuth refresh token (future, not yet available)
* Option 2: App password (works today, recommended)
* Shows "Active" badge when provisioned
* Displays credential type and provisioned timestamp
Routes:
- POST /api/v1/background-sync/credentials - Store app password
- GET /api/v1/background-sync/status - Get provisioning status
- DELETE /api/v1/background-sync/credentials - Revoke credentials
- GET /api/v1/background-sync/credentials/{userId} - Admin only
**Testing:**
- test_astrolabe_settings_buttons.py: Integration test for UI buttons
**Workflow:**
1. User generates app password in Nextcloud Security settings
2. User navigates to Astrolabe personal settings
3. User enters app password in "Option 2: App Password" form
4. Backend validates password via OCS API call
5. Password stored encrypted in oc_preferences
6. Page reloads showing "Active" badge with credential details
7. MCP server can now use stored password for background operations
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The pattern 'version' was too broad and matched multiple lines:
- <?xml version="1.0"?>
- <version>0.2.1</version>
- min-version="30" max-version="32"
Changed to '<version>' to specifically match only the version tag.
Also fixed version mismatch: info.xml now correctly shows 0.3.0 to match
the version in .cz.toml and package.json.
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
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>
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>
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>
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>
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>