diff --git a/package.json b/package.json new file mode 100644 index 0000000..eed2fae --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "@verstak/plugin-sdk", + "version": "0.1.0", + "description": "Verstak Plugin SDK — TypeScript types and runtime API for plugin development", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist/", + "schemas/" + ], + "scripts": { + "build": "tsc", + "test": "vitest run", + "lint": "tsc --noEmit", + "prepack": "npm run build" + }, + "devDependencies": { + "typescript": "^5.4.0", + "vitest": "^1.6.0" + } +} diff --git a/schemas/capabilities.json b/schemas/capabilities.json new file mode 100644 index 0000000..12c4585 --- /dev/null +++ b/schemas/capabilities.json @@ -0,0 +1,66 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.mirv.top/verstak/verstak-sdk/schemas/capabilities.json", + "title": "Verstak Capability Registry", + "description": "Known capability names and their descriptions for the Verstak platform", + "type": "object", + "properties": { + "capabilities": { + "type": "array", + "items": { + "$ref": "#/$defs/CapabilityEntry" + } + } + }, + "$defs": { + "CapabilityEntry": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "description": { "type": "string" }, + "status": { + "type": "string", + "enum": ["stable", "draft", "deprecated"] + } + }, + "required": ["name", "description", "status"] + } + }, + "capabilities": [ + { "name": "editor.text", "description": "Text editing capability (any format)", "status": "draft" }, + { "name": "editor.text.markdown", "description": "Markdown text editing", "status": "draft" }, + { "name": "editor.note.markdown", "description": "Markdown note editing (extends editor.text.markdown with note metadata)", "status": "draft" }, + { "name": "viewer.file", "description": "File viewer (any type)", "status": "draft" }, + { "name": "viewer.image", "description": "Image viewer", "status": "draft" }, + { "name": "viewer.text", "description": "Text file viewer", "status": "draft" }, + { "name": "viewer.markdown", "description": "Markdown rendered viewer", "status": "draft" }, + { "name": "preview.markdown", "description": "Markdown preview panel", "status": "draft" }, + { "name": "preview.file", "description": "File preview panel", "status": "draft" }, + { "name": "workspace.files", "description": "File workspace management", "status": "draft" }, + { "name": "workspace.notes", "description": "Note workspace management", "status": "draft" }, + { "name": "vault.files", "description": "Low-level vault file read/write access", "status": "draft" }, + { "name": "entity.file", "description": "File entity type support", "status": "draft" }, + { "name": "entity.note", "description": "Note entity type support", "status": "draft" }, + { "name": "note.registry", "description": "Note metadata registry", "status": "draft" }, + { "name": "file.browser", "description": "File browser tree/list UI", "status": "draft" }, + { "name": "activity.log", "description": "Activity event logging", "status": "draft" }, + { "name": "activity.provider", "description": "Activity event provider", "status": "draft" }, + { "name": "activity.reconstruction", "description": "Activity reconstruction from events", "status": "draft" }, + { "name": "worklog", "description": "Worklog/journal entry capability", "status": "draft" }, + { "name": "journal", "description": "Journal UI capability", "status": "draft" }, + { "name": "report.worklog", "description": "Worklog report generation", "status": "draft" }, + { "name": "capture.browser", "description": "Browser capture receiver", "status": "draft" }, + { "name": "browser.inbox", "description": "Browser inbox UI capability", "status": "draft" }, + { "name": "domain.binding", "description": "Domain-to-case binding", "status": "draft" }, + { "name": "search", "description": "Full-text search capability", "status": "draft" }, + { "name": "search.provider", "description": "Search result provider", "status": "draft" }, + { "name": "search.indexer", "description": "Search indexer service", "status": "draft" }, + { "name": "secret-store", "description": "Encrypted secret storage", "status": "draft" }, + { "name": "secrets.read-ui", "description": "Secret read user interface", "status": "draft" }, + { "name": "secrets.write-ui", "description": "Secret write user interface", "status": "draft" }, + { "name": "case.templates", "description": "Case template provider", "status": "draft" }, + { "name": "link.resolver", "description": "Internal link resolver (verstak://)", "status": "draft" }, + { "name": "importer", "description": "Data import capability", "status": "draft" }, + { "name": "exporter", "description": "Data export capability", "status": "draft" } + ] +} diff --git a/schemas/contributions.json b/schemas/contributions.json new file mode 100644 index 0000000..3da65cf --- /dev/null +++ b/schemas/contributions.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.mirv.top/verstak/verstak-sdk/schemas/contributions.json", + "title": "Verstak Contribution Points Registry", + "description": "Known contribution points that plugins can register", + "type": "object", + "properties": { + "contributionPoints": { + "type": "array", + "items": { + "$ref": "#/$defs/ContributionPoint" + } + } + }, + "$defs": { + "ContributionPoint": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "description": { "type": "string" }, + "type": { + "type": "string", + "enum": ["view", "command", "item", "provider", "panel", "action", "entry"] + }, + "status": { + "type": "string", + "enum": ["stable", "draft", "deprecated"] + } + }, + "required": ["id", "description", "type", "status"] + } + }, + "contributionPoints": [ + { "id": "sidebar.items", "description": "Sidebar navigation items", "type": "item", "status": "draft" }, + { "id": "main.views", "description": "Main content views", "type": "view", "status": "draft" }, + { "id": "case.tabs", "description": "Case detail tabs", "type": "view", "status": "draft" }, + { "id": "file.actions", "description": "File context actions", "type": "action", "status": "draft" }, + { "id": "note.actions", "description": "Note context actions", "type": "action", "status": "draft" }, + { "id": "context.menu", "description": "Context menu entries", "type": "entry", "status": "draft" }, + { "id": "commands", "description": "Command palette commands", "type": "command", "status": "draft" }, + { "id": "settings.panels", "description": "Plugin settings panels", "type": "panel", "status": "draft" }, + { "id": "search.providers", "description": "Search result providers", "type": "provider", "status": "draft" }, + { "id": "activity.providers", "description": "Activity event providers", "type": "provider", "status": "draft" }, + { "id": "status.bar.items", "description": "Status bar items", "type": "item", "status": "draft" }, + { "id": "importers", "description": "Data import providers", "type": "provider", "status": "draft" }, + { "id": "exporters", "description": "Data export providers", "type": "provider", "status": "draft" }, + { "id": "protocol.receivers", "description": "Custom protocol message receivers", "type": "provider", "status": "draft" } + ] +} diff --git a/schemas/events/browser.json b/schemas/events/browser.json new file mode 100644 index 0000000..17c9392 --- /dev/null +++ b/schemas/events/browser.json @@ -0,0 +1,131 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.mirv.top/verstak/verstak-sdk/schemas/events/browser.json", + "title": "Verstak Browser Events", + "description": "Event schemas for browser extension and browser-inbox plugin communication", + "type": "object", + "properties": { + "events": { + "type": "array", + "items": { + "$ref": "#/$defs/BrowserEvent" + } + } + }, + "$defs": { + "BrowserEvent": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "description": { "type": "string" }, + "schema": { + "type": "object", + "properties": { + "type": { "type": "string", "const": "object" }, + "properties": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { "type": "string" }, + { "type": "integer" }, + { "type": "boolean" }, + { "type": "array" }, + { "type": "object" } + ] + } + }, + "required": { + "type": "array", + "items": { "type": "string" } + } + }, + "required": ["type", "properties", "required"] + } + }, + "required": ["name", "description", "schema"] + } + }, + "events": [ + { + "name": "browser.capture.page", + "description": "Full page capture sent from browser extension", + "schema": { + "type": "object", + "properties": { + "url": { "type": "string", "description": "Page URL" }, + "title": { "type": "string", "description": "Page title" }, + "html": { "type": "string", "description": "Page HTML content" }, + "text": { "type": "string", "description": "Page text content" }, + "capturedAt": { "type": "string", "description": "ISO 8601 timestamp" }, + "domain": { "type": "string", "description": "Extracted domain" } + }, + "required": ["url", "title", "capturedAt"] + } + }, + { + "name": "browser.capture.selection", + "description": "Selected text sent from browser extension", + "schema": { + "type": "object", + "properties": { + "url": { "type": "string", "description": "Source page URL" }, + "title": { "type": "string", "description": "Source page title" }, + "text": { "type": "string", "description": "Selected text content" }, + "capturedAt": { "type": "string", "description": "ISO 8601 timestamp" }, + "domain": { "type": "string", "description": "Extracted domain" } + }, + "required": ["url", "title", "text", "capturedAt"] + } + }, + { + "name": "browser.capture.link", + "description": "Link sent from browser extension", + "schema": { + "type": "object", + "properties": { + "url": { "type": "string", "description": "Link URL" }, + "title": { "type": "string", "description": "Link text/title" }, + "capturedAt": { "type": "string", "description": "ISO 8601 timestamp" }, + "domain": { "type": "string", "description": "Extracted domain" } + }, + "required": ["url", "capturedAt"] + } + }, + { + "name": "browser.desktop.status", + "description": "Desktop status response to browser extension ping", + "schema": { + "type": "object", + "properties": { + "online": { "type": "boolean", "description": "Whether desktop is reachable" }, + "version": { "type": "string", "description": "Desktop version" }, + "checkedAt": { "type": "string", "description": "ISO 8601 timestamp" } + }, + "required": ["online", "checkedAt"] + } + }, + { + "name": "browser.pending.flush", + "description": "Flush pending captures from browser queue to desktop", + "schema": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { "type": "string", "enum": ["page", "selection", "link"] }, + "payload": { "type": "object" }, + "queuedAt": { "type": "string" } + }, + "required": ["type", "payload"] + } + }, + "flushedAt": { "type": "string", "description": "ISO 8601 timestamp" } + }, + "required": ["items", "flushedAt"] + } + } + ] +} diff --git a/schemas/events/lifecycle.json b/schemas/events/lifecycle.json new file mode 100644 index 0000000..22856a8 --- /dev/null +++ b/schemas/events/lifecycle.json @@ -0,0 +1,145 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.mirv.top/verstak/verstak-sdk/schemas/events/lifecycle.json", + "title": "Verstak Lifecycle Events", + "description": "Event schemas for plugin and application lifecycle events", + "type": "object", + "properties": { + "events": { + "type": "array", + "items": { + "$ref": "#/$defs/LifecycleEvent" + } + } + }, + "$defs": { + "LifecycleEvent": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "description": { "type": "string" }, + "schema": { + "type": "object", + "properties": { + "type": { "type": "string", "const": "object" }, + "properties": { "type": "object" }, + "required": { "type": "array", "items": { "type": "string" } } + }, + "required": ["type", "properties", "required"] + } + }, + "required": ["name", "description", "schema"] + } + }, + "events": [ + { + "name": "plugin.enabled", + "description": "A plugin has been enabled", + "schema": { + "type": "object", + "properties": { + "pluginId": { "type": "string", "description": "Plugin identifier" }, + "version": { "type": "string", "description": "Plugin version" }, + "enabledAt": { "type": "string", "description": "ISO 8601 timestamp" } + }, + "required": ["pluginId", "enabledAt"] + } + }, + { + "name": "plugin.disabled", + "description": "A plugin has been disabled", + "schema": { + "type": "object", + "properties": { + "pluginId": { "type": "string", "description": "Plugin identifier" }, + "disabledAt": { "type": "string", "description": "ISO 8601 timestamp" } + }, + "required": ["pluginId", "disabledAt"] + } + }, + { + "name": "plugin.failed", + "description": "A plugin has failed to load or run", + "schema": { + "type": "object", + "properties": { + "pluginId": { "type": "string", "description": "Plugin identifier" }, + "error": { "type": "string", "description": "Error message" }, + "phase": { + "type": "string", + "description": "Lifecycle phase where failure occurred", + "enum": ["discovery", "validation", "dependency", "permissions", "sidecar", "frontend", "registration", "runtime"] + }, + "failedAt": { "type": "string", "description": "ISO 8601 timestamp" } + }, + "required": ["pluginId", "error", "phase", "failedAt"] + } + }, + { + "name": "plugin.installed", + "description": "A new plugin has been installed", + "schema": { + "type": "object", + "properties": { + "pluginId": { "type": "string" }, + "version": { "type": "string" }, + "source": { "type": "string", "enum": ["official", "local", "third-party"] }, + "installedAt": { "type": "string" } + }, + "required": ["pluginId", "version", "source", "installedAt"] + } + }, + { + "name": "plugin.uninstalled", + "description": "A plugin has been uninstalled", + "schema": { + "type": "object", + "properties": { + "pluginId": { "type": "string" }, + "preserveData": { "type": "boolean", "description": "Whether user chose to keep data" }, + "uninstalledAt": { "type": "string" } + }, + "required": ["pluginId", "uninstalledAt"] + } + }, + { + "name": "app.started", + "description": "Application has started", + "schema": { + "type": "object", + "properties": { + "version": { "type": "string", "description": "Application version" }, + "startedAt": { "type": "string", "description": "ISO 8601 timestamp" }, + "pluginsLoaded": { "type": "integer", "description": "Number of plugins loaded" }, + "pluginsFailed": { "type": "integer", "description": "Number of plugins failed" } + }, + "required": ["version", "startedAt"] + } + }, + { + "name": "app.shuttingdown", + "description": "Application is shutting down", + "schema": { + "type": "object", + "properties": { + "shutdownAt": { "type": "string", "description": "ISO 8601 timestamp" }, + "reason": { "type": "string", "description": "Shutdown reason" } + }, + "required": ["shutdownAt"] + } + }, + { + "name": "capability.changed", + "description": "Available capabilities have changed (plugin enabled/disabled)", + "schema": { + "type": "object", + "properties": { + "added": { "type": "array", "items": { "type": "string" }, "description": "Capabilities that became available" }, + "removed": { "type": "array", "items": { "type": "string" }, "description": "Capabilities that were removed" }, + "changedAt": { "type": "string", "description": "ISO 8601 timestamp" } + }, + "required": ["changedAt"] + } + } + ] +} diff --git a/schemas/events/vault.json b/schemas/events/vault.json new file mode 100644 index 0000000..d199971 --- /dev/null +++ b/schemas/events/vault.json @@ -0,0 +1,159 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.mirv.top/verstak/verstak-sdk/schemas/events/vault.json", + "title": "Verstak Vault Events", + "description": "Event schemas for vault and case lifecycle events", + "type": "object", + "properties": { + "events": { + "type": "array", + "items": { + "$ref": "#/$defs/VaultEvent" + } + } + }, + "$defs": { + "VaultEvent": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "description": { "type": "string" }, + "schema": { + "type": "object", + "properties": { + "type": { "type": "string", "const": "object" }, + "properties": { "type": "object" }, + "required": { "type": "array", "items": { "type": "string" } } + }, + "required": ["type", "properties", "required"] + } + }, + "required": ["name", "description", "schema"] + } + }, + "events": [ + { + "name": "vault.opened", + "description": "Vault has been opened and is ready", + "schema": { + "type": "object", + "properties": { + "path": { "type": "string", "description": "Absolute path to vault root" }, + "version": { "type": "string", "description": "Vault schema version" }, + "openedAt": { "type": "string", "description": "ISO 8601 timestamp" } + }, + "required": ["path", "openedAt"] + } + }, + { + "name": "vault.closed", + "description": "Vault is about to close", + "schema": { + "type": "object", + "properties": { + "closedAt": { "type": "string", "description": "ISO 8601 timestamp" }, + "clean": { "type": "boolean", "description": "Whether vault closed cleanly" } + }, + "required": ["closedAt"] + } + }, + { + "name": "case.selected", + "description": "A case has been selected in the navigation", + "schema": { + "type": "object", + "properties": { + "caseId": { "type": "string", "description": "Case identifier" }, + "casePath": { "type": "string", "description": "Case filesystem path" }, + "caseType": { "type": "string", "description": "Case type (client, project, etc.)" }, + "selectedAt": { "type": "string", "description": "ISO 8601 timestamp" } + }, + "required": ["caseId", "casePath"] + } + }, + { + "name": "file.added", + "description": "A file has been added to the vault", + "schema": { + "type": "object", + "properties": { + "path": { "type": "string", "description": "File path relative to vault" }, + "size": { "type": "integer", "description": "File size in bytes" }, + "mimeType": { "type": "string", "description": "File MIME type" }, + "caseId": { "type": "string", "description": "Associated case identifier" }, + "addedAt": { "type": "string", "description": "ISO 8601 timestamp" } + }, + "required": ["path", "addedAt"] + } + }, + { + "name": "file.changed", + "description": "A file in the vault has been modified", + "schema": { + "type": "object", + "properties": { + "path": { "type": "string", "description": "File path relative to vault" }, + "size": { "type": "integer", "description": "New file size" }, + "changedAt": { "type": "string", "description": "ISO 8601 timestamp" } + }, + "required": ["path", "changedAt"] + } + }, + { + "name": "file.deleted", + "description": "A file has been deleted from the vault", + "schema": { + "type": "object", + "properties": { + "path": { "type": "string", "description": "File path relative to vault" }, + "deletedAt": { "type": "string", "description": "ISO 8601 timestamp" } + }, + "required": ["path", "deletedAt"] + } + }, + { + "name": "note.saved", + "description": "A note has been saved", + "schema": { + "type": "object", + "properties": { + "noteId": { "type": "string", "description": "Note identifier" }, + "title": { "type": "string", "description": "Note title" }, + "path": { "type": "string", "description": "Note file path relative to vault" }, + "caseId": { "type": "string", "description": "Associated case identifier" }, + "savedAt": { "type": "string", "description": "ISO 8601 timestamp" } + }, + "required": ["noteId", "path", "savedAt"] + } + }, + { + "name": "activity.recorded", + "description": "An activity event has been recorded", + "schema": { + "type": "object", + "properties": { + "type": { "type": "string", "description": "Activity type (file.opened, note.saved, etc.)" }, + "caseId": { "type": "string", "description": "Associated case" }, + "details": { "type": "object", "description": "Activity-specific details" }, + "recordedAt": { "type": "string", "description": "ISO 8601 timestamp" } + }, + "required": ["type", "recordedAt"] + } + }, + { + "name": "case.created", + "description": "A new case has been created", + "schema": { + "type": "object", + "properties": { + "caseId": { "type": "string" }, + "casePath": { "type": "string" }, + "caseType": { "type": "string" }, + "template": { "type": "string", "description": "Template used, if any" }, + "createdAt": { "type": "string" } + }, + "required": ["caseId", "casePath", "createdAt"] + } + } + ] +} diff --git a/schemas/manifest.json b/schemas/manifest.json new file mode 100644 index 0000000..b3cd309 --- /dev/null +++ b/schemas/manifest.json @@ -0,0 +1,362 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.mirv.top/verstak/verstak-sdk/schemas/manifest.json", + "title": "Verstak Plugin Manifest", + "description": "Schema for Verstak plugin.json manifest files", + "type": "object", + "required": [ + "schemaVersion", + "id", + "name", + "version", + "apiVersion", + "provides", + "permissions" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "description": "Schema version for the manifest format", + "enum": [1] + }, + "id": { + "type": "string", + "description": "Unique plugin identifier (e.g. 'official.notes')", + "pattern": "^[a-zA-Z][a-zA-Z0-9.-]*$" + }, + "name": { + "type": "string", + "description": "Human-readable plugin name" + }, + "version": { + "type": "string", + "description": "Plugin semantic version", + "pattern": "^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9.]+)?$" + }, + "apiVersion": { + "type": "string", + "description": "Verstak Platform API version this plugin targets", + "pattern": "^\\d+$" + }, + "description": { + "type": "string", + "description": "Brief description of the plugin's purpose" + }, + "source": { + "type": "string", + "description": "Plugin source classification", + "enum": ["official", "local", "third-party"] + }, + "icon": { + "type": "string", + "description": "Path to plugin icon (relative to plugin root)" + }, + "provides": { + "type": "array", + "description": "Capabilities this plugin provides", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "requires": { + "type": "array", + "description": "Required capabilities — plugin cannot load without these", + "items": { + "type": "string" + }, + "default": [] + }, + "optionalRequires": { + "type": "array", + "description": "Optional capabilities — plugin degrades gracefully without these", + "items": { + "type": "string" + }, + "default": [] + }, + "permissions": { + "type": "array", + "description": "Runtime permissions requested by the plugin", + "items": { + "type": "string", + "enum": [ + "vault.read", + "vault.write", + "vault.watch", + "storage.namespace", + "storage.migrations", + "events.publish", + "events.subscribe", + "ui.register", + "commands.register", + "network.local", + "network.remote", + "process.spawn", + "secrets.read", + "secrets.write", + "sync.participate" + ] + }, + "minItems": 1 + }, + "frontend": { + "type": "object", + "description": "Frontend bundle configuration", + "properties": { + "entry": { + "type": "string", + "description": "Path to the frontend entry JavaScript file" + }, + "style": { + "type": "string", + "description": "Path to the frontend stylesheet" + } + }, + "required": ["entry"] + }, + "backend": { + "type": "object", + "description": "Backend sidecar configuration", + "properties": { + "type": { + "type": "string", + "enum": ["sidecar"], + "description": "Backend type (only 'sidecar' supported)" + }, + "entry": { + "type": "object", + "description": "Platform-specific binaries", + "patternProperties": { + "^(linux|windows|darwin)-(amd64|arm64)$": { + "type": "string", + "description": "Path to the binary for this platform" + } + }, + "minProperties": 1 + }, + "healthCheck": { + "type": "object", + "description": "Sidecar health check configuration", + "properties": { + "type": { + "type": "string", + "enum": ["rpc", "stdio", "tcp"], + "default": "rpc" + }, + "timeout": { + "type": "integer", + "description": "Health check timeout in milliseconds", + "default": 5000 + } + } + } + }, + "required": ["type", "entry"] + }, + "migrations": { + "type": "object", + "description": "Database migration configuration", + "properties": { + "path": { + "type": "string", + "description": "Path to migrations directory" + } + } + }, + "contributes": { + "type": "object", + "description": "UI and action contributions", + "properties": { + "views": { + "type": "array", + "items": { + "$ref": "#/$defs/ContributionView" + } + }, + "commands": { + "type": "array", + "items": { + "$ref": "#/$defs/ContributionCommand" + } + }, + "settingsPanels": { + "type": "array", + "items": { + "$ref": "#/$defs/ContributionSettingsPanel" + } + }, + "sidebarItems": { + "type": "array", + "items": { + "$ref": "#/$defs/ContributionSidebarItem" + } + }, + "fileActions": { + "type": "array", + "items": { + "$ref": "#/$defs/ContributionAction" + } + }, + "noteActions": { + "type": "array", + "items": { + "$ref": "#/$defs/ContributionAction" + } + }, + "contextMenuEntries": { + "type": "array", + "items": { + "$ref": "#/$defs/ContributionContextMenuEntry" + } + }, + "searchProviders": { + "type": "array", + "items": { + "$ref": "#/$defs/ContributionSearchProvider" + } + }, + "activityProviders": { + "type": "array", + "items": { + "$ref": "#/$defs/ContributionActivityProvider" + } + }, + "statusBarItems": { + "type": "array", + "items": { + "$ref": "#/$defs/ContributionStatusBarItem" + } + } + } + }, + "sync": { + "type": "object", + "description": "Sync configuration — what data this plugin allows syncing", + "properties": { + "namespaces": { + "type": "array", + "description": "Storage namespaces allowed for sync", + "items": { + "type": "string" + } + }, + "participate": { + "type": "boolean", + "description": "Whether this plugin participates in sync at all", + "default": false + } + } + } + }, + "$defs": { + "ContributionView": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "title": { "type": "string" }, + "icon": { "type": "string" }, + "component": { "type": "string" } + }, + "required": ["id", "component"] + }, + "ContributionCommand": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "title": { "type": "string" }, + "keybinding": { "type": "string" }, + "icon": { "type": "string" }, + "handler": { "type": "string" } + }, + "required": ["id", "title"] + }, + "ContributionSettingsPanel": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "title": { "type": "string" }, + "component": { "type": "string" }, + "icon": { "type": "string" } + }, + "required": ["id", "title", "component"] + }, + "ContributionSidebarItem": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "title": { "type": "string" }, + "icon": { "type": "string" }, + "view": { "type": "string" }, + "position": { + "type": "integer", + "description": "Sort order (lower = higher)" + } + }, + "required": ["id", "title", "view"] + }, + "ContributionAction": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "label": { "type": "string" }, + "icon": { "type": "string" }, + "capability": { + "type": "string", + "description": "Required capability for this action to appear" + }, + "handler": { "type": "string" } + }, + "required": ["id", "label"] + }, + "ContributionContextMenuEntry": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "label": { "type": "string" }, + "context": { + "type": "string", + "enum": ["file", "note", "case", "folder"] + }, + "group": { "type": "string" }, + "capability": { "type": "string" }, + "handler": { "type": "string" } + }, + "required": ["id", "label", "context"] + }, + "ContributionSearchProvider": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "label": { "type": "string" }, + "handler": { "type": "string" } + }, + "required": ["id", "label", "handler"] + }, + "ContributionActivityProvider": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "events": { + "type": "array", + "items": { "type": "string" } + }, + "handler": { "type": "string" } + }, + "required": ["id", "handler"] + }, + "ContributionStatusBarItem": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "label": { "type": "string" }, + "position": { + "type": "string", + "enum": ["left", "right"] + }, + "handler": { "type": "string" } + }, + "required": ["id", "label"] + } + } +} diff --git a/schemas/permissions.json b/schemas/permissions.json new file mode 100644 index 0000000..f60a032 --- /dev/null +++ b/schemas/permissions.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.mirv.top/verstak/verstak-sdk/schemas/permissions.json", + "title": "Verstak Permissions Registry", + "description": "Known runtime permissions and their safety levels", + "type": "object", + "properties": { + "permissions": { + "type": "array", + "items": { + "$ref": "#/$defs/PermissionEntry" + } + } + }, + "$defs": { + "PermissionEntry": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "description": { "type": "string" }, + "dangerous": { + "type": "boolean", + "description": "If true, user must explicitly approve before enabling plugin" + } + }, + "required": ["name", "description", "dangerous"] + } + }, + "permissions": [ + { "name": "vault.read", "description": "Read vault files and metadata", "dangerous": false }, + { "name": "vault.write", "description": "Write vault files and metadata", "dangerous": true }, + { "name": "vault.watch", "description": "Watch vault file changes", "dangerous": false }, + { "name": "storage.namespace", "description": "Read/write plugin's own storage namespace", "dangerous": false }, + { "name": "storage.migrations", "description": "Run database migrations in plugin namespace", "dangerous": false }, + { "name": "events.publish", "description": "Publish events to the event bus", "dangerous": false }, + { "name": "events.subscribe", "description": "Subscribe to events on the event bus", "dangerous": false }, + { "name": "ui.register", "description": "Register UI components and contributions", "dangerous": false }, + { "name": "commands.register", "description": "Register command palette commands", "dangerous": false }, + { "name": "network.local", "description": "Connect to localhost network services", "dangerous": false }, + { "name": "network.remote", "description": "Connect to remote network services", "dangerous": true }, + { "name": "process.spawn", "description": "Spawn external processes", "dangerous": true }, + { "name": "secrets.read", "description": "Read secrets from the secret store", "dangerous": true }, + { "name": "secrets.write", "description": "Write secrets to the secret store", "dangerous": true }, + { "name": "sync.participate", "description": "Participate in vault sync", "dangerous": true } + ] +} diff --git a/schemas/sync.json b/schemas/sync.json new file mode 100644 index 0000000..b792e55 --- /dev/null +++ b/schemas/sync.json @@ -0,0 +1,177 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.mirv.top/verstak/verstak-sdk/schemas/sync.json", + "title": "Verstak Sync Operations", + "description": "Sync operation schemas for vault synchronization between devices", + "type": "object", + "properties": { + "SyncOperation": { + "$ref": "#/$defs/SyncOperation" + }, + "SyncBatch": { + "$ref": "#/$defs/SyncBatch" + }, + "SyncManifest": { + "$ref": "#/$defs/SyncManifest" + } + }, + "$defs": { + "SyncOperation": { + "type": "object", + "description": "A single sync operation entry", + "required": ["op", "id", "timestamp"], + "properties": { + "op": { + "type": "string", + "description": "Operation type", + "enum": ["add", "modify", "delete", "rename"] + }, + "id": { + "type": "string", + "description": "Unique operation identifier (UUID)" + }, + "timestamp": { + "type": "string", + "description": "ISO 8601 timestamp of the operation" + }, + "deviceId": { + "type": "string", + "description": "Originating device identifier" + }, + "entityType": { + "type": "string", + "description": "Entity type being synced", + "enum": ["file", "note", "plugin_state", "vault_meta"] + }, + "entityPath": { + "type": "string", + "description": "Entity path relative to vault root" + }, + "hash": { + "type": "string", + "description": "Content hash (SHA-256) for content verification" + }, + "size": { + "type": "integer", + "description": "File size in bytes" + }, + "mimeType": { + "type": "string", + "description": "MIME type of the entity" + }, + "pluginNamespace": { + "type": "string", + "description": "Plugin namespace, if syncing plugin state" + }, + "oldPath": { + "type": "string", + "description": "Previous path for rename operations" + }, + "metadata": { + "type": "object", + "description": "Arbitrary metadata key-value pairs", + "additionalProperties": { "type": "string" } + } + } + }, + "SyncBatch": { + "type": "object", + "description": "A batch of sync operations sent between devices", + "required": ["batchId", "deviceId", "operations", "timestamp"], + "properties": { + "batchId": { + "type": "string", + "description": "Unique batch identifier (UUID)" + }, + "deviceId": { + "type": "string", + "description": "Originating device identifier" + }, + "operations": { + "type": "array", + "description": "List of sync operations in this batch", + "items": { + "$ref": "#/$defs/SyncOperation" + } + }, + "timestamp": { + "type": "string", + "description": "ISO 8601 timestamp of batch creation" + }, + "lastSyncTimestamp": { + "type": "string", + "description": "Timestamp of the last successful sync from this device" + }, + "sequence": { + "type": "integer", + "description": "Sequence number for ordering batches" + } + } + }, + "SyncManifest": { + "type": "object", + "description": "Vault sync manifest for initial reconciliation", + "required": ["deviceId", "entries"], + "properties": { + "deviceId": { "type": "string" }, + "entries": { + "type": "array", + "items": { + "type": "object", + "required": ["path", "hash", "updatedAt"], + "properties": { + "path": { "type": "string" }, + "hash": { "type": "string" }, + "size": { "type": "integer" }, + "updatedAt": { "type": "string" }, + "deleted": { "type": "boolean", "default": false } + } + } + } + } + }, + "Conflict": { + "type": "object", + "description": "Conflict record when two devices modify the same entity", + "required": ["entityPath", "localHash", "remoteHash", "localTimestamp", "remoteTimestamp"], + "properties": { + "entityPath": { "type": "string" }, + "localHash": { "type": "string" }, + "remoteHash": { "type": "string" }, + "localTimestamp": { "type": "string" }, + "remoteTimestamp": { "type": "string" }, + "resolution": { + "type": "string", + "enum": ["local_wins", "remote_wins", "manual"], + "description": "How the conflict was resolved" + }, + "resolvedAt": { "type": "string" } + } + }, + "PairingRequest": { + "type": "object", + "description": "Device pairing request payload", + "required": ["deviceName", "deviceType", "publicKey"], + "properties": { + "deviceName": { "type": "string" }, + "deviceType": { + "type": "string", + "enum": ["desktop", "mobile"] + }, + "publicKey": { "type": "string", "description": "Device public key for auth" }, + "clientVersion": { "type": "string" } + } + }, + "PairingResponse": { + "type": "object", + "description": "Device pairing response", + "required": ["deviceId", "token", "pairedAt"], + "properties": { + "deviceId": { "type": "string" }, + "token": { "type": "string", "description": "Auth token for this device" }, + "pairedAt": { "type": "string" }, + "serverVersion": { "type": "string" } + } + } + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..5dea4f3 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,11 @@ +// Verstak Plugin SDK — Public API + +export * from './types'; +export { VerstakPluginAPI, createPluginAPI } from './plugin-api'; +export { RPCServer, RPCClient } from './rpc'; +export { + createTestManifest, + createTestPluginState, + createMockPluginAPI, + validateManifest, +} from './test-utils'; diff --git a/src/plugin-api.ts b/src/plugin-api.ts new file mode 100644 index 0000000..4af96ea --- /dev/null +++ b/src/plugin-api.ts @@ -0,0 +1,166 @@ +// Verstak Plugin SDK — VerstakPluginAPI +// The official runtime API available to all plugins in the frontend context. + +import type { PluginSettings } from './types'; + +/** + * VerstakPluginAPI — единственный способ для frontend плагина + * общаться с core платформы. + * + * Экземпляр API передаётся плагину при активации через глобальную + * переменную `window.__VERSTAK_PLUGIN_API__`. + */ +export class VerstakPluginAPI { + private pluginId: string; + private capabilities = new Set(); + + constructor(pluginId: string) { + this.pluginId = pluginId; + } + + /** + * Инициализация API — вызывается core после загрузки frontend bundle. + * @internal + */ + _init(capabilities: string[]): void { + this.capabilities = new Set(capabilities); + } + + // ─── View Registration ───────────────────────────────────── + + /** + * Зарегистрировать view для отображения в UI Shell. + */ + registerView(id: string, component: unknown): void { + this._postMessage('register.view', { id, component }); + } + + /** + * Зарегистрировать панель настроек плагина. + */ + registerSettingsPanel(id: string, title: string, component: unknown): void { + this._postMessage('register.settingsPanel', { id, title, component }); + } + + /** + * Зарегистрировать команду для command palette. + */ + registerCommand(id: string, title: string, handler: () => void, keybinding?: string): void { + this._postMessage('register.command', { id, title, keybinding, handler: handler.toString() }); + } + + /** + * Зарегистрировать действия для файлов. + */ + registerFileAction(id: string, label: string, handler: (filePath: string) => void, capability?: string): void { + this._postMessage('register.fileAction', { id, label, handler: handler.toString(), capability }); + } + + /** + * Зарегистрировать действия для заметок. + */ + registerNoteAction(id: string, label: string, handler: (noteId: string) => void, capability?: string): void { + this._postMessage('register.noteAction', { id, label, handler: handler.toString(), capability }); + } + + /** + * Зарегистрировать provider поиска. + */ + registerSearchProvider(id: string, label: string, handler: (query: string) => unknown[]): void { + this._postMessage('register.searchProvider', { id, label, handler: handler.toString() }); + } + + // ─── Capabilities ────────────────────────────────────────── + + /** + * Проверить, доступна ли capability. + */ + hasCapability(name: string): boolean { + return this.capabilities.has(name); + } + + /** + * Получить список всех доступных capabilities. + */ + getAvailableCapabilities(): string[] { + return Array.from(this.capabilities); + } + + // ─── Backend Communication ───────────────────────────────── + + /** + * Вызвать backend метод плагина через RPC. + */ + async callBackend(method: string, args: unknown[] = []): Promise { + return this._rpcCall(method, args); + } + + // ─── Settings ────────────────────────────────────────────── + + /** + * Прочитать настройки плагина. + */ + async readSettings(): Promise { + const result = await this._rpcCall('readSettings', []); + return result as PluginSettings; + } + + /** + * Записать настройки плагина. + */ + async writeSettings(settings: PluginSettings): Promise { + await this._rpcCall('writeSettings', [settings]); + } + + // ─── Event Bus ───────────────────────────────────────────── + + /** + * Подписаться на событие event bus. + */ + subscribe(event: string, handler: (payload: unknown) => void): void { + this._postMessage('subscribe', { event, handler: handler.toString() }); + } + + /** + * Опубликовать событие в event bus. + */ + publish(event: string, payload: unknown): void { + this._postMessage('publish', { event, payload }); + } + + // ─── Internal ────────────────────────────────────────────── + + private _postMessage(type: string, data: Record): void { + window.dispatchEvent(new CustomEvent('verstak:plugin', { + detail: { pluginId: this.pluginId, type, data } + })); + } + + private async _rpcCall(method: string, args: unknown[]): Promise { + return new Promise((resolve, reject) => { + const callId = `${this.pluginId}:${Date.now()}:${Math.random()}`; + const handler = (event: CustomEvent) => { + if (event.detail.callId === callId) { + window.removeEventListener('verstak:rpc:response', handler as EventListener); + if (event.detail.error) { + reject(new Error(event.detail.error)); + } else { + resolve(event.detail.result); + } + } + }; + window.addEventListener('verstak:rpc:response', handler as EventListener); + this._postMessage('rpc', { callId, method, args }); + }); + } +} + +/** + * Создать экземпляр VerstakPluginAPI. + * Core вызывает эту функцию после загрузки frontend bundle, + * передавая pluginId и список доступных capabilities. + */ +export function createPluginAPI(pluginId: string): VerstakPluginAPI { + const api = new VerstakPluginAPI(pluginId); + return api; +} diff --git a/src/rpc.ts b/src/rpc.ts new file mode 100644 index 0000000..9a8a6c5 --- /dev/null +++ b/src/rpc.ts @@ -0,0 +1,112 @@ +// Verstak Plugin SDK — RPC Client for Sidecar Communication + +export type RPCTransport = 'stdio' | 'tcp'; + +export interface RPCRequest { + jsonrpc: '2.0'; + id: string; + method: string; + params: unknown[]; +} + +export interface RPCResponse { + jsonrpc: '2.0'; + id: string; + result?: unknown; + error?: RPCError; +} + +export interface RPCError { + code: number; + message: string; + data?: unknown; +} + +/** + * RPC клиент для общения backend sidecar с core платформы. + * Использует JSON-RPC 2.0 протокол. + */ +export class RPCServer { + private handlers = new Map Promise>(); + + constructor() { + } + + /** + * Зарегистрировать обработчик RPC метода. + */ + registerMethod(method: string, handler: (params: unknown[]) => Promise): void { + this.handlers.set(method, handler); + } + + /** + * Обработать входящий RPC запрос. + */ + async handleRequest(request: RPCRequest): Promise { + const handler = this.handlers.get(request.method); + if (!handler) { + return { + jsonrpc: '2.0', + id: request.id, + error: { code: -32601, message: `Method not found: ${request.method}` } + }; + } + + try { + const result = await handler(request.params); + return { jsonrpc: '2.0', id: request.id, result }; + } catch (err) { + return { + jsonrpc: '2.0', + id: request.id, + error: { code: -32000, message: err instanceof Error ? err.message : String(err) } + }; + } + } + + /** + * Создать RPC запрос. + */ + createRequest(method: string, params: unknown[] = []): RPCRequest { + return { + jsonrpc: '2.0', + id: `${Date.now()}:${Math.random()}`, + method, + params + }; + } + + /** + * Разобрать RPC ответ. + */ + parseResponse(data: string): RPCResponse { + return JSON.parse(data) as RPCResponse; + } +} + +/** + * RPC клиент (для core, вызывает методы sidecar). + */ +export class RPCClient { + private requestId = 0; + + /** + * Создать JSON-RPC запрос. + */ + call(method: string, params: unknown[] = []): string { + const request: RPCRequest = { + jsonrpc: '2.0', + id: `${++this.requestId}`, + method, + params + }; + return JSON.stringify(request) + '\n'; + } + + /** + * Разобрать ответ. + */ + parseResponse(data: string): RPCResponse { + return JSON.parse(data.trim()) as RPCResponse; + } +} diff --git a/src/test-utils.ts b/src/test-utils.ts new file mode 100644 index 0000000..b91a475 --- /dev/null +++ b/src/test-utils.ts @@ -0,0 +1,101 @@ +// Verstak Plugin SDK — Test Utilities + +import type { PluginManifest, PluginState } from './types'; + +/** + * Создать тестовый manifest для unit-тестов. + */ +export function createTestManifest(overrides?: Partial): PluginManifest { + return { + schemaVersion: 1, + id: 'test.plugin', + name: 'Test Plugin', + version: '0.1.0', + apiVersion: '1', + description: 'A test plugin for platform verification', + source: 'local', + provides: ['test.capability'], + requires: [], + optionalRequires: [], + permissions: ['events.publish', 'events.subscribe'], + ...overrides + }; +} + +/** + * Создать тестовое состояние плагина. + */ +export function createTestPluginState(overrides?: Partial): PluginState { + return { + id: 'test.plugin', + manifest: createTestManifest(), + status: 'loaded', + enabled: true, + loadedAt: new Date().toISOString(), + ...overrides + }; +} + +/** + * Создать заглушку VerstakPluginAPI для тестов. + */ +export function createMockPluginAPI(): { + registerView: ReturnType; + registerCommand: ReturnType; + registerSettingsPanel: ReturnType; + hasCapability: ReturnType; + callBackend: ReturnType; + subscribe: ReturnType; + publish: ReturnType; +} { + return { + registerView: vi.fn(), + registerCommand: vi.fn(), + registerSettingsPanel: vi.fn(), + hasCapability: vi.fn().mockReturnValue(false), + callBackend: vi.fn().mockResolvedValue(undefined), + subscribe: vi.fn(), + publish: vi.fn(), + }; +} + +/** + * Валидатор plugin manifest. + */ +export function validateManifest(manifest: unknown): { valid: boolean; errors: string[] } { + const errors: string[] = []; + + if (!manifest || typeof manifest !== 'object') { + return { valid: false, errors: ['Manifest must be an object'] }; + } + + const m = manifest as Record; + + if (m.schemaVersion !== 1) { + errors.push(`schemaVersion must be 1, got ${m.schemaVersion}`); + } + if (typeof m.id !== 'string' || !m.id) { + errors.push('id must be a non-empty string'); + } + if (typeof m.name !== 'string' || !m.name) { + errors.push('name must be a non-empty string'); + } + if (typeof m.version !== 'string' || !/^\d+\.\d+\.\d+/.test(m.version as string)) { + errors.push('version must be a valid semver (e.g. 0.1.0)'); + } + if (typeof m.apiVersion !== 'string' || !m.apiVersion) { + errors.push('apiVersion must be a non-empty string'); + } + if (!Array.isArray(m.provides) || m.provides.length === 0) { + errors.push('provides must be a non-empty array'); + } + if (!Array.isArray(m.permissions) || m.permissions.length === 0) { + errors.push('permissions must be a non-empty array'); + } + + return { valid: errors.length === 0, errors }; +} + +// Re-export vi for test files +import { vi } from 'vitest'; +export { vi }; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..2e62b45 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,345 @@ +// Verstak Plugin SDK — Core TypeScript Types + +// ─── Manifest ──────────────────────────────────────────────── + +export type PluginSource = 'official' | 'local' | 'third-party'; + +export interface PluginManifest { + schemaVersion: 1; + id: string; + name: string; + version: string; + apiVersion: string; + description?: string; + source?: PluginSource; + icon?: string; + provides: string[]; + requires?: string[]; + optionalRequires?: string[]; + permissions: Permission[]; + frontend?: FrontendConfig; + backend?: BackendConfig; + migrations?: MigrationConfig; + contributes?: ContributionPoints; + sync?: SyncConfig; +} + +export interface FrontendConfig { + entry: string; + style?: string; +} + +export interface BackendConfig { + type: 'sidecar'; + entry: Record; + healthCheck?: HealthCheckConfig; +} + +export interface HealthCheckConfig { + type?: 'rpc' | 'stdio' | 'tcp'; + timeout?: number; +} + +export interface MigrationConfig { + path: string; +} + +export interface SyncConfig { + namespaces?: string[]; + participate?: boolean; +} + +// ─── Capabilities ──────────────────────────────────────────── + +export type CapabilityName = string; + +export interface CapabilityEntry { + name: CapabilityName; + description: string; + status: 'stable' | 'draft' | 'deprecated'; +} + +// ─── Permissions ───────────────────────────────────────────── + +export type Permission = + | 'vault.read' + | 'vault.write' + | 'vault.watch' + | 'storage.namespace' + | 'storage.migrations' + | 'events.publish' + | 'events.subscribe' + | 'ui.register' + | 'commands.register' + | 'network.local' + | 'network.remote' + | 'process.spawn' + | 'secrets.read' + | 'secrets.write' + | 'sync.participate'; + +export interface PermissionEntry { + name: Permission; + description: string; + dangerous: boolean; +} + +// ─── Contribution Points ───────────────────────────────────── + +export interface ContributionPoints { + views?: ContributionView[]; + commands?: ContributionCommand[]; + settingsPanels?: ContributionSettingsPanel[]; + sidebarItems?: ContributionSidebarItem[]; + fileActions?: ContributionAction[]; + noteActions?: ContributionAction[]; + contextMenuEntries?: ContributionContextMenuEntry[]; + searchProviders?: ContributionSearchProvider[]; + activityProviders?: ContributionActivityProvider[]; + statusBarItems?: ContributionStatusBarItem[]; +} + +export interface ContributionView { + id: string; + title: string; + icon?: string; + component: string; +} + +export interface ContributionCommand { + id: string; + title: string; + keybinding?: string; + icon?: string; + handler?: string; +} + +export interface ContributionSettingsPanel { + id: string; + title: string; + component: string; + icon?: string; +} + +export interface ContributionSidebarItem { + id: string; + title: string; + icon?: string; + view: string; + position?: number; +} + +export interface ContributionAction { + id: string; + label: string; + icon?: string; + capability?: CapabilityName; + handler?: string; +} + +export interface ContributionContextMenuEntry { + id: string; + label: string; + context: 'file' | 'note' | 'case' | 'folder'; + group?: string; + capability?: CapabilityName; + handler?: string; +} + +export interface ContributionSearchProvider { + id: string; + label: string; + handler: string; +} + +export interface ContributionActivityProvider { + id: string; + events?: string[]; + handler: string; +} + +export interface ContributionStatusBarItem { + id: string; + label: string; + position?: 'left' | 'right'; + handler?: string; +} + +// ─── Plugin State ──────────────────────────────────────────── + +export type PluginStatus = + | 'discovered' + | 'disabled' + | 'loading' + | 'loaded' + | 'degraded' + | 'failed' + | 'incompatible' + | 'missing-required-capability'; + +export interface PluginState { + id: string; + manifest: PluginManifest; + status: PluginStatus; + error?: string; + enabled: boolean; + loadedAt?: string; +} + +// ─── Events ────────────────────────────────────────────────── + +export interface VerstakEvent { + name: string; + timestamp: string; + payload: Record; +} + +// Browser events +export interface BrowserCapturePageEvent extends VerstakEvent { + name: 'browser.capture.page'; + payload: { + url: string; + title: string; + html?: string; + text?: string; + capturedAt: string; + domain?: string; + }; +} + +export interface BrowserCaptureSelectionEvent extends VerstakEvent { + name: 'browser.capture.selection'; + payload: { + url: string; + title: string; + text: string; + capturedAt: string; + domain?: string; + }; +} + +export interface BrowserCaptureLinkEvent extends VerstakEvent { + name: 'browser.capture.link'; + payload: { + url: string; + title?: string; + capturedAt: string; + domain?: string; + }; +} + +// Vault events +export interface VaultOpenedEvent extends VerstakEvent { + name: 'vault.opened'; + payload: { + path: string; + version?: string; + openedAt: string; + }; +} + +export interface CaseSelectedEvent extends VerstakEvent { + name: 'case.selected'; + payload: { + caseId: string; + casePath: string; + caseType?: string; + selectedAt: string; + }; +} + +export interface FileChangedEvent extends VerstakEvent { + name: 'file.changed'; + payload: { + path: string; + size?: number; + changedAt: string; + }; +} + +export interface NoteSavedEvent extends VerstakEvent { + name: 'note.saved'; + payload: { + noteId: string; + title?: string; + path: string; + caseId?: string; + savedAt: string; + }; +} + +// Lifecycle events +export interface PluginEnabledEvent extends VerstakEvent { + name: 'plugin.enabled'; + payload: { + pluginId: string; + version?: string; + enabledAt: string; + }; +} + +export interface PluginDisabledEvent extends VerstakEvent { + name: 'plugin.disabled'; + payload: { + pluginId: string; + disabledAt: string; + }; +} + +// ─── Sync Types ────────────────────────────────────────────── + +export type SyncOpType = 'add' | 'modify' | 'delete' | 'rename'; +export type SyncEntityType = 'file' | 'note' | 'plugin_state' | 'vault_meta'; + +export interface SyncOperation { + op: SyncOpType; + id: string; + timestamp: string; + deviceId?: string; + entityType?: SyncEntityType; + entityPath?: string; + hash?: string; + size?: number; + mimeType?: string; + pluginNamespace?: string; + oldPath?: string; + metadata?: Record; +} + +export interface SyncBatch { + batchId: string; + deviceId: string; + operations: SyncOperation[]; + timestamp: string; + lastSyncTimestamp?: string; + sequence?: number; +} + +export interface SyncManifestEntry { + path: string; + hash: string; + size?: number; + updatedAt: string; + deleted?: boolean; +} + +export interface SyncManifest { + deviceId: string; + entries: SyncManifestEntry[]; +} + +export interface Conflict { + entityPath: string; + localHash: string; + remoteHash: string; + localTimestamp: string; + remoteTimestamp: string; + resolution?: 'local_wins' | 'remote_wins' | 'manual'; + resolvedAt?: string; +} + +// ─── Settings ──────────────────────────────────────────────── + +export interface PluginSettings { + [key: string]: unknown; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..95dd596 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "dist", + "rootDir": "src", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "exactOptionalPropertyTypes": false, + "skipLibCheck": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "**/*.test.ts"] +}