@camstack/addon-provider-homeassistant 1.2.648 → 1.2.650
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/CHANGELOG.md +13 -0
- package/dist/addon.js +2 -1
- package/dist/addon.mjs +2 -1
- package/dist/{ha-broker-source-iLA_DsW8.mjs → ha-broker-source-B2v2dnPe.mjs} +294 -105
- package/dist/{ha-broker-source-Ba20mhmb.js → ha-broker-source-DUj9pUWr.js} +294 -105
- package/dist/ha-export/ha-export.addon.js +123 -41
- package/dist/ha-export/ha-export.addon.mjs +123 -41
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as consumableKindOf, Bt as BaseAddon, Dt as sendToExportedTarget, Ht as baseUnitOf, Jt as isReadingQuantity, K as deviceExportCapability, Wt as defineAddonMigrations, Yt as nodePin, Zt as array, _ as PLUG_APPLIANCE_TYPES, _t as pruneDeletedEntities, c as COCO_TO_MACRO, d as ConsumableItemSchema, f as ConsumableStatusSchema, fn as EventCategory, g as MediaPlayerRepeatSchema, h as HvacModeSchema, i as AlarmArmModeSchema, in as string, j as buildAddonRouteProvider, l as CameraSwitchIdSchema, m as FanDirectionSchema, nn as object, on as unknown, pt as preconditionOf, r as listHomeAssistantBrokers, s as CAMERA_SWITCH_ORDER, u as ConsumableFormSchema, ut as oauthIntegrationCapability, x as addonRoutesCapability, xt as runExportedAction, z as consumableFormOf } from "../ha-broker-source-
|
|
1
|
+
import { B as consumableKindOf, Bt as BaseAddon, Dt as sendToExportedTarget, Ht as baseUnitOf, Jt as isReadingQuantity, K as deviceExportCapability, Wt as defineAddonMigrations, Yt as nodePin, Zt as array, _ as PLUG_APPLIANCE_TYPES, _t as pruneDeletedEntities, c as COCO_TO_MACRO, d as ConsumableItemSchema, f as ConsumableStatusSchema, fn as EventCategory, g as MediaPlayerRepeatSchema, h as HvacModeSchema, i as AlarmArmModeSchema, in as string, j as buildAddonRouteProvider, l as CameraSwitchIdSchema, m as FanDirectionSchema, nn as object, on as unknown, pt as preconditionOf, r as listHomeAssistantBrokers, s as CAMERA_SWITCH_ORDER, u as ConsumableFormSchema, ut as oauthIntegrationCapability, x as addonRoutesCapability, xt as runExportedAction, z as consumableFormOf } from "../ha-broker-source-B2v2dnPe.mjs";
|
|
2
2
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
3
3
|
import "node:zlib";
|
|
4
4
|
import "node:fs";
|
|
@@ -940,19 +940,22 @@ var NATIVE_CAP_PLATFORMS = {
|
|
|
940
940
|
*/
|
|
941
941
|
cover: {
|
|
942
942
|
platform: "cover",
|
|
943
|
+
availableWhenOnline: true,
|
|
943
944
|
primary: { from: null },
|
|
944
945
|
controls: {
|
|
945
946
|
position: {
|
|
946
947
|
from: "position",
|
|
947
948
|
min: 0,
|
|
948
949
|
max: 100,
|
|
949
|
-
step: 1
|
|
950
|
+
step: 1,
|
|
951
|
+
requiresFeature: "cover-positionable"
|
|
950
952
|
},
|
|
951
953
|
tilt: {
|
|
952
954
|
from: "tilt",
|
|
953
955
|
min: 0,
|
|
954
956
|
max: 100,
|
|
955
|
-
step: 1
|
|
957
|
+
step: 1,
|
|
958
|
+
requiresFeature: "cover-tilt"
|
|
956
959
|
},
|
|
957
960
|
open: {
|
|
958
961
|
from: "open",
|
|
@@ -964,7 +967,8 @@ var NATIVE_CAP_PLATFORMS = {
|
|
|
964
967
|
},
|
|
965
968
|
stop: {
|
|
966
969
|
from: "stop",
|
|
967
|
-
commandOnly: true
|
|
970
|
+
commandOnly: true,
|
|
971
|
+
requiresFeature: "cover-stoppable"
|
|
968
972
|
}
|
|
969
973
|
}
|
|
970
974
|
},
|
|
@@ -1353,9 +1357,11 @@ function buildNativeComponent(input) {
|
|
|
1353
1357
|
if (native === void 0) return null;
|
|
1354
1358
|
const primary = resolveBinding(input, native.primary);
|
|
1355
1359
|
if (primary === null) return null;
|
|
1360
|
+
const features = input.features ?? [];
|
|
1356
1361
|
const absorbed = new Set([derivedEntityId(input.capName), ...primary.entities]);
|
|
1357
1362
|
const controls = {};
|
|
1358
1363
|
for (const [name, binding] of Object.entries(native.controls ?? {})) {
|
|
1364
|
+
if (binding.requiresFeature !== void 0 && !features.includes(binding.requiresFeature)) continue;
|
|
1359
1365
|
const resolved = resolveBinding(input, binding);
|
|
1360
1366
|
if (resolved === null) continue;
|
|
1361
1367
|
controls[name] = resolved.control;
|
|
@@ -1374,6 +1380,11 @@ function buildNativeComponent(input) {
|
|
|
1374
1380
|
...primary.control,
|
|
1375
1381
|
...native.deviceClass !== void 0 ? { device_class: native.deviceClass } : {},
|
|
1376
1382
|
...native.icon !== void 0 ? { icon: native.icon } : {},
|
|
1383
|
+
...native.availableWhenOnline === true ? {
|
|
1384
|
+
availability_topic: stateTopic(input.deviceKey, "online"),
|
|
1385
|
+
payload_available: "true",
|
|
1386
|
+
payload_not_available: "false"
|
|
1387
|
+
} : {},
|
|
1377
1388
|
...Object.keys(controls).length > 0 ? { controls } : {},
|
|
1378
1389
|
...native.constants,
|
|
1379
1390
|
...flags
|
|
@@ -2308,7 +2319,8 @@ var CAP_ENTITY_MAP = {
|
|
|
2308
2319
|
unit: "%",
|
|
2309
2320
|
icon: "mdi:arrow-up-down",
|
|
2310
2321
|
writable: true,
|
|
2311
|
-
range: PERCENT_RANGE
|
|
2322
|
+
range: PERCENT_RANGE,
|
|
2323
|
+
requiresFeature: "cover-positionable"
|
|
2312
2324
|
},
|
|
2313
2325
|
tilt: {
|
|
2314
2326
|
platform: "number",
|
|
@@ -2318,7 +2330,8 @@ var CAP_ENTITY_MAP = {
|
|
|
2318
2330
|
icon: "mdi:angle-acute",
|
|
2319
2331
|
writable: true,
|
|
2320
2332
|
range: PERCENT_RANGE,
|
|
2321
|
-
enabledByDefault: false
|
|
2333
|
+
enabledByDefault: false,
|
|
2334
|
+
requiresFeature: "cover-tilt"
|
|
2322
2335
|
}
|
|
2323
2336
|
},
|
|
2324
2337
|
commands: {
|
|
@@ -2333,7 +2346,8 @@ var CAP_ENTITY_MAP = {
|
|
|
2333
2346
|
stop: {
|
|
2334
2347
|
label: "Stop",
|
|
2335
2348
|
icon: "mdi:stop",
|
|
2336
|
-
enabledByDefault: false
|
|
2349
|
+
enabledByDefault: false,
|
|
2350
|
+
requiresFeature: "cover-stoppable"
|
|
2337
2351
|
}
|
|
2338
2352
|
}
|
|
2339
2353
|
},
|
|
@@ -3340,7 +3354,8 @@ function capEntities(capName) {
|
|
|
3340
3354
|
for (const [suffix, extra] of Object.entries(mapping.extras ?? {})) refs.push({
|
|
3341
3355
|
entity: derivedExtraEntityId(capName, suffix),
|
|
3342
3356
|
label: extra.label,
|
|
3343
|
-
descriptor: extra
|
|
3357
|
+
descriptor: extra,
|
|
3358
|
+
...extra.requiresFeature !== void 0 ? { requiresFeature: extra.requiresFeature } : {}
|
|
3344
3359
|
});
|
|
3345
3360
|
return refs;
|
|
3346
3361
|
}
|
|
@@ -3359,8 +3374,9 @@ function deriveCapValue(derivation, raw) {
|
|
|
3359
3374
|
return derivation.kind === "equals" ? raw === derivation.value : raw !== derivation.value;
|
|
3360
3375
|
}
|
|
3361
3376
|
/** A capability's entities as the catalog builders consume them. */
|
|
3362
|
-
function capEntitySpecs(capName) {
|
|
3363
|
-
const
|
|
3377
|
+
function capEntitySpecs(capName, features = []) {
|
|
3378
|
+
const declared = (required) => required === void 0 || features.includes(required);
|
|
3379
|
+
const specs = capEntities(capName).filter((ref) => declared(ref.requiresFeature)).map((ref) => ({
|
|
3364
3380
|
entity: ref.entity,
|
|
3365
3381
|
platform: ref.descriptor.platform,
|
|
3366
3382
|
label: ref.label,
|
|
@@ -3373,15 +3389,18 @@ function capEntitySpecs(capName) {
|
|
|
3373
3389
|
...ref.descriptor.entityCategory !== void 0 ? { entityCategory: ref.descriptor.entityCategory } : {},
|
|
3374
3390
|
...ref.descriptor.writable === true ? { writable: true } : {}
|
|
3375
3391
|
}));
|
|
3376
|
-
for (const ref of capCommandEntities(capName))
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3392
|
+
for (const ref of capCommandEntities(capName)) {
|
|
3393
|
+
if (!declared(ref.command.requiresFeature)) continue;
|
|
3394
|
+
specs.push({
|
|
3395
|
+
entity: ref.entity,
|
|
3396
|
+
platform: "button",
|
|
3397
|
+
label: ref.label,
|
|
3398
|
+
enabledByDefault: ref.command.enabledByDefault ?? true,
|
|
3399
|
+
writable: true,
|
|
3400
|
+
...ref.command.icon !== void 0 ? { icon: ref.command.icon } : {},
|
|
3401
|
+
...ref.command.entityCategory !== void 0 ? { entityCategory: ref.command.entityCategory } : {}
|
|
3402
|
+
});
|
|
3403
|
+
}
|
|
3385
3404
|
return specs;
|
|
3386
3405
|
}
|
|
3387
3406
|
function readControlOptions(slice) {
|
|
@@ -3759,11 +3778,12 @@ function buildDerivedPlan(device, options = UNNEGOTIATED_PLAN_OPTIONS) {
|
|
|
3759
3778
|
deviceId: device.id,
|
|
3760
3779
|
capName: cap,
|
|
3761
3780
|
label: humanise(cap),
|
|
3781
|
+
features: device.features,
|
|
3762
3782
|
slice: device.capSlices?.[cap],
|
|
3763
3783
|
mapping
|
|
3764
3784
|
});
|
|
3765
3785
|
if (plan === null) {
|
|
3766
|
-
specs.push(...capEntitySpecs(cap).map((spec) => degradeUnsupported(spec, options)));
|
|
3786
|
+
specs.push(...capEntitySpecs(cap, device.features).map((spec) => degradeUnsupported(spec, options)));
|
|
3767
3787
|
continue;
|
|
3768
3788
|
}
|
|
3769
3789
|
native[toComponentKey(plan.component.platform, derivedEntityId(cap))] = plan.component;
|
|
@@ -3774,7 +3794,7 @@ function buildDerivedPlan(device, options = UNNEGOTIATED_PLAN_OPTIONS) {
|
|
|
3774
3794
|
* to make the device look tidy would be a silent loss of a value an
|
|
3775
3795
|
* operator already automates on.
|
|
3776
3796
|
*/
|
|
3777
|
-
specs.push(...capEntitySpecs(cap).filter((spec) => !plan.absorbed.has(spec.entity)));
|
|
3797
|
+
specs.push(...capEntitySpecs(cap, device.features).filter((spec) => !plan.absorbed.has(spec.entity)));
|
|
3778
3798
|
}
|
|
3779
3799
|
const assembled = assemble(device, specs);
|
|
3780
3800
|
return {
|
|
@@ -4636,6 +4656,15 @@ function isCameraCommand(command) {
|
|
|
4636
4656
|
function isCapCommand(command) {
|
|
4637
4657
|
return !isCameraCommand(command);
|
|
4638
4658
|
}
|
|
4659
|
+
var APPLIED = { ok: true };
|
|
4660
|
+
/** The 422 body of a command that was not applied: the reason travels with it. */
|
|
4661
|
+
function commandNotAppliedBody(refusal) {
|
|
4662
|
+
return {
|
|
4663
|
+
error: "command not applied",
|
|
4664
|
+
reason: refusal.reason,
|
|
4665
|
+
...refusal.message !== void 0 ? { message: refusal.message } : {}
|
|
4666
|
+
};
|
|
4667
|
+
}
|
|
4639
4668
|
/**
|
|
4640
4669
|
* Make the call the command names.
|
|
4641
4670
|
*
|
|
@@ -4915,18 +4944,29 @@ async function call(slice, capName, make) {
|
|
|
4915
4944
|
*
|
|
4916
4945
|
* Idempotent: a store without the key yields an empty plan, which writes nothing.
|
|
4917
4946
|
*/
|
|
4918
|
-
/** The
|
|
4919
|
-
var
|
|
4947
|
+
/** The addon id, the prefix of its rows in the canonical settings table. */
|
|
4948
|
+
var HA_EXPORT_ADDON_ID = "homeassistant-export";
|
|
4949
|
+
/**
|
|
4950
|
+
* Where the addon's global settings live: the canonical `addon-settings` table, one row per
|
|
4951
|
+
* key, id `<addonId>.<key>`, data `{ addonId, key, value }` (never a per-addon scoped table,
|
|
4952
|
+
* which exists only when something lazily declared it).
|
|
4953
|
+
*/
|
|
4954
|
+
var ADDON_SETTINGS_TABLE = "addon-settings";
|
|
4920
4955
|
var RETIRED_URL_KEYS = ["publicBaseUrl"];
|
|
4921
4956
|
async function planDropPublicBaseUrl(ports) {
|
|
4922
4957
|
const stored = await ports.readStored();
|
|
4923
4958
|
const present = RETIRED_URL_KEYS.filter((key) => key in stored);
|
|
4924
4959
|
if (present.length === 0) return { edits: [] };
|
|
4960
|
+
const rowId = (key) => `${HA_EXPORT_ADDON_ID}.${key}`;
|
|
4925
4961
|
return { edits: [{
|
|
4926
4962
|
op: "deleteKeys",
|
|
4927
|
-
scope:
|
|
4928
|
-
keys:
|
|
4929
|
-
precondition: preconditionOf(Object.fromEntries(present.map((key) => [key,
|
|
4963
|
+
scope: ADDON_SETTINGS_TABLE,
|
|
4964
|
+
keys: present.map(rowId),
|
|
4965
|
+
precondition: preconditionOf(Object.fromEntries(present.map((key) => [rowId(key), {
|
|
4966
|
+
addonId: HA_EXPORT_ADDON_ID,
|
|
4967
|
+
key,
|
|
4968
|
+
value: stored[key]
|
|
4969
|
+
}])))
|
|
4930
4970
|
}] };
|
|
4931
4971
|
}
|
|
4932
4972
|
//#endregion
|
|
@@ -9122,9 +9162,23 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9122
9162
|
reply.send({ error: `unroutable command (${resolution.reason})` });
|
|
9123
9163
|
return;
|
|
9124
9164
|
}
|
|
9125
|
-
const
|
|
9126
|
-
|
|
9127
|
-
|
|
9165
|
+
const outcome = await this.dispatch(resolution.command);
|
|
9166
|
+
if (outcome.ok) {
|
|
9167
|
+
reply.status(200);
|
|
9168
|
+
reply.send({});
|
|
9169
|
+
return;
|
|
9170
|
+
}
|
|
9171
|
+
this.ctx.logger.warn("ha-export: command answered 422", {
|
|
9172
|
+
tags: { deviceId: exported.deviceId },
|
|
9173
|
+
meta: {
|
|
9174
|
+
topic,
|
|
9175
|
+
entity: parsed.entity,
|
|
9176
|
+
reason: outcome.reason,
|
|
9177
|
+
message: outcome.message
|
|
9178
|
+
}
|
|
9179
|
+
});
|
|
9180
|
+
reply.status(422);
|
|
9181
|
+
reply.send(commandNotAppliedBody(outcome));
|
|
9128
9182
|
}
|
|
9129
9183
|
async dispatch(command) {
|
|
9130
9184
|
try {
|
|
@@ -9138,7 +9192,7 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9138
9192
|
*/
|
|
9139
9193
|
if (isCapCommand(command)) {
|
|
9140
9194
|
const outcome = await applyCapCommand(await this.ctx.fetchDevice(command.deviceId), command);
|
|
9141
|
-
if (outcome.ok) return
|
|
9195
|
+
if (outcome.ok) return APPLIED;
|
|
9142
9196
|
if (outcome.reason === "no-provider") return this.noProvider(command.deviceId, outcome.capName);
|
|
9143
9197
|
this.ctx.logger.warn("ha-export: the device refused the command", {
|
|
9144
9198
|
tags: { deviceId: command.deviceId },
|
|
@@ -9148,7 +9202,11 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9148
9202
|
...outcome.error !== void 0 ? { error: outcome.error } : {}
|
|
9149
9203
|
}
|
|
9150
9204
|
});
|
|
9151
|
-
return
|
|
9205
|
+
return {
|
|
9206
|
+
ok: false,
|
|
9207
|
+
reason: outcome.reason,
|
|
9208
|
+
...outcome.error !== void 0 ? { message: outcome.error } : {}
|
|
9209
|
+
};
|
|
9152
9210
|
}
|
|
9153
9211
|
switch (command.kind) {
|
|
9154
9212
|
case "camera-function": {
|
|
@@ -9158,7 +9216,11 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9158
9216
|
tags: { deviceId: command.deviceId },
|
|
9159
9217
|
meta: { switchId: command.switchId }
|
|
9160
9218
|
});
|
|
9161
|
-
return
|
|
9219
|
+
return {
|
|
9220
|
+
ok: false,
|
|
9221
|
+
reason: "unknown-function",
|
|
9222
|
+
message: "the switch id is not a camera function"
|
|
9223
|
+
};
|
|
9162
9224
|
}
|
|
9163
9225
|
/**
|
|
9164
9226
|
* A FRESH read for this one device — the pass-level caches are up to
|
|
@@ -9168,7 +9230,11 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9168
9230
|
const device = await this.ctx.api.deviceManager.getDevice.query({ deviceId: command.deviceId });
|
|
9169
9231
|
if (device === null) {
|
|
9170
9232
|
this.ctx.logger.warn("ha-export: dropping a camera-function command, the device is not in the registry", { tags: { deviceId: command.deviceId } });
|
|
9171
|
-
return
|
|
9233
|
+
return {
|
|
9234
|
+
ok: false,
|
|
9235
|
+
reason: "device-not-found",
|
|
9236
|
+
message: "the device is not in the registry"
|
|
9237
|
+
};
|
|
9172
9238
|
}
|
|
9173
9239
|
const privacySlice = await this.ctx.api.deviceState.getCapSlice.query({
|
|
9174
9240
|
deviceId: command.deviceId,
|
|
@@ -9204,22 +9270,26 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9204
9270
|
tags: { deviceId: command.deviceId },
|
|
9205
9271
|
meta: { switchId: parsed.data }
|
|
9206
9272
|
});
|
|
9207
|
-
return
|
|
9273
|
+
return {
|
|
9274
|
+
ok: false,
|
|
9275
|
+
reason: "function-unavailable",
|
|
9276
|
+
message: "the camera function is not available"
|
|
9277
|
+
};
|
|
9208
9278
|
}
|
|
9209
9279
|
await applyCameraFunctionWrite(this.ctx.api, command.deviceId, write, sourceNodeId);
|
|
9210
|
-
return
|
|
9280
|
+
return APPLIED;
|
|
9211
9281
|
}
|
|
9212
9282
|
case "reboot": {
|
|
9213
9283
|
const device = await this.ctx.fetchDevice(command.deviceId);
|
|
9214
9284
|
if (device.reboot === void 0) return this.noProvider(command.deviceId, "reboot");
|
|
9215
9285
|
await device.reboot.reboot({});
|
|
9216
|
-
return
|
|
9286
|
+
return APPLIED;
|
|
9217
9287
|
}
|
|
9218
9288
|
case "ptz-move": {
|
|
9219
9289
|
const device = await this.ctx.fetchDevice(command.deviceId);
|
|
9220
9290
|
if (device.ptz === void 0) return this.noProvider(command.deviceId, "ptz");
|
|
9221
9291
|
await device.ptz.move(PTZ_MOVE_BY_DIRECTION[command.direction] ?? {});
|
|
9222
|
-
return
|
|
9292
|
+
return APPLIED;
|
|
9223
9293
|
}
|
|
9224
9294
|
case "ptz-preset": {
|
|
9225
9295
|
const device = await this.ctx.fetchDevice(command.deviceId);
|
|
@@ -9230,10 +9300,14 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9230
9300
|
tags: { deviceId: command.deviceId },
|
|
9231
9301
|
meta: { preset: command.preset }
|
|
9232
9302
|
});
|
|
9233
|
-
return
|
|
9303
|
+
return {
|
|
9304
|
+
ok: false,
|
|
9305
|
+
reason: "no-such-preset",
|
|
9306
|
+
message: "no preset by that name"
|
|
9307
|
+
};
|
|
9234
9308
|
}
|
|
9235
9309
|
await device.ptz.goToPreset({ presetId: preset.id });
|
|
9236
|
-
return
|
|
9310
|
+
return APPLIED;
|
|
9237
9311
|
}
|
|
9238
9312
|
}
|
|
9239
9313
|
} catch (err) {
|
|
@@ -9249,7 +9323,11 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9249
9323
|
error: errMsg(err)
|
|
9250
9324
|
}
|
|
9251
9325
|
});
|
|
9252
|
-
return
|
|
9326
|
+
return {
|
|
9327
|
+
ok: false,
|
|
9328
|
+
reason: "error",
|
|
9329
|
+
message: errMsg(err)
|
|
9330
|
+
};
|
|
9253
9331
|
}
|
|
9254
9332
|
}
|
|
9255
9333
|
/** A cap the device does not provide. Refused, and never silently. */
|
|
@@ -9258,7 +9336,11 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9258
9336
|
tags: { deviceId },
|
|
9259
9337
|
meta: { capName }
|
|
9260
9338
|
});
|
|
9261
|
-
return
|
|
9339
|
+
return {
|
|
9340
|
+
ok: false,
|
|
9341
|
+
reason: "no-provider",
|
|
9342
|
+
message: `the device has no provider for ${capName}`
|
|
9343
|
+
};
|
|
9262
9344
|
}
|
|
9263
9345
|
/**
|
|
9264
9346
|
* A public, signed, expiring route Home Assistant can fetch bytes from.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-provider-homeassistant",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.650",
|
|
4
4
|
"description": "Home Assistant device provider addon for CamStack",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
"description": "Device provider (import) + native entity export (push). Two addons, one bundle, separate processes."
|
|
128
128
|
},
|
|
129
129
|
"requires": {
|
|
130
|
-
"server": ">=1.2.
|
|
130
|
+
"server": ">=1.2.740"
|
|
131
131
|
}
|
|
132
132
|
},
|
|
133
133
|
"files": [
|