@majkapp/plugin-kit 3.5.5 โ†’ 3.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,6 +15,7 @@ Build type-safe, production-ready MAJK plugins with excellent developer experien
15
15
  ๐Ÿ”ง **Tool Management** - Declare tools with schema validation
16
16
  ๐Ÿ’พ **Storage Integration** - Direct access to plugin storage
17
17
  ๐Ÿ“ก **Event Bus** - Subscribe to system events
18
+ ๐Ÿ”— **RPC Callbacks** - Pass callbacks across plugin boundaries with automatic serialization
18
19
  ๐Ÿงน **Auto Cleanup** - Managed lifecycle with automatic resource cleanup
19
20
  โค๏ธ **Health Checks** - Built-in health monitoring
20
21
 
@@ -446,6 +447,90 @@ if (imageProviders.length > 0) {
446
447
  - Integrate multiple AI providers
447
448
  - Generate content, analyze data, summarize text
448
449
 
450
+ ### Plugin Management API
451
+
452
+ Generate authenticated URLs for opening plugin UIs in external browsers or creating cross-plugin links:
453
+
454
+ ```typescript
455
+ // Simple convenience method - get URL for another plugin's screen
456
+ const url = await ctx.majk.plugins.getExternalUrl(
457
+ '@analytics/dashboard', // Plugin ID (package name format)
458
+ 'main', // Screen ID (optional)
459
+ '#/overview' // Hash fragment (optional)
460
+ );
461
+
462
+ // Opens: http://localhost:9000/plugins/analytics/dashboard/ui/main?token=abc123#/overview
463
+ await ctx.shell?.openExternal(url);
464
+
465
+ // Advanced: Full control over URL generation
466
+ const customUrl = await ctx.majk.plugins.getPluginScreenUrl('@myorg/plugin', {
467
+ screenId: 'settings', // Specific screen
468
+ theme: 'dark', // Theme preference
469
+ hash: '#/advanced', // Client-side route
470
+ queryParams: { // Custom query parameters
471
+ view: 'compact',
472
+ filter: 'active'
473
+ }
474
+ });
475
+
476
+ // Direct URL with custom path (when you know the exact endpoint)
477
+ const apiUrl = await ctx.majk.plugins.getPluginExternalUrl('plugin-id', {
478
+ path: '/api/export', // Custom path within plugin
479
+ queryParams: { format: 'json' }
480
+ });
481
+ ```
482
+
483
+ **API Methods:**
484
+
485
+ - **`getExternalUrl(pluginId, screenId?, hash?)`** - Simple convenience method for getting screen URLs
486
+ - **`getPluginScreenUrl(pluginId, options)`** - Screen-aware URL generation with full options
487
+ - **`getPluginExternalUrl(pluginId, options)`** - Low-level URL generation for custom paths
488
+
489
+ **URL Format:**
490
+ ```
491
+ http://localhost:{port}/plugins/{org}/{plugin}/ui/{screen}?token={token}&theme={theme}#{hash}
492
+ ```
493
+
494
+ **Security:**
495
+ - URLs include one-time authentication tokens (60s TTL)
496
+ - After expiration, users are redirected to login if required
497
+ - Tokens are automatically validated by the plugin server
498
+
499
+ **Use Cases:**
500
+ - Open plugin screens in external browser windows
501
+ - Create shareable links to plugin functionality
502
+ - Cross-plugin navigation and deep linking
503
+ - Programmatic browser-based testing
504
+ - Integration with external tools and workflows
505
+
506
+ **Example - Cross-Plugin Integration:**
507
+
508
+ ```typescript
509
+ .apiRoute({
510
+ method: 'POST',
511
+ path: '/api/generate-report',
512
+ name: 'Generate Report',
513
+ description: 'Generates analytics report. Creates report and returns link to view.',
514
+ handler: async (req, res, { majk }) => {
515
+ // Generate report data
516
+ const reportId = await generateReport(req.body);
517
+
518
+ // Create link to analytics plugin's viewer
519
+ const viewerUrl = await majk.plugins.getExternalUrl(
520
+ '@analytics/viewer',
521
+ 'report',
522
+ `#/report/${reportId}`
523
+ );
524
+
525
+ return {
526
+ success: true,
527
+ reportId,
528
+ viewUrl: viewerUrl // User can click to view in analytics plugin
529
+ };
530
+ }
531
+ })
532
+ ```
533
+
449
534
  ### PluginStorage
450
535
 
451
536
  Simple key-value storage scoped to your plugin:
@@ -646,6 +731,69 @@ See `example.ts` for a comprehensive example showing:
646
731
  - Storage usage
647
732
  - Health checks
648
733
 
734
+ ## RPC & Callbacks
735
+
736
+ Plugins can communicate with each other using RPC services and pass callbacks across plugin boundaries:
737
+
738
+ ### Basic RPC Service
739
+
740
+ ```typescript
741
+ // Plugin A: Register a service
742
+ .onLoad(async (ctx) => {
743
+ await ctx.rpc.registerService('fileProcessor', {
744
+ async processFile(path: string, onProgress: (percent: number) => void): Promise<string> {
745
+ await onProgress(0);
746
+ await onProgress(50);
747
+ await onProgress(100);
748
+ return `Processed: ${path}`;
749
+ }
750
+ });
751
+ })
752
+ ```
753
+
754
+ ### Consuming with Callbacks
755
+
756
+ ```typescript
757
+ // Plugin B: Use the service with callbacks
758
+ .onLoad(async (ctx) => {
759
+ const processor = await ctx.rpc.createProxy<{
760
+ processFile(path: string, onProgress: (p: number) => void): Promise<string>
761
+ }>('fileProcessor');
762
+
763
+ // Pass callback - it just works!
764
+ const result = await processor.processFile('/file.txt', (progress) => {
765
+ console.log(`Progress: ${progress}%`);
766
+ });
767
+ })
768
+ ```
769
+
770
+ ### Explicit Callbacks with Cleanup
771
+
772
+ For long-lived callbacks (subscriptions, event listeners), use `createCallback()`:
773
+
774
+ ```typescript
775
+ // Auto-cleanup after 10 calls
776
+ const callback = await ctx.rpc.createCallback!(
777
+ (event) => console.log('Event:', event),
778
+ { maxCalls: 10 }
779
+ );
780
+ await eventService.subscribe(callback);
781
+
782
+ // Auto-cleanup after 5 seconds
783
+ const callback = await ctx.rpc.createCallback!(
784
+ (data) => console.log('Data:', data),
785
+ { timeout: 5000 }
786
+ );
787
+ await dataStream.subscribe(callback);
788
+
789
+ // Manual cleanup
790
+ const callback = await ctx.rpc.createCallback!((msg) => console.log(msg));
791
+ // ... later
792
+ ctx.rpc.cleanupCallback!(callback);
793
+ ```
794
+
795
+ See [docs/RPC_CALLBACKS.md](docs/RPC_CALLBACKS.md) for comprehensive documentation, patterns, and best practices.
796
+
649
797
  ## TypeScript
650
798
 
651
799
  Full TypeScript support with:
@@ -58,6 +58,12 @@ cli.addCommand('--services',
58
58
  { description: 'Organize functions by service' }
59
59
  );
60
60
 
61
+ // RPC and callbacks
62
+ cli.addCommand('--rpc',
63
+ cli.createFullDocCommand('RPC_CALLBACKS.md'),
64
+ { description: 'Inter-plugin RPC with callbacks' }
65
+ );
66
+
61
67
  // Teammates
62
68
  cli.addCommand('--teammates',
63
69
  cli.createFullDocCommand('TEAMMATES.md'),
@@ -39,6 +39,7 @@ const path = __importStar(require("path"));
39
39
  const fs = __importStar(require("fs"));
40
40
  const child_process_1 = require("child_process");
41
41
  const generator_1 = require("./generator");
42
+ const extract_command_1 = require("./extract-command");
42
43
  // Check args BEFORE commander parses to intercept for documentation
43
44
  const args = process.argv.slice(2);
44
45
  // If -h or --help, let commander handle it normally (show generator help)
@@ -163,6 +164,26 @@ commander_1.program
163
164
  await generate();
164
165
  }
165
166
  });
167
+ // Extract capabilities command
168
+ commander_1.program
169
+ .command('extract')
170
+ .description('Extract plugin capabilities to .majk/capabilities.json for market indexing')
171
+ .option('-e, --entry <path>', 'Path to the compiled plugin entry file', './dist/index.js')
172
+ .option('-o, --output <path>', 'Output directory for capabilities.json', '.majk')
173
+ .option('-s, --silent', 'Suppress output messages', false)
174
+ .action(async (options) => {
175
+ try {
176
+ await (0, extract_command_1.extractCapabilities)({
177
+ entry: options.entry,
178
+ output: options.output,
179
+ silent: options.silent
180
+ });
181
+ }
182
+ catch (error) {
183
+ console.error(error.message);
184
+ process.exit(1);
185
+ }
186
+ });
166
187
  // Add documentation options to help
167
188
  commander_1.program
168
189
  .option('--llm', 'Show plugin development documentation (quick start)')
@@ -198,6 +219,8 @@ async function extractMetadata(entryPath) {
198
219
  }
199
220
  const content = fs.readFileSync(tempFile, 'utf-8');
200
221
  const metadata = JSON.parse(content);
222
+ // NEW: Also save capabilities.json for market indexing
223
+ await saveCapabilitiesForMarket(metadata);
201
224
  // Clean up temp file
202
225
  fs.unlinkSync(tempFile);
203
226
  return metadata;
@@ -210,3 +233,70 @@ async function extractMetadata(entryPath) {
210
233
  throw new Error(`Failed to extract metadata: ${error.message}`);
211
234
  }
212
235
  }
236
+ /**
237
+ * Save capabilities.json to .majk/ directory and auto-update package.json
238
+ */
239
+ async function saveCapabilitiesForMarket(metadata) {
240
+ try {
241
+ // Find project root (where package.json is)
242
+ const projectRoot = process.cwd();
243
+ // Check if plugin opted out
244
+ const pkgPath = path.join(projectRoot, 'package.json');
245
+ if (fs.existsSync(pkgPath)) {
246
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
247
+ if (pkg.majk?.skipCapabilitiesExtraction === true) {
248
+ console.log(' โ„น๏ธ Skipping capabilities extraction (skipCapabilitiesExtraction: true)');
249
+ return;
250
+ }
251
+ }
252
+ // 1. Create .majk directory
253
+ const majkDir = path.join(projectRoot, '.majk');
254
+ fs.mkdirSync(majkDir, { recursive: true });
255
+ // 2. Write capabilities.json
256
+ const capabilitiesPath = path.join(majkDir, 'capabilities.json');
257
+ fs.writeFileSync(capabilitiesPath, JSON.stringify(metadata, null, 2) + '\n');
258
+ console.log(' โœ“ Generated .majk/capabilities.json for market indexing');
259
+ // 3. Check and update package.json files array
260
+ if (fs.existsSync(pkgPath)) {
261
+ const content = fs.readFileSync(pkgPath, 'utf-8');
262
+ const pkg = JSON.parse(content);
263
+ if (pkg.files) {
264
+ if (!pkg.files.includes('.majk')) {
265
+ // Detect original indentation
266
+ const indent = detectIndent(content);
267
+ pkg.files.push('.majk');
268
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, indent) + '\n');
269
+ console.log(' โœ“ Added .majk to package.json files array (commit this change)');
270
+ }
271
+ }
272
+ else {
273
+ // No files array means all files included by default
274
+ console.log(' โ„น๏ธ No "files" array in package.json - .majk will be included by default');
275
+ }
276
+ }
277
+ // 4. Check for service implementations
278
+ const capabilities = metadata.capabilities || [];
279
+ const services = capabilities.filter((c) => c.type === 'service');
280
+ const implementations = services.filter((s) => s.implements);
281
+ if (implementations.length > 0) {
282
+ console.log(' ๐Ÿ”Œ Service interface implementations detected:');
283
+ implementations.forEach((impl) => {
284
+ console.log(` โ€ข ${impl.implements}`);
285
+ });
286
+ }
287
+ }
288
+ catch (error) {
289
+ // Non-fatal: log but don't fail the build
290
+ console.warn(` โš ๏ธ Could not save capabilities for market: ${error.message}`);
291
+ }
292
+ }
293
+ /**
294
+ * Detect indentation used in a JSON file
295
+ */
296
+ function detectIndent(jsonString) {
297
+ const match = jsonString.match(/^(\s+)/m);
298
+ if (match) {
299
+ return match[1].length;
300
+ }
301
+ return 2; // Default to 2 spaces
302
+ }
@@ -0,0 +1,10 @@
1
+ export interface ExtractOptions {
2
+ entry?: string;
3
+ output?: string;
4
+ silent?: boolean;
5
+ }
6
+ /**
7
+ * Extract plugin capabilities and write to .majk/capabilities.json
8
+ */
9
+ export declare function extractCapabilities(options?: ExtractOptions): Promise<void>;
10
+ //# sourceMappingURL=extract-command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract-command.d.ts","sourceRoot":"","sources":["../../src/generator/extract-command.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyFrF"}
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.extractCapabilities = extractCapabilities;
37
+ /**
38
+ * Extract capabilities command - Creates .majk/capabilities.json for market indexing
39
+ */
40
+ const path = __importStar(require("path"));
41
+ const fs = __importStar(require("fs"));
42
+ const child_process_1 = require("child_process");
43
+ /**
44
+ * Extract plugin capabilities and write to .majk/capabilities.json
45
+ */
46
+ async function extractCapabilities(options = {}) {
47
+ const entryPath = path.resolve(options.entry || './dist/index.js');
48
+ const outputDir = path.resolve(options.output || '.majk');
49
+ const outputFile = path.join(outputDir, 'capabilities.json');
50
+ const silent = options.silent || false;
51
+ if (!silent) {
52
+ console.log('๐Ÿ” Extracting plugin capabilities...');
53
+ console.log(` Entry: ${entryPath}`);
54
+ console.log(` Output: ${outputFile}`);
55
+ console.log('');
56
+ }
57
+ // Check if entry file exists
58
+ if (!fs.existsSync(entryPath)) {
59
+ throw new Error(`Entry file not found: ${entryPath}\n` +
60
+ `Make sure to run 'npm run build' first to compile your plugin.`);
61
+ }
62
+ // Ensure output directory exists
63
+ fs.mkdirSync(outputDir, { recursive: true });
64
+ try {
65
+ // Run the plugin in extract mode
66
+ (0, child_process_1.execSync)(`node "${entryPath}"`, {
67
+ env: {
68
+ ...process.env,
69
+ PLUGIN_KIT_MODE: 'extract',
70
+ PLUGIN_KIT_OUTPUT: outputFile
71
+ },
72
+ cwd: path.dirname(entryPath),
73
+ stdio: silent ? 'pipe' : 'inherit'
74
+ });
75
+ // Verify the file was created
76
+ if (!fs.existsSync(outputFile)) {
77
+ throw new Error('Capabilities file was not created');
78
+ }
79
+ // Validate it's valid JSON
80
+ const content = fs.readFileSync(outputFile, 'utf-8');
81
+ const capabilities = JSON.parse(content);
82
+ if (!silent) {
83
+ console.log('โœ… Successfully extracted capabilities!');
84
+ console.log('');
85
+ console.log('๐Ÿ“Š Summary:');
86
+ console.log(` Plugin: ${capabilities.name} v${capabilities.version}`);
87
+ console.log(` Functions: ${capabilities.functions?.length || 0}`);
88
+ console.log(` Services: ${capabilities.capabilities?.filter((c) => c.type === 'service').length || 0}`);
89
+ console.log(` Screens: ${capabilities.screens?.length || 0}`);
90
+ console.log(` Tools: ${capabilities.tools?.length || 0}`);
91
+ console.log('');
92
+ // Check for service implementations
93
+ const services = capabilities.capabilities?.filter((c) => c.type === 'service') || [];
94
+ const implementations = services.filter((s) => s.implements);
95
+ if (implementations.length > 0) {
96
+ console.log('๐Ÿ”Œ Service Interface Implementations:');
97
+ implementations.forEach((impl) => {
98
+ console.log(` โ€ข ${impl.implements} (${impl.serviceName})`);
99
+ });
100
+ console.log('');
101
+ }
102
+ console.log('๐Ÿ“ Next steps:');
103
+ console.log(' 1. Ensure ".majk" is in your package.json "files" array');
104
+ console.log(' 2. Run this command before publishing (add to prepublishOnly script)');
105
+ console.log(' 3. Publish to npm/CodeArtifact as usual');
106
+ console.log('');
107
+ console.log('๐Ÿ’ก Tip: Add this to package.json scripts:');
108
+ console.log(' "scripts": {');
109
+ console.log(' "extract": "plugin-kit extract",');
110
+ console.log(' "prepublishOnly": "npm run build && npm run extract"');
111
+ console.log(' }');
112
+ }
113
+ }
114
+ catch (error) {
115
+ if (!silent) {
116
+ console.error('');
117
+ console.error('โŒ Failed to extract capabilities');
118
+ console.error('');
119
+ }
120
+ throw new Error(`Failed to extract capabilities: ${error.message}`);
121
+ }
122
+ }
package/dist/index.d.ts CHANGED
@@ -18,6 +18,90 @@ export { definePlugin, FluentBuilder, ServiceBuilder } from './plugin-kit';
18
18
  export { FunctionRegistryImpl, FunctionProviderImpl } from './registry';
19
19
  export { FilesystemResourceProvider } from './resource-provider';
20
20
  export { generateClient } from './generator/generator';
21
- export type { PluginContext, PluginLogger, PluginStorage, ScopedTimers, ScopedIpcRegistry, PluginCapabilities, PluginCapability, ToolImplementation, InProcessPlugin, PluginHealthStatus, ToolSpec, ToolHandler, ApiMethod, ApiRouteDef, JsonSchema, RouteHandler, RequestLike, ResponseLike, UiConfig, HistoryMode, ScreenBase, ReactScreen, HtmlScreen, ConfigWizardDef, SettingsDef, Scope, EntityType, CleanupFn, HealthCheckFn, MCPServerEntity, TeamMemberEntity, ServiceFunctionBinding, FunctionHandler, SubscriptionHandler, FunctionDefinition, SubscriptionDefinition, FunctionRegistry, ClientGenerationConfig, Plugin, FunctionProvider, ResourceProvider, FunctionInfo, AsyncPluginHealthStatus, ServiceCapability, ServiceMetadata, ServiceFunctionMetadata, ServiceParameterMetadata, ServiceReturnMetadata, FunctionEnrichment, IdentityDefinitionCapability, IdentityProviderInfo, IdentitySetupInstructions, IdentityTestConfig, IdentityRequirement } from './types';
22
- export type { MajkInterface, EntityId, Unsubscribe, BaseEntity, StandardEventType, PluginEventType, RepositoryEventType, EntityType as MajkEntityType, RepositoryEvent, EventFilter, EventListener, Subscription, QueryBuilder, QueryableEventChannel, EventBusAPI, PluginInfo, PluginHealth, PluginCapabilityInfo, PluginLog, InstallPluginOptions, InstallPluginResult, GetLogsOptions, PluginManagementAPI, SecretScope, SecretInfo, ScopedSecretsAPI, SecretsAPI, StartTaskRequest, TaskFilter, TaskMessage, TaskProgress, TaskResult, TaskError, TaskHandle, TaskEventHandler, TaskEvent, TaskAPI, AccountType, AccountMetadata, TimeWindow, CredentialOptions, RefreshOptions, RefreshResult, ValidationResult, TokenInfo, AutoRefreshConfig, AutoRefreshHandle, AwsSdkCredentials, CognitoTokenSet, AzureSdkCredentials, AccountJSON, AwsCognitoCredentials, AzureEntraCredentials, ApiKeyCredentials, SystemDefaultCredentials, Credentials, SystemAccount, OAuthAccount, AwsAccount, AzureAccount, AuthEvent, AuthStateChangeEvent, AuthTokenRefreshedEvent, AuthErrorEvent, AccountChangedEvent, AuthAPI, Message, Conversation, Agent, TeamMember, TeamMemberSkills, TeamMemberPersonality, TeamMemberCodeStats, TeamMemberMetadata, ServiceFunctionBinding as MajkServiceFunctionBinding, Todo, WorkStrategy, Project, MCPServer, ConversationFilter, MessageQueryOptions, ConversationHandle, ConversationAPI, CreateTeammateRequest, TeammateUpdate, TeammateFilter, TeammateHandle, TeammateAPI, CreateTodoRequest, TodoUpdate, TodoFilter, SpawnOptions, TodoHandle, TodoAPI, CreateProjectRequest, ProjectUpdate, ProjectFilter, ProjectHandle, ProjectAPI, CreateMCPServerRequest, MCPServerUpdate, MCPServerFilter, ConnectionTestResult, MCPServerHandle, MCPServerScopedHandle, DiscoveredTool, MCPServerAPI, EverywhereScope, EverywhereScopedHandle, CreateAgentRequest, AgentUpdate, AgentFilter, TestResult, AgentHandle, AgentAPI, AddKnowledgeInput, KnowledgeNode, KnowledgeTree, KnowledgeSearchOptions, KnowledgeAPI, AIAPI, AIProvider, AIProviderCapabilities, AIProviderError, LLMInterface, ModelInfo, AIMessage, ContentBlock, PromptParams, PromptResult, PromptChunk, AIFunctionDefinition, AIJsonSchema, FunctionCallParams, FunctionCallResult, ImageGenerationParams, ImageResult, TranscriptionParams, TranscriptionResult, ProviderStatus } from './majk-interface-types';
21
+ /**
22
+ * Service Discovery Utilities
23
+ *
24
+ * Helper functions for discovering services that implement specific interfaces at runtime.
25
+ * Uses ctx.capabilities to query installed plugins and filter by interface implementation.
26
+ */
27
+ export declare const ServiceDiscovery: {
28
+ /**
29
+ * Find all installed services that implement a specific interface
30
+ *
31
+ * @param ctx - Plugin context with capabilities API
32
+ * @param interfacePackage - Interface package identifier (e.g., "@majk/service-registry/task-service")
33
+ * @returns Array of CapabilityMetadata for services implementing the interface
34
+ *
35
+ * @example
36
+ * const openaiServices = await ServiceDiscovery.findImplementations(
37
+ * ctx,
38
+ * '@majk/service-registry/openai-services'
39
+ * );
40
+ *
41
+ * if (openaiServices.length > 0) {
42
+ * const service = await ctx.rpc.createProxy(openaiServices[0].serviceName);
43
+ * const result = await service.prompt({ message: 'Hello!' });
44
+ * }
45
+ */
46
+ findImplementations(ctx: any, interfacePackage: string): Promise<any[]>;
47
+ /**
48
+ * Find the first service that implements a specific interface
49
+ *
50
+ * @param ctx - Plugin context with capabilities API
51
+ * @param interfacePackage - Interface package identifier
52
+ * @returns CapabilityMetadata for the first matching service, or undefined if none found
53
+ *
54
+ * @example
55
+ * const taskService = await ServiceDiscovery.findFirst(
56
+ * ctx,
57
+ * '@majk/service-registry/task-service'
58
+ * );
59
+ *
60
+ * if (taskService) {
61
+ * const service = await ctx.rpc.createProxy(taskService.serviceName);
62
+ * await service.createTask({ title: 'New task' });
63
+ * }
64
+ */
65
+ findFirst(ctx: any, interfacePackage: string): Promise<any | undefined>;
66
+ /**
67
+ * Create an RPC proxy for the first service implementing an interface
68
+ *
69
+ * @param ctx - Plugin context with capabilities and RPC APIs
70
+ * @param interfacePackage - Interface package identifier
71
+ * @returns RPC proxy to the service, or undefined if no implementation found
72
+ *
73
+ * @example
74
+ * const openai = await ServiceDiscovery.resolve(
75
+ * ctx,
76
+ * '@majk/service-registry/openai-services'
77
+ * );
78
+ *
79
+ * if (openai) {
80
+ * const result = await openai.prompt({ message: 'Hello!' });
81
+ * } else {
82
+ * throw new Error('No OpenAI service implementation found');
83
+ * }
84
+ */
85
+ resolve<T = any>(ctx: any, interfacePackage: string): Promise<T | undefined>;
86
+ /**
87
+ * Create an RPC proxy for a service implementing an interface, or throw an error
88
+ *
89
+ * @param ctx - Plugin context with capabilities and RPC APIs
90
+ * @param interfacePackage - Interface package identifier
91
+ * @returns RPC proxy to the service
92
+ * @throws Error if no implementation is found
93
+ *
94
+ * @example
95
+ * const storage = await ServiceDiscovery.resolveOrThrow(
96
+ * ctx,
97
+ * '@majk/service-registry/storage-service'
98
+ * );
99
+ *
100
+ * // Guaranteed to be defined - will throw if not found
101
+ * await storage.save({ key: 'data', value: '...' });
102
+ */
103
+ resolveOrThrow<T = any>(ctx: any, interfacePackage: string): Promise<T>;
104
+ };
105
+ export type { PluginContext, PluginLogger, PluginStorage, ScopedTimers, ScopedIpcRegistry, PluginCapabilities, PluginCapability, ToolImplementation, InProcessPlugin, PluginHealthStatus, ToolSpec, ToolHandler, ApiMethod, ApiRouteDef, JsonSchema, RouteHandler, RequestLike, ResponseLike, UiConfig, HistoryMode, ScreenBase, ReactScreen, HtmlScreen, ConfigWizardDef, SettingsDef, Scope, EntityType, CleanupFn, HealthCheckFn, MCPServerEntity, TeamMemberEntity, ServiceFunctionBinding, CallbackHandle, CreateCallbackOptions, CallbackStats, FunctionHandler, SubscriptionHandler, FunctionDefinition, SubscriptionDefinition, FunctionRegistry, ClientGenerationConfig, Plugin, FunctionProvider, ResourceProvider, FunctionInfo, AsyncPluginHealthStatus, ServiceCapability, ServiceMetadata, ServiceFunctionMetadata, ServiceParameterMetadata, ServiceReturnMetadata, FunctionEnrichment, IdentityDefinitionCapability, IdentityProviderInfo, IdentitySetupInstructions, IdentityTestConfig, IdentityRequirement } from './types';
106
+ export type { MajkInterface, EntityId, Unsubscribe, BaseEntity, StandardEventType, PluginEventType, RepositoryEventType, EntityType as MajkEntityType, RepositoryEvent, EventFilter, EventListener, Subscription, QueryBuilder, QueryableEventChannel, EventBusAPI, PluginInfo, PluginHealth, PluginCapabilityInfo, PluginLog, InstallPluginOptions, InstallPluginResult, GetLogsOptions, GetPluginExternalUrlOptions, GetPluginScreenUrlOptions, PluginManagementAPI, SecretScope, SecretInfo, ScopedSecretsAPI, SecretsAPI, StartTaskRequest, TaskFilter, TaskMessage, TaskProgress, TaskResult, TaskError, TaskHandle, TaskEventHandler, TaskEvent, TaskAPI, AccountType, AccountMetadata, TimeWindow, CredentialOptions, RefreshOptions, RefreshResult, ValidationResult, TokenInfo, AutoRefreshConfig, AutoRefreshHandle, AwsSdkCredentials, CognitoTokenSet, AzureSdkCredentials, AccountJSON, AwsCognitoCredentials, AzureEntraCredentials, ApiKeyCredentials, SystemDefaultCredentials, Credentials, SystemAccount, OAuthAccount, AwsAccount, AzureAccount, AuthEvent, AuthStateChangeEvent, AuthTokenRefreshedEvent, AuthErrorEvent, AccountChangedEvent, AuthAPI, Message, Conversation, Agent, TeamMember, TeamMemberSkills, TeamMemberPersonality, TeamMemberCodeStats, TeamMemberMetadata, ServiceFunctionBinding as MajkServiceFunctionBinding, Todo, WorkStrategy, Project, MCPServer, ConversationFilter, MessageQueryOptions, ConversationHandle, ConversationAPI, CreateTeammateRequest, TeammateUpdate, TeammateFilter, TeammateHandle, TeammateAPI, CreateTodoRequest, TodoUpdate, TodoFilter, SpawnOptions, TodoHandle, TodoAPI, CreateProjectRequest, ProjectUpdate, ProjectFilter, ProjectHandle, ProjectAPI, CreateMCPServerRequest, MCPServerUpdate, MCPServerFilter, ConnectionTestResult, MCPServerHandle, MCPServerScopedHandle, DiscoveredTool, MCPServerAPI, EverywhereScope, EverywhereScopedHandle, CreateAgentRequest, AgentUpdate, AgentFilter, TestResult, AgentHandle, AgentAPI, AddKnowledgeInput, KnowledgeNode, KnowledgeTree, KnowledgeSearchOptions, KnowledgeAPI, AIAPI, AIProvider, AIProviderCapabilities, AIProviderError, LLMInterface, ModelInfo, AIMessage, ContentBlock, PromptParams, PromptResult, PromptChunk, AIFunctionDefinition, AIJsonSchema, FunctionCallParams, FunctionCallResult, ImageGenerationParams, ImageResult, TranscriptionParams, TranscriptionResult, ProviderStatus } from './majk-interface-types';
23
107
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@
16
16
  * - Lifecycle hooks with cleanup management
17
17
  */
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.generateClient = exports.FilesystemResourceProvider = exports.FunctionProviderImpl = exports.FunctionRegistryImpl = exports.definePlugin = void 0;
19
+ exports.ServiceDiscovery = exports.generateClient = exports.FilesystemResourceProvider = exports.FunctionProviderImpl = exports.FunctionRegistryImpl = exports.definePlugin = void 0;
20
20
  var plugin_kit_1 = require("./plugin-kit");
21
21
  Object.defineProperty(exports, "definePlugin", { enumerable: true, get: function () { return plugin_kit_1.definePlugin; } });
22
22
  var registry_1 = require("./registry");
@@ -26,3 +26,106 @@ var resource_provider_1 = require("./resource-provider");
26
26
  Object.defineProperty(exports, "FilesystemResourceProvider", { enumerable: true, get: function () { return resource_provider_1.FilesystemResourceProvider; } });
27
27
  var generator_1 = require("./generator/generator");
28
28
  Object.defineProperty(exports, "generateClient", { enumerable: true, get: function () { return generator_1.generateClient; } });
29
+ /**
30
+ * Service Discovery Utilities
31
+ *
32
+ * Helper functions for discovering services that implement specific interfaces at runtime.
33
+ * Uses ctx.capabilities to query installed plugins and filter by interface implementation.
34
+ */
35
+ exports.ServiceDiscovery = {
36
+ /**
37
+ * Find all installed services that implement a specific interface
38
+ *
39
+ * @param ctx - Plugin context with capabilities API
40
+ * @param interfacePackage - Interface package identifier (e.g., "@majk/service-registry/task-service")
41
+ * @returns Array of CapabilityMetadata for services implementing the interface
42
+ *
43
+ * @example
44
+ * const openaiServices = await ServiceDiscovery.findImplementations(
45
+ * ctx,
46
+ * '@majk/service-registry/openai-services'
47
+ * );
48
+ *
49
+ * if (openaiServices.length > 0) {
50
+ * const service = await ctx.rpc.createProxy(openaiServices[0].serviceName);
51
+ * const result = await service.prompt({ message: 'Hello!' });
52
+ * }
53
+ */
54
+ async findImplementations(ctx, interfacePackage) {
55
+ const allServices = await ctx.capabilities.getProviders('service');
56
+ return allServices.filter((s) => s.metadata?.implements === interfacePackage);
57
+ },
58
+ /**
59
+ * Find the first service that implements a specific interface
60
+ *
61
+ * @param ctx - Plugin context with capabilities API
62
+ * @param interfacePackage - Interface package identifier
63
+ * @returns CapabilityMetadata for the first matching service, or undefined if none found
64
+ *
65
+ * @example
66
+ * const taskService = await ServiceDiscovery.findFirst(
67
+ * ctx,
68
+ * '@majk/service-registry/task-service'
69
+ * );
70
+ *
71
+ * if (taskService) {
72
+ * const service = await ctx.rpc.createProxy(taskService.serviceName);
73
+ * await service.createTask({ title: 'New task' });
74
+ * }
75
+ */
76
+ async findFirst(ctx, interfacePackage) {
77
+ const implementations = await this.findImplementations(ctx, interfacePackage);
78
+ return implementations[0];
79
+ },
80
+ /**
81
+ * Create an RPC proxy for the first service implementing an interface
82
+ *
83
+ * @param ctx - Plugin context with capabilities and RPC APIs
84
+ * @param interfacePackage - Interface package identifier
85
+ * @returns RPC proxy to the service, or undefined if no implementation found
86
+ *
87
+ * @example
88
+ * const openai = await ServiceDiscovery.resolve(
89
+ * ctx,
90
+ * '@majk/service-registry/openai-services'
91
+ * );
92
+ *
93
+ * if (openai) {
94
+ * const result = await openai.prompt({ message: 'Hello!' });
95
+ * } else {
96
+ * throw new Error('No OpenAI service implementation found');
97
+ * }
98
+ */
99
+ async resolve(ctx, interfacePackage) {
100
+ const service = await this.findFirst(ctx, interfacePackage);
101
+ if (!service) {
102
+ return undefined;
103
+ }
104
+ return (await ctx.rpc.createProxy(service.serviceName));
105
+ },
106
+ /**
107
+ * Create an RPC proxy for a service implementing an interface, or throw an error
108
+ *
109
+ * @param ctx - Plugin context with capabilities and RPC APIs
110
+ * @param interfacePackage - Interface package identifier
111
+ * @returns RPC proxy to the service
112
+ * @throws Error if no implementation is found
113
+ *
114
+ * @example
115
+ * const storage = await ServiceDiscovery.resolveOrThrow(
116
+ * ctx,
117
+ * '@majk/service-registry/storage-service'
118
+ * );
119
+ *
120
+ * // Guaranteed to be defined - will throw if not found
121
+ * await storage.save({ key: 'data', value: '...' });
122
+ */
123
+ async resolveOrThrow(ctx, interfacePackage) {
124
+ const service = await this.resolve(ctx, interfacePackage);
125
+ if (!service) {
126
+ throw new Error(`No service found implementing interface: ${interfacePackage}. ` +
127
+ `Make sure the required plugin is installed and enabled.`);
128
+ }
129
+ return service;
130
+ }
131
+ };