ultravisor-beacon 0.0.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/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "ultravisor-beacon",
3
+ "version": "0.0.1",
4
+ "description": "Ultravisor Beacon: lightweight beacon client and Fable service for remote task execution",
5
+ "main": "source/Ultravisor-Beacon-Service.cjs",
6
+ "scripts": {
7
+ "test": "npx mocha -u tdd --exit --timeout 10000 ./test/*_tests*",
8
+ "coverage": "npx nyc --reporter=lcov --reporter=text-summary npx mocha -- -u tdd --exit --timeout 10000 ./test/*_tests*"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/stevenvelozo/ultravisor-beacon.git"
13
+ },
14
+ "keywords": [
15
+ "ultravisor",
16
+ "beacon",
17
+ "remote",
18
+ "task",
19
+ "orchestration",
20
+ "fable"
21
+ ],
22
+ "author": "Steven Velozo <steven@velozo.com>",
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "fable-serviceproviderbase": "^3.0.0"
26
+ },
27
+ "devDependencies": {
28
+ "mocha": "^10.0.0"
29
+ }
30
+ }
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Ultravisor Beacon CLI
4
+ *
5
+ * Usage:
6
+ * node Ultravisor-Beacon-CLI.cjs --server http://localhost:54321 --name GPU-Worker-1 --capabilities Shell,FileSystem
7
+ *
8
+ * Options:
9
+ * --server URL Ultravisor server URL (default: http://localhost:54321)
10
+ * --name NAME Beacon worker name (default: beacon-worker)
11
+ * --capabilities LIST Comma-separated capabilities (default: Shell)
12
+ * --password PASSWORD Authentication password for server connection
13
+ *
14
+ * For advanced provider configuration, use a .ultravisor-beacon.json file
15
+ * with a "Providers" array instead of --capabilities.
16
+ * --max-concurrent N Max concurrent work items (default: 1)
17
+ * --poll-interval MS Poll interval in ms (default: 5000)
18
+ * --staging-path PATH Local staging directory (default: cwd)
19
+ */
20
+
21
+ const libPath = require('path');
22
+ const libFS = require('fs');
23
+
24
+ const libBeaconClient = require('./Ultravisor-Beacon-Client.cjs');
25
+
26
+ // Parse command-line arguments
27
+ let tmpConfig = {
28
+ ServerURL: 'http://localhost:54321',
29
+ Name: 'beacon-worker',
30
+ Capabilities: ['Shell'],
31
+ MaxConcurrent: 1,
32
+ PollIntervalMs: 5000,
33
+ HeartbeatIntervalMs: 30000,
34
+ StagingPath: process.cwd(),
35
+ Password: '',
36
+ Tags: {}
37
+ };
38
+
39
+ // Check for config file
40
+ let tmpConfigFilePath = libPath.resolve(process.cwd(), '.ultravisor-beacon.json');
41
+ if (libFS.existsSync(tmpConfigFilePath))
42
+ {
43
+ try
44
+ {
45
+ let tmpFileConfig = JSON.parse(libFS.readFileSync(tmpConfigFilePath, 'utf8'));
46
+ tmpConfig = Object.assign(tmpConfig, tmpFileConfig);
47
+ console.log(`[Beacon CLI] Loaded config from ${tmpConfigFilePath}`);
48
+ }
49
+ catch (pError)
50
+ {
51
+ console.warn(`[Beacon CLI] Warning: could not parse ${tmpConfigFilePath}: ${pError.message}`);
52
+ }
53
+ }
54
+
55
+ // Parse CLI arguments (override config file)
56
+ for (let i = 2; i < process.argv.length; i++)
57
+ {
58
+ switch (process.argv[i])
59
+ {
60
+ case '--server':
61
+ tmpConfig.ServerURL = process.argv[++i] || tmpConfig.ServerURL;
62
+ break;
63
+ case '--name':
64
+ tmpConfig.Name = process.argv[++i] || tmpConfig.Name;
65
+ break;
66
+ case '--capabilities':
67
+ tmpConfig.Capabilities = (process.argv[++i] || 'Shell').split(',').map(s => s.trim());
68
+ break;
69
+ case '--max-concurrent':
70
+ tmpConfig.MaxConcurrent = parseInt(process.argv[++i]) || 1;
71
+ break;
72
+ case '--poll-interval':
73
+ tmpConfig.PollIntervalMs = parseInt(process.argv[++i]) || 5000;
74
+ break;
75
+ case '--staging-path':
76
+ tmpConfig.StagingPath = process.argv[++i] || process.cwd();
77
+ break;
78
+ case '--password':
79
+ tmpConfig.Password = process.argv[++i] || '';
80
+ break;
81
+ case '--help':
82
+ case '-h':
83
+ console.log('Ultravisor Beacon Worker');
84
+ console.log('');
85
+ console.log('Usage: node Ultravisor-Beacon-CLI.cjs [options]');
86
+ console.log('');
87
+ console.log('Options:');
88
+ console.log(' --server URL Ultravisor server URL (default: http://localhost:54321)');
89
+ console.log(' --name NAME Beacon worker name (default: beacon-worker)');
90
+ console.log(' --capabilities LIST Comma-separated capabilities (default: Shell)');
91
+ console.log(' --max-concurrent N Max concurrent work items (default: 1)');
92
+ console.log(' --poll-interval MS Poll interval in ms (default: 5000)');
93
+ console.log(' --staging-path PATH Local staging directory (default: cwd)');
94
+ console.log(' --password PASSWORD Authentication password for server connection');
95
+ console.log(' --help, -h Show this help');
96
+ console.log('');
97
+ console.log('Provider Configuration:');
98
+ console.log(' For advanced provider configuration, create a .ultravisor-beacon.json');
99
+ console.log(' file with a "Providers" array:');
100
+ console.log('');
101
+ console.log(' {');
102
+ console.log(' "Providers": [');
103
+ console.log(' { "Source": "Shell" },');
104
+ console.log(' { "Source": "FileSystem", "Config": { "AllowedPaths": ["/data"] } },');
105
+ console.log(' { "Source": "./my-custom-provider.cjs", "Config": {} }');
106
+ console.log(' ]');
107
+ console.log(' }');
108
+ process.exit(0);
109
+ }
110
+ }
111
+
112
+ // Create and start the Beacon client
113
+ let tmpClient = new libBeaconClient(tmpConfig);
114
+
115
+ tmpClient.start((pError) =>
116
+ {
117
+ if (pError)
118
+ {
119
+ console.error(`[Beacon CLI] Failed to start: ${pError.message}`);
120
+ process.exit(1);
121
+ }
122
+
123
+ console.log(`[Beacon CLI] Beacon is running. Polling every ${tmpConfig.PollIntervalMs}ms.`);
124
+ console.log(`[Beacon CLI] Press Ctrl+C to stop.`);
125
+ });
126
+
127
+ // Handle graceful shutdown
128
+ process.on('SIGINT', () =>
129
+ {
130
+ console.log('\n[Beacon CLI] Shutting down...');
131
+ tmpClient.stop(() =>
132
+ {
133
+ process.exit(0);
134
+ });
135
+ });
136
+
137
+ process.on('SIGTERM', () =>
138
+ {
139
+ tmpClient.stop(() =>
140
+ {
141
+ process.exit(0);
142
+ });
143
+ });
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Ultravisor Beacon Capability Adapter
3
+ *
4
+ * Bridges Fable-world capability descriptors to the thin client's
5
+ * CapabilityProvider interface. One adapter instance is created per
6
+ * capability registered with the Beacon Service.
7
+ *
8
+ * The adapter extends the thin-client base class so it can be loaded
9
+ * by the ProviderRegistry exactly like a built-in provider (Shell,
10
+ * FileSystem, etc.).
11
+ *
12
+ * Lifecycle:
13
+ * 1. Constructed by CapabilityManager with a capability descriptor
14
+ * 2. Registered with the thin client's ProviderRegistry
15
+ * 3. execute() delegates to the descriptor's Handler functions
16
+ * 4. initialize()/shutdown() delegate to descriptor hooks if present
17
+ */
18
+
19
+ const libCapabilityProvider = require('./Ultravisor-Beacon-CapabilityProvider.cjs');
20
+
21
+ class UltravisorBeaconCapabilityAdapter extends libCapabilityProvider
22
+ {
23
+ constructor(pDescriptor)
24
+ {
25
+ super(pDescriptor.Config || {});
26
+
27
+ this._Descriptor = pDescriptor;
28
+
29
+ // Set identity from the descriptor
30
+ this.Name = pDescriptor.Name || pDescriptor.Capability || 'AdaptedProvider';
31
+ this.Capability = pDescriptor.Capability || 'Unknown';
32
+ }
33
+
34
+ /**
35
+ * Return the actions map from the descriptor, stripping Handler
36
+ * functions (which are internal) and keeping only Description
37
+ * and SettingsSchema for introspection.
38
+ */
39
+ get actions()
40
+ {
41
+ let tmpDescriptorActions = (this._Descriptor && this._Descriptor.actions) ? this._Descriptor.actions : {};
42
+ let tmpActionNames = Object.keys(tmpDescriptorActions);
43
+ let tmpActions = {};
44
+
45
+ for (let i = 0; i < tmpActionNames.length; i++)
46
+ {
47
+ let tmpName = tmpActionNames[i];
48
+ let tmpSrc = tmpDescriptorActions[tmpName];
49
+
50
+ tmpActions[tmpName] = {
51
+ Description: tmpSrc.Description || '',
52
+ SettingsSchema: tmpSrc.SettingsSchema || []
53
+ };
54
+ }
55
+
56
+ return tmpActions;
57
+ }
58
+
59
+ /**
60
+ * Execute a work item by delegating to the descriptor's Handler.
61
+ *
62
+ * @param {string} pAction - Action name (e.g. 'ReadFile')
63
+ * @param {object} pWorkItem - Full work item from the server
64
+ * @param {object} pContext - Execution context: { StagingPath }
65
+ * @param {function} fCallback - function(pError, pResult)
66
+ * @param {function} [fReportProgress] - Optional progress callback
67
+ */
68
+ execute(pAction, pWorkItem, pContext, fCallback, fReportProgress)
69
+ {
70
+ let tmpDescriptorActions = (this._Descriptor && this._Descriptor.actions) ? this._Descriptor.actions : {};
71
+ let tmpActionDef = tmpDescriptorActions[pAction];
72
+
73
+ if (!tmpActionDef || typeof tmpActionDef.Handler !== 'function')
74
+ {
75
+ return fCallback(new Error(
76
+ `CapabilityAdapter "${this.Name}" has no Handler for action "${pAction}".`));
77
+ }
78
+
79
+ try
80
+ {
81
+ tmpActionDef.Handler(pWorkItem, pContext, fCallback, fReportProgress);
82
+ }
83
+ catch (pError)
84
+ {
85
+ return fCallback(pError);
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Delegate initialization to the descriptor if present.
91
+ */
92
+ initialize(fCallback)
93
+ {
94
+ if (this._Descriptor && typeof this._Descriptor.initialize === 'function')
95
+ {
96
+ return this._Descriptor.initialize(fCallback);
97
+ }
98
+
99
+ return fCallback(null);
100
+ }
101
+
102
+ /**
103
+ * Delegate shutdown to the descriptor if present.
104
+ */
105
+ shutdown(fCallback)
106
+ {
107
+ if (this._Descriptor && typeof this._Descriptor.shutdown === 'function')
108
+ {
109
+ return this._Descriptor.shutdown(fCallback);
110
+ }
111
+
112
+ return fCallback(null);
113
+ }
114
+ }
115
+
116
+ module.exports = UltravisorBeaconCapabilityAdapter;
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Ultravisor Beacon Capability Manager
3
+ *
4
+ * Manages capabilities registered by a host application. Each
5
+ * capability descriptor declares a Capability name, actions with
6
+ * Handler functions, and optional lifecycle hooks.
7
+ *
8
+ * When the Beacon Service calls enable(), the CapabilityManager
9
+ * converts all registered descriptors into provider descriptors
10
+ * that the thin client's ProviderRegistry can load.
11
+ *
12
+ * Capability Descriptor shape:
13
+ * {
14
+ * Capability: 'ContentSystem',
15
+ * Name: 'ContentSystemProvider',
16
+ * actions: {
17
+ * 'ReadFile': {
18
+ * Description: 'Read a content file',
19
+ * SettingsSchema: [{ Name: 'FilePath', DataType: 'String', Required: true }],
20
+ * Handler: function(pWorkItem, pContext, fCallback, fReportProgress) { ... }
21
+ * }
22
+ * },
23
+ * initialize: function(fCallback) { ... }, // optional
24
+ * shutdown: function(fCallback) { ... } // optional
25
+ * }
26
+ */
27
+
28
+ const libCapabilityAdapter = require('./Ultravisor-Beacon-CapabilityAdapter.cjs');
29
+
30
+ class UltravisorBeaconCapabilityManager
31
+ {
32
+ constructor()
33
+ {
34
+ // Map of Capability name -> descriptor
35
+ this._Capabilities = {};
36
+ }
37
+
38
+ /**
39
+ * Register a capability from the host application.
40
+ *
41
+ * @param {object} pDescriptor - Capability descriptor
42
+ * @returns {boolean} true if registered successfully
43
+ */
44
+ registerCapability(pDescriptor)
45
+ {
46
+ if (!pDescriptor || !pDescriptor.Capability)
47
+ {
48
+ console.error('[CapabilityManager] Descriptor must have a Capability name.');
49
+ return false;
50
+ }
51
+
52
+ if (!pDescriptor.actions || Object.keys(pDescriptor.actions).length === 0)
53
+ {
54
+ console.warn(`[CapabilityManager] Capability "${pDescriptor.Capability}" has no actions.`);
55
+ }
56
+
57
+ this._Capabilities[pDescriptor.Capability] = pDescriptor;
58
+ return true;
59
+ }
60
+
61
+ /**
62
+ * Remove a previously registered capability.
63
+ *
64
+ * @param {string} pCapabilityName - The capability to remove
65
+ * @returns {boolean} true if removed
66
+ */
67
+ removeCapability(pCapabilityName)
68
+ {
69
+ if (this._Capabilities[pCapabilityName])
70
+ {
71
+ delete this._Capabilities[pCapabilityName];
72
+ return true;
73
+ }
74
+
75
+ return false;
76
+ }
77
+
78
+ /**
79
+ * Get the list of registered capability names.
80
+ *
81
+ * @returns {string[]}
82
+ */
83
+ getCapabilityNames()
84
+ {
85
+ return Object.keys(this._Capabilities);
86
+ }
87
+
88
+ /**
89
+ * Get all registered capability descriptors.
90
+ *
91
+ * @returns {object} Map of capability name -> descriptor
92
+ */
93
+ getCapabilities()
94
+ {
95
+ return this._Capabilities;
96
+ }
97
+
98
+ /**
99
+ * Build provider descriptors for the thin client.
100
+ *
101
+ * Creates a CapabilityAdapter instance per registered capability
102
+ * and returns them as provider descriptors compatible with
103
+ * ProviderRegistry.loadProvider().
104
+ *
105
+ * The descriptors use a pre-instantiated object format (the adapter
106
+ * instances already have execute() on them), which ProviderRegistry
107
+ * supports as a direct registration path.
108
+ *
109
+ * @returns {Array<object>} Provider descriptors for the thin client
110
+ */
111
+ buildProviderDescriptors()
112
+ {
113
+ let tmpDescriptors = [];
114
+ let tmpCapabilityNames = Object.keys(this._Capabilities);
115
+
116
+ for (let i = 0; i < tmpCapabilityNames.length; i++)
117
+ {
118
+ let tmpCapName = tmpCapabilityNames[i];
119
+ let tmpCapDescriptor = this._Capabilities[tmpCapName];
120
+
121
+ // Create an adapter instance that bridges this descriptor
122
+ // to the CapabilityProvider interface
123
+ let tmpAdapter = new libCapabilityAdapter(tmpCapDescriptor);
124
+
125
+ tmpDescriptors.push(tmpAdapter);
126
+ }
127
+
128
+ return tmpDescriptors;
129
+ }
130
+ }
131
+
132
+ module.exports = UltravisorBeaconCapabilityManager;
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Ultravisor Beacon Capability Provider — Base Class
3
+ *
4
+ * Providers extend this class to implement a specific capability
5
+ * (Shell, FileSystem, MediaProcessing, etc.) that the Beacon can
6
+ * advertise and execute work items for.
7
+ *
8
+ * This is a plain JavaScript class — no Fable dependency — keeping
9
+ * the Beacon lightweight and deployable with minimal dependencies.
10
+ *
11
+ * Lifecycle:
12
+ * 1. constructor(pProviderConfig) — receive per-provider config
13
+ * 2. initialize(fCallback) — async init (validate prereqs)
14
+ * 3. execute(...) — called per work item
15
+ * 4. shutdown(fCallback) — cleanup on beacon stop
16
+ */
17
+
18
+ class UltravisorBeaconCapabilityProvider
19
+ {
20
+ constructor(pProviderConfig)
21
+ {
22
+ this._ProviderConfig = pProviderConfig || {};
23
+
24
+ // Subclasses MUST set these
25
+ this.Name = 'BaseProvider';
26
+ this.Capability = 'Unknown';
27
+ }
28
+
29
+ /**
30
+ * Return the actions this provider supports.
31
+ *
32
+ * Override in subclasses. Each key is an action name, value is
33
+ * an object with Description and optional SettingsSchema.
34
+ *
35
+ * @returns {object} Map of ActionName → { Description, SettingsSchema? }
36
+ *
37
+ * Example:
38
+ * {
39
+ * 'Execute': { Description: 'Run a shell command.' },
40
+ * 'Script': { Description: 'Run a script file.',
41
+ * SettingsSchema: [{ Name: 'ScriptPath', DataType: 'String', Required: true }] }
42
+ * }
43
+ */
44
+ get actions()
45
+ {
46
+ return {};
47
+ }
48
+
49
+ /**
50
+ * Return the list of capability strings this provider advertises.
51
+ *
52
+ * Usually just [this.Capability]. Override for multi-capability
53
+ * providers.
54
+ *
55
+ * @returns {string[]}
56
+ */
57
+ getCapabilities()
58
+ {
59
+ return [this.Capability];
60
+ }
61
+
62
+ /**
63
+ * Return a structured description of all supported actions.
64
+ * Used for logging and introspection.
65
+ *
66
+ * @returns {Array<{ Capability: string, Action: string, Description: string }>}
67
+ */
68
+ describeActions()
69
+ {
70
+ let tmpResult = [];
71
+ let tmpActions = this.actions;
72
+ let tmpActionNames = Object.keys(tmpActions);
73
+
74
+ for (let i = 0; i < tmpActionNames.length; i++)
75
+ {
76
+ tmpResult.push({
77
+ Capability: this.Capability,
78
+ Action: tmpActionNames[i],
79
+ Description: tmpActions[tmpActionNames[i]].Description || ''
80
+ });
81
+ }
82
+
83
+ return tmpResult;
84
+ }
85
+
86
+ /**
87
+ * Execute a work item for the given action.
88
+ *
89
+ * @param {string} pAction - The action to perform (e.g. 'Execute', 'Read')
90
+ * @param {object} pWorkItem - The full work item from the server:
91
+ * { WorkItemHash, Capability, Action, Settings, TimeoutMs, OperationHash }
92
+ * @param {object} pContext - Execution context: { StagingPath }
93
+ * @param {function} fCallback - function(pError, pResult)
94
+ * pResult = { Outputs: { ... }, Log: [...] }
95
+ * @param {function} [fReportProgress] - Optional progress callback:
96
+ * function({ Percent, Message, Step, TotalSteps, Log })
97
+ * All fields optional. Call during long-running operations.
98
+ */
99
+ execute(pAction, pWorkItem, pContext, fCallback, fReportProgress)
100
+ {
101
+ return fCallback(new Error(
102
+ `Provider "${this.Name}" has not implemented execute() for action "${pAction}".`));
103
+ }
104
+
105
+ /**
106
+ * Optional lifecycle hook: called after provider is loaded,
107
+ * before the beacon starts polling. Use for async initialization
108
+ * (e.g. verifying that ffmpeg exists, connecting to a local API).
109
+ *
110
+ * @param {function} fCallback - function(pError)
111
+ */
112
+ initialize(fCallback)
113
+ {
114
+ return fCallback(null);
115
+ }
116
+
117
+ /**
118
+ * Optional lifecycle hook: called when the beacon is shutting down.
119
+ * Use for cleanup (e.g. closing connections, flushing buffers).
120
+ *
121
+ * @param {function} fCallback - function(pError)
122
+ */
123
+ shutdown(fCallback)
124
+ {
125
+ return fCallback(null);
126
+ }
127
+ }
128
+
129
+ module.exports = UltravisorBeaconCapabilityProvider;