64 Commits

Author SHA1 Message Date
Nick Farina
7a561f8754 Move slackin deployment to mitigate NodeJS security issue 2017-07-14 15:06:21 -07:00
Khaos Tian
d9d58855cd 0.4.22 2017-07-02 14:46:27 -07:00
Khaos Tian
3acded3ea2 Bump hap-nodejs version.
This version addressed connectivity problem with node 8.0
2017-07-02 14:46:23 -07:00
Khaos Tian
8a35d75cb5 0.4.21 2017-07-01 22:02:04 -06:00
Khaos Tian
c70cb8be07 Bump hap-nodejs version 2017-07-01 22:01:56 -06:00
Olivier Guerriat
7406f6c9f3 Added "limitations" to the README (#1338) 2017-06-10 12:09:09 -07:00
Brent Unkrich
cbae68afdd updated formatting (#1284)
took out extra spaces and fixed capitalization.
2017-05-11 19:23:08 -07:00
Khaos Tian
495143e1a1 Update hap-nodejs version 2017-05-01 23:30:05 -07:00
Khaos Tian
7585cf3f83 0.4.19
Bump hap-nodejs version to add support for programmable switch label.
2017-04-16 17:35:44 -07:00
Khaos Tian
d521755a49 0.4.18
Add support for updated programmable service profile.
2017-04-16 13:32:00 -07:00
Khaos Tian
6f4ce80aea Bump hap-nodejs version. 2017-04-07 22:06:17 -07:00
Stephan Esch
efda0fac11 Added serverVersion to API; bumped API version to 2.2 (#1064)
* Added serverVersion to API; bumped API version to 2.1.1

* Fixed version number to 2.2
2017-01-13 09:48:56 -08:00
Khaos Tian
600760884d Address a metadata issue that blocks camera support. 2016-12-13 15:50:19 +08:00
Khaos Tian
3a4830ee57 Update hap-nodejs to support new services in iOS 10.2 2016-12-13 11:05:30 +08:00
Khaos Tian
582e00a6ef 0.4.12 2016-12-10 14:23:53 -08:00
Khaos Tian
debba05d2f Merge pull request #967 from andig/patch-1
Upgrade hap-nodejs
2016-12-02 01:20:34 -08:00
andig
027a693c0d Update package.json 2016-12-02 10:19:58 +01:00
andig
b5e1fc52a8 Upgrade hap-nodejs 2016-12-02 10:18:49 +01:00
Khaos Tian
f17fe59590 Fix an warning in nodejs v7 2016-11-29 18:42:17 -08:00
Khaos Tian
69e3ed5ee4 Merge pull request #491 from straccio/master
Added pluginPath based on npm
2016-11-28 13:21:27 -08:00
Khaos Tian
3354842e81 Merge pull request #917 from crzcrz/master
Use chalk instead of hardcoded ANSI codes
2016-11-28 13:17:35 -08:00
straccio
e5e2a400ec Merge remote-tracking branch 'nfarina/master'
# Conflicts:
#	lib/plugin.js
2016-11-28 13:36:00 +01:00
Khaos Tian
4884087041 0.4.10 2016-11-27 15:24:43 -08:00
Khaos Tian
e1867b2bc0 Improve error handling. 2016-11-27 15:24:27 -08:00
Khaos Tian
4d0f9d86f6 Merge pull request #941 from djMax/master
Allow externally specified config
2016-11-21 12:04:57 -08:00
Max Metral
ecda18029f Allow externally specified config 2016-11-21 14:58:39 -05:00
straccio
7acac442a8 Merge remote-tracking branch 'nfarina/master' 2016-11-21 09:12:23 +01:00
Khaos Tian
efc570e5a9 Update example plugin
Add comment for service name.
2016-11-18 00:33:36 -08:00
straccio
7955049337 Merge remote-tracking branch 'nfarina/master' 2016-11-14 11:24:01 +01:00
Stanislav Kljuhhin
8c6cb53dcb Use chalk instead of hardcoded ANSI codes
Improves the readability of the source code and prevents the raw ANSI codes from appearing in the log, if ran as a systemd service.
2016-11-13 16:29:10 +01:00
Khaos Tian
b6cfe3ba7c 0.4.9 2016-11-09 18:11:57 -08:00
Khaos Tian
f836d4a42c Bump hap-nodejs version to fix #791 2016-11-09 18:11:41 -08:00
straccio
f893322887 Merge remote-tracking branch 'nfarina/master' 2016-11-04 10:05:11 +01:00
Khaos Tian
63ab1025e9 Update hap-nodejs to allow homebridge work with nodejs v4.x 2016-11-03 12:20:46 -07:00
straccio
9a25a363d4 Merge remote-tracking branch 'nfarina/master' 2016-11-03 17:05:53 +01:00
Khaos Tian
dc43d0b7c4 Update hap-nodejs
This version requires node v5.10.0 or later.
2016-11-01 18:33:59 -07:00
straccio
1513e5398f Merge remote-tracking branch 'nfarina/master' 2016-10-11 15:52:58 +02:00
Khaos Tian
7c3543ba61 Merge pull request #839 from rxseger/collision-name
Fix camera name in collision error message
2016-10-09 17:14:29 -07:00
rxseger
5adb5f3282 Fix camera name in collision error message 2016-10-09 23:36:53 +00:00
straccio
ffe343c65f Merge remote-tracking branch 'nfarina/master' 2016-10-03 09:24:28 +02:00
Khaos Tian
fedd341970 0.4.6 2016-10-01 16:48:05 -07:00
Khaos Tian
c7c9aa0150 Bump hap-nodejs version. 2016-10-01 16:48:01 -07:00
straccio
a2baa93801 Merge remote-tracking branch 'nfarina/master' 2016-09-29 09:00:45 +02:00
straccio
8192fc2672 Merge remote-tracking branch 'nfarina/master' 2016-09-28 08:19:30 +02:00
straccio
332385d605 Merge remote-tracking branch 'nfarina/master' 2016-09-27 08:38:18 +02:00
straccio
41c53f8f10 Merge remote-tracking branch 'nfarina/master' 2016-09-20 09:17:15 +02:00
straccio
4251b15291 Merge remote-tracking branch 'nfarina/master' 2016-05-31 08:52:59 +02:00
straccio
3f2cd08383 Merge remote-tracking branch 'nfarina/master' 2016-05-02 16:44:42 +02:00
straccio
c8cb0731ff Merge remote-tracking branch 'nfarina/master' 2016-04-11 09:45:54 +02:00
straccio
39af2ebbef Merge remote-tracking branch 'nfarina/master'
# Conflicts:
#	lib/logger.js
2016-03-21 08:43:36 +01:00
straccio
f73783787d Merge remote-tracking branch 'nfarina/master' 2016-03-11 08:18:56 +01:00
straccio
b94c3caa3b Changed color for logging timestamp to white. 2016-03-08 15:59:44 +01:00
straccio
1a710badef Merge remote-tracking branch 'nfarina/master' 2016-03-08 08:45:04 +01:00
straccio
73fdec5928 Revert to upstream/master, no need to skip cached accessories 2016-03-08 08:44:52 +01:00
straccio
911f088df9 A way to skip cached accessories from being loaded in HAP 2016-03-07 17:32:18 +01:00
straccio
6fade3c3cc Merge remote-tracking branch 'nfarina/master' 2016-03-07 17:28:41 +01:00
straccio
191c75c281 Merge remote-tracking branch 'upstream/master' 2016-03-02 08:58:03 +01:00
straccio
1fb58be2b9 ignore idea 2016-03-02 08:47:15 +01:00
straccio
ca66cc3499 Merge branch 'master' of https://github.com/straccio/homebridge 2016-02-29 09:06:32 +01:00
straccio
6ae2a19d37 missed ";" 2016-02-29 09:05:42 +01:00
straccio
ffe4232c3b Added pluginPath based on npm 2016-02-29 09:05:42 +01:00
straccio
fa9561d98a missed ";" 2016-02-22 10:01:25 +01:00
straccio
16a29f302d Merge remote-tracking branch 'upstream/master' 2016-02-22 08:53:35 +01:00
straccio
40fc7acbed Added pluginPath based on npm 2016-01-27 07:40:26 +01:00
10 changed files with 79 additions and 61 deletions

2
.gitignore vendored
View File

@@ -10,3 +10,5 @@ npm-debug.log
# Ignore any extra plugins in the example directory that aren't in Git already
# (this is a sandbox for the user)
example-plugins
.idea

View File

@@ -1,5 +1,5 @@
[![Slack Status](https://homebridge-slackin.herokuapp.com/badge.svg)](https://homebridge-slackin.herokuapp.com)
[![Slack Status](https://homebridge-slackin.herokuapp.com/badge.svg)](https://slackin-xiwztisllv.now.sh)
# Homebridge
@@ -49,9 +49,9 @@ Once you've installed a Plugin or two, you can run Homebridge again:
However, Homebridge won't do anything until you've created a `config.json` file containing your accessories and/or platforms. You can start by copying and modifying the included `config-sample.json` file which includes declarations for some example accessories and platforms. Each Plugin will have its own expected configuration; the documentation for Plugins should give you some real-world examples for that plugin.
**NOTE**: Your `config.json` file MUST be inside of `.homebridge`, which is inside of your home folder. On MacOS and Linux, the full path for your `config.json` would be `~/.homebridge/config.json`. Any error messages will contain the exact path where your config is expected to be found.
**NOTE**: Your `config.json` file MUST be inside of `.homebridge`, which is inside of your home folder. On macOS and Linux, the full path for your `config.json` would be `~/.homebridge/config.json`. Any error messages will contain the exact path where your config is expected to be found.
**REALLY IMPORTANT**: You must use a "plain text" editor to create or modify `config.json`. Do NOT use apps like TextEdit on Mac or Wordpad on Windows. Apps like these will corrupt the formatting of the file in hard-to-debug ways, making improper `"` signs is an example. I suggest using the free [Atom text editor](http://atom.io).
**REALLY IMPORTANT**: You must use a "plain text" editor to create or modify `config.json`. Do NOT use apps like TextEdit on Mac or Wordpad on Windows. Apps like these will corrupt the formatting of the file in hard-to-debug ways, making improper `"` signs is an example. I suggest using the free [Atom text editor](http://atom.io).
Once you've added your config file, you should be able to run Homebridge again:
@@ -150,6 +150,11 @@ The following errors are experienced when starting Homebridge and can be safely
*** WARNING *** For more information see http://0pointerde/avahi-compat?s=libdns_sd&e=nodejs&f=DNSServiceRegister
```
### Limitations
* One installation of Homebridge can only expose 100 accessories due to a HomeKit limit. You can however run multiple Homebridge instances by pointing them to different config and persistence paths (see issue #827).
* Once an accessory has been added to the Home app, changing its name via Homebridge won't be automatically reflected in iOS. You must change it via the Home app as well.
# Why Homebridge?
Technically, the device manufacturers should be the ones implementing the HomeKit API. And I'm sure they will - eventually. When they do, this project will be obsolete, and I hope that happens soon. In the meantime, Homebridge is a fun way to get a taste of the future, for those who just can't bear to wait until "real" HomeKit devices are on the market.
@@ -157,5 +162,3 @@ Technically, the device manufacturers should be the ones implementing the HomeKi
# Credit
The original HomeKit API work was done by [KhaosT](http://twitter.com/khaost) in his [HAP-NodeJS](https://github.com/KhaosT/HAP-NodeJS) project.

View File

@@ -189,7 +189,8 @@ SamplePlatform.prototype.addAccessory = function(accessoryName) {
// Plugin can save context on accessory
// To help restore accessory in configureAccessory()
// newAccessory.context.something = "Something"
// Make sure you provided a name for service otherwise it may not visible in some HomeKit apps.
newAccessory.addService(Service.Lightbulb, "Test Light")
.getCharacteristic(Characteristic.On)
.on('set', function(value, callback) {

View File

@@ -5,6 +5,7 @@ var hapLegacyTypes = require("hap-nodejs/accessories/types.js");
var log = require("./logger")._system;
var User = require("./user").User;
var PlatformAccessory = require("./platformAccessory").PlatformAccessory;
var serverVersion = require("./version");
// The official homebridge API is the object we feed the plugin's exported initializer function.
@@ -20,7 +21,10 @@ function API() {
this._dynamicPlatforms = {}; // this._dynamicPlatforms[pluginName.platformName] = platform constructor
// expose the homebridge API version
this.version = 2.1;
this.version = 2.2;
// expose the homebridge server version
this.serverVersion = serverVersion;
// expose the User class methods to plugins to get paths. Example: homebridge.user.storagePath()
this.user = User;

View File

@@ -178,7 +178,7 @@ BridgeSetupSession.prototype.handleManageAccessory = function(request) {
BridgeSetupSession.prototype.sendResponse = function(response) {
if (this.validSession) {
var serializedReponse = JSON.stringify(response);
var respData = Buffer(serializedReponse).toString('base64');
var respData = new Buffer(serializedReponse).toString('base64');
this.lastResponse = respData;
setTimeout(function() {
this.controlChar.setValue(respData);

View File

@@ -31,7 +31,7 @@ Logger.prototype.debug = function(msg) {
if (DEBUG_ENABLED)
this.log.apply(this, ['debug'].concat(Array.prototype.slice.call(arguments)));
}
Logger.prototype.info = function(msg) {
this.log.apply(this, ['info'].concat(Array.prototype.slice.call(arguments)));
}
@@ -43,35 +43,35 @@ Logger.prototype.warn = function(msg) {
Logger.prototype.error = function(msg) {
this.log.apply(this, ['error'].concat(Array.prototype.slice.call(arguments)));
}
Logger.prototype.log = function(level, msg) {
msg = util.format.apply(util, Array.prototype.slice.call(arguments, 1));
func = console.log;
if (level == 'debug') {
msg = chalk.gray(msg);
}
else if (level == 'warn') {
msg = chalk.yellow(msg);
func = console.error;
func = console.error;
}
else if (level == 'error') {
msg = chalk.bold.red(msg);
func = console.error;
}
// prepend prefix if applicable
if (this.prefix)
msg = chalk.cyan("[" + this.prefix + "]") + " " + msg;
// prepend timestamp
var date = new Date();
msg = "[" + date.toLocaleString() + "]" + " " + msg;
msg = chalk.white("[" + date.toLocaleString() + "]") + " " + msg;
func(msg);
}
Logger.withPrefix = function(prefix) {
if (!loggerCache[prefix]) {
@@ -87,6 +87,6 @@ Logger.withPrefix = function(prefix) {
log.prefix = logger.prefix;
loggerCache[prefix] = log;
}
return loggerCache[prefix];
}

View File

@@ -185,6 +185,7 @@ PlatformAccessory.prototype._dictionaryPresentation = function() {
characteristicPresentation.UUID = characteristic.UUID;
characteristicPresentation.props = characteristic.props;
characteristicPresentation.value = characteristic.value;
characteristicPresentation.eventOnlyCharacteristic = characteristic.eventOnlyCharacteristic;
characteristics.push(characteristicPresentation);
}
@@ -214,6 +215,7 @@ PlatformAccessory.prototype._configFromData = function(data) {
for (var cIndex in service.characteristics) {
var characteristic = service.characteristics[cIndex];
var hapCharacteristic = new Characteristic(characteristic.displayName, characteristic.UUID, characteristic.props);
hapCharacteristic.eventOnlyCharacteristic = characteristic.eventOnlyCharacteristic;
hapCharacteristic.value = characteristic.value;
characteristics.push(hapCharacteristic);
}

View File

@@ -13,7 +13,7 @@ module.exports = {
/**
* Homebridge Plugin.
*
*
* Allows for discovering and loading installed Homebridge plugins.
*/
@@ -28,39 +28,39 @@ Plugin.prototype.name = function() {
Plugin.prototype.load = function(options) {
options = options || {};
// does this plugin exist at all?
if (!fs.existsSync(this.pluginPath)) {
throw new Error("Plugin " + this.pluginPath + " was not found. Make sure the module '" + this.pluginPath + "' is installed.");
}
// attempt to load package.json
var pjson = Plugin.loadPackageJSON(this.pluginPath);
// very temporary fix for first wave of plugins
if (pjson.peerDepdendencies && (!pjson.engines || !pjson.engines.homebridge)) {
var engines = pjson.engines || {}
engines.homebridge = pjson.peerDepdendencies.homebridge;
pjson.engines = engines;
}
// pluck out the HomeBridge version requirement
if (!pjson.engines || !pjson.engines.homebridge) {
throw new Error("Plugin " + this.pluginPath + " does not contain the 'homebridge' package in 'engines'.");
}
var versionRequired = pjson.engines.homebridge;
// make sure the version is satisfied by the currently running version of HomeBridge
if (!semver.satisfies(version, versionRequired)) {
throw new Error("Plugin " + this.pluginPath + " requires a HomeBridge version of " + versionRequired + " which does not satisfy the current HomeBridge version of " + version + ". You may need to upgrade your installation of HomeBridge.");
}
// figure out the main module - index.js unless otherwise specified
var main = pjson.main || "./index.js";
var mainPath = path.join(this.pluginPath, main);
// try to require() it and grab the exported initialization hook
this.initializer = require(mainPath);
}
@@ -69,11 +69,11 @@ Plugin.loadPackageJSON = function(pluginPath) {
// check for a package.json
var pjsonPath = path.join(pluginPath, "package.json");
var pjson = null;
if (!fs.existsSync(pjsonPath)) {
throw new Error("Plugin " + pluginPath + " does not contain a package.json.");
}
try {
// attempt to parse package.json
pjson = JSON.parse(fs.readFileSync(pjsonPath));
@@ -81,7 +81,7 @@ Plugin.loadPackageJSON = function(pluginPath) {
catch (err) {
throw new Error("Plugin " + pluginPath + " contains an invalid package.json. Error: " + err);
}
// make sure the name is prefixed with 'homebridge-'
if (!pjson.name || pjson.name.indexOf('homebridge-') != 0) {
throw new Error("Plugin " + pluginPath + " does not have a package name that begins with 'homebridge-'.");
@@ -91,7 +91,7 @@ Plugin.loadPackageJSON = function(pluginPath) {
if (!pjson.keywords || pjson.keywords.indexOf("homebridge-plugin") == -1) {
throw new Error("Plugin " + pluginPath + " package.json does not contain the keyword 'homebridge-plugin'.");
}
return pjson;
}
@@ -119,9 +119,10 @@ Plugin.getDefaultPaths = function() {
} else {
paths.push('/usr/local/lib/node_modules');
paths.push('/usr/lib/node_modules');
const exec = require('child_process').execSync;
paths.push(exec('/bin/echo -n "$(npm -g prefix)/lib/node_modules"').toString('utf8'));
}
}
return paths;
}
@@ -138,17 +139,17 @@ Plugin.installed = function() {
var plugins = [];
var pluginsByName = {}; // don't add duplicate plugins
var searchedPaths = {}; // don't search the same paths twice
// search for plugins among all known paths, in order
for (var index in Plugin.paths) {
var requirePath = Plugin.paths[index];
// did we already search this path?
if (searchedPaths[requirePath])
continue;
searchedPaths[requirePath] = true;
// just because this path is in require.main.paths doesn't mean it necessarily exists!
if (!fs.existsSync(requirePath))
continue;
@@ -158,17 +159,19 @@ Plugin.installed = function() {
// does this path point inside a single plugin and not a directory containing plugins?
if (fs.existsSync(path.join(requirePath, "package.json")))
names = [""];
// read through each directory in this node_modules folder
for (var index2 in names) {
var name = names[index2];
// reconstruct full path
var pluginPath = path.join(requirePath, name);
// we only care about directories
if (!fs.statSync(pluginPath).isDirectory()) continue;
try {
// we only care about directories
if (!fs.statSync(pluginPath).isDirectory()) continue;
} catch (e) {
continue;
}
// does this module contain a package.json?
var pjson;
try {
@@ -180,14 +183,14 @@ Plugin.installed = function() {
if (!name || name.indexOf('homebridge-') == 0) {
log.warn(err.message);
}
// skip this module
continue;
}
// get actual name if this path points inside a single plugin
if (!name) name = pjson.name;
// add it to the return list
if (!pluginsByName[name]) {
pluginsByName[name] = pluginPath;
@@ -198,6 +201,6 @@ Plugin.installed = function() {
}
}
}
return plugins;
}

View File

@@ -16,6 +16,7 @@ var BridgeSetupManager = require("./bridgeSetupManager").BridgeSetupManager;
var log = require("./logger")._system;
var Logger = require('./logger').Logger;
var mac = require("./util/mac");
var chalk = require('chalk');
'use strict';
@@ -23,7 +24,9 @@ module.exports = {
Server: Server
}
function Server(insecureAccess) {
function Server(insecureAccess, opts) {
opts = opts || {};
// Setup Accessory Cache Storage
accessoryStorage.initSync({ dir: User.cachedAccessoryPath() });
@@ -46,7 +49,7 @@ function Server(insecureAccess) {
}.bind(this));
this._plugins = this._loadPlugins(); // plugins[name] = Plugin instance
this._config = this._loadConfig();
this._config = opts.config || this._loadConfig();
this._cachedPlatformAccessories = this._loadCachedPlatformAccessories();
this._bridge = this._createBridge();
@@ -55,7 +58,7 @@ function Server(insecureAccess) {
this._publishedCameras = {};
this._setupManager = new BridgeSetupManager();
this._setupManager.on('newConfig', this._handleNewConfig.bind(this));
this._setupManager.on('requestCurrentConfig', function(callback) {
callback(this._config);
}.bind(this));
@@ -93,7 +96,7 @@ Server.prototype.run = function() {
Server.prototype._publish = function() {
// pull out our custom Bridge settings from config.json, if any
var bridgeConfig = this._config.bridge || {};
var info = this._bridge.getService(Service.AccessoryInformation);
if (bridgeConfig.manufacturer)
info.setCharacteristic(Characteristic.Manufacturer, bridgeConfig.manufacturer);
@@ -168,7 +171,7 @@ Server.prototype._loadConfig = function() {
// Complain and exit if it doesn't exist yet
if (!fs.existsSync(configPath)) {
log.warn("config.json (%s) not found.", configPath);
var config = {};
config.bridge = {
@@ -427,7 +430,7 @@ Server.prototype._handleRegisterPlatformAccessories = function(accessories) {
accessory._prepareAssociatedHAPAccessory();
hapAccessories.push(accessory._associatedHAPAccessory);
this._cachedPlatformAccessories.push(accessory);
}
@@ -473,7 +476,7 @@ Server.prototype._handlePublishCameraAccessories = function(accessories) {
var advertiseAddress = mac.generate(accessory.UUID);
if (this._publishedCameras[advertiseAddress]) {
throw new Error("Camera accessory %s experienced an address collision.", accessory.displayName);
throw new Error("Camera accessory " + accessory.displayName + " experienced an address collision.");
} else {
this._publishedCameras[advertiseAddress] = accessory;
}
@@ -513,7 +516,7 @@ Server.prototype._handleNewConfig = function(type, name, replace, config) {
} else {
var targetName;
if (name.indexOf('.') !== -1) {
targetName = name.split(".")[1];
targetName = name.split(".")[1];
}
var found = false;
for (var index in this._config.accessories) {
@@ -545,7 +548,7 @@ Server.prototype._handleNewConfig = function(type, name, replace, config) {
} else {
var targetName;
if (name.indexOf('.') !== -1) {
targetName = name.split(".")[1];
targetName = name.split(".")[1];
}
var found = false;
@@ -578,9 +581,9 @@ Server.prototype._handleNewConfig = function(type, name, replace, config) {
// Returns the setup code in a scannable format.
Server.prototype._printPin = function(pin) {
console.log("Scan this code with your HomeKit App on your iOS device to pair with Homebridge:");
console.log("\x1b[30;47m%s\x1b[0m", " ");
console.log("\x1b[30;47m%s\x1b[0m", " ┌────────────┐ ");
console.log("\x1b[30;47m%s\x1b[0m", " │ " + pin + " │ ");
console.log("\x1b[30;47m%s\x1b[0m", " └────────────┘ ");
console.log("\x1b[30;47m%s\x1b[0m", " ");
console.log(chalk.black.bgWhite(" "));
console.log(chalk.black.bgWhite(" ┌────────────┐ "));
console.log(chalk.black.bgWhite(" │ " + pin + " │ "));
console.log(chalk.black.bgWhite(" └────────────┘ "));
console.log(chalk.black.bgWhite(" "));
}

View File

@@ -1,7 +1,7 @@
{
"name": "homebridge",
"description": "HomeKit support for the impatient",
"version": "0.4.5",
"version": "0.4.22",
"scripts": {
"dev": "DEBUG=* ./bin/homebridge -D -P example-plugins/ || true"
},
@@ -26,7 +26,7 @@
"dependencies": {
"chalk": "^1.1.1",
"commander": "2.8.1",
"hap-nodejs": "0.4.10",
"hap-nodejs": "0.4.27",
"semver": "5.0.3",
"node-persist": "^0.0.8"
}