camstack 1.1.18 → 1.1.20
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.
|
@@ -14522,7 +14522,7 @@ function date4(params) {
|
|
|
14522
14522
|
// ../../node_modules/zod/v4/classic/external.js
|
|
14523
14523
|
config(en_default());
|
|
14524
14524
|
|
|
14525
|
-
// ../types/dist/sleep-
|
|
14525
|
+
// ../types/dist/sleep-CZDdRBua.mjs
|
|
14526
14526
|
var WELL_KNOWN_TABS = [
|
|
14527
14527
|
{
|
|
14528
14528
|
id: "overview",
|
|
@@ -15539,7 +15539,16 @@ var DecoderStatsSchema = external_exports.object({
|
|
|
15539
15539
|
inputFps: external_exports.number(),
|
|
15540
15540
|
outputFps: external_exports.number(),
|
|
15541
15541
|
avgDecodeTimeMs: external_exports.number(),
|
|
15542
|
-
droppedFrames: external_exports.number()
|
|
15542
|
+
droppedFrames: external_exports.number(),
|
|
15543
|
+
/**
|
|
15544
|
+
* Pull-mode adaptive-fps telemetry (optional — only pull sessions run the
|
|
15545
|
+
* lag-driven controller; push sessions omit these). `lagMs` is the EWMA of
|
|
15546
|
+
* the decoder's real-time drift (rising = falling behind live); `adaptiveFps`
|
|
15547
|
+
* is the current lag-throttled emit rate (≤ `effectiveFps` ceiling).
|
|
15548
|
+
*/
|
|
15549
|
+
lagMs: external_exports.number().optional(),
|
|
15550
|
+
effectiveFps: external_exports.number().optional(),
|
|
15551
|
+
adaptiveFps: external_exports.number().optional()
|
|
15543
15552
|
});
|
|
15544
15553
|
var DecoderSessionConfigSchema = external_exports.object({
|
|
15545
15554
|
codec: external_exports.string(),
|
|
@@ -15580,7 +15589,15 @@ var DecoderSessionConfigSchema = external_exports.object({
|
|
|
15580
15589
|
* other — `pullFrames` returns nothing for an `'shm'` session and
|
|
15581
15590
|
* `pullHandles` returns nothing for a `'callback'` session.
|
|
15582
15591
|
*/
|
|
15583
|
-
frameSink: external_exports.enum(["callback", "shm"]).default("callback")
|
|
15592
|
+
frameSink: external_exports.enum(["callback", "shm"]).default("callback"),
|
|
15593
|
+
/**
|
|
15594
|
+
* Per-camera decoder DEBUG facility. When `true`, a pull-mode session emits
|
|
15595
|
+
* a throttled (~1Hz) structured `decoder debug` line (effective/adaptive fps,
|
|
15596
|
+
* real-time lag, dropped-frame delta, avg decode time, hwaccel). Mirrors the
|
|
15597
|
+
* stream-broker's `streamingDebug` gate — off by default so production logs
|
|
15598
|
+
* stay quiet and the emit path pays zero per-frame cost when disabled.
|
|
15599
|
+
*/
|
|
15600
|
+
debug: external_exports.boolean().optional()
|
|
15584
15601
|
});
|
|
15585
15602
|
var VideoEncodeSchema = external_exports.object({
|
|
15586
15603
|
codec: external_exports.enum([
|
|
@@ -18137,6 +18154,67 @@ var coverCapability = {
|
|
|
18137
18154
|
*/
|
|
18138
18155
|
runtimeState: CoverStatusSchema
|
|
18139
18156
|
};
|
|
18157
|
+
var DayNightModeSchema = external_exports.enum([
|
|
18158
|
+
"auto",
|
|
18159
|
+
"day",
|
|
18160
|
+
"night",
|
|
18161
|
+
"schedule"
|
|
18162
|
+
]);
|
|
18163
|
+
var NormalizedRangeSchema$1 = external_exports.object({
|
|
18164
|
+
min: external_exports.number(),
|
|
18165
|
+
max: external_exports.number(),
|
|
18166
|
+
step: external_exports.number()
|
|
18167
|
+
});
|
|
18168
|
+
var DayNightStatusSchema = external_exports.object({
|
|
18169
|
+
mode: DayNightModeSchema,
|
|
18170
|
+
/** IR-cut trigger sensitivity, NORMALIZED 0–100 (higher = switches to night sooner). */
|
|
18171
|
+
sensitivity: external_exports.number().optional(),
|
|
18172
|
+
/** Delay before the IR-cut filter flips, in seconds. */
|
|
18173
|
+
switchDelaySec: external_exports.number().optional(),
|
|
18174
|
+
lastFetchedAt: external_exports.number()
|
|
18175
|
+
});
|
|
18176
|
+
var DayNightOptionsSchema = external_exports.object({
|
|
18177
|
+
/** Modes this camera accepts. Empty → the camera has no configurable day/night mode. */
|
|
18178
|
+
modes: external_exports.array(DayNightModeSchema),
|
|
18179
|
+
supportsSensitivity: external_exports.boolean(),
|
|
18180
|
+
/** Present when `supportsSensitivity` — the normalized 0–100 range. */
|
|
18181
|
+
sensitivity: NormalizedRangeSchema$1.optional(),
|
|
18182
|
+
supportsSwitchDelay: external_exports.boolean(),
|
|
18183
|
+
/** Present when `supportsSwitchDelay` — the allowed delay range in seconds. */
|
|
18184
|
+
switchDelaySec: NormalizedRangeSchema$1.optional()
|
|
18185
|
+
});
|
|
18186
|
+
var DayNightSettingsPatchSchema = external_exports.object({
|
|
18187
|
+
mode: DayNightModeSchema.optional(),
|
|
18188
|
+
sensitivity: external_exports.number().optional(),
|
|
18189
|
+
switchDelaySec: external_exports.number().optional()
|
|
18190
|
+
});
|
|
18191
|
+
var dayNightCapability = {
|
|
18192
|
+
name: "day-night",
|
|
18193
|
+
scope: "device",
|
|
18194
|
+
deviceNative: true,
|
|
18195
|
+
mode: "singleton",
|
|
18196
|
+
deviceTypes: [DeviceType.Camera],
|
|
18197
|
+
deviceConfig: { ui: {
|
|
18198
|
+
kind: "derived-form",
|
|
18199
|
+
builderId: "day-night",
|
|
18200
|
+
tab: "image"
|
|
18201
|
+
} },
|
|
18202
|
+
methods: {
|
|
18203
|
+
getOptions: method(external_exports.object({ deviceId: external_exports.number() }), DayNightOptionsSchema),
|
|
18204
|
+
setSettings: method(external_exports.object({
|
|
18205
|
+
deviceId: external_exports.number(),
|
|
18206
|
+
settings: DayNightSettingsPatchSchema
|
|
18207
|
+
}), external_exports.void(), {
|
|
18208
|
+
kind: "mutation",
|
|
18209
|
+
auth: "admin"
|
|
18210
|
+
})
|
|
18211
|
+
},
|
|
18212
|
+
status: {
|
|
18213
|
+
schema: DayNightStatusSchema,
|
|
18214
|
+
kind: "poll"
|
|
18215
|
+
},
|
|
18216
|
+
runtimeState: DayNightStatusSchema
|
|
18217
|
+
};
|
|
18140
18218
|
var SourceInfoSchema = external_exports.object({
|
|
18141
18219
|
/** Live dispatch key — mutable when the source system allows rename. */
|
|
18142
18220
|
id: external_exports.string(),
|
|
@@ -18579,6 +18657,107 @@ var imageCapability = {
|
|
|
18579
18657
|
*/
|
|
18580
18658
|
runtimeState: ImageStatusSchema
|
|
18581
18659
|
};
|
|
18660
|
+
var ImageRotateSchema = external_exports.enum([
|
|
18661
|
+
"0",
|
|
18662
|
+
"90",
|
|
18663
|
+
"180",
|
|
18664
|
+
"270"
|
|
18665
|
+
]);
|
|
18666
|
+
var WhiteBalanceModeSchema = external_exports.enum(["auto", "manual"]);
|
|
18667
|
+
var ExposureModeSchema = external_exports.enum(["auto", "manual"]);
|
|
18668
|
+
var BacklightModeSchema = external_exports.enum([
|
|
18669
|
+
"off",
|
|
18670
|
+
"blc",
|
|
18671
|
+
"wdr",
|
|
18672
|
+
"hlc"
|
|
18673
|
+
]);
|
|
18674
|
+
var NormalizedRangeSchema = external_exports.object({
|
|
18675
|
+
min: external_exports.number(),
|
|
18676
|
+
max: external_exports.number(),
|
|
18677
|
+
step: external_exports.number()
|
|
18678
|
+
});
|
|
18679
|
+
var ImageSettingsStatusSchema = external_exports.object({
|
|
18680
|
+
/** Normalized 0–100. */
|
|
18681
|
+
brightness: external_exports.number().optional(),
|
|
18682
|
+
/** Normalized 0–100. */
|
|
18683
|
+
contrast: external_exports.number().optional(),
|
|
18684
|
+
/** Normalized 0–100. */
|
|
18685
|
+
saturation: external_exports.number().optional(),
|
|
18686
|
+
/** Normalized 0–100. */
|
|
18687
|
+
sharpness: external_exports.number().optional(),
|
|
18688
|
+
mirror: external_exports.boolean().optional(),
|
|
18689
|
+
flip: external_exports.boolean().optional(),
|
|
18690
|
+
rotate: ImageRotateSchema.optional(),
|
|
18691
|
+
whiteBalance: WhiteBalanceModeSchema.optional(),
|
|
18692
|
+
/** Manual white-balance warmth, NORMALIZED 0–100. Meaningful only when `whiteBalance === 'manual'`. */
|
|
18693
|
+
warmth: external_exports.number().optional(),
|
|
18694
|
+
exposureMode: ExposureModeSchema.optional(),
|
|
18695
|
+
backlightMode: BacklightModeSchema.optional(),
|
|
18696
|
+
lastFetchedAt: external_exports.number()
|
|
18697
|
+
});
|
|
18698
|
+
var ImageSettingsOptionsSchema = external_exports.object({
|
|
18699
|
+
supportsBrightness: external_exports.boolean(),
|
|
18700
|
+
brightness: NormalizedRangeSchema.optional(),
|
|
18701
|
+
supportsContrast: external_exports.boolean(),
|
|
18702
|
+
contrast: NormalizedRangeSchema.optional(),
|
|
18703
|
+
supportsSaturation: external_exports.boolean(),
|
|
18704
|
+
saturation: NormalizedRangeSchema.optional(),
|
|
18705
|
+
supportsSharpness: external_exports.boolean(),
|
|
18706
|
+
sharpness: NormalizedRangeSchema.optional(),
|
|
18707
|
+
supportsMirror: external_exports.boolean(),
|
|
18708
|
+
supportsFlip: external_exports.boolean(),
|
|
18709
|
+
/** Supported rotation values. Empty → rotation not configurable. */
|
|
18710
|
+
rotateOptions: external_exports.array(ImageRotateSchema),
|
|
18711
|
+
/** Supported white-balance modes. Empty → white-balance not configurable. */
|
|
18712
|
+
whiteBalanceModes: external_exports.array(WhiteBalanceModeSchema),
|
|
18713
|
+
supportsWarmth: external_exports.boolean(),
|
|
18714
|
+
/** Present when `supportsWarmth` — the normalized 0–100 range. */
|
|
18715
|
+
warmth: NormalizedRangeSchema.optional(),
|
|
18716
|
+
/** Supported exposure modes. Empty → exposure not configurable. */
|
|
18717
|
+
exposureModes: external_exports.array(ExposureModeSchema),
|
|
18718
|
+
/** Supported backlight modes. Empty → backlight-compensation not configurable. */
|
|
18719
|
+
backlightModes: external_exports.array(BacklightModeSchema)
|
|
18720
|
+
});
|
|
18721
|
+
var ImageSettingsPatchSchema = external_exports.object({
|
|
18722
|
+
brightness: external_exports.number().optional(),
|
|
18723
|
+
contrast: external_exports.number().optional(),
|
|
18724
|
+
saturation: external_exports.number().optional(),
|
|
18725
|
+
sharpness: external_exports.number().optional(),
|
|
18726
|
+
mirror: external_exports.boolean().optional(),
|
|
18727
|
+
flip: external_exports.boolean().optional(),
|
|
18728
|
+
rotate: ImageRotateSchema.optional(),
|
|
18729
|
+
whiteBalance: WhiteBalanceModeSchema.optional(),
|
|
18730
|
+
warmth: external_exports.number().optional(),
|
|
18731
|
+
exposureMode: ExposureModeSchema.optional(),
|
|
18732
|
+
backlightMode: BacklightModeSchema.optional()
|
|
18733
|
+
});
|
|
18734
|
+
var imageSettingsCapability = {
|
|
18735
|
+
name: "image-settings",
|
|
18736
|
+
scope: "device",
|
|
18737
|
+
deviceNative: true,
|
|
18738
|
+
mode: "singleton",
|
|
18739
|
+
deviceTypes: [DeviceType.Camera],
|
|
18740
|
+
deviceConfig: { ui: {
|
|
18741
|
+
kind: "derived-form",
|
|
18742
|
+
builderId: "image-settings",
|
|
18743
|
+
tab: "image"
|
|
18744
|
+
} },
|
|
18745
|
+
methods: {
|
|
18746
|
+
getOptions: method(external_exports.object({ deviceId: external_exports.number() }), ImageSettingsOptionsSchema),
|
|
18747
|
+
setSettings: method(external_exports.object({
|
|
18748
|
+
deviceId: external_exports.number(),
|
|
18749
|
+
settings: ImageSettingsPatchSchema
|
|
18750
|
+
}), external_exports.void(), {
|
|
18751
|
+
kind: "mutation",
|
|
18752
|
+
auth: "admin"
|
|
18753
|
+
})
|
|
18754
|
+
},
|
|
18755
|
+
status: {
|
|
18756
|
+
schema: ImageSettingsStatusSchema,
|
|
18757
|
+
kind: "poll"
|
|
18758
|
+
},
|
|
18759
|
+
runtimeState: ImageSettingsStatusSchema
|
|
18760
|
+
};
|
|
18582
18761
|
var LawnMowerActivitySchema = external_exports.enum([
|
|
18583
18762
|
"idle",
|
|
18584
18763
|
"mowing",
|
|
@@ -19555,6 +19734,16 @@ var RunnerCameraConfigSchema = external_exports.object({
|
|
|
19555
19734
|
* this gate is bypassed.
|
|
19556
19735
|
*/
|
|
19557
19736
|
onboardMotionDrivesAnalyzer: external_exports.boolean().default(true),
|
|
19737
|
+
/**
|
|
19738
|
+
* Master toggle for the occupancy re-check. When `false` (DEFAULT) the runner
|
|
19739
|
+
* never arms the periodic recheck timer, regardless of `occupancyRecheckSec` —
|
|
19740
|
+
* this is off by default because the recheck re-subscribes a detection session
|
|
19741
|
+
* every N seconds while `watching`, a major source of pull-decoder re-dial
|
|
19742
|
+
* churn (each cycle creates+tears a session → RTSP re-dial → latency). The
|
|
19743
|
+
* `occupancyRecheckSec` / `occupancyRecheckFrames` sliders only take effect
|
|
19744
|
+
* (and only render) when this is enabled.
|
|
19745
|
+
*/
|
|
19746
|
+
occupancyRecheckEnabled: external_exports.boolean().default(false),
|
|
19558
19747
|
occupancyRecheckSec: external_exports.number().min(occupancyRecheckSecField.min).max(occupancyRecheckSecField.max).default(occupancyRecheckSecField.default),
|
|
19559
19748
|
occupancyRecheckFrames: external_exports.number().min(occupancyRecheckFramesField.min).max(occupancyRecheckFramesField.max).default(occupancyRecheckFramesField.default),
|
|
19560
19749
|
/**
|
|
@@ -19590,6 +19779,8 @@ var RunnerCameraDeviceUIFields = [
|
|
|
19590
19779
|
default: motionFpsField.default,
|
|
19591
19780
|
showValue: true,
|
|
19592
19781
|
unit: "fps",
|
|
19782
|
+
nullable: true,
|
|
19783
|
+
nullLabel: "Default",
|
|
19593
19784
|
showWhen: {
|
|
19594
19785
|
field: "motionSources",
|
|
19595
19786
|
includes: "analyzer"
|
|
@@ -19604,7 +19795,9 @@ var RunnerCameraDeviceUIFields = [
|
|
|
19604
19795
|
step: detectionFpsField.step,
|
|
19605
19796
|
default: detectionFpsField.default,
|
|
19606
19797
|
showValue: true,
|
|
19607
|
-
unit: "fps"
|
|
19798
|
+
unit: "fps",
|
|
19799
|
+
nullable: true,
|
|
19800
|
+
nullLabel: "Default"
|
|
19608
19801
|
},
|
|
19609
19802
|
{
|
|
19610
19803
|
key: "motionCooldownMs",
|
|
@@ -19617,7 +19810,9 @@ var RunnerCameraDeviceUIFields = [
|
|
|
19617
19810
|
default: motionCooldownMsField.default,
|
|
19618
19811
|
showValue: true,
|
|
19619
19812
|
unit: "s",
|
|
19620
|
-
displayScale: 1e3
|
|
19813
|
+
displayScale: 1e3,
|
|
19814
|
+
nullable: true,
|
|
19815
|
+
nullLabel: "Default"
|
|
19621
19816
|
},
|
|
19622
19817
|
{
|
|
19623
19818
|
key: "onboardMotionDrivesAnalyzer",
|
|
@@ -19630,17 +19825,29 @@ var RunnerCameraDeviceUIFields = [
|
|
|
19630
19825
|
includes: "onboard"
|
|
19631
19826
|
}
|
|
19632
19827
|
},
|
|
19828
|
+
{
|
|
19829
|
+
key: "occupancyRecheckEnabled",
|
|
19830
|
+
type: "boolean",
|
|
19831
|
+
style: "checkbox",
|
|
19832
|
+
label: "Occupancy re-check",
|
|
19833
|
+
description: "Periodically re-sample a few frames during the watching phase to confirm the scene is truly empty (catches stationary objects motion-gating would miss). Off by default \u2014 it adds decoder re-dial churn.",
|
|
19834
|
+
default: false
|
|
19835
|
+
},
|
|
19633
19836
|
{
|
|
19634
19837
|
key: "occupancyRecheckSec",
|
|
19635
19838
|
type: "slider",
|
|
19636
19839
|
label: "Occupancy re-check interval",
|
|
19637
|
-
description: "How often (in seconds) the runner re-samples a few frames to confirm the scene is truly empty during the watching phase.
|
|
19840
|
+
description: "How often (in seconds) the runner re-samples a few frames to confirm the scene is truly empty during the watching phase.",
|
|
19638
19841
|
min: occupancyRecheckSecField.min,
|
|
19639
19842
|
max: occupancyRecheckSecField.max,
|
|
19640
19843
|
step: occupancyRecheckSecField.step,
|
|
19641
19844
|
default: occupancyRecheckSecField.default,
|
|
19642
19845
|
showValue: true,
|
|
19643
|
-
unit: "s"
|
|
19846
|
+
unit: "s",
|
|
19847
|
+
showWhen: {
|
|
19848
|
+
field: "occupancyRecheckEnabled",
|
|
19849
|
+
equals: true
|
|
19850
|
+
}
|
|
19644
19851
|
},
|
|
19645
19852
|
{
|
|
19646
19853
|
key: "occupancyRecheckFrames",
|
|
@@ -19651,7 +19858,11 @@ var RunnerCameraDeviceUIFields = [
|
|
|
19651
19858
|
max: occupancyRecheckFramesField.max,
|
|
19652
19859
|
step: occupancyRecheckFramesField.step,
|
|
19653
19860
|
default: occupancyRecheckFramesField.default,
|
|
19654
|
-
showValue: true
|
|
19861
|
+
showValue: true,
|
|
19862
|
+
showWhen: {
|
|
19863
|
+
field: "occupancyRecheckEnabled",
|
|
19864
|
+
equals: true
|
|
19865
|
+
}
|
|
19655
19866
|
}
|
|
19656
19867
|
];
|
|
19657
19868
|
var RunnerLocalLoadSchema = external_exports.object({
|
|
@@ -22155,7 +22366,7 @@ var audioCodecCapability = {
|
|
|
22155
22366
|
name: "audio-codec",
|
|
22156
22367
|
scope: "system",
|
|
22157
22368
|
mode: "singleton",
|
|
22158
|
-
preferredProvider: "
|
|
22369
|
+
preferredProvider: "decoder-nodeav",
|
|
22159
22370
|
methods: {
|
|
22160
22371
|
/** Probe the local runtime and return the supported codec matrix. */
|
|
22161
22372
|
listSupportedCodecs: method(external_exports.void(), external_exports.array(AudioCodecInfoSchema).readonly()),
|
|
@@ -22667,14 +22878,16 @@ var decoderCapability = {
|
|
|
22667
22878
|
name: "decoder",
|
|
22668
22879
|
scope: "system",
|
|
22669
22880
|
mode: "singleton",
|
|
22670
|
-
preferredProvider: "decoder-
|
|
22881
|
+
preferredProvider: "decoder-nodeav",
|
|
22671
22882
|
methods: {
|
|
22672
22883
|
supportsCodec: method(external_exports.object({ codec: external_exports.string() }), external_exports.boolean()),
|
|
22673
22884
|
getInfo: method(external_exports.void(), external_exports.object({
|
|
22674
22885
|
id: external_exports.string(),
|
|
22675
22886
|
name: external_exports.string(),
|
|
22676
22887
|
isPullMode: external_exports.boolean().optional(),
|
|
22677
|
-
priority: external_exports.number().optional()
|
|
22888
|
+
priority: external_exports.number().optional(),
|
|
22889
|
+
hwaccel: external_exports.string().optional(),
|
|
22890
|
+
probedBestHwaccel: external_exports.string().optional()
|
|
22678
22891
|
})),
|
|
22679
22892
|
createSession: method(DecoderSessionConfigSchema, external_exports.object({
|
|
22680
22893
|
sessionId: external_exports.string(),
|
|
@@ -29738,6 +29951,18 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
29738
29951
|
addonId: null,
|
|
29739
29952
|
access: "view"
|
|
29740
29953
|
},
|
|
29954
|
+
"dayNight.getOptions": {
|
|
29955
|
+
capName: "day-night",
|
|
29956
|
+
capScope: "device",
|
|
29957
|
+
addonId: null,
|
|
29958
|
+
access: "view"
|
|
29959
|
+
},
|
|
29960
|
+
"dayNight.setSettings": {
|
|
29961
|
+
capName: "day-night",
|
|
29962
|
+
capScope: "device",
|
|
29963
|
+
addonId: null,
|
|
29964
|
+
access: "create"
|
|
29965
|
+
},
|
|
29741
29966
|
"decoder.createSession": {
|
|
29742
29967
|
capName: "decoder",
|
|
29743
29968
|
capScope: "system",
|
|
@@ -30668,6 +30893,18 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
30668
30893
|
addonId: null,
|
|
30669
30894
|
access: "create"
|
|
30670
30895
|
},
|
|
30896
|
+
"imageSettings.getOptions": {
|
|
30897
|
+
capName: "image-settings",
|
|
30898
|
+
capScope: "device",
|
|
30899
|
+
addonId: null,
|
|
30900
|
+
access: "view"
|
|
30901
|
+
},
|
|
30902
|
+
"imageSettings.setSettings": {
|
|
30903
|
+
capName: "image-settings",
|
|
30904
|
+
capScope: "device",
|
|
30905
|
+
addonId: null,
|
|
30906
|
+
access: "create"
|
|
30907
|
+
},
|
|
30671
30908
|
"integrations.create": {
|
|
30672
30909
|
capName: "integrations",
|
|
30673
30910
|
capScope: "system",
|
|
@@ -33400,6 +33637,10 @@ async function resolveHubFromDiscovered(nodes, httpsPort = DEFAULT_HUB_HTTPS_POR
|
|
|
33400
33637
|
}
|
|
33401
33638
|
return null;
|
|
33402
33639
|
}
|
|
33640
|
+
async function filterHubNodes(nodes, httpsPort = DEFAULT_HUB_HTTPS_PORT) {
|
|
33641
|
+
const verdicts = await Promise.all(nodes.map((n) => probeHttps(n.address, httpsPort)));
|
|
33642
|
+
return nodes.filter((_, i) => verdicts[i] === true);
|
|
33643
|
+
}
|
|
33403
33644
|
function parsePacket(text) {
|
|
33404
33645
|
const parts = text.split("|");
|
|
33405
33646
|
if (parts.length !== PACKET_FIELD_COUNT) return null;
|
|
@@ -33464,49 +33705,40 @@ async function runDiscover(args) {
|
|
|
33464
33705
|
const group = typeof values["multicast-group"] === "string" ? values["multicast-group"] : void 0;
|
|
33465
33706
|
const filterLabel = namespace ? `namespace "${namespace}"` : "all namespaces";
|
|
33466
33707
|
console.log(
|
|
33467
|
-
`[camstack] Listening for camstack
|
|
33708
|
+
`[camstack] Listening for camstack hubs on ${filterLabel} (multicast ${group ?? DEFAULT_MULTICAST_GROUP}:${udpPort ?? DEFAULT_UDP_PORT}, ${(timeoutMs ?? 6e3) / 1e3}s)\u2026`
|
|
33468
33709
|
);
|
|
33469
|
-
const
|
|
33710
|
+
const discovered = await discoverNodes({
|
|
33470
33711
|
...namespace !== void 0 ? { namespace } : {},
|
|
33471
33712
|
...timeoutMs !== void 0 ? { timeoutMs } : {},
|
|
33472
33713
|
...udpPort !== void 0 ? { udpPort } : {},
|
|
33473
33714
|
...group !== void 0 ? { multicastGroup: group } : {}
|
|
33474
33715
|
});
|
|
33716
|
+
const hubs = await filterHubNodes(discovered);
|
|
33475
33717
|
if (values.json === true) {
|
|
33476
|
-
console.log(JSON.stringify(
|
|
33718
|
+
console.log(JSON.stringify(hubs, null, 2));
|
|
33477
33719
|
return;
|
|
33478
33720
|
}
|
|
33479
|
-
if (
|
|
33721
|
+
if (hubs.length === 0) {
|
|
33480
33722
|
console.log(
|
|
33481
|
-
"[camstack] No
|
|
33723
|
+
"[camstack] No hubs responded. Verify the hub is running with a matching namespace + that you are on the same LAN."
|
|
33482
33724
|
);
|
|
33483
33725
|
return;
|
|
33484
33726
|
}
|
|
33485
|
-
console.log(`[camstack] Found ${
|
|
33486
|
-
const idWidth = Math.max(...
|
|
33487
|
-
const nsWidth = Math.max(...
|
|
33488
|
-
for (const n of
|
|
33727
|
+
console.log(`[camstack] Found ${hubs.length} hub(s):`);
|
|
33728
|
+
const idWidth = Math.max(...hubs.map((n) => n.nodeID.length));
|
|
33729
|
+
const nsWidth = Math.max(...hubs.map((n) => n.namespace.length));
|
|
33730
|
+
for (const n of hubs) {
|
|
33489
33731
|
console.log(
|
|
33490
|
-
` \u2022 ${n.nodeID.padEnd(idWidth)} ${n.address} ns=${n.namespace.padEnd(nsWidth)} (
|
|
33732
|
+
` \u2022 ${n.nodeID.padEnd(idWidth)} ${n.address} ns=${n.namespace.padEnd(nsWidth)} (https :${DEFAULT_HUB_HTTPS_PORT})`
|
|
33491
33733
|
);
|
|
33492
|
-
|
|
33493
|
-
if (namespace) {
|
|
33494
|
-
const hub = await resolveHubFromDiscovered(nodes);
|
|
33495
|
-
if (hub) {
|
|
33496
|
-
console.log(``);
|
|
33497
|
-
console.log(`[camstack] Hub HTTPS surface: https://${hub.address}:${hub.port}`);
|
|
33498
|
-
console.log(`[camstack] \u2192 camstack login -s https://${hub.address}:${hub.port}`);
|
|
33499
|
-
} else {
|
|
33500
|
-
console.log(``);
|
|
33501
|
-
console.log(
|
|
33502
|
-
`[camstack] No node responded on default HTTPS port ${DEFAULT_HUB_HTTPS_PORT}. Pass --server manually.`
|
|
33503
|
-
);
|
|
33504
|
-
}
|
|
33734
|
+
console.log(` \u2192 camstack login -s https://${n.address}:${DEFAULT_HUB_HTTPS_PORT}`);
|
|
33505
33735
|
}
|
|
33506
33736
|
}
|
|
33507
33737
|
|
|
33508
33738
|
export {
|
|
33739
|
+
DEFAULT_HUB_HTTPS_PORT,
|
|
33509
33740
|
discoverNodes,
|
|
33510
33741
|
resolveHubFromDiscovered,
|
|
33742
|
+
filterHubNodes,
|
|
33511
33743
|
runDiscover
|
|
33512
33744
|
};
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runDiscover
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-VL5TLTQF.js";
|
|
5
5
|
import "./chunk-K3NQKI34.js";
|
|
6
6
|
|
|
7
7
|
// src/cli.ts
|
|
@@ -137,8 +137,59 @@ function runSetup(args) {
|
|
|
137
137
|
|
|
138
138
|
// src/commands/deploy.ts
|
|
139
139
|
import { execSync } from "child_process";
|
|
140
|
+
import { randomBytes } from "crypto";
|
|
140
141
|
import * as fs2 from "fs";
|
|
141
142
|
import * as path2 from "path";
|
|
143
|
+
|
|
144
|
+
// src/http/hub-http.ts
|
|
145
|
+
import * as http from "http";
|
|
146
|
+
import * as https from "https";
|
|
147
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
148
|
+
var insecureHubAgent = new https.Agent({ rejectUnauthorized: false, keepAlive: false });
|
|
149
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
150
|
+
function hubRequest(url, opts = {}) {
|
|
151
|
+
const parsed = new URL(url);
|
|
152
|
+
const isHttps = parsed.protocol === "https:";
|
|
153
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
154
|
+
return new Promise((resolve4, reject) => {
|
|
155
|
+
const requestOptions = {
|
|
156
|
+
protocol: parsed.protocol,
|
|
157
|
+
hostname: parsed.hostname,
|
|
158
|
+
port: parsed.port || (isHttps ? 443 : 80),
|
|
159
|
+
path: `${parsed.pathname}${parsed.search}`,
|
|
160
|
+
method: opts.method ?? "GET",
|
|
161
|
+
headers: { ...opts.headers },
|
|
162
|
+
timeout: timeoutMs,
|
|
163
|
+
...isHttps ? { agent: insecureHubAgent } : {}
|
|
164
|
+
};
|
|
165
|
+
const onResponse = (res) => {
|
|
166
|
+
const chunks = [];
|
|
167
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
168
|
+
res.on("end", () => {
|
|
169
|
+
const buffer = Buffer2.concat(chunks);
|
|
170
|
+
const status = res.statusCode ?? 0;
|
|
171
|
+
resolve4({
|
|
172
|
+
ok: status >= 200 && status < 300,
|
|
173
|
+
status,
|
|
174
|
+
statusText: res.statusMessage ?? "",
|
|
175
|
+
headers: res.headers,
|
|
176
|
+
text: () => Promise.resolve(buffer.toString("utf8")),
|
|
177
|
+
json: () => Promise.resolve().then(() => JSON.parse(buffer.toString("utf8")))
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
res.on("error", reject);
|
|
181
|
+
};
|
|
182
|
+
const req = isHttps ? https.request(requestOptions, onResponse) : http.request(requestOptions, onResponse);
|
|
183
|
+
req.on("error", reject);
|
|
184
|
+
req.on("timeout", () => {
|
|
185
|
+
req.destroy(new Error(`Hub request to ${parsed.host} timed out after ${timeoutMs}ms`));
|
|
186
|
+
});
|
|
187
|
+
if (opts.body !== void 0) req.write(opts.body);
|
|
188
|
+
req.end();
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/commands/deploy.ts
|
|
142
193
|
function isRecord(value) {
|
|
143
194
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
144
195
|
}
|
|
@@ -167,7 +218,7 @@ async function withNetworkRetry(fn, opts) {
|
|
|
167
218
|
async function verifyHubHealthy(serverUrl, opts) {
|
|
168
219
|
for (let attempt = 1; attempt <= opts.attempts; attempt++) {
|
|
169
220
|
try {
|
|
170
|
-
const res = await
|
|
221
|
+
const res = await hubRequest(`${serverUrl}/health`, { timeoutMs: 5e3 });
|
|
171
222
|
if (res.ok) {
|
|
172
223
|
const body = await res.json().catch(() => ({}));
|
|
173
224
|
if (body.ok === true) return true;
|
|
@@ -326,7 +377,7 @@ function packAddon(addonDir) {
|
|
|
326
377
|
}
|
|
327
378
|
async function fetchOnlineAgents(serverUrl, token) {
|
|
328
379
|
const url = `${serverUrl}/trpc/nodes.topology?input=${encodeURIComponent('{"json":null}')}`;
|
|
329
|
-
const res = await
|
|
380
|
+
const res = await hubRequest(url, {
|
|
330
381
|
headers: { Authorization: `Bearer ${token}` }
|
|
331
382
|
});
|
|
332
383
|
if (!res.ok) {
|
|
@@ -341,15 +392,36 @@ async function fetchOnlineAgents(serverUrl, token) {
|
|
|
341
392
|
async function uploadToTarget(args) {
|
|
342
393
|
const { serverUrl, token, tgzPath, nodeId, addonId } = args;
|
|
343
394
|
const fileBuffer = fs2.readFileSync(tgzPath);
|
|
344
|
-
const
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
395
|
+
const boundary = `----camstackcli${randomBytes(16).toString("hex")}`;
|
|
396
|
+
const CRLF = "\r\n";
|
|
397
|
+
const parts = [
|
|
398
|
+
Buffer.from(
|
|
399
|
+
`--${boundary}${CRLF}Content-Disposition: form-data; name="file"; filename="${path2.basename(tgzPath)}"${CRLF}Content-Type: application/gzip${CRLF}${CRLF}`
|
|
400
|
+
),
|
|
401
|
+
fileBuffer,
|
|
402
|
+
Buffer.from(CRLF)
|
|
403
|
+
];
|
|
404
|
+
const appendField = (name, value) => {
|
|
405
|
+
parts.push(
|
|
406
|
+
Buffer.from(
|
|
407
|
+
`--${boundary}${CRLF}Content-Disposition: form-data; name="${name}"${CRLF}${CRLF}${value}${CRLF}`
|
|
408
|
+
)
|
|
409
|
+
);
|
|
410
|
+
};
|
|
411
|
+
if (nodeId) appendField("nodeId", nodeId);
|
|
412
|
+
if (addonId) appendField("addonId", addonId);
|
|
413
|
+
parts.push(Buffer.from(`--${boundary}--${CRLF}`));
|
|
414
|
+
const body = Buffer.concat(parts);
|
|
415
|
+
const response = await hubRequest(`${serverUrl}/api/addons/upload`, {
|
|
350
416
|
method: "POST",
|
|
351
|
-
headers: {
|
|
352
|
-
|
|
417
|
+
headers: {
|
|
418
|
+
Authorization: `Bearer ${token}`,
|
|
419
|
+
"Content-Type": `multipart/form-data; boundary=${boundary}`,
|
|
420
|
+
"Content-Length": String(body.length)
|
|
421
|
+
},
|
|
422
|
+
body,
|
|
423
|
+
// Large tarballs + a hub that may be mid-bounce — give it room.
|
|
424
|
+
timeoutMs: 12e4
|
|
353
425
|
});
|
|
354
426
|
const result = toDeployResult(await response.json().catch(() => ({})));
|
|
355
427
|
if (!response.ok) {
|
|
@@ -557,7 +629,7 @@ async function askSelect(message, options) {
|
|
|
557
629
|
async function callTrpcMutation(url, authorization, payload, isPayload) {
|
|
558
630
|
const headers = { "Content-Type": "application/json" };
|
|
559
631
|
if (authorization) headers["Authorization"] = authorization;
|
|
560
|
-
const res = await
|
|
632
|
+
const res = await hubRequest(url, {
|
|
561
633
|
method: "POST",
|
|
562
634
|
headers,
|
|
563
635
|
body: JSON.stringify({ "0": { json: payload } })
|
|
@@ -608,7 +680,7 @@ function isUnknown(_value) {
|
|
|
608
680
|
return true;
|
|
609
681
|
}
|
|
610
682
|
async function resolveServerInteractive(presetNamespace) {
|
|
611
|
-
const { discoverNodes, resolveHubFromDiscovered } = await import("./discover-
|
|
683
|
+
const { discoverNodes, resolveHubFromDiscovered, filterHubNodes, DEFAULT_HUB_HTTPS_PORT } = await import("./discover-3WTX7YIA.js");
|
|
612
684
|
if (presetNamespace) {
|
|
613
685
|
const spinner4 = clack.spinner();
|
|
614
686
|
spinner4.start(`Discovering hub on LAN (namespace "${presetNamespace}")`);
|
|
@@ -619,41 +691,36 @@ async function resolveServerInteractive(presetNamespace) {
|
|
|
619
691
|
}
|
|
620
692
|
const hub = await resolveHubFromDiscovered(filtered);
|
|
621
693
|
if (!hub) {
|
|
622
|
-
spinner4.stop(`Found ${filtered.length} node(s) but none
|
|
694
|
+
spinner4.stop(`Found ${filtered.length} node(s) but none served the hub API on HTTPS :4443.`);
|
|
623
695
|
throw new Error("Pass --server explicitly if the hub uses a non-default port.");
|
|
624
696
|
}
|
|
625
697
|
spinner4.stop(`Hub: ${hub.nodeID} @ https://${hub.address}:${hub.port}`);
|
|
626
698
|
return `https://${hub.address}:${hub.port}`;
|
|
627
699
|
}
|
|
628
700
|
const spinner3 = clack.spinner();
|
|
629
|
-
spinner3.start("
|
|
701
|
+
spinner3.start("Discovering camstack hub on LAN (UDP multicast)");
|
|
630
702
|
const all = await discoverNodes({});
|
|
631
|
-
|
|
632
|
-
|
|
703
|
+
const hubs = await filterHubNodes(all);
|
|
704
|
+
if (hubs.length === 0) {
|
|
705
|
+
spinner3.stop("No camstack hub responded on LAN.");
|
|
633
706
|
throw new Error("Either pass --server or verify the hub is running on the same network.");
|
|
634
707
|
}
|
|
635
|
-
|
|
708
|
+
if (hubs.length === 1) {
|
|
709
|
+
const only = hubs[0];
|
|
710
|
+
spinner3.stop(`Hub: ${only.nodeID} @ https://${only.address}:${DEFAULT_HUB_HTTPS_PORT}`);
|
|
711
|
+
return `https://${only.address}:${DEFAULT_HUB_HTTPS_PORT}`;
|
|
712
|
+
}
|
|
713
|
+
spinner3.stop(`Found ${hubs.length} hubs.`);
|
|
636
714
|
const chosen = await askSelect(
|
|
637
715
|
"Select a hub to log into",
|
|
638
|
-
|
|
639
|
-
value: `${n.address}|${n.
|
|
716
|
+
hubs.map((n) => ({
|
|
717
|
+
value: `${n.address}|${n.nodeID}`,
|
|
640
718
|
label: `${n.nodeID} ${n.address} (ns: ${n.namespace || "(none)"})`,
|
|
641
|
-
hint: `
|
|
719
|
+
hint: `https :${DEFAULT_HUB_HTTPS_PORT}`
|
|
642
720
|
}))
|
|
643
721
|
);
|
|
644
|
-
const [addr
|
|
645
|
-
|
|
646
|
-
const probeSpinner = clack.spinner();
|
|
647
|
-
probeSpinner.start(`Probing https://${chosenNode.address}:4443`);
|
|
648
|
-
const probed = await resolveHubFromDiscovered([chosenNode]);
|
|
649
|
-
if (!probed) {
|
|
650
|
-
probeSpinner.stop(
|
|
651
|
-
`Node ${chosenNode.nodeID} at ${chosenNode.address} did not respond on HTTPS :4443.`
|
|
652
|
-
);
|
|
653
|
-
throw new Error("Pass --server explicitly if the hub uses a non-default port.");
|
|
654
|
-
}
|
|
655
|
-
probeSpinner.stop(`Reachable at https://${probed.address}:${probed.port}`);
|
|
656
|
-
return `https://${probed.address}:${probed.port}`;
|
|
722
|
+
const [addr] = chosen.split("|");
|
|
723
|
+
return `https://${addr}:${DEFAULT_HUB_HTTPS_PORT}`;
|
|
657
724
|
}
|
|
658
725
|
var BACK_SENTINEL = "__back__";
|
|
659
726
|
async function askWithBack(label, defaultValue) {
|
|
@@ -822,13 +889,10 @@ async function whoamiCommand(opts) {
|
|
|
822
889
|
console.log("");
|
|
823
890
|
console.log(`[camstack] Pinging ${session.server}/api/auth/whoami\u2026`);
|
|
824
891
|
try {
|
|
825
|
-
const
|
|
826
|
-
const timer = setTimeout(() => controller.abort(), 3e3);
|
|
827
|
-
const res = await fetch(`${session.server}/api/auth/whoami`, {
|
|
892
|
+
const res = await hubRequest(`${session.server}/api/auth/whoami`, {
|
|
828
893
|
headers: { Authorization: `Bearer ${session.token}` },
|
|
829
|
-
|
|
894
|
+
timeoutMs: 3e3
|
|
830
895
|
});
|
|
831
|
-
clearTimeout(timer);
|
|
832
896
|
if (res.status === 401) {
|
|
833
897
|
clearSession(session.server);
|
|
834
898
|
console.error(`[camstack] \u2717 Token rejected by server (revoked / expired).`);
|
|
@@ -1230,7 +1294,7 @@ function printLine(e, addonId) {
|
|
|
1230
1294
|
async function fetchLogs(server, token, addonId, limit) {
|
|
1231
1295
|
const input = encodeURIComponent(JSON.stringify({ json: { addonId, limit } }));
|
|
1232
1296
|
const url = `${server}/trpc/addons.getLogs?input=${input}`;
|
|
1233
|
-
const res = await
|
|
1297
|
+
const res = await hubRequest(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
1234
1298
|
if (!res.ok) {
|
|
1235
1299
|
const text2 = await res.text().catch(() => "");
|
|
1236
1300
|
throw new Error(`getLogs HTTP ${res.status}: ${text2.slice(0, 200)}`);
|
|
@@ -1408,12 +1472,13 @@ function buildCommands() {
|
|
|
1408
1472
|
help: () => [
|
|
1409
1473
|
"Usage: camstack login [options]",
|
|
1410
1474
|
"",
|
|
1411
|
-
"Default flow (no flags):
|
|
1412
|
-
"
|
|
1475
|
+
"Default flow (no flags): auto-discovers the hub on the LAN via UDP multicast",
|
|
1476
|
+
" (hub-only \u2014 agents never surface), then prompts for user + password. When a",
|
|
1477
|
+
" single hub is found it is used automatically; multiple hubs show a picker.",
|
|
1413
1478
|
"",
|
|
1414
|
-
"Options:",
|
|
1479
|
+
"Options (all optional):",
|
|
1415
1480
|
" -s, --server <url> Skip discovery, use this URL directly ($CAMSTACK_SERVER)",
|
|
1416
|
-
" -n, --namespace <ns>
|
|
1481
|
+
" -n, --namespace <ns> Filter discovery to this cluster namespace ($CAMSTACK_NAMESPACE)",
|
|
1417
1482
|
" -u, --username <name> Username (skips prompt)",
|
|
1418
1483
|
" -p, --password <pwd> Password (skips prompt \u2014 prefer letting the CLI prompt)",
|
|
1419
1484
|
` --token-name <name> Name shown server-side (default: camstack-cli@${os4.hostname()})`
|
|
@@ -1421,12 +1486,13 @@ function buildCommands() {
|
|
|
1421
1486
|
},
|
|
1422
1487
|
{
|
|
1423
1488
|
name: "discover",
|
|
1424
|
-
summary: "Probe the LAN for camstack hubs
|
|
1489
|
+
summary: "Probe the LAN for camstack hubs via UDP multicast",
|
|
1425
1490
|
run: runDiscover,
|
|
1426
1491
|
help: () => [
|
|
1427
1492
|
"Usage: camstack discover [options]",
|
|
1428
1493
|
"",
|
|
1429
|
-
"With no flags: lists every camstack
|
|
1494
|
+
"With no flags: lists every camstack HUB visible on the LAN (any namespace).",
|
|
1495
|
+
" Agents are never listed \u2014 only nodes serving the hub HTTPS API on :4443.",
|
|
1430
1496
|
"",
|
|
1431
1497
|
"Options:",
|
|
1432
1498
|
" -n, --namespace <ns> Filter results to one namespace ($CAMSTACK_NAMESPACE)",
|
|
@@ -1590,7 +1656,12 @@ async function runLogin(args) {
|
|
|
1590
1656
|
namespace: { type: "string", short: "n" },
|
|
1591
1657
|
username: { type: "string", short: "u" },
|
|
1592
1658
|
password: { type: "string", short: "p" },
|
|
1593
|
-
"token-name": { type: "string" }
|
|
1659
|
+
"token-name": { type: "string" },
|
|
1660
|
+
// Accepted-and-ignored: `--no-verify` is a `deploy`-only flag (skip the
|
|
1661
|
+
// post-deploy health gate). Login has no verify step, but operators
|
|
1662
|
+
// muscle-memory it in — we swallow it here so `camstack login --no-verify`
|
|
1663
|
+
// never errors with "Unknown option --no-verify".
|
|
1664
|
+
"no-verify": { type: "boolean" }
|
|
1594
1665
|
},
|
|
1595
1666
|
false
|
|
1596
1667
|
);
|
|
@@ -1712,9 +1783,6 @@ function optionalString(values, key, outKey) {
|
|
|
1712
1783
|
return { [outKey ?? key]: v };
|
|
1713
1784
|
}
|
|
1714
1785
|
async function main() {
|
|
1715
|
-
if (!process.env.CAMSTACK_STRICT_TLS) {
|
|
1716
|
-
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
1717
|
-
}
|
|
1718
1786
|
maybeNotifyUpdate("camstack", pkgVersion);
|
|
1719
1787
|
const argv = process.argv.slice(2);
|
|
1720
1788
|
const commands = buildCommands();
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
DEFAULT_HUB_HTTPS_PORT,
|
|
3
4
|
discoverNodes,
|
|
5
|
+
filterHubNodes,
|
|
4
6
|
resolveHubFromDiscovered,
|
|
5
7
|
runDiscover
|
|
6
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-VL5TLTQF.js";
|
|
7
9
|
import "./chunk-K3NQKI34.js";
|
|
8
10
|
export {
|
|
11
|
+
DEFAULT_HUB_HTTPS_PORT,
|
|
9
12
|
discoverNodes,
|
|
13
|
+
filterHubNodes,
|
|
10
14
|
resolveHubFromDiscovered,
|
|
11
15
|
runDiscover
|
|
12
16
|
};
|