@camera.ui/cli 0.0.49 → 0.0.51

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.
Files changed (54) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +10 -1
  3. package/dist/commands/artifacts.d.ts +9 -0
  4. package/dist/commands/artifacts.js +116 -0
  5. package/dist/commands/artifacts.js.map +1 -0
  6. package/dist/commands/bundle.d.ts +34 -1
  7. package/dist/commands/bundle.js +402 -57
  8. package/dist/commands/bundle.js.map +1 -1
  9. package/dist/commands/create.js +140 -93
  10. package/dist/commands/create.js.map +1 -1
  11. package/dist/commands/publish.js +95 -39
  12. package/dist/commands/publish.js.map +1 -1
  13. package/dist/index.js +13 -4
  14. package/dist/index.js.map +1 -1
  15. package/dist/types.d.ts +24 -5
  16. package/dist/utils/banners.d.ts +3 -1
  17. package/dist/utils/banners.js +15 -11
  18. package/dist/utils/banners.js.map +1 -1
  19. package/dist/utils/logger.d.ts +12 -24
  20. package/dist/utils/logger.js +57 -83
  21. package/dist/utils/logger.js.map +1 -1
  22. package/dist/utils/parser.d.ts +11 -4
  23. package/dist/utils/parser.js +27 -185
  24. package/dist/utils/parser.js.map +1 -1
  25. package/dist/utils/templates.d.ts +13 -1
  26. package/dist/utils/templates.js +148 -68
  27. package/dist/utils/templates.js.map +1 -1
  28. package/dist/utils/utils.js +13 -11
  29. package/dist/utils/utils.js.map +1 -1
  30. package/dist/utils/versions.js +1 -2
  31. package/dist/utils/versions.js.map +1 -1
  32. package/package.json +29 -43
  33. package/templates/base/README.md +2 -2
  34. package/templates/base/SECURITY.md +1 -1
  35. package/templates/base/package.json +2 -3
  36. package/templates/base/updates.config.js +1 -1
  37. package/templates/go/go.mod +5 -0
  38. package/templates/go/postinstall.js +85 -0
  39. package/templates/go/src/main.go +51 -0
  40. package/templates/python/requirements.txt +1 -1
  41. package/templates/python/ruff.toml +2 -2
  42. package/templates/python/src/main.py +299 -28
  43. package/templates/typescript/contract.ts +36 -0
  44. package/templates/typescript/eslint.config.js +13 -3
  45. package/templates/typescript/package.eslint.json +2 -2
  46. package/templates/typescript/package.json +4 -4
  47. package/templates/typescript/src/index.ts +96 -27
  48. package/templates/typescript/src/sensor.ts +207 -0
  49. package/CHANGELOG.md +0 -8
  50. package/CONTRIBUTING.md +0 -1
  51. package/scripts/chmod.js +0 -17
  52. package/templates/base/cameraui.config.js +0 -10
  53. package/templates/base/eslint.config.js +0 -64
  54. package/templates/base/src/index.js +0 -41
@@ -1,46 +1,115 @@
1
- import type { CameraDevice, CuiPlugin, CuiPluginCapability, LoggerService, PluginAPI } from '@camera.ui/types';
1
+ import { API_EVENT, BasePlugin } from '@camera.ui/sdk';
2
2
 
3
- export default class SamplePlugin implements CuiPlugin {
4
- public logger: LoggerService;
5
- public api: PluginAPI;
3
+ import { ExampleLightControl, ExampleMotionSensor } from './sensor.js';
6
4
 
5
+ import type { CameraDevice, DeviceStorage, LightControl, LoggerService, MotionSensor, PluginAPI } from '@camera.ui/sdk';
6
+
7
+ /**
8
+ * Sample Plugin
9
+ *
10
+ * Demonstrates the camera.ui plugin architecture with sensors:
11
+ * - MotionSensor: External motion events (webhooks, ONVIF, etc.)
12
+ * - LightControl: Controllable light with on/brightness
13
+ *
14
+ * The contract (provides/consumes) is defined in contract.ts.
15
+ */
16
+ export default class SamplePlugin extends BasePlugin {
17
+ // Maps to track cameras and sensors
7
18
  private cameras = new Map<string, CameraDevice>();
19
+ private motionSensors = new Map<string, MotionSensor>();
20
+ private lightControls = new Map<string, LightControl>();
21
+
22
+ constructor(logger: LoggerService, api: PluginAPI, storage: DeviceStorage<any>) {
23
+ super(logger, api, storage);
24
+
25
+ // Register lifecycle event handlers
26
+ this.api.on(API_EVENT.FINISH_LAUNCHING, this.onFinishLaunching.bind(this));
27
+ this.api.on(API_EVENT.SHUTDOWN, this.onShutdown.bind(this));
28
+ }
8
29
 
9
- constructor(logger: LoggerService, api: PluginAPI) {
10
- this.logger = logger;
11
- this.api = api;
30
+ /**
31
+ * Configure cameras at startup.
32
+ * Called for cameras already assigned to this plugin.
33
+ */
34
+ public async configureCameras(cameras: CameraDevice[]): Promise<void> {
35
+ for (const camera of cameras) {
36
+ await this.setupCamera(camera);
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Called when a camera is selected for this plugin at runtime.
42
+ */
43
+ public async onCameraAdded(camera: CameraDevice): Promise<void> {
44
+ this.logger.log('Camera selected:', camera.name);
45
+ await this.setupCamera(camera);
46
+ }
12
47
 
13
- this.api.on('finishLaunching', this.start.bind(this));
14
- this.api.on('shutdown', this.stop.bind(this));
48
+ /**
49
+ * Called when a camera is deselected from this plugin.
50
+ */
51
+ public async onCameraReleased(cameraId: string): Promise<void> {
52
+ const camera = this.cameras.get(cameraId);
53
+ if (!camera) return;
15
54
 
16
- this.api.deviceManager.on('cameraSelected', (cameraDevice: CameraDevice) => {
17
- this.logger.log('Camera selected:', cameraDevice.name);
55
+ this.logger.log('Camera deselected:', camera.name);
18
56
 
19
- if (!this.cameras.has(cameraDevice.id)) {
20
- this.cameras.set(cameraDevice.id, cameraDevice);
21
- }
22
- });
57
+ // Remove sensors
58
+ const motion = this.motionSensors.get(cameraId);
59
+ if (motion) {
60
+ await camera.removeSensor(motion.id);
61
+ this.motionSensors.delete(cameraId);
62
+ }
23
63
 
24
- this.api.deviceManager.on('cameraDeselected', (cameraId: string) => {
25
- const cameraDevice = this.cameras.get(cameraId);
26
- if (cameraDevice) {
27
- this.logger.log('Camera deselected:', cameraDevice.name);
28
- this.cameras.delete(cameraId);
29
- }
30
- });
64
+ const light = this.lightControls.get(cameraId);
65
+ if (light) {
66
+ await camera.removeSensor(light.id);
67
+ this.lightControls.delete(cameraId);
68
+ }
69
+
70
+ this.cameras.delete(cameraId);
31
71
  }
32
72
 
33
- public async configureCameras(): Promise<void> {}
73
+ /**
74
+ * Set up sensors for a camera.
75
+ */
76
+ private async setupCamera(camera: CameraDevice): Promise<void> {
77
+ if (this.cameras.has(camera.id)) return;
78
+
79
+ this.cameras.set(camera.id, camera);
80
+
81
+ // Create motion sensor
82
+ const motion = new ExampleMotionSensor(`Motion - ${camera.name}`);
83
+ this.motionSensors.set(camera.id, motion);
84
+ await camera.addSensor(motion);
34
85
 
35
- public async getCapabilities(): Promise<CuiPluginCapability[]> {
36
- return [];
86
+ // Create light control
87
+ const light = new ExampleLightControl(camera, `Light - ${camera.name}`);
88
+ this.lightControls.set(camera.id, light);
89
+ await camera.addSensor(light);
90
+
91
+ this.logger.log(`Sensors registered for ${camera.name}`);
92
+
93
+ // Example: Trigger motion after 5 seconds (for testing)
94
+ // setTimeout(() => motion.trigger(), 5000);
37
95
  }
38
96
 
39
- private start(): void {
97
+ /**
98
+ * Called when the plugin has finished launching.
99
+ */
100
+ private onFinishLaunching(): void {
40
101
  this.logger.log('Plugin started');
41
102
  }
42
103
 
43
- private stop(): void {
104
+ /**
105
+ * Called when camera.ui is shutting down.
106
+ */
107
+ private async onShutdown(): Promise<void> {
44
108
  this.logger.log('Shutting down plugin');
109
+
110
+ // Cleanup all sensors
111
+ this.motionSensors.clear();
112
+ this.lightControls.clear();
113
+ this.cameras.clear();
45
114
  }
46
115
  }
@@ -0,0 +1,207 @@
1
+ import { ClassifierDetectorSensor, LightControl, MotionSensor } from '@camera.ui/sdk';
2
+
3
+ import type { CameraDevice, ClassifierResult, Detection, JsonSchema, ModelSpec, VideoFrameData } from '@camera.ui/sdk';
4
+
5
+ // ============ MOTION SENSOR (External Events) ============
6
+
7
+ /**
8
+ * Example Motion Sensor
9
+ *
10
+ * Use this for external motion sources:
11
+ * - ONVIF camera events
12
+ * - SMTP notifications
13
+ * - Webhook triggers
14
+ * - API polling
15
+ *
16
+ * For frame-based detection, extend MotionDetectorSensor instead.
17
+ */
18
+ export class ExampleMotionSensor extends MotionSensor {
19
+ constructor(name: string) {
20
+ super(name);
21
+ }
22
+
23
+ /**
24
+ * Trigger motion from external event
25
+ */
26
+ trigger(detections: Detection[] = []): void {
27
+ this.detected = true;
28
+ this.detections = detections;
29
+ }
30
+
31
+ /**
32
+ * Clear motion state
33
+ */
34
+ reset(): void {
35
+ this.detected = false;
36
+ this.detections = [];
37
+ }
38
+ }
39
+
40
+ // ============ LIGHT CONTROL ============
41
+
42
+ /**
43
+ * Example Light Control
44
+ *
45
+ * Bidirectional control sensor - consumers can read and write state.
46
+ * Implements setOn/setBrightness to handle state changes.
47
+ */
48
+ export class ExampleLightControl extends LightControl {
49
+ private cameraDevice: CameraDevice;
50
+
51
+ /**
52
+ * Storage schema for per-sensor configuration
53
+ * These settings are persisted and shown in the UI.
54
+ */
55
+ schema: JsonSchema[] = [
56
+ {
57
+ type: 'number',
58
+ key: 'defaultBrightness',
59
+ title: 'Default Brightness',
60
+ description: 'Default brightness level (0-100)',
61
+ defaultValue: 100,
62
+ minimum: 0,
63
+ maximum: 100,
64
+ store: true,
65
+ },
66
+ {
67
+ type: 'boolean',
68
+ key: 'autoOff',
69
+ title: 'Auto-Off',
70
+ description: 'Automatically turn off after timeout',
71
+ defaultValue: false,
72
+ store: true,
73
+ },
74
+ ];
75
+
76
+ constructor(camera: CameraDevice, name = 'Light') {
77
+ super(name);
78
+ this.cameraDevice = camera;
79
+
80
+ // Initialize state
81
+ this.on = false;
82
+ this.brightness = 100;
83
+
84
+ // Log state changes
85
+ this.onPropertyChanged.subscribe(({ property, value }) => {
86
+ this.cameraDevice.logger.debug(`${this.name}: ${property} = ${value}`);
87
+ });
88
+ }
89
+
90
+ /**
91
+ * Called when consumer sets 'on' property
92
+ */
93
+ async setOn(value: boolean): Promise<void> {
94
+ this.cameraDevice.logger.log(`Light turned ${value ? 'ON' : 'OFF'}`);
95
+ this.on = value;
96
+
97
+ // Apply default brightness when turning on
98
+ if (value && this.storage) {
99
+ const defaultBrightness = this.storage.values.defaultBrightness ?? 100;
100
+ this.brightness = defaultBrightness;
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Called when consumer sets 'brightness' property
106
+ */
107
+ async setBrightness(value: number): Promise<void> {
108
+ this.cameraDevice.logger.log(`Light brightness: ${value}%`);
109
+ this.brightness = value;
110
+ }
111
+ }
112
+
113
+ // ============ CLASSIFIER (Multi-Provider Example) ============
114
+
115
+ /**
116
+ * Example Classifier Sensor
117
+ *
118
+ * Multi-provider sensor: Multiple classifiers can be registered per camera.
119
+ * Example use cases:
120
+ * - Bird species classifier (triggers on 'bird' from object detection)
121
+ * - Dog breed classifier (triggers on 'dog')
122
+ * - Plant species classifier
123
+ *
124
+ * The DetectionCoordinator calls detectClassifications() when triggerLabels are detected.
125
+ */
126
+ export class ExampleClassifier extends ClassifierDetectorSensor {
127
+ private cameraDevice: CameraDevice;
128
+
129
+ /**
130
+ * Schema for classifier configuration
131
+ */
132
+ schema: JsonSchema[] = [
133
+ {
134
+ type: 'number',
135
+ key: 'confidenceThreshold',
136
+ title: 'Confidence Threshold',
137
+ description: 'Minimum confidence for classifications (0-1)',
138
+ defaultValue: 0.5,
139
+ minimum: 0.1,
140
+ maximum: 1.0,
141
+ step: 0.05,
142
+ store: true,
143
+ },
144
+ ];
145
+
146
+ constructor(camera: CameraDevice, name = 'Classifier') {
147
+ super(name);
148
+ this.cameraDevice = camera;
149
+ }
150
+
151
+ /**
152
+ * Model specification
153
+ *
154
+ * - input: Frame size and format expected by the model
155
+ * - outputLabels: Labels this classifier can output
156
+ * - triggerLabels: Object labels that trigger classification
157
+ */
158
+ get modelSpec(): ModelSpec {
159
+ return {
160
+ input: {
161
+ width: 224,
162
+ height: 224,
163
+ format: 'rgb',
164
+ },
165
+ // Trigger when object detection finds these labels
166
+ triggerLabels: ['animal'],
167
+ };
168
+ }
169
+
170
+ /**
171
+ * Classify objects in a frame
172
+ *
173
+ * Called by DetectionCoordinator when triggerLabels are detected.
174
+ * The frame is pre-scaled to modelSpec.input dimensions.
175
+ */
176
+ async detectClassifications(frame: VideoFrameData, _triggerRegions?: Detection[]): Promise<ClassifierResult> {
177
+ const threshold = this.storage?.values.confidenceThreshold ?? 0.5;
178
+
179
+ // TODO: Implement your classification model here
180
+ // Example: Load TensorFlow model and run inference
181
+ //
182
+ // const predictions = await this.model.classify(frame.data);
183
+ // return {
184
+ // detected: predictions.length > 0,
185
+ // detections: predictions.map(p => ({
186
+ // label: p.label,
187
+ // confidence: p.score,
188
+ // box: triggerRegions?.[0]?.box ?? { x: 0, y: 0, width: 1, height: 1 },
189
+ // })),
190
+ // };
191
+
192
+ this.cameraDevice.logger.debug(`Classifying frame ${frame.width}x${frame.height}, threshold: ${threshold}`);
193
+
194
+ // Return empty result (placeholder)
195
+ return {
196
+ detected: false,
197
+ detections: [],
198
+ };
199
+ }
200
+
201
+ /**
202
+ * Cleanup when sensor is destroyed
203
+ */
204
+ async destroy(): Promise<void> {
205
+ // Release model resources if needed
206
+ }
207
+ }
package/CHANGELOG.md DELETED
@@ -1,8 +0,0 @@
1
- All notable changes to this project will be documented in this file.
2
-
3
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
4
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
-
6
- ## [X.X.X] - ???
7
-
8
- - Initial Release
package/CONTRIBUTING.md DELETED
@@ -1 +0,0 @@
1
- # Contributing
package/scripts/chmod.js DELETED
@@ -1,17 +0,0 @@
1
- import { execSync } from 'node:child_process';
2
- import { platform } from 'node:os';
3
- import { dirname, resolve } from 'node:path';
4
- import { fileURLToPath } from 'node:url';
5
-
6
- const __filename = fileURLToPath(import.meta.url);
7
- const __dirname = dirname(__filename);
8
-
9
- const isWindows = platform() === 'win32';
10
-
11
- function chmod() {
12
- const scriptPath = resolve(__dirname, '../dist/index.js');
13
- const arg = isWindows ? 'echo "Skipping chmod command on Windows"' : `chmod +x ${scriptPath}`;
14
- execSync(arg, { stdio: 'inherit' });
15
- }
16
-
17
- chmod();
@@ -1,10 +0,0 @@
1
- const mode = process.env.MODE || 'production';
2
-
3
- const config = {
4
- input: ['src/index.js'],
5
- mode: mode === 'development' ? 'development' : 'production',
6
- external: [],
7
- additionalFiles: [],
8
- };
9
-
10
- export default config;
@@ -1,64 +0,0 @@
1
- import jsLint from '@eslint/js';
2
- import stylistic from '@stylistic/eslint-plugin';
3
- import globals from 'globals';
4
-
5
- export default [
6
- {
7
- files: ['**/*.{js,mjs,cjs,ts,mts}'],
8
- },
9
- {
10
- ignores: ['**/dist/**', '**/node_modules/**', '**/public/**', '**/build/**', '**/bundle/**', '**/test/**', '**/wasm/**', '**/example/**'],
11
- },
12
- jsLint.configs.recommended,
13
- stylistic.configs['disable-legacy'],
14
- stylistic.configs.customize({
15
- indent: 2,
16
- quotes: 'single',
17
- semi: true,
18
- commaDangle: 'always-multiline',
19
- jsx: false,
20
- arrowParens: true,
21
- braceStyle: '1tbs',
22
- blockSpacing: true,
23
- quoteProps: 'as-needed',
24
- }),
25
- {
26
- languageOptions: {
27
- globals: { ...globals.node },
28
- },
29
-
30
- rules: {
31
- '@stylistic/generator-star-spacing': ['error', { before: true, after: false }],
32
-
33
- // Stylistic specific rules
34
- '@stylistic/max-len': ['error', { code: 170, tabWidth: 2 }],
35
- '@stylistic/quotes': ['error', 'single', { avoidEscape: true }],
36
- '@stylistic/operator-linebreak': 'off',
37
- '@stylistic/comma-dangle': [
38
- 'error',
39
- {
40
- arrays: 'always-multiline',
41
- objects: 'always-multiline',
42
- imports: 'always-multiline',
43
- exports: 'only-multiline',
44
- functions: 'always-multiline',
45
- enums: 'always-multiline',
46
- generics: 'always-multiline',
47
- tuples: 'always-multiline',
48
- },
49
- ],
50
-
51
- semi: [1, 'always'],
52
- // quotes: ['error', 'single'],
53
- 'comma-dangle': ['error', 'only-multiline'],
54
- 'no-multiple-empty-lines': ['warn', { max: 1, maxEOF: 0 }],
55
- 'eol-last': ['error', 'always'],
56
- 'space-before-function-paren': ['error', { named: 'never' }],
57
-
58
- 'no-unused-vars': 'off',
59
- 'no-case-declarations': 'off',
60
- 'no-async-promise-executor': 'off',
61
- 'no-control-regex': 'off',
62
- },
63
- },
64
- ];
@@ -1,41 +0,0 @@
1
- export default class SamplePlugin {
2
- constructor(logger, api) {
3
- this.logger = logger;
4
- this.api = api;
5
-
6
- this.camera = new Map();
7
-
8
- this.api.on('finishLaunching', this.start.bind(this));
9
- this.api.on('shutdown', this.stop.bind(this));
10
-
11
- this.api.deviceManager.on('cameraSelected', (cameraDevice) => {
12
- this.logger.log('Camera selected:', cameraDevice.name);
13
-
14
- if (!this.cameras.has(cameraDevice.id)) {
15
- this.cameras.set(cameraDevice.id, cameraDevice);
16
- }
17
- });
18
-
19
- this.api.deviceManager.on('cameraDeselected', (cameraId) => {
20
- const cameraDevice = this.cameras.get(cameraId);
21
- if (cameraDevice) {
22
- this.logger.log('Camera deselected:', cameraDevice.name);
23
- this.cameras.delete(cameraId);
24
- }
25
- });
26
- }
27
-
28
- async configureCameras() {}
29
-
30
- async getCapabilities() {
31
- return [];
32
- }
33
-
34
- start() {
35
- this.logger.log('Finished launching plugin');
36
- }
37
-
38
- stop() {
39
- this.logger.log('Shutting down plugin');
40
- }
41
- }