diff --git a/.gitignore b/.gitignore
index d9359b85e608b9646bff929ad48a13a5227346ef..9750ff56a776ba602ccadbd0830c70a9a5bdeda3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,4 @@
 node_modules
-junit.xml
-coverage/
-build/
 **/*.crt
 **/*.key
-src/testy.ts
-lib
-lib-esm
-_bundles
\ No newline at end of file
+src/testy.ts
\ No newline at end of file
diff --git a/.npmignore b/.npmignore
index 309f273ad762b93667c96df810044fb3cc5da6c8..5e1b8e31d60ca28aac4ca522e149fa60cc3a2410 100644
--- a/.npmignore
+++ b/.npmignore
@@ -3,4 +3,5 @@
 **/webpack.config.js
 node_modules
 src
-public
\ No newline at end of file
+public
+tests
\ No newline at end of file
diff --git a/node/commands/add.d.ts b/node/commands/add.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5e00b673fef4d5d0922d1ef792b1d08ca25a83fa
--- /dev/null
+++ b/node/commands/add.d.ts
@@ -0,0 +1,6 @@
+import { DecodeFn, EncodeFn } from "../types";
+declare const _default: {
+    decode: DecodeFn;
+    encode: EncodeFn;
+};
+export default _default;
diff --git a/node/commands/add.js b/node/commands/add.js
new file mode 100644
index 0000000000000000000000000000000000000000..b2c01260afa76f0f26017173546c686751b1d547
--- /dev/null
+++ b/node/commands/add.js
@@ -0,0 +1,85 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var util = __importStar(require("./util"));
+var decode = function (msg) {
+    var paths = util.searchAll(msg, "instantiatedPath");
+    if (paths && paths.length === 1)
+        return [paths[0]];
+    return [paths];
+};
+var isObj = function (v) {
+    return typeof v === "object" && v.required !== undefined && v.value !== undefined;
+};
+var encode = function (_a) {
+    var value = _a.value, path = _a.path;
+    var allowPartial = value && value.allowPartial;
+    var pairs = value
+        ? Object.entries(value).map(function (_a) {
+            var k = _a[0], v = _a[1];
+            return isObj(v)
+                ? [k, v.value.toString(), v.required]
+                : [k, v.toString(), false];
+        })
+        : [];
+    return {
+        lookup: "Msg",
+        header: {
+            lookup: "Header",
+            msgId: util.uniq("ADD@"),
+            msgType: "ADD",
+        },
+        body: {
+            lookup: "Body",
+            request: {
+                lookup: "Request",
+                add: {
+                    allowPartial: allowPartial,
+                    createObjs: [
+                        {
+                            lookup: "Add.CreateObject",
+                            objPath: path,
+                            paramSettings: pairs
+                                .filter(function (_a) {
+                                var k = _a[0];
+                                return k !== "allowPartial";
+                            })
+                                .map(function (_a) {
+                                var param = _a[0], value = _a[1], required = _a[2];
+                                return ({
+                                    lookup: "Add.CreateParamSetting",
+                                    param: param,
+                                    value: value,
+                                    required: required,
+                                });
+                            }),
+                        },
+                    ],
+                },
+            },
+        },
+    };
+};
+exports.default = {
+    decode: decode,
+    encode: encode
+};
+//# sourceMappingURL=add.js.map
\ No newline at end of file
diff --git a/node/commands/add.js.map b/node/commands/add.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..bde2dafde0ba19f4a84bef4f9e887bece6efab83
--- /dev/null
+++ b/node/commands/add.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"add.js","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AACA,2CAA+B;AAE/B,IAAM,MAAM,GAAa,UAAC,GAAG;IAC3B,IAAM,KAAK,GAAyB,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;IAC5E,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;IAClD,OAAO,CAAC,KAAK,CAAC,CAAC;AACjB,CAAC,CAAC;AAEF,IAAM,KAAK,GAAG,UAAC,CAAC;IACd,OAAA,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS;AAA1E,CAA0E,CAAC;AAE7E,IAAM,MAAM,GAAa,UAAC,EAAe;QAAb,KAAK,WAAA,EAAE,IAAI,UAAA;IACrC,IAAM,YAAY,GAAG,KAAK,IAAI,KAAK,CAAC,YAAY,CAAC;IACjD,IAAM,KAAK,GAA6B,KAAK;QAC3C,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAc,CAAC,CAAC,GAAG,CAAC,UAAC,EAAM;gBAAL,CAAC,QAAA,EAAE,CAAC,QAAA;YACvC,OAAA,KAAK,CAAC,CAAC,CAAC;gBACN,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC;gBACrC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC;QAF5B,CAE4B,CAC7B;QACH,CAAC,CAAC,EAAE,CAAC;IACP,OAAO;QACL,MAAM,EAAE,KAAK;QACb,MAAM,EAAE;YACN,MAAM,EAAE,QAAQ;YAChB,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;YACxB,OAAO,EAAE,KAAK;SACf;QACD,IAAI,EAAE;YACJ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,MAAM,EAAE,SAAS;gBACjB,GAAG,EAAE;oBACH,YAAY,cAAA;oBACZ,UAAU,EAAE;wBACV;4BACE,MAAM,EAAE,kBAAkB;4BAC1B,OAAO,EAAE,IAAI;4BACb,aAAa,EAAE,KAAK;iCACjB,MAAM,CAAC,UAAC,EAAG;oCAAF,CAAC,QAAA;gCAAM,OAAA,CAAC,KAAK,cAAc;4BAApB,CAAoB,CAAC;iCACrC,GAAG,CAAC,UAAC,EAAwB;oCAAvB,KAAK,QAAA,EAAE,KAAK,QAAA,EAAE,QAAQ,QAAA;gCAAM,OAAA,CAAC;oCAClC,MAAM,EAAE,wBAAwB;oCAChC,KAAK,OAAA;oCACL,KAAK,OAAA;oCACL,QAAQ,UAAA;iCACT,CAAC;4BALiC,CAKjC,CAAC;yBACN;qBACF;iBACF;aACF;SACF;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,kBAAe;IACb,MAAM,QAAA;IACN,MAAM,QAAA;CACP,CAAC"}
\ No newline at end of file
diff --git a/node/commands/del.d.ts b/node/commands/del.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5e00b673fef4d5d0922d1ef792b1d08ca25a83fa
--- /dev/null
+++ b/node/commands/del.d.ts
@@ -0,0 +1,6 @@
+import { DecodeFn, EncodeFn } from "../types";
+declare const _default: {
+    decode: DecodeFn;
+    encode: EncodeFn;
+};
+export default _default;
diff --git a/node/commands/del.js b/node/commands/del.js
new file mode 100644
index 0000000000000000000000000000000000000000..5310addc49e71c458c59cecd6f450583e95b81e8
--- /dev/null
+++ b/node/commands/del.js
@@ -0,0 +1,32 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+var util_1 = require("./util");
+var decode = function (_msg) {
+    return [null];
+};
+var encode = function (_a) {
+    var paths = _a.paths, allowPartial = _a.allowPartial;
+    return ({
+        lookup: "Msg",
+        header: {
+            msgId: util_1.uniq("DELETE@"),
+            msgType: "DELETE",
+            lookup: "Header",
+        },
+        body: {
+            lookup: "Body",
+            request: {
+                lookup: "Request",
+                delete: {
+                    objPaths: Array.isArray(paths) ? paths : [paths],
+                    allowPartial: allowPartial || false
+                },
+            },
+        },
+    });
+};
+exports.default = {
+    decode: decode,
+    encode: encode
+};
+//# sourceMappingURL=del.js.map
\ No newline at end of file
diff --git a/node/commands/del.js.map b/node/commands/del.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..e23e0150e23edabb55bc5c915df4ad9d9c81e6cf
--- /dev/null
+++ b/node/commands/del.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"del.js","sourceRoot":"","sources":["../../src/commands/del.ts"],"names":[],"mappings":";;AACA,+BAA8B;AAE9B,IAAM,MAAM,GAAa,UAAC,IAAI;IAC5B,OAAO,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC,CAAC;AAEF,IAAM,MAAM,GAAa,UAAC,EAAuB;QAArB,KAAK,WAAA,EAAE,YAAY,kBAAA;IAAO,OAAA,CAAC;QACrD,MAAM,EAAE,KAAK;QACb,MAAM,EAAE;YACN,KAAK,EAAE,WAAI,CAAC,SAAS,CAAC;YACtB,OAAO,EAAE,QAAQ;YACjB,MAAM,EAAE,QAAQ;SACjB;QACD,IAAI,EAAE;YACJ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,MAAM,EAAE,SAAS;gBACjB,MAAM,EAAE;oBACN,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;oBAChD,YAAY,EAAE,YAAY,IAAI,KAAK;iBACpC;aACF;SACF;KACF,CAAC;AAjBoD,CAiBpD,CAAC;AAEH,kBAAe;IACb,MAAM,QAAA;IACN,MAAM,QAAA;CACP,CAAC"}
\ No newline at end of file
diff --git a/node/commands/get.d.ts b/node/commands/get.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5e00b673fef4d5d0922d1ef792b1d08ca25a83fa
--- /dev/null
+++ b/node/commands/get.d.ts
@@ -0,0 +1,6 @@
+import { DecodeFn, EncodeFn } from "../types";
+declare const _default: {
+    decode: DecodeFn;
+    encode: EncodeFn;
+};
+export default _default;
diff --git a/node/commands/get.js b/node/commands/get.js
new file mode 100644
index 0000000000000000000000000000000000000000..8dd178b2941c34f4d2f6746595b5617f95dd546e
--- /dev/null
+++ b/node/commands/get.js
@@ -0,0 +1,58 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var util = __importStar(require("./util"));
+var decode = function (msg) {
+    var resolvedPathResultsArr = util.searchAll(msg, "resolvedPathResults");
+    if (resolvedPathResultsArr) {
+        var unflattened = resolvedPathResultsArr.map(function (v) {
+            return util.unflatten(v.resultParams);
+        });
+        var data = util.unwrapObject(util.unwrapArray(unflattened));
+        return [data];
+    }
+    return [null];
+};
+var encode = function (_a) {
+    var paths = _a.paths;
+    return ({
+        lookup: "Msg",
+        header: {
+            msgId: util.uniq("GET@"),
+            msgType: "GET",
+            lookup: "Header",
+        },
+        body: {
+            lookup: "Body",
+            request: {
+                lookup: "Request",
+                get: {
+                    paramPaths: Array.isArray(paths) ? paths : [paths],
+                },
+            },
+        },
+    });
+};
+exports.default = {
+    decode: decode,
+    encode: encode
+};
+//# sourceMappingURL=get.js.map
\ No newline at end of file
diff --git a/node/commands/get.js.map b/node/commands/get.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..08d043894d034e19467840a03645a4c9c7043228
--- /dev/null
+++ b/node/commands/get.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"get.js","sourceRoot":"","sources":["../../src/commands/get.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AACA,2CAA+B;AAE/B,IAAM,MAAM,GAAa,UAAC,GAAG;IAC3B,IAAM,sBAAsB,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,qBAAqB,CAAC,CAAC;IAC1E,IAAI,sBAAsB,EAAE;QAC1B,IAAM,WAAW,GAAG,sBAAsB,CAAC,GAAG,CAAC,UAAC,CAAC;YAC/C,OAAA,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC;QAA9B,CAA8B,CAC/B,CAAC;QACF,IAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC;QAC9D,OAAO,CAAC,IAAI,CAAC,CAAC;KACf;IACD,OAAO,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC,CAAC;AAEF,IAAM,MAAM,GAAa,UAAC,EAAS;QAAP,KAAK,WAAA;IAAO,OAAA,CAAC;QACvC,MAAM,EAAE,KAAK;QACb,MAAM,EAAE;YACN,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;YACxB,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,QAAQ;SACjB;QACD,IAAI,EAAE;YACJ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,MAAM,EAAE,SAAS;gBACjB,GAAG,EAAE;oBACH,UAAU,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;iBACnD;aACF;SACF;KACF,CAAC;AAhBsC,CAgBtC,CAAC;AAEH,kBAAe;IACb,MAAM,QAAA;IACN,MAAM,QAAA;CACP,CAAC"}
\ No newline at end of file
diff --git a/node/commands/index.d.ts b/node/commands/index.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..99f0c783d1d726bdf6cb38859ad436408c31b04e
--- /dev/null
+++ b/node/commands/index.d.ts
@@ -0,0 +1,10 @@
+import protobuf from "protobufjs";
+import { CommandType, CallFn, OnFn, DecodeResponse } from "../types";
+export declare const rootRecord: protobuf.Root;
+export declare const rootMsg: protobuf.Root;
+export declare const header: any;
+export declare const makeRecipes: (call: CallFn, on: OnFn) => any;
+export declare const decodeId: (data: any) => string;
+export declare const readMsg: (data: any) => Record<string, any>;
+export declare const decode: (parsedMsg: any) => DecodeResponse;
+export declare const makeEncode: (options?: Record<string, string> | undefined) => (command: CommandType, args: Record<string, any>) => [string, any, string | null];
diff --git a/node/commands/index.js b/node/commands/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..f680c5948c51fe59e0e82a6ebc0583fabbfb3a43
--- /dev/null
+++ b/node/commands/index.js
@@ -0,0 +1,157 @@
+"use strict";
+var __assign = (this && this.__assign) || function () {
+    __assign = Object.assign || function(t) {
+        for (var s, i = 1, n = arguments.length; i < n; i++) {
+            s = arguments[i];
+            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
+                t[p] = s[p];
+        }
+        return t;
+    };
+    return __assign.apply(this, arguments);
+};
+var __spreadArray = (this && this.__spreadArray) || function (to, from) {
+    for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
+        to[j] = from[i];
+    return to;
+};
+var __importDefault = (this && this.__importDefault) || function (mod) {
+    return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.makeEncode = exports.decode = exports.readMsg = exports.decodeId = exports.makeRecipes = exports.header = exports.rootMsg = exports.rootRecord = void 0;
+var protobufjs_1 = __importDefault(require("protobufjs"));
+var usp_record_1_1_json_1 = __importDefault(require("../specs/usp-record-1-1.json"));
+var usp_msg_1_1_json_1 = __importDefault(require("../specs/usp-msg-1-1.json"));
+var util_1 = require("./util");
+exports.rootRecord = protobufjs_1.default.Root.fromJSON(usp_record_1_1_json_1.default);
+exports.rootMsg = protobufjs_1.default.Root.fromJSON(usp_msg_1_1_json_1.default);
+exports.header = exports.rootMsg.lookupType("usp.Header");
+var get_1 = __importDefault(require("./get"));
+var set_1 = __importDefault(require("./set"));
+var add_1 = __importDefault(require("./add"));
+var del_1 = __importDefault(require("./del"));
+var operate_1 = __importDefault(require("./operate"));
+var supported_1 = __importDefault(require("./supported"));
+var proto_1 = __importDefault(require("./proto"));
+var instances_1 = __importDefault(require("./instances"));
+var notify_1 = __importDefault(require("./notify"));
+var resolve_1 = __importDefault(require("./recipes/resolve"));
+var operate_2 = __importDefault(require("./recipes/operate"));
+var subscribe_1 = __importDefault(require("./recipes/subscribe"));
+var commands = {
+    GET: get_1.default,
+    ADD: add_1.default,
+    DELETE: del_1.default,
+    GET_INSTANCES: instances_1.default,
+    GET_SUPPORTED_DM: supported_1.default,
+    GET_SUPPORTED_PROTO: proto_1.default,
+    NOTIFY: notify_1.default,
+    OPERATE: operate_1.default,
+    SET: set_1.default,
+};
+var recipes = [resolve_1.default, operate_2.default, subscribe_1.default];
+var makeRecipes = function (call, on) {
+    return recipes.reduce(function (acc, _a) {
+        var _b;
+        var make = _a.make, name = _a.name;
+        return (__assign(__assign({}, acc), (_b = {}, _b[name] = make(call, on), _b)));
+    }, {});
+};
+exports.makeRecipes = makeRecipes;
+var decodeId = function (data) { return String(data); };
+exports.decodeId = decodeId;
+var unkownErr = function (msg) { return ["error", "", msg]; };
+var readMsg = function (data) {
+    var record = exports.rootRecord.lookupType("usp_record.Record");
+    var decodedRecord = record.decode(data);
+    var msg = exports.rootMsg.lookupType("usp.Msg");
+    var decodedMsg = msg.decode(decodedRecord.noSessionContext.payload);
+    return JSON.parse(JSON.stringify(decodedMsg)); // forces conversions
+};
+exports.readMsg = readMsg;
+var decode = function (parsedMsg) {
+    var id = util_1.search(parsedMsg, "msgId") || null;
+    var err = util_1.searchParent(parsedMsg, "errMsg") || null;
+    if (err)
+        return [id, null, err];
+    var command = util_1.extractCommand(parsedMsg);
+    if (!command)
+        return unkownErr(parsedMsg);
+    var cmd = commands[command.replace("_RESP", "")] || null;
+    if (!cmd)
+        return unkownErr(parsedMsg);
+    var _a = cmd.decode(parsedMsg), ddata = _a[0], did = _a[1], derr = _a[2];
+    return [did || id, ddata, derr || err];
+};
+exports.decode = decode;
+var makeEncode = function (options) { return function (command, args) {
+    var cmd = commands[command] || null;
+    if (!cmd)
+        return ["error", null, "Uknown command: " + command];
+    return __spreadArray([], convert(cmd.encode(args), options));
+}; };
+exports.makeEncode = makeEncode;
+var convert = function (msg, bufferOptions) {
+    var id = msg.header.msgId;
+    msg.header.msgType = exports.header.MsgType[msg.header.msgType];
+    var converted = _convert(msg);
+    if (isError(converted))
+        return [id, null, converted];
+    var encoded = exports.rootMsg.lookupType("usp.Msg").encode(converted).finish();
+    var buffer = util_1.makeBuffer(exports.rootRecord, encoded, bufferOptions || {});
+    return [id, buffer, null];
+};
+var isError = function (o) { return typeof o == "string"; };
+var internalKeys = ["lookup"];
+var isInternal = function (key) { return internalKeys.includes(key); };
+var makePayload = function (items, isArr) {
+    return items
+        .filter(function (_a) {
+        var k = _a[0];
+        return !isInternal(k);
+    })
+        .reduce(function (acc, _a) {
+        var _b;
+        var k = _a[0], v = _a[1];
+        return isArr
+            ? __spreadArray(__spreadArray([], acc), [v]) : __assign(__assign({}, acc), (_b = {}, _b[k] = v, _b));
+    }, isArr ? [] : {});
+};
+var isStringArray = function (obj) {
+    return Array.isArray(obj) && obj.every(function (v) { return typeof v === "string"; });
+};
+var needsConversion = function (v) { return typeof v === "object" && !isStringArray(v); };
+var _convert = function (value) {
+    var skip = value.lookup === undefined;
+    var lookup = "usp." + value.lookup;
+    var item = skip ? null : exports.rootMsg.lookupType(lookup);
+    var simpleValues = Object.entries(value).filter(function (_a) {
+        var v = _a[1];
+        return !needsConversion(v);
+    });
+    var toConvert = Object.entries(value).filter(function (_a) {
+        var v = _a[1];
+        return needsConversion(v);
+    });
+    var converted = toConvert.map(function (_a) {
+        var k = _a[0], v = _a[1];
+        return [
+            k,
+            _convert(v),
+        ];
+    });
+    var err = converted.find(function (_a) {
+        var v = _a[1];
+        return isError(v);
+    });
+    if (err)
+        return err[1];
+    var total = converted.concat(simpleValues);
+    var payload = makePayload(total, Array.isArray(value));
+    var payloadErr = item === null || item === void 0 ? void 0 : item.verify(payload);
+    if (payloadErr)
+        return payloadErr;
+    return item ? item.create(payload) : payload;
+};
+//# sourceMappingURL=index.js.map
\ No newline at end of file
diff --git a/node/commands/index.js.map b/node/commands/index.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..0ac63606999294142094d9c40e5e6eff70e284c6
--- /dev/null
+++ b/node/commands/index.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA,0DAAkC;AAClC,qFAA0D;AAC1D,+EAAoD;AACpD,+BAA0E;AAW7D,QAAA,UAAU,GAAG,oBAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,6BAAc,CAAC,CAAC;AACpD,QAAA,OAAO,GAAG,oBAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,0BAAW,CAAC,CAAC;AAC9C,QAAA,MAAM,GAAQ,eAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AAE5D,8CAAwB;AACxB,8CAAwB;AACxB,8CAAwB;AACxB,8CAAwB;AACxB,sDAAgC;AAChC,0DAAoC;AACpC,kDAA4B;AAC5B,0DAAoC;AACpC,oDAA8B;AAE9B,8DAAwC;AACxC,8DAA8C;AAC9C,kEAA4C;AAE5C,IAAM,QAAQ,GAAuC;IACnD,GAAG,EAAE,aAAG;IACR,GAAG,EAAE,aAAG;IACR,MAAM,EAAE,aAAG;IACX,aAAa,EAAE,mBAAS;IACxB,gBAAgB,EAAE,mBAAS;IAC3B,mBAAmB,EAAE,eAAK;IAC1B,MAAM,EAAE,gBAAM;IACd,OAAO,EAAE,iBAAO;IAChB,GAAG,EAAE,aAAG;CACT,CAAC;AAEF,IAAM,OAAO,GAAmB,CAAC,iBAAO,EAAE,iBAAa,EAAE,mBAAS,CAAC,CAAC;AAE7D,IAAM,WAAW,GAAG,UAAC,IAAY,EAAE,EAAQ;IAChD,OAAA,OAAO,CAAC,MAAM,CACZ,UAAC,GAAG,EAAE,EAAc;;YAAZ,IAAI,UAAA,EAAE,IAAI,UAAA;QAAO,OAAA,uBAAM,GAAG,gBAAG,IAAI,IAAG,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,OAAG;IAApC,CAAoC,EAC7D,EAAE,CACH;AAHD,CAGC,CAAC;AAJS,QAAA,WAAW,eAIpB;AAEG,IAAM,QAAQ,GAAG,UAAC,IAAS,IAAK,OAAA,MAAM,CAAC,IAAI,CAAC,EAAZ,CAAY,CAAA;AAAtC,QAAA,QAAQ,YAA8B;AAEnD,IAAM,SAAS,GAAG,UAChB,GAA2B,IACe,OAAA,CAAC,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAlB,CAAkB,CAAC;AAExD,IAAM,OAAO,GAAG,UAAC,IAAS;IAC/B,IAAM,MAAM,GAAG,kBAAU,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAAC;IAC1D,IAAM,aAAa,GAAQ,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAE/C,IAAM,GAAG,GAAG,eAAO,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IAC1C,IAAM,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAEtE,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,qBAAqB;AACtE,CAAC,CAAA;AARY,QAAA,OAAO,WAQnB;AAEM,IAAM,MAAM,GAAG,UACpB,SAAc;IAEd,IAAM,EAAE,GAAG,aAAM,CAAC,SAAS,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC;IAC9C,IAAM,GAAG,GAAG,mBAAY,CAAC,SAAS,EAAE,QAAQ,CAAC,IAAI,IAAI,CAAC;IACtD,IAAI,GAAG;QAAE,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;IAEhC,IAAM,OAAO,GAAG,qBAAc,CAAC,SAAS,CAAC,CAAC;IAC1C,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC,SAAS,CAAC,CAAC;IAE1C,IAAM,GAAG,GACP,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC;IACjD,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC,SAAS,CAAC,CAAC;IAEhC,IAAA,KAAqB,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,EAAzC,KAAK,QAAA,EAAE,GAAG,QAAA,EAAE,IAAI,QAAyB,CAAC;IACjD,OAAO,CAAC,GAAG,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,IAAI,GAAG,CAAC,CAAC;AACzC,CAAC,CAAC;AAhBW,QAAA,MAAM,UAgBjB;AAEK,IAAM,UAAU,GAAG,UAAC,OAAgC,IAAK,OAAA,UAC9D,OAAoB,EACpB,IAAyB;IAEzB,IAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;IACtC,IAAI,CAAC,GAAG;QAAE,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,qBAAmB,OAAS,CAAC,CAAC;IAC/D,yBAAW,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,EAAE;AACjD,CAAC,EAP+D,CAO/D,CAAC;AAPW,QAAA,UAAU,cAOrB;AAEF,IAAM,OAAO,GAAG,UACd,GAAqB,EACrB,aAAsC;IAEtC,IAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC;IAC5B,GAAG,CAAC,MAAM,CAAC,OAAO,GAAG,cAAM,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAExD,IAAM,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IAChC,IAAI,OAAO,CAAC,SAAS,CAAC;QAAE,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;IAErD,IAAM,OAAO,GAAG,eAAO,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,CAAC;IACzE,IAAM,MAAM,GAAG,iBAAU,CAAC,kBAAU,EAAE,OAAO,EAAE,aAAa,IAAI,EAAE,CAAC,CAAC;IACpE,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;AAC5B,CAAC,CAAC;AAEF,IAAM,OAAO,GAAG,UAAC,CAAM,IAAkB,OAAA,OAAO,CAAC,IAAI,QAAQ,EAApB,CAAoB,CAAC;AAE9D,IAAM,YAAY,GAAG,CAAC,QAAQ,CAAC,CAAC;AAChC,IAAM,UAAU,GAAG,UAAC,GAAW,IAAK,OAAA,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC,EAA1B,CAA0B,CAAC;AAC/D,IAAM,WAAW,GAAG,UAAC,KAAsB,EAAE,KAAc;IACzD,OAAA,KAAK;SACF,MAAM,CAAC,UAAC,EAAG;YAAF,CAAC,QAAA;QAAM,OAAA,CAAC,UAAU,CAAC,CAAC,CAAC;IAAd,CAAc,CAAC;SAC/B,MAAM,CACL,UAAC,GAAQ,EAAE,EAAM;;YAAL,CAAC,QAAA,EAAE,CAAC,QAAA;QACd,OAAA,KAAK;YACH,CAAC,iCAAK,GAAG,IAAE,CAAC,GACZ,CAAC,uBACM,GAAG,gBACL,CAAC,IAAG,CAAC,MACP;IALL,CAKK,EACP,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAChB;AAXH,CAWG,CAAC;AAEN,IAAM,aAAa,GAAG,UAAC,GAAQ;IAC7B,OAAA,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,UAAC,CAAC,IAAK,OAAA,OAAO,CAAC,KAAK,QAAQ,EAArB,CAAqB,CAAC;AAA7D,CAA6D,CAAC;AAChE,IAAM,eAAe,GAAG,UAAC,CAAM,IAAK,OAAA,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,EAA1C,CAA0C,CAAC;AAE/E,IAAM,QAAQ,GAAG,UAAC,KAAU;IAC1B,IAAM,IAAI,GAAG,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC;IACxC,IAAM,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IACrC,IAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,eAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAEtD,IAAM,YAAY,GAAU,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,CACtD,UAAC,EAAK;YAAF,CAAC,QAAA;QAAM,OAAA,CAAC,eAAe,CAAC,CAAC,CAAC;IAAnB,CAAmB,CAC/B,CAAC;IACF,IAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,UAAC,EAAK;YAAF,CAAC,QAAA;QAAM,OAAA,eAAe,CAAC,CAAC,CAAC;IAAlB,CAAkB,CAAC,CAAC;IAC9E,IAAM,SAAS,GAAoB,SAAS,CAAC,GAAG,CAAC,UAAC,EAAM;YAAL,CAAC,QAAA,EAAE,CAAC,QAAA;QAAM,OAAA;YAC3D,CAAC;YACD,QAAQ,CAAC,CAAC,CAAC;SACZ;IAH4D,CAG5D,CAAC,CAAC;IAEH,IAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,UAAC,EAAK;YAAF,CAAC,QAAA;QAAM,OAAA,OAAO,CAAC,CAAC,CAAC;IAAV,CAAU,CAAC,CAAC;IAClD,IAAI,GAAG;QAAE,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC;IAEvB,IAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAC7C,IAAM,OAAO,GAAG,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IACzD,IAAM,UAAU,GAAG,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IACzC,IAAI,UAAU;QAAE,OAAO,UAAU,CAAC;IAElC,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;AAC/C,CAAC,CAAC"}
\ No newline at end of file
diff --git a/node/commands/instances.d.ts b/node/commands/instances.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5e00b673fef4d5d0922d1ef792b1d08ca25a83fa
--- /dev/null
+++ b/node/commands/instances.d.ts
@@ -0,0 +1,6 @@
+import { DecodeFn, EncodeFn } from "../types";
+declare const _default: {
+    decode: DecodeFn;
+    encode: EncodeFn;
+};
+export default _default;
diff --git a/node/commands/instances.js b/node/commands/instances.js
new file mode 100644
index 0000000000000000000000000000000000000000..1741c9219256d0f57c2df74b186c0c9e8673f205
--- /dev/null
+++ b/node/commands/instances.js
@@ -0,0 +1,52 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var util = __importStar(require("./util"));
+var decode = function (msg) {
+    var results = util.search(msg, "reqPathResults");
+    return [results];
+};
+var encode = function (_a) {
+    var paths = _a.paths, _b = _a.opts, opts = _b === void 0 ? {} : _b;
+    return ({
+        lookup: "Msg",
+        header: {
+            msgId: util.uniq("GET_INSTANCES@"),
+            msgType: "GET_INSTANCES",
+            lookup: "Header",
+        },
+        body: {
+            lookup: "Body",
+            request: {
+                lookup: "Request",
+                getInstances: {
+                    objPaths: Array.isArray(paths) ? paths : [paths],
+                    firstLevelOnly: opts.firstLevelOnly || false
+                },
+            },
+        },
+    });
+};
+exports.default = {
+    decode: decode,
+    encode: encode
+};
+//# sourceMappingURL=instances.js.map
\ No newline at end of file
diff --git a/node/commands/instances.js.map b/node/commands/instances.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..61fd3fca0ecbcd682b20d02aeca5258c687ba6b3
--- /dev/null
+++ b/node/commands/instances.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"instances.js","sourceRoot":"","sources":["../../src/commands/instances.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AACA,2CAA+B;AAE/B,IAAM,MAAM,GAAa,UAAC,GAAG;IAC3B,IAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;IACnD,OAAO,CAAC,OAAO,CAAC,CAAC;AACnB,CAAC,CAAC;AAEF,IAAM,MAAM,GAAa,UAAC,EAAoB;QAAlB,KAAK,WAAA,EAAE,YAAS,EAAT,IAAI,mBAAG,EAAE,KAAA;IAAO,OAAA,CAAC;QAClD,MAAM,EAAE,KAAK;QACb,MAAM,EAAE;YACN,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC;YAClC,OAAO,EAAE,eAAe;YACxB,MAAM,EAAE,QAAQ;SACjB;QACD,IAAI,EAAE;YACJ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,MAAM,EAAE,SAAS;gBACjB,YAAY,EAAE;oBACZ,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;oBAChD,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,KAAK;iBAC7C;aACF;SACF;KACF,CAAC;AAjBiD,CAiBjD,CAAC;AAEH,kBAAe;IACb,MAAM,QAAA;IACN,MAAM,QAAA;CACP,CAAC"}
\ No newline at end of file
diff --git a/node/commands/notify.d.ts b/node/commands/notify.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f30f242ea34768f4f96f6add2d06deb0363cc423
--- /dev/null
+++ b/node/commands/notify.d.ts
@@ -0,0 +1,12 @@
+import { DecodeFn, EncodeFn, MakeFn } from "../types";
+declare const _default: {
+    decode: DecodeFn;
+    encode: EncodeFn;
+    make: MakeFn;
+    name: string;
+    trigger: {
+        encode: string;
+        decode: string;
+    };
+};
+export default _default;
diff --git a/node/commands/notify.js b/node/commands/notify.js
new file mode 100644
index 0000000000000000000000000000000000000000..3f22041a4cfbb750fde8d2cbea51924ba20a9d96
--- /dev/null
+++ b/node/commands/notify.js
@@ -0,0 +1,64 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var util = __importStar(require("./util"));
+var parseInfo = function (key, data) { return util.unflatten(util.search(data, key === "operComplete" ? "outputArgs" : key)); };
+var decode = function (msg) {
+    var parent = util.searchParent(msg, 'subscriptionId');
+    if (parent) {
+        var id = parent.subscriptionId;
+        var relField = Object.keys(parent).find(function (k) { return k !== "subscriptionId"; });
+        return (id && relField) ? [parseInfo(relField, msg), id, null] : [null, id, null];
+    }
+    return [null];
+};
+var encode = function (_a) {
+    var paths = _a.paths;
+    return ({
+        lookup: "Msg",
+        header: {
+            msgId: util.uniq("NOTIFY@"),
+            msgType: "GET",
+            lookup: "Header",
+        },
+        body: {
+            lookup: "Body",
+            request: {
+                lookup: "Request",
+                get: {
+                    paramPaths: Array.isArray(paths) ? paths : [paths],
+                },
+            },
+        },
+    });
+};
+var make = function (call) { return function (paths) { return call("GET", { paths: paths }); }; };
+exports.default = {
+    decode: decode,
+    encode: encode,
+    make: make,
+    name: "get",
+    trigger: {
+        encode: "GET",
+        decode: "GET_RESP"
+    }
+};
+//# sourceMappingURL=notify.js.map
\ No newline at end of file
diff --git a/node/commands/notify.js.map b/node/commands/notify.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..4a2f6b01a484cded7fe2ba67711a3673c9425ddf
--- /dev/null
+++ b/node/commands/notify.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"notify.js","sourceRoot":"","sources":["../../src/commands/notify.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AACA,2CAA+B;AAE/B,IAAM,SAAS,GAAG,UAAC,GAAW,EAAE,IAAyB,IAAK,OAAA,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,KAAK,cAAc,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAA9E,CAA8E,CAAA;AAE5I,IAAM,MAAM,GAAa,UAAC,GAAG;IAC3B,IAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAA;IACvD,IAAI,MAAM,EAAE;QACV,IAAM,EAAE,GAAG,MAAM,CAAC,cAAc,CAAA;QAChC,IAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,UAAA,CAAC,IAAI,OAAA,CAAC,KAAK,gBAAgB,EAAtB,CAAsB,CAAC,CAAA;QACtE,OAAO,CAAC,EAAE,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;KACnF;IACD,OAAO,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC,CAAC;AAEF,IAAM,MAAM,GAAa,UAAC,EAAS;QAAP,KAAK,WAAA;IAAO,OAAA,CAAC;QACvC,MAAM,EAAE,KAAK;QACb,MAAM,EAAE;YACN,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC;YAC3B,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,QAAQ;SACjB;QACD,IAAI,EAAE;YACJ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,MAAM,EAAE,SAAS;gBACjB,GAAG,EAAE;oBACH,UAAU,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;iBACnD;aACF;SACF;KACF,CAAC;AAhBsC,CAgBtC,CAAC;AAEH,IAAM,IAAI,GAAW,UAAC,IAAI,IAAiB,OAAA,UAAC,KAAK,IAAK,OAAA,IAAI,CAAC,KAAK,EAAE,EAAE,KAAK,OAAA,EAAE,CAAC,EAAtB,CAAsB,EAAjC,CAAiC,CAAC;AAE7E,kBAAe;IACb,MAAM,QAAA;IACN,MAAM,QAAA;IACN,IAAI,MAAA;IACJ,IAAI,EAAE,KAAK;IACX,OAAO,EAAE;QACP,MAAM,EAAE,KAAK;QACb,MAAM,EAAE,UAAU;KACnB;CACF,CAAC"}
\ No newline at end of file
diff --git a/node/commands/operate.d.ts b/node/commands/operate.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5e00b673fef4d5d0922d1ef792b1d08ca25a83fa
--- /dev/null
+++ b/node/commands/operate.d.ts
@@ -0,0 +1,6 @@
+import { DecodeFn, EncodeFn } from "../types";
+declare const _default: {
+    decode: DecodeFn;
+    encode: EncodeFn;
+};
+export default _default;
diff --git a/node/commands/operate.js b/node/commands/operate.js
new file mode 100644
index 0000000000000000000000000000000000000000..3f129dc53a1c82ab0cebb0aba964d04d0be95f38
--- /dev/null
+++ b/node/commands/operate.js
@@ -0,0 +1,57 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var util = __importStar(require("./util"));
+var decode = function (msg) {
+    var data = util.searchAll(msg, "operationResults");
+    if (data && data.length === 1)
+        return [util.unflatten(data[0])];
+    var unflattened = data.map(function (v) { return util.unflatten(v); });
+    return [unflattened];
+};
+var encode = function (_a) {
+    var path = _a.path, input = _a.input, id = _a.id;
+    return ({
+        lookup: "Msg",
+        header: {
+            msgId: id,
+            msgType: "OPERATE",
+            lookup: "Header",
+        },
+        body: {
+            lookup: "Body",
+            request: {
+                lookup: "Request",
+                operate: {
+                    command: path,
+                    commandKey: "",
+                    sendResp: false,
+                    inputArgs: input || {},
+                },
+            },
+        },
+    });
+};
+exports.default = {
+    decode: decode,
+    encode: encode
+};
+//# sourceMappingURL=operate.js.map
\ No newline at end of file
diff --git a/node/commands/operate.js.map b/node/commands/operate.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..5c6e56cba1e411d6340be3ab67adfac6a8950f7c
--- /dev/null
+++ b/node/commands/operate.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"operate.js","sourceRoot":"","sources":["../../src/commands/operate.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AAIA,2CAA+B;AAE/B,IAAM,MAAM,GAAa,UAAC,GAAG;IAC3B,IAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;IACrD,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAChE,IAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,UAAC,CAAC,IAAK,OAAA,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAjB,CAAiB,CAAC,CAAC;IACvD,OAAO,CAAC,WAAW,CAAC,CAAC;AACvB,CAAC,CAAC;AAEF,IAAM,MAAM,GAAa,UAAC,EAAmB;QAAjB,IAAI,UAAA,EAAE,KAAK,WAAA,EAAE,EAAE,QAAA;IAAO,OAAA,CAAC;QACjD,MAAM,EAAE,KAAK;QACb,MAAM,EAAE;YACN,KAAK,EAAE,EAAE;YACT,OAAO,EAAE,SAAS;YAClB,MAAM,EAAE,QAAQ;SACjB;QACD,IAAI,EAAE;YACJ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,MAAM,EAAE,SAAS;gBACjB,OAAO,EAAE;oBACP,OAAO,EAAE,IAAI;oBACb,UAAU,EAAE,EAAE;oBACd,QAAQ,EAAE,KAAK;oBACf,SAAS,EAAE,KAAK,IAAI,EAAE;iBACvB;aACF;SACF;KACF,CAAC;AAnBgD,CAmBhD,CAAC;AAEH,kBAAe;IACb,MAAM,QAAA;IACN,MAAM,QAAA;CACP,CAAC"}
\ No newline at end of file
diff --git a/node/commands/proto.d.ts b/node/commands/proto.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5e00b673fef4d5d0922d1ef792b1d08ca25a83fa
--- /dev/null
+++ b/node/commands/proto.d.ts
@@ -0,0 +1,6 @@
+import { DecodeFn, EncodeFn } from "../types";
+declare const _default: {
+    decode: DecodeFn;
+    encode: EncodeFn;
+};
+export default _default;
diff --git a/node/commands/proto.js b/node/commands/proto.js
new file mode 100644
index 0000000000000000000000000000000000000000..df7569cffd0b8de956b287a813cc52adde0cbe5f
--- /dev/null
+++ b/node/commands/proto.js
@@ -0,0 +1,51 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var util = __importStar(require("./util"));
+var decode = function (msg) {
+    var results = util.search(msg, "agentSupportedProtocolVersions");
+    return [results];
+};
+var encode = function (_a) {
+    var versions = _a.versions;
+    return ({
+        lookup: "Msg",
+        header: {
+            msgId: util.uniq("GET_SUPPORTED_PROTO@"),
+            msgType: "GET_SUPPORTED_PROTO",
+            lookup: "Header",
+        },
+        body: {
+            lookup: "Body",
+            request: {
+                lookup: "Request",
+                getSupportedProtocol: {
+                    controllerSupportedProtocolVersions: versions,
+                },
+            },
+        },
+    });
+};
+exports.default = {
+    decode: decode,
+    encode: encode
+};
+//# sourceMappingURL=proto.js.map
\ No newline at end of file
diff --git a/node/commands/proto.js.map b/node/commands/proto.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..c08521588a8c9b75857a59f984a6810a466106ac
--- /dev/null
+++ b/node/commands/proto.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"proto.js","sourceRoot":"","sources":["../../src/commands/proto.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AACA,2CAA+B;AAE/B,IAAM,MAAM,GAAa,UAAC,GAAG;IAC3B,IAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,gCAAgC,CAAC,CAAC;IACnE,OAAO,CAAC,OAAO,CAAC,CAAC;AACnB,CAAC,CAAC;AAEF,IAAM,MAAM,GAAa,UAAC,EAAY;QAAV,QAAQ,cAAA;IAAO,OAAA,CAAC;QAC1C,MAAM,EAAE,KAAK;QACb,MAAM,EAAE;YACN,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,sBAAsB,CAAC;YACxC,OAAO,EAAE,qBAAqB;YAC9B,MAAM,EAAE,QAAQ;SACjB;QACD,IAAI,EAAE;YACJ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,MAAM,EAAE,SAAS;gBACjB,oBAAoB,EAAE;oBACpB,mCAAmC,EAAE,QAAQ;iBAC9C;aACF;SACF;KACF,CAAC;AAhByC,CAgBzC,CAAC;AAEH,kBAAe;IACb,MAAM,QAAA;IACN,MAAM,QAAA;CACP,CAAC"}
\ No newline at end of file
diff --git a/node/commands/recipes/operate.d.ts b/node/commands/recipes/operate.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5ce835a2fe8690c2913a7d7ef49b60be818210ed
--- /dev/null
+++ b/node/commands/recipes/operate.d.ts
@@ -0,0 +1,6 @@
+import { MakeFn } from "../../types";
+declare const _default: {
+    name: string;
+    make: MakeFn;
+};
+export default _default;
diff --git a/node/commands/recipes/operate.js b/node/commands/recipes/operate.js
new file mode 100644
index 0000000000000000000000000000000000000000..122533b69b7bd7e01042cd89768f6cd0426f1877
--- /dev/null
+++ b/node/commands/recipes/operate.js
@@ -0,0 +1,77 @@
+"use strict";
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+    return new (P || (P = Promise))(function (resolve, reject) {
+        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+        step((generator = generator.apply(thisArg, _arguments || [])).next());
+    });
+};
+var __generator = (this && this.__generator) || function (thisArg, body) {
+    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
+    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
+    function verb(n) { return function (v) { return step([n, v]); }; }
+    function step(op) {
+        if (f) throw new TypeError("Generator is already executing.");
+        while (_) try {
+            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
+            if (y = 0, t) op = [op[0] & 2, t.value];
+            switch (op[0]) {
+                case 0: case 1: t = op; break;
+                case 4: _.label++; return { value: op[1], done: false };
+                case 5: _.label++; y = op[1]; op = [0]; continue;
+                case 7: op = _.ops.pop(); _.trys.pop(); continue;
+                default:
+                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
+                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
+                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
+                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
+                    if (t[2]) _.ops.pop();
+                    _.trys.pop(); continue;
+            }
+            op = body.call(thisArg, _);
+        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
+        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
+    }
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var util_1 = require("../util");
+var operateSubscriptionPath = "Device.LocalAgent.Subscription.";
+var make = function (call) { return function (path, opts) { return __awaiter(void 0, void 0, void 0, function () {
+    var Persistent, id, operateInput, newSubPath, command, cleanup;
+    return __generator(this, function (_a) {
+        switch (_a.label) {
+            case 0:
+                Persistent = (opts === null || opts === void 0 ? void 0 : opts.Persistent) === undefined ? false : opts.Persistent;
+                id = "NOTIFY@" + ((opts === null || opts === void 0 ? void 0 : opts.ID) || util_1.uniq(path));
+                operateInput = {
+                    Enable: true,
+                    ID: id,
+                    NotifType: "OperationComplete",
+                    ReferenceList: path,
+                    Persistent: Persistent,
+                };
+                return [4 /*yield*/, call("ADD", {
+                        path: operateSubscriptionPath,
+                        value: operateInput,
+                    })];
+            case 1:
+                newSubPath = _a.sent();
+                command = function (input) {
+                    return call("OPERATE", {
+                        path: path,
+                        input: input,
+                        id: id,
+                    });
+                };
+                cleanup = function () { return call("DELETE", { paths: newSubPath }); };
+                return [2 /*return*/, [command, cleanup]];
+        }
+    });
+}); }; };
+exports.default = {
+    name: "operate",
+    make: make,
+};
+//# sourceMappingURL=operate.js.map
\ No newline at end of file
diff --git a/node/commands/recipes/operate.js.map b/node/commands/recipes/operate.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..7ffbd0afa171400fa9e1d3d2839094ee0fbc1251
--- /dev/null
+++ b/node/commands/recipes/operate.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"operate.js","sourceRoot":"","sources":["../../../src/commands/recipes/operate.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,gCAA+B;AAE/B,IAAM,uBAAuB,GAAG,iCAAiC,CAAC;AAElE,IAAM,IAAI,GAAW,UAAC,IAAI,IAAoB,OAAA,UAAO,IAAI,EAAE,IAAI;;;;;gBACvD,UAAU,GAAG,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,UAAU,MAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC;gBACtE,EAAE,GAAG,SAAS,GAAG,CAAC,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,EAAE,KAAI,WAAI,CAAC,IAAI,CAAC,CAAC,CAAC;gBAC1C,YAAY,GAAG;oBACnB,MAAM,EAAE,IAAI;oBACZ,EAAE,EAAE,EAAE;oBACN,SAAS,EAAE,mBAAmB;oBAC9B,aAAa,EAAE,IAAI;oBACnB,UAAU,YAAA;iBACX,CAAC;gBAEiB,qBAAM,IAAI,CAAC,KAAK,EAAE;wBACnC,IAAI,EAAE,uBAAuB;wBAC7B,KAAK,EAAE,YAAY;qBACpB,CAAC,EAAA;;gBAHI,UAAU,GAAG,SAGjB;gBAEI,OAAO,GAAc,UAAC,KAA2B;oBACrD,OAAA,IAAI,CAAC,SAAS,EAAE;wBACd,IAAI,MAAA;wBACJ,KAAK,OAAA;wBACL,EAAE,IAAA;qBACH,CAAC;gBAJF,CAIE,CAAC;gBAEC,OAAO,GAAmB,cAAM,OAAA,IAAI,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,EAArC,CAAqC,CAAC;gBAE5E,sBAAO,CAAC,OAAO,EAAE,OAAO,CAAC,EAAC;;;KAC3B,EA1B6C,CA0B7C,CAAC;AAEF,kBAAe;IACb,IAAI,EAAE,SAAS;IACf,IAAI,MAAA;CACL,CAAC"}
\ No newline at end of file
diff --git a/node/commands/recipes/resolve.d.ts b/node/commands/recipes/resolve.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a8a21edf32e6e0b10c4c46ea9186de7bb369dc13
--- /dev/null
+++ b/node/commands/recipes/resolve.d.ts
@@ -0,0 +1,6 @@
+import { MakeRecipeFn } from "../../types";
+declare const _default: {
+    name: string;
+    make: MakeRecipeFn;
+};
+export default _default;
diff --git a/node/commands/recipes/resolve.js b/node/commands/recipes/resolve.js
new file mode 100644
index 0000000000000000000000000000000000000000..0172b12f25ff56f25ef3eae7405fad71791076ec
--- /dev/null
+++ b/node/commands/recipes/resolve.js
@@ -0,0 +1,110 @@
+"use strict";
+var __assign = (this && this.__assign) || function () {
+    __assign = Object.assign || function(t) {
+        for (var s, i = 1, n = arguments.length; i < n; i++) {
+            s = arguments[i];
+            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
+                t[p] = s[p];
+        }
+        return t;
+    };
+    return __assign.apply(this, arguments);
+};
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+    return new (P || (P = Promise))(function (resolve, reject) {
+        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+        step((generator = generator.apply(thisArg, _arguments || [])).next());
+    });
+};
+var __generator = (this && this.__generator) || function (thisArg, body) {
+    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
+    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
+    function verb(n) { return function (v) { return step([n, v]); }; }
+    function step(op) {
+        if (f) throw new TypeError("Generator is already executing.");
+        while (_) try {
+            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
+            if (y = 0, t) op = [op[0] & 2, t.value];
+            switch (op[0]) {
+                case 0: case 1: t = op; break;
+                case 4: _.label++; return { value: op[1], done: false };
+                case 5: _.label++; y = op[1]; op = [0]; continue;
+                case 7: op = _.ops.pop(); _.trys.pop(); continue;
+                default:
+                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
+                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
+                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
+                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
+                    if (t[2]) _.ops.pop();
+                    _.trys.pop(); continue;
+            }
+            op = body.call(thisArg, _);
+        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
+        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
+    }
+};
+var __spreadArray = (this && this.__spreadArray) || function (to, from) {
+    for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
+        to[j] = from[i];
+    return to;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var splitReference = function (s) { return s.split(","); };
+var isReference = function (s) { return s.startsWith("Device."); };
+var addDot = function (s) { return (s.endsWith(".") ? s : s + "."); };
+var resolveReferences = function (message, call, level) { return __awaiter(void 0, void 0, void 0, function () {
+    var msg, msg, _i, _a, _b, key, val, item, _c, _d, _e, _f;
+    return __generator(this, function (_g) {
+        switch (_g.label) {
+            case 0:
+                if (level === 0 || !["string", "object"].includes(typeof message))
+                    return [2 /*return*/, message];
+                if (!(typeof message === "string")) return [3 /*break*/, 3];
+                return [4 /*yield*/, call("GET", { paths: splitReference(message).map(addDot) })];
+            case 1:
+                msg = _g.sent();
+                return [4 /*yield*/, resolveReferences(msg, call, level - 1)];
+            case 2: return [2 /*return*/, _g.sent()];
+            case 3:
+                if (!(typeof message === "object")) return [3 /*break*/, 11];
+                msg = Array.isArray(message) ? __spreadArray([], message) : __assign({}, message);
+                _i = 0, _a = Object.entries(message);
+                _g.label = 4;
+            case 4:
+                if (!(_i < _a.length)) return [3 /*break*/, 10];
+                _b = _a[_i], key = _b[0], val = _b[1];
+                if (!(typeof val === "string" && isReference(val))) return [3 /*break*/, 7];
+                return [4 /*yield*/, call("GET", { paths: splitReference(val).map(addDot) })];
+            case 5:
+                item = _g.sent();
+                _c = msg;
+                _d = key;
+                return [4 /*yield*/, resolveReferences(item, call, level - 1)];
+            case 6:
+                _c[_d] = _g.sent();
+                return [3 /*break*/, 9];
+            case 7:
+                if (!(typeof val === "object")) return [3 /*break*/, 9];
+                _e = msg;
+                _f = key;
+                return [4 /*yield*/, resolveReferences(msg[key], call, level)];
+            case 8:
+                _e[_f] = _g.sent();
+                _g.label = 9;
+            case 9:
+                _i++;
+                return [3 /*break*/, 4];
+            case 10: return [2 /*return*/, msg];
+            case 11: return [2 /*return*/, message];
+        }
+    });
+}); };
+var make = function (call) { return function (msg, level) { return resolveReferences(msg, call, level || 1); }; };
+exports.default = {
+    name: "resolve",
+    make: make
+};
+//# sourceMappingURL=resolve.js.map
\ No newline at end of file
diff --git a/node/commands/recipes/resolve.js.map b/node/commands/recipes/resolve.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..61b4547a2486001873d8097e8c537c1c5fa44df1
--- /dev/null
+++ b/node/commands/recipes/resolve.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../../src/commands/recipes/resolve.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,IAAM,cAAc,GAAG,UAAC,CAAS,IAAK,OAAA,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAZ,CAAY,CAAC;AACnD,IAAM,WAAW,GAAG,UAAC,CAAS,IAAc,OAAA,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,EAAvB,CAAuB,CAAC;AACpE,IAAM,MAAM,GAAG,UAAC,CAAS,IAAK,OAAA,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,EAA/B,CAA+B,CAAC;AAE9D,IAAM,iBAAiB,GAAG,UACxB,OAAkB,EAClB,IAAY,EACZ,KAAa;;;;;gBAEb,IAAI,KAAK,KAAK,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,OAAO,OAAO,CAAC;oBAAE,sBAAO,OAAO,EAAC;qBAC9E,CAAA,OAAO,OAAO,KAAK,QAAQ,CAAA,EAA3B,wBAA2B;gBACjB,qBAAM,IAAI,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAA;;gBAAvE,GAAG,GAAG,SAAiE;gBACtE,qBAAM,iBAAiB,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,EAAA;oBAApD,sBAAO,SAA6C,EAAC;;qBAC5C,CAAA,OAAO,OAAO,KAAK,QAAQ,CAAA,EAA3B,yBAA2B;gBAC9B,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,mBAAK,OAAO,EAAE,CAAC,cAAM,OAAO,CAAE,CAAC;sBACnB,EAAvB,KAAA,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;;;qBAAvB,CAAA,cAAuB,CAAA;gBAArC,WAAU,EAAT,GAAG,QAAA,EAAE,GAAG,QAAA;qBACd,CAAA,OAAO,GAAG,KAAK,QAAQ,IAAI,WAAW,CAAC,GAAG,CAAC,CAAA,EAA3C,wBAA2C;gBAChC,qBAAM,IAAI,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAA;;gBAApE,IAAI,GAAG,SAA6D;gBAC1E,KAAA,GAAG,CAAA;gBAAC,KAAA,GAAG,CAAA;gBAAI,qBAAM,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,EAAA;;gBAAzD,MAAQ,GAAG,SAA8C,CAAC;;;qBACjD,CAAA,OAAO,GAAG,KAAK,QAAQ,CAAA,EAAvB,wBAAuB;gBAChC,KAAA,GAAG,CAAA;gBAAC,KAAA,GAAG,CAAA;gBAAI,qBAAM,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,EAAA;;gBAAzD,MAAQ,GAAG,SAA8C,CAAC;;;gBALrC,IAAuB,CAAA;;qBAOhD,sBAAO,GAAG,EAAC;qBAEb,sBAAO,OAAO,EAAC;;;KAChB,CAAC;AAEF,IAAM,IAAI,GAAiB,UAAC,IAAI,IAAoB,OAAA,UAAC,GAAG,EAAE,KAAK,IAAK,OAAA,iBAAiB,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC,CAAC,EAAxC,CAAwC,EAAxD,CAAwD,CAAA;AAE5G,kBAAe;IACb,IAAI,EAAE,SAAS;IACf,IAAI,MAAA;CACL,CAAA"}
\ No newline at end of file
diff --git a/node/commands/recipes/subscribe.d.ts b/node/commands/recipes/subscribe.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5ce835a2fe8690c2913a7d7ef49b60be818210ed
--- /dev/null
+++ b/node/commands/recipes/subscribe.d.ts
@@ -0,0 +1,6 @@
+import { MakeFn } from "../../types";
+declare const _default: {
+    name: string;
+    make: MakeFn;
+};
+export default _default;
diff --git a/node/commands/recipes/subscribe.js b/node/commands/recipes/subscribe.js
new file mode 100644
index 0000000000000000000000000000000000000000..d41ae8b6c6a26c09c941049b8afef01b16a6c391
--- /dev/null
+++ b/node/commands/recipes/subscribe.js
@@ -0,0 +1,72 @@
+"use strict";
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+    return new (P || (P = Promise))(function (resolve, reject) {
+        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+        step((generator = generator.apply(thisArg, _arguments || [])).next());
+    });
+};
+var __generator = (this && this.__generator) || function (thisArg, body) {
+    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
+    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
+    function verb(n) { return function (v) { return step([n, v]); }; }
+    function step(op) {
+        if (f) throw new TypeError("Generator is already executing.");
+        while (_) try {
+            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
+            if (y = 0, t) op = [op[0] & 2, t.value];
+            switch (op[0]) {
+                case 0: case 1: t = op; break;
+                case 4: _.label++; return { value: op[1], done: false };
+                case 5: _.label++; y = op[1]; op = [0]; continue;
+                case 7: op = _.ops.pop(); _.trys.pop(); continue;
+                default:
+                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
+                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
+                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
+                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
+                    if (t[2]) _.ops.pop();
+                    _.trys.pop(); continue;
+            }
+            op = body.call(thisArg, _);
+        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
+        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
+    }
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var util_1 = require("../util");
+var subscriptionPath = "Device.LocalAgent.Subscription.";
+var make = function (call, on) { return function (opts, callback) { return __awaiter(void 0, void 0, void 0, function () {
+    var id, refList, newSubPath, clear;
+    return __generator(this, function (_a) {
+        switch (_a.label) {
+            case 0:
+                id = "NOTIFY@" + (opts.id || util_1.uniq());
+                refList = Array.isArray(opts.reference) ? opts.reference.join(",") : opts.reference;
+                return [4 /*yield*/, call("ADD", {
+                        path: subscriptionPath,
+                        value: {
+                            Enable: true,
+                            ID: id,
+                            NotifType: opts.notif,
+                            ReferenceList: refList,
+                            Persistent: false,
+                        },
+                    })];
+            case 1:
+                newSubPath = _a.sent();
+                clear = on(id, callback);
+                return [2 /*return*/, function () {
+                        clear();
+                        return call("DELETE", { paths: newSubPath });
+                    }];
+        }
+    });
+}); }; };
+exports.default = {
+    name: "subscribe",
+    make: make,
+};
+//# sourceMappingURL=subscribe.js.map
\ No newline at end of file
diff --git a/node/commands/recipes/subscribe.js.map b/node/commands/recipes/subscribe.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..86ac014cfe04c7ccba38d858e1d898925f46c4ed
--- /dev/null
+++ b/node/commands/recipes/subscribe.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"subscribe.js","sourceRoot":"","sources":["../../../src/commands/recipes/subscribe.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,gCAA+B;AAE/B,IAAM,gBAAgB,GAAG,iCAAiC,CAAC;AAE3D,IAAM,IAAI,GAAW,UAAC,IAAI,EAAE,EAAE,IAAsB,OAAA,UAAO,IAAI,EAAE,QAAQ;;;;;gBACjE,EAAE,GAAG,SAAS,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,WAAI,EAAE,CAAC,CAAC;gBACrC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAA;gBAEtE,qBAAM,IAAI,CAAC,KAAK,EAAE;wBACnC,IAAI,EAAE,gBAAgB;wBACtB,KAAK,EAAE;4BACL,MAAM,EAAE,IAAI;4BACZ,EAAE,EAAE,EAAE;4BACN,SAAS,EAAE,IAAI,CAAC,KAAK;4BACrB,aAAa,EAAE,OAAO;4BACtB,UAAU,EAAE,KAAK;yBAClB;qBACF,CAAC,EAAA;;gBATI,UAAU,GAAG,SASjB;gBAEI,KAAK,GAAG,EAAE,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;gBAC/B,sBAAO;wBACL,KAAK,EAAE,CAAC;wBACR,OAAO,IAAI,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAA;oBAC9C,CAAC,EAAA;;;KACF,EApBmD,CAoBnD,CAAC;AAEF,kBAAe;IACb,IAAI,EAAE,WAAW;IACjB,IAAI,MAAA;CACL,CAAC"}
\ No newline at end of file
diff --git a/node/commands/set.d.ts b/node/commands/set.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5e00b673fef4d5d0922d1ef792b1d08ca25a83fa
--- /dev/null
+++ b/node/commands/set.d.ts
@@ -0,0 +1,6 @@
+import { DecodeFn, EncodeFn } from "../types";
+declare const _default: {
+    decode: DecodeFn;
+    encode: EncodeFn;
+};
+export default _default;
diff --git a/node/commands/set.js b/node/commands/set.js
new file mode 100644
index 0000000000000000000000000000000000000000..ef39cba0206efa53c61718be957b2616c7fdea75
--- /dev/null
+++ b/node/commands/set.js
@@ -0,0 +1,85 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var util = __importStar(require("./util"));
+var decode = function (_msg) {
+    return [null];
+};
+var isObject = function (v) {
+    return typeof v === "object" && v.required !== undefined && v.value !== undefined;
+};
+var encode = function (_a) {
+    var value = _a.value, initialPath = _a.path;
+    var isObj = typeof value === "object";
+    var allowPartial = isObj && value.allowPartial !== undefined ? value.allowPartial : false;
+    var attr = initialPath.split(".").pop() || "";
+    var pairs = isObj
+        ? Object.entries(value).map(function (_a) {
+            var k = _a[0], v = _a[1];
+            return isObject(v)
+                ? [k, v.value.toString(), v.required]
+                : [k, v.toString(), false];
+        })
+        : [[attr, value]];
+    var path = initialPath.endsWith(".") ? initialPath : initialPath.slice(0, initialPath.lastIndexOf('.') + 1);
+    return {
+        lookup: "Msg",
+        header: {
+            lookup: "Header",
+            msgId: util.uniq("SET@"),
+            msgType: "SET",
+        },
+        body: {
+            lookup: "Body",
+            request: {
+                lookup: "Request",
+                set: {
+                    allowPartial: allowPartial,
+                    updateObjs: [
+                        {
+                            lookup: "Set.UpdateObject",
+                            objPath: path,
+                            paramSettings: pairs
+                                .filter(function (_a) {
+                                var k = _a[0];
+                                return k !== "allowPartial";
+                            })
+                                .map(function (_a) {
+                                var param = _a[0], value = _a[1], required = _a[2];
+                                return ({
+                                    lookup: "Set.UpdateParamSetting",
+                                    param: param,
+                                    value: value,
+                                    required: required,
+                                });
+                            }),
+                        },
+                    ],
+                },
+            },
+        },
+    };
+};
+exports.default = {
+    decode: decode,
+    encode: encode
+};
+//# sourceMappingURL=set.js.map
\ No newline at end of file
diff --git a/node/commands/set.js.map b/node/commands/set.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..00087436d047e9bf1e991977e636e11a632b60a3
--- /dev/null
+++ b/node/commands/set.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"set.js","sourceRoot":"","sources":["../../src/commands/set.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AACA,2CAA+B;AAE/B,IAAM,MAAM,GAAa,UAAC,IAAI;IAC5B,OAAO,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC,CAAC;AAEF,IAAM,QAAQ,GAAG,UAAC,CAAC;IACjB,OAAA,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS;AAA1E,CAA0E,CAAC;AAE7E,IAAM,MAAM,GAAa,UAAC,EAA4B;QAA1B,KAAK,WAAA,EAAQ,WAAW,UAAA;IAClD,IAAM,KAAK,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC;IACxC,IAAM,YAAY,GAAG,KAAK,IAAI,KAAK,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC;IAC5F,IAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;IAChD,IAAM,KAAK,GAAG,KAAK;QACjB,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAc,CAAC,CAAC,GAAG,CAAC,UAAC,EAAM;gBAAL,CAAC,QAAA,EAAE,CAAC,QAAA;YACvC,OAAA,QAAQ,CAAC,CAAC,CAAC;gBACT,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC;gBACrC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC;QAF5B,CAE4B,CAC7B;QACH,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;IACpB,IAAM,IAAI,GAAG,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IAE9G,OAAO;QACL,MAAM,EAAE,KAAK;QACb,MAAM,EAAE;YACN,MAAM,EAAE,QAAQ;YAChB,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;YACxB,OAAO,EAAE,KAAK;SACf;QACD,IAAI,EAAE;YACJ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,MAAM,EAAE,SAAS;gBACjB,GAAG,EAAE;oBACH,YAAY,cAAA;oBACZ,UAAU,EAAE;wBACV;4BACE,MAAM,EAAE,kBAAkB;4BAC1B,OAAO,EAAE,IAAI;4BACb,aAAa,EAAE,KAAK;iCACjB,MAAM,CAAC,UAAC,EAAG;oCAAF,CAAC,QAAA;gCAAM,OAAA,CAAC,KAAK,cAAc;4BAApB,CAAoB,CAAC;iCACrC,GAAG,CAAC,UAAC,EAAwB;oCAAvB,KAAK,QAAA,EAAE,KAAK,QAAA,EAAE,QAAQ,QAAA;gCAAM,OAAA,CAAC;oCAClC,MAAM,EAAE,wBAAwB;oCAChC,KAAK,OAAA;oCACL,KAAK,OAAA;oCACL,QAAQ,UAAA;iCACT,CAAC;4BALiC,CAKjC,CAAC;yBACN;qBACF;iBACF;aACF;SACF;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,kBAAe;IACb,MAAM,QAAA;IACN,MAAM,QAAA;CACP,CAAC"}
\ No newline at end of file
diff --git a/node/commands/supported.d.ts b/node/commands/supported.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5e00b673fef4d5d0922d1ef792b1d08ca25a83fa
--- /dev/null
+++ b/node/commands/supported.d.ts
@@ -0,0 +1,6 @@
+import { DecodeFn, EncodeFn } from "../types";
+declare const _default: {
+    decode: DecodeFn;
+    encode: EncodeFn;
+};
+export default _default;
diff --git a/node/commands/supported.js b/node/commands/supported.js
new file mode 100644
index 0000000000000000000000000000000000000000..28c7b8ccd2c59da772097a7357bfee69cbdeb992
--- /dev/null
+++ b/node/commands/supported.js
@@ -0,0 +1,55 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var util = __importStar(require("./util"));
+var decode = function (msg) {
+    var results = util.search(msg, "reqObjResults");
+    return [results];
+};
+var encode = function (_a) {
+    var paths = _a.paths, _b = _a.opts, opts = _b === void 0 ? {} : _b;
+    return ({
+        lookup: "Msg",
+        header: {
+            msgId: util.uniq("GET_SUPPORTED_DM@"),
+            msgType: "GET_SUPPORTED_DM",
+            lookup: "Header",
+        },
+        body: {
+            lookup: "Body",
+            request: {
+                lookup: "Request",
+                getSupportedDm: {
+                    objPaths: Array.isArray(paths) ? paths : [paths],
+                    firstLevelOnly: opts.firstLevelOnly || false,
+                    returnCommands: opts.returnCommands || false,
+                    returnEvents: opts.returnEvents || false,
+                    returnParams: opts.returnParams || false,
+                },
+            },
+        },
+    });
+};
+exports.default = {
+    decode: decode,
+    encode: encode
+};
+//# sourceMappingURL=supported.js.map
\ No newline at end of file
diff --git a/node/commands/supported.js.map b/node/commands/supported.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..630139da915b2695a4db408ef314cf5ef6d4bca7
--- /dev/null
+++ b/node/commands/supported.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"supported.js","sourceRoot":"","sources":["../../src/commands/supported.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AACA,2CAA+B;AAE/B,IAAM,MAAM,GAAa,UAAC,GAAG;IAC3B,IAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;IAClD,OAAO,CAAC,OAAO,CAAC,CAAC;AACnB,CAAC,CAAC;AAEF,IAAM,MAAM,GAAa,UAAC,EAAoB;QAAlB,KAAK,WAAA,EAAE,YAAS,EAAT,IAAI,mBAAG,EAAE,KAAA;IAAO,OAAA,CAAC;QAClD,MAAM,EAAE,KAAK;QACb,MAAM,EAAE;YACN,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC;YACrC,OAAO,EAAE,kBAAkB;YAC3B,MAAM,EAAE,QAAQ;SACjB;QACD,IAAI,EAAE;YACJ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,MAAM,EAAE,SAAS;gBACjB,cAAc,EAAE;oBACd,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;oBAChD,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,KAAK;oBAC5C,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,KAAK;oBAC5C,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,KAAK;oBACxC,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,KAAK;iBACzC;aACF;SACF;KACF,CAAC;AApBiD,CAoBjD,CAAC;AAEH,kBAAe;IACb,MAAM,QAAA;IACN,MAAM,QAAA;CACP,CAAC"}
\ No newline at end of file
diff --git a/node/commands/util.d.ts b/node/commands/util.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..867f5de4d6cfb6baebcba9204dcdf86044f9b44e
--- /dev/null
+++ b/node/commands/util.d.ts
@@ -0,0 +1,13 @@
+export declare const unflatten: (obj: any) => {};
+export declare const search: (obj: any, key: string) => any;
+export declare const searchParent: (obj: any, key: string) => Record<string, any> | undefined;
+export declare const searchAll: (obj: any, key: string) => any[];
+export declare const extractCommand: (msg: {
+    [key: string]: any;
+}) => string | undefined;
+/** Unwraps object with single key */
+export declare const unwrapObject: (data: any) => any;
+/** Unwraps array with single item */
+export declare const unwrapArray: (arr: any) => any;
+export declare function makeBuffer(rootRecord: protobuf.Root, payload: any, options: Record<string, string>): any;
+export declare const uniq: (initial?: string | undefined) => string;
diff --git a/node/commands/util.js b/node/commands/util.js
new file mode 100644
index 0000000000000000000000000000000000000000..57b80e4bb68bd1b4f60cea6efb8c7111ae8fdaf1
--- /dev/null
+++ b/node/commands/util.js
@@ -0,0 +1,119 @@
+"use strict";
+var __assign = (this && this.__assign) || function () {
+    __assign = Object.assign || function(t) {
+        for (var s, i = 1, n = arguments.length; i < n; i++) {
+            s = arguments[i];
+            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
+                t[p] = s[p];
+        }
+        return t;
+    };
+    return __assign.apply(this, arguments);
+};
+var __spreadArray = (this && this.__spreadArray) || function (to, from) {
+    for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
+        to[j] = from[i];
+    return to;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.uniq = exports.makeBuffer = exports.unwrapArray = exports.unwrapObject = exports.extractCommand = exports.searchAll = exports.searchParent = exports.search = exports.unflatten = void 0;
+var digitRe = /^\d+$/;
+var digitDotRe = /^\d+\..*$/;
+var isDigit = function (v) { return digitRe.test(v); };
+var firstIsIndex = function (s) { return digitDotRe.test(s); };
+// Based on: https://www.30secondsofcode.org/js/s/unflatten-object
+var unflatten = function (obj) {
+    return Object.keys(obj).reduce(function (res, k) {
+        k.split(".")
+            .map(function (v) { return (isDigit(v) ? parseInt(v) - 1 : v); })
+            .reduce(function (acc, e, i, keys) {
+            return acc[e] ||
+                (acc[e] = isNaN(Number(keys[i + 1]))
+                    ? keys.length - 1 === i
+                        ? obj[k]
+                        : {}
+                    : []);
+        }, res);
+        return res;
+    }, firstIsIndex(Object.keys(obj)[0]) ? [] : {});
+};
+exports.unflatten = unflatten;
+var search = function (obj, key) {
+    if (typeof obj !== "object")
+        return null;
+    if (obj[key])
+        return obj[key];
+    for (var _i = 0, _a = Object.values(obj); _i < _a.length; _i++) {
+        var val = _a[_i];
+        var s = exports.search(val, key);
+        if (s)
+            return s;
+    }
+};
+exports.search = search;
+var searchParent = function (obj, key) {
+    if (typeof obj !== "object")
+        return;
+    if (obj[key])
+        return obj;
+    for (var _i = 0, _a = Object.values(obj); _i < _a.length; _i++) {
+        var val = _a[_i];
+        var s = exports.searchParent(val, key);
+        if (s)
+            return s;
+    }
+};
+exports.searchParent = searchParent;
+var _searchAll = function (obj, key) {
+    return typeof obj !== "object"
+        ? []
+        : Object.entries(obj).reduce(function (acc, _a) {
+            var k = _a[0], v = _a[1];
+            return __spreadArray(__spreadArray([], acc), [k === key ? v : _searchAll(v, key)]);
+        }, []);
+};
+var searchAll = function (obj, key) {
+    return _searchAll(obj, key).flat(Infinity);
+};
+exports.searchAll = searchAll;
+var extractCommand = function (msg) {
+    var msgType = exports.search(msg, "msgType");
+    if (!msgType) {
+        var id = exports.search(msg, "msgId");
+        var frst = (id ? id.split("@") : [""])[0];
+        return frst.toUpperCase();
+    }
+    return msgType;
+};
+exports.extractCommand = extractCommand;
+/** Unwraps object with single key */
+var unwrapObject = function (data) {
+    return !Array.isArray(data) &&
+        typeof data === "object" &&
+        Object.keys(data).length === 1
+        ? Object.values(data)[0]
+        : data;
+};
+exports.unwrapObject = unwrapObject;
+/** Unwraps array with single item */
+var unwrapArray = function (arr) {
+    return Array.isArray(arr) && arr.length === 1 ? arr[0] : arr;
+};
+exports.unwrapArray = unwrapArray;
+function makeBuffer(rootRecord, payload, options) {
+    var NoSessionContextRecord = rootRecord.lookupType("usp_record.NoSessionContextRecord");
+    var noSessionContextRecordMsg = NoSessionContextRecord.create({
+        payload: payload,
+    });
+    var record = rootRecord.lookupType("usp_record.Record");
+    var recordMsg = record.create(__assign({ version: "1.0", PayloadSecurity: record.PayloadSecurity.PLAINTEXT, noSessionContext: noSessionContextRecordMsg }, options));
+    var buffer = record.encode(recordMsg).finish();
+    return buffer;
+}
+exports.makeBuffer = makeBuffer;
+var uniq = function (initial) {
+    return (initial || "") +
+        (Date.now().toString(36) + Math.random().toString(36).substr(2, 5)).toUpperCase();
+};
+exports.uniq = uniq;
+//# sourceMappingURL=util.js.map
\ No newline at end of file
diff --git a/node/commands/util.js.map b/node/commands/util.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..b44edfd5c16f5ec39082724630f28dc746b2fce5
--- /dev/null
+++ b/node/commands/util.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/commands/util.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/node/index.d.ts b/node/index.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..88e4ae2e92477d409cb5bc098bf5d8a8544a279d
--- /dev/null
+++ b/node/index.d.ts
@@ -0,0 +1,9 @@
+import { Connect } from "./types";
+/**
+ * Connect to device
+ * @param opts - Connection options
+ * @param events - Optional event handlers
+ * @returns A set of functions for interacting with the device
+ */
+declare const connect: Connect;
+export default connect;
diff --git a/node/index.js b/node/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..8f4e4e8a505567ddeb90ed48ef93e33a8e4fdd5f
--- /dev/null
+++ b/node/index.js
@@ -0,0 +1,166 @@
+"use strict";
+var __assign = (this && this.__assign) || function () {
+    __assign = Object.assign || function(t) {
+        for (var s, i = 1, n = arguments.length; i < n; i++) {
+            s = arguments[i];
+            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
+                t[p] = s[p];
+        }
+        return t;
+    };
+    return __assign.apply(this, arguments);
+};
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+    return new (P || (P = Promise))(function (resolve, reject) {
+        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+        step((generator = generator.apply(thisArg, _arguments || [])).next());
+    });
+};
+var __generator = (this && this.__generator) || function (thisArg, body) {
+    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
+    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
+    function verb(n) { return function (v) { return step([n, v]); }; }
+    function step(op) {
+        if (f) throw new TypeError("Generator is already executing.");
+        while (_) try {
+            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
+            if (y = 0, t) op = [op[0] & 2, t.value];
+            switch (op[0]) {
+                case 0: case 1: t = op; break;
+                case 4: _.label++; return { value: op[1], done: false };
+                case 5: _.label++; y = op[1]; op = [0]; continue;
+                case 7: op = _.ops.pop(); _.trys.pop(); continue;
+                default:
+                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
+                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
+                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
+                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
+                    if (t[2]) _.ops.pop();
+                    _.trys.pop(); continue;
+            }
+            op = body.call(thisArg, _);
+        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
+        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
+    }
+};
+var __importDefault = (this && this.__importDefault) || function (mod) {
+    return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var async_mqtt_1 = __importDefault(require("async-mqtt"));
+var commands_1 = require("./commands");
+var util_1 = require("./util");
+var defaultPublishEndpoint = "/usp/endpoint";
+var defaultSubscribeEndpoint = "/usp/controller";
+var defaultIdEndpoint = "obuspa/EndpointID";
+var defaultFromId = "proto::interop-usp-controller";
+var idResolveTimeout = 5000;
+var isURL = function (opts) {
+    return "url" in opts;
+};
+var _connect = function (opts) {
+    var _a;
+    if (isURL(opts))
+        return async_mqtt_1.default.connectAsync(opts.url, opts);
+    else
+        return ((_a = opts.protocol) === null || _a === void 0 ? void 0 : _a.startsWith("ws"))
+            ? async_mqtt_1.default.connectAsync(opts.protocol + "://" + opts.host + ":" + opts.port, opts)
+            : async_mqtt_1.default.connectAsync(opts);
+};
+var fixId = function (s) { return s.split("+").join("%2B"); };
+/**
+ * Connect to device
+ * @param opts - Connection options
+ * @param events - Optional event handlers
+ * @returns A set of functions for interacting with the device
+ */
+var connect = function (options, events) { return __awaiter(void 0, void 0, void 0, function () {
+    var subscribeEndpoint, publishEndpoint, idEndpoint, router, callbackRouter, handleError, client, handleInit, toId, _a, _b, fromId, on, encode, call;
+    return __generator(this, function (_c) {
+        switch (_c.label) {
+            case 0:
+                subscribeEndpoint = options.subscribeEndpoint || defaultSubscribeEndpoint;
+                publishEndpoint = options.publishEndpoint || defaultPublishEndpoint;
+                idEndpoint = options.idEndpoint || defaultIdEndpoint;
+                router = util_1.makeRouter();
+                callbackRouter = util_1.makeCallbackRouter();
+                handleError = function (err) {
+                    return events && events.onError && events.onError(err);
+                };
+                callbackRouter.add("error", handleError);
+                return [4 /*yield*/, _connect(options)];
+            case 1:
+                client = _c.sent();
+                handleInit = function () {
+                    return new Promise(function (resolve, reject) {
+                        var id = setTimeout(function () { return reject({ errMsg: "toId was not received within timeout(" + idResolveTimeout + ")" }); }, idResolveTimeout);
+                        client.on("message", function (_topic, data) {
+                            clearTimeout(id);
+                            client.unsubscribe(idEndpoint);
+                            resolve(commands_1.decodeId(data));
+                        });
+                        client.subscribe(idEndpoint);
+                    });
+                };
+                _a = fixId;
+                _b = options.toId;
+                if (_b) return [3 /*break*/, 3];
+                return [4 /*yield*/, handleInit()];
+            case 2:
+                _b = (_c.sent());
+                _c.label = 3;
+            case 3:
+                toId = _a.apply(void 0, [_b]);
+                fromId = options.fromId || defaultFromId;
+                client.on("message", function (_topic, data) {
+                    var parsedMsg = commands_1.readMsg(data);
+                    var _a = commands_1.decode(parsedMsg), id = _a[0], message = _a[1], err = _a[2];
+                    var call = router.get(id);
+                    if (call && call.resolve && call.resolve) {
+                        if (err)
+                            call.reject(err);
+                        else
+                            call.resolve(message);
+                    }
+                    var cbs = callbackRouter.get(id);
+                    cbs.forEach(function (cb) {
+                        if (message)
+                            cb(message, parsedMsg);
+                        else if (err)
+                            cb(err, parsedMsg);
+                    });
+                });
+                client.on("error", function (err) {
+                    callbackRouter.get("error").forEach(function (cb) { return cb(err); });
+                    handleError(JSON.stringify(err, null, 2));
+                });
+                on = function (ident, callback) {
+                    callbackRouter.add(ident, callback);
+                    return function () {
+                        callbackRouter.del(ident);
+                    };
+                };
+                encode = commands_1.makeEncode({ fromId: fromId, toId: toId });
+                call = function (command, args) {
+                    return new Promise(function (resolve, reject) {
+                        var _a = encode(command, args), id = _a[0], msg = _a[1], err = _a[2];
+                        if (err)
+                            reject(err);
+                        else {
+                            router.add(id, { resolve: resolve, reject: reject });
+                            client.publish(publishEndpoint, msg);
+                        }
+                    });
+                };
+                return [4 /*yield*/, client.subscribe(subscribeEndpoint)];
+            case 4:
+                _c.sent();
+                return [2 /*return*/, __assign(__assign({ get: function (paths) { return call("GET", { paths: paths }); }, set: function (path, value) { return call("SET", { path: path, value: value }); }, add: function (path, value) { return call("ADD", { path: path, value: value }); }, del: function (paths, allowPartial) { return call("DELETE", { paths: paths, allowPartial: allowPartial }); }, instances: function (paths, opts) { return call("GET_INSTANCES", { paths: paths, opts: opts }); }, supportedDM: function (paths, opts) { return call("GET_SUPPORTED_DM", { paths: paths, opts: opts }); }, supportedProto: function (versions) { return call("GET_SUPPORTED_PROTO", { versions: versions }); }, _operate: function (path, id, input) { return call("OPERATE", { path: path, input: input, id: id }); }, on: on }, commands_1.makeRecipes(call, on)), { disconnect: function () { return client.end(); } })];
+        }
+    });
+}); };
+exports.default = connect;
+//# sourceMappingURL=index.js.map
\ No newline at end of file
diff --git a/node/index.js.map b/node/index.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..a61b4276799343a3a6bfc02afdb0c9f722dc9a96
--- /dev/null
+++ b/node/index.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/node/specs/usp-msg-1-1.json b/node/specs/usp-msg-1-1.json
new file mode 100644
index 0000000000000000000000000000000000000000..7f528a8e6aab83341084cc0a651029e532472244
--- /dev/null
+++ b/node/specs/usp-msg-1-1.json
@@ -0,0 +1,1175 @@
+{
+    "nested": {
+        "usp": {
+            "nested": {
+                "Msg": {
+                    "fields": {
+                        "header": {
+                            "type": "Header",
+                            "id": 1
+                        },
+                        "body": {
+                            "type": "Body",
+                            "id": 2
+                        }
+                    }
+                },
+                "Header": {
+                    "fields": {
+                        "msgId": {
+                            "type": "string",
+                            "id": 1
+                        },
+                        "msgType": {
+                            "type": "MsgType",
+                            "id": 2
+                        }
+                    },
+                    "nested": {
+                        "MsgType": {
+                            "values": {
+                                "ERROR": 0,
+                                "GET": 1,
+                                "GET_RESP": 2,
+                                "NOTIFY": 3,
+                                "SET": 4,
+                                "SET_RESP": 5,
+                                "OPERATE": 6,
+                                "OPERATE_RESP": 7,
+                                "ADD": 8,
+                                "ADD_RESP": 9,
+                                "DELETE": 10,
+                                "DELETE_RESP": 11,
+                                "GET_SUPPORTED_DM": 12,
+                                "GET_SUPPORTED_DM_RESP": 13,
+                                "GET_INSTANCES": 14,
+                                "GET_INSTANCES_RESP": 15,
+                                "NOTIFY_RESP": 16,
+                                "GET_SUPPORTED_PROTO": 17,
+                                "GET_SUPPORTED_PROTO_RESP": 18
+                            }
+                        }
+                    }
+                },
+                "Body": {
+                    "oneofs": {
+                        "msgBody": {
+                            "oneof": [
+                                "request",
+                                "response",
+                                "error"
+                            ]
+                        }
+                    },
+                    "fields": {
+                        "request": {
+                            "type": "Request",
+                            "id": 1
+                        },
+                        "response": {
+                            "type": "Response",
+                            "id": 2
+                        },
+                        "error": {
+                            "type": "Error",
+                            "id": 3
+                        }
+                    }
+                },
+                "Request": {
+                    "oneofs": {
+                        "reqType": {
+                            "oneof": [
+                                "get",
+                                "getSupportedDm",
+                                "getInstances",
+                                "set",
+                                "add",
+                                "delete",
+                                "operate",
+                                "notify",
+                                "getSupportedProtocol"
+                            ]
+                        }
+                    },
+                    "fields": {
+                        "get": {
+                            "type": "Get",
+                            "id": 1
+                        },
+                        "getSupportedDm": {
+                            "type": "GetSupportedDM",
+                            "id": 2
+                        },
+                        "getInstances": {
+                            "type": "GetInstances",
+                            "id": 3
+                        },
+                        "set": {
+                            "type": "Set",
+                            "id": 4
+                        },
+                        "add": {
+                            "type": "Add",
+                            "id": 5
+                        },
+                        "delete": {
+                            "type": "Delete",
+                            "id": 6
+                        },
+                        "operate": {
+                            "type": "Operate",
+                            "id": 7
+                        },
+                        "notify": {
+                            "type": "Notify",
+                            "id": 8
+                        },
+                        "getSupportedProtocol": {
+                            "type": "GetSupportedProtocol",
+                            "id": 9
+                        }
+                    }
+                },
+                "Response": {
+                    "oneofs": {
+                        "respType": {
+                            "oneof": [
+                                "getResp",
+                                "getSupportedDmResp",
+                                "getInstancesResp",
+                                "setResp",
+                                "addResp",
+                                "deleteResp",
+                                "operateResp",
+                                "notifyResp",
+                                "getSupportedProtocolResp"
+                            ]
+                        }
+                    },
+                    "fields": {
+                        "getResp": {
+                            "type": "GetResp",
+                            "id": 1
+                        },
+                        "getSupportedDmResp": {
+                            "type": "GetSupportedDMResp",
+                            "id": 2
+                        },
+                        "getInstancesResp": {
+                            "type": "GetInstancesResp",
+                            "id": 3
+                        },
+                        "setResp": {
+                            "type": "SetResp",
+                            "id": 4
+                        },
+                        "addResp": {
+                            "type": "AddResp",
+                            "id": 5
+                        },
+                        "deleteResp": {
+                            "type": "DeleteResp",
+                            "id": 6
+                        },
+                        "operateResp": {
+                            "type": "OperateResp",
+                            "id": 7
+                        },
+                        "notifyResp": {
+                            "type": "NotifyResp",
+                            "id": 8
+                        },
+                        "getSupportedProtocolResp": {
+                            "type": "GetSupportedProtocolResp",
+                            "id": 9
+                        }
+                    }
+                },
+                "Error": {
+                    "fields": {
+                        "errCode": {
+                            "type": "fixed32",
+                            "id": 1
+                        },
+                        "errMsg": {
+                            "type": "string",
+                            "id": 2
+                        },
+                        "paramErrs": {
+                            "rule": "repeated",
+                            "type": "ParamError",
+                            "id": 3
+                        }
+                    },
+                    "nested": {
+                        "ParamError": {
+                            "fields": {
+                                "paramPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "errCode": {
+                                    "type": "fixed32",
+                                    "id": 2
+                                },
+                                "errMsg": {
+                                    "type": "string",
+                                    "id": 3
+                                }
+                            }
+                        }
+                    }
+                },
+                "Get": {
+                    "fields": {
+                        "paramPaths": {
+                            "rule": "repeated",
+                            "type": "string",
+                            "id": 1
+                        }
+                    }
+                },
+                "GetResp": {
+                    "fields": {
+                        "reqPathResults": {
+                            "rule": "repeated",
+                            "type": "RequestedPathResult",
+                            "id": 1
+                        }
+                    },
+                    "nested": {
+                        "RequestedPathResult": {
+                            "fields": {
+                                "requestedPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "errCode": {
+                                    "type": "fixed32",
+                                    "id": 2
+                                },
+                                "errMsg": {
+                                    "type": "string",
+                                    "id": 3
+                                },
+                                "resolvedPathResults": {
+                                    "rule": "repeated",
+                                    "type": "ResolvedPathResult",
+                                    "id": 4
+                                }
+                            }
+                        },
+                        "ResolvedPathResult": {
+                            "fields": {
+                                "resolvedPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "resultParams": {
+                                    "keyType": "string",
+                                    "type": "string",
+                                    "id": 2
+                                }
+                            }
+                        }
+                    }
+                },
+                "GetSupportedDM": {
+                    "fields": {
+                        "objPaths": {
+                            "rule": "repeated",
+                            "type": "string",
+                            "id": 1
+                        },
+                        "firstLevelOnly": {
+                            "type": "bool",
+                            "id": 2
+                        },
+                        "returnCommands": {
+                            "type": "bool",
+                            "id": 3
+                        },
+                        "returnEvents": {
+                            "type": "bool",
+                            "id": 4
+                        },
+                        "returnParams": {
+                            "type": "bool",
+                            "id": 5
+                        }
+                    }
+                },
+                "GetSupportedDMResp": {
+                    "fields": {
+                        "reqObjResults": {
+                            "rule": "repeated",
+                            "type": "RequestedObjectResult",
+                            "id": 1
+                        }
+                    },
+                    "nested": {
+                        "RequestedObjectResult": {
+                            "fields": {
+                                "reqObjPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "errCode": {
+                                    "type": "fixed32",
+                                    "id": 2
+                                },
+                                "errMsg": {
+                                    "type": "string",
+                                    "id": 3
+                                },
+                                "dataModelInstUri": {
+                                    "type": "string",
+                                    "id": 4
+                                },
+                                "supportedObjs": {
+                                    "rule": "repeated",
+                                    "type": "SupportedObjectResult",
+                                    "id": 5
+                                }
+                            }
+                        },
+                        "SupportedObjectResult": {
+                            "fields": {
+                                "supportedObjPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "access": {
+                                    "type": "ObjAccessType",
+                                    "id": 2
+                                },
+                                "isMultiInstance": {
+                                    "type": "bool",
+                                    "id": 3
+                                },
+                                "supportedCommands": {
+                                    "rule": "repeated",
+                                    "type": "SupportedCommandResult",
+                                    "id": 4
+                                },
+                                "supportedEvents": {
+                                    "rule": "repeated",
+                                    "type": "SupportedEventResult",
+                                    "id": 5
+                                },
+                                "supportedParams": {
+                                    "rule": "repeated",
+                                    "type": "SupportedParamResult",
+                                    "id": 6
+                                }
+                            }
+                        },
+                        "SupportedParamResult": {
+                            "fields": {
+                                "paramName": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "access": {
+                                    "type": "ParamAccessType",
+                                    "id": 2
+                                }
+                            }
+                        },
+                        "SupportedCommandResult": {
+                            "fields": {
+                                "commandName": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "inputArgNames": {
+                                    "rule": "repeated",
+                                    "type": "string",
+                                    "id": 2
+                                },
+                                "outputArgNames": {
+                                    "rule": "repeated",
+                                    "type": "string",
+                                    "id": 3
+                                }
+                            }
+                        },
+                        "SupportedEventResult": {
+                            "fields": {
+                                "eventName": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "argNames": {
+                                    "rule": "repeated",
+                                    "type": "string",
+                                    "id": 2
+                                }
+                            }
+                        },
+                        "ParamAccessType": {
+                            "values": {
+                                "PARAM_READ_ONLY": 0,
+                                "PARAM_READ_WRITE": 1,
+                                "PARAM_WRITE_ONLY": 2
+                            }
+                        },
+                        "ObjAccessType": {
+                            "values": {
+                                "OBJ_READ_ONLY": 0,
+                                "OBJ_ADD_DELETE": 1,
+                                "OBJ_ADD_ONLY": 2,
+                                "OBJ_DELETE_ONLY": 3
+                            }
+                        }
+                    }
+                },
+                "GetInstances": {
+                    "fields": {
+                        "objPaths": {
+                            "rule": "repeated",
+                            "type": "string",
+                            "id": 1
+                        },
+                        "firstLevelOnly": {
+                            "type": "bool",
+                            "id": 2
+                        }
+                    }
+                },
+                "GetInstancesResp": {
+                    "fields": {
+                        "reqPathResults": {
+                            "rule": "repeated",
+                            "type": "RequestedPathResult",
+                            "id": 1
+                        }
+                    },
+                    "nested": {
+                        "RequestedPathResult": {
+                            "fields": {
+                                "requestedPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "errCode": {
+                                    "type": "fixed32",
+                                    "id": 2
+                                },
+                                "errMsg": {
+                                    "type": "string",
+                                    "id": 3
+                                },
+                                "currInsts": {
+                                    "rule": "repeated",
+                                    "type": "CurrInstance",
+                                    "id": 4
+                                }
+                            }
+                        },
+                        "CurrInstance": {
+                            "fields": {
+                                "instantiatedObjPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "uniqueKeys": {
+                                    "keyType": "string",
+                                    "type": "string",
+                                    "id": 2
+                                }
+                            }
+                        }
+                    }
+                },
+                "GetSupportedProtocol": {
+                    "fields": {
+                        "controllerSupportedProtocolVersions": {
+                            "type": "string",
+                            "id": 1
+                        }
+                    }
+                },
+                "GetSupportedProtocolResp": {
+                    "fields": {
+                        "agentSupportedProtocolVersions": {
+                            "type": "string",
+                            "id": 1
+                        }
+                    }
+                },
+                "Add": {
+                    "fields": {
+                        "allowPartial": {
+                            "type": "bool",
+                            "id": 1
+                        },
+                        "createObjs": {
+                            "rule": "repeated",
+                            "type": "CreateObject",
+                            "id": 2
+                        }
+                    },
+                    "nested": {
+                        "CreateObject": {
+                            "fields": {
+                                "objPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "paramSettings": {
+                                    "rule": "repeated",
+                                    "type": "CreateParamSetting",
+                                    "id": 2
+                                }
+                            }
+                        },
+                        "CreateParamSetting": {
+                            "fields": {
+                                "param": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "value": {
+                                    "type": "string",
+                                    "id": 2
+                                },
+                                "required": {
+                                    "type": "bool",
+                                    "id": 3
+                                }
+                            }
+                        }
+                    }
+                },
+                "AddResp": {
+                    "fields": {
+                        "createdObjResults": {
+                            "rule": "repeated",
+                            "type": "CreatedObjectResult",
+                            "id": 1
+                        }
+                    },
+                    "nested": {
+                        "CreatedObjectResult": {
+                            "fields": {
+                                "requestedPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "operStatus": {
+                                    "type": "OperationStatus",
+                                    "id": 2
+                                }
+                            },
+                            "nested": {
+                                "OperationStatus": {
+                                    "oneofs": {
+                                        "operStatus": {
+                                            "oneof": [
+                                                "operFailure",
+                                                "operSuccess"
+                                            ]
+                                        }
+                                    },
+                                    "fields": {
+                                        "operFailure": {
+                                            "type": "OperationFailure",
+                                            "id": 1
+                                        },
+                                        "operSuccess": {
+                                            "type": "OperationSuccess",
+                                            "id": 2
+                                        }
+                                    },
+                                    "nested": {
+                                        "OperationFailure": {
+                                            "fields": {
+                                                "errCode": {
+                                                    "type": "fixed32",
+                                                    "id": 1
+                                                },
+                                                "errMsg": {
+                                                    "type": "string",
+                                                    "id": 2
+                                                }
+                                            }
+                                        },
+                                        "OperationSuccess": {
+                                            "fields": {
+                                                "instantiatedPath": {
+                                                    "type": "string",
+                                                    "id": 1
+                                                },
+                                                "paramErrs": {
+                                                    "rule": "repeated",
+                                                    "type": "ParameterError",
+                                                    "id": 2
+                                                },
+                                                "uniqueKeys": {
+                                                    "keyType": "string",
+                                                    "type": "string",
+                                                    "id": 3
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "ParameterError": {
+                            "fields": {
+                                "param": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "errCode": {
+                                    "type": "fixed32",
+                                    "id": 2
+                                },
+                                "errMsg": {
+                                    "type": "string",
+                                    "id": 3
+                                }
+                            }
+                        }
+                    }
+                },
+                "Delete": {
+                    "fields": {
+                        "allowPartial": {
+                            "type": "bool",
+                            "id": 1
+                        },
+                        "objPaths": {
+                            "rule": "repeated",
+                            "type": "string",
+                            "id": 2
+                        }
+                    }
+                },
+                "DeleteResp": {
+                    "fields": {
+                        "deletedObjResults": {
+                            "rule": "repeated",
+                            "type": "DeletedObjectResult",
+                            "id": 1
+                        }
+                    },
+                    "nested": {
+                        "DeletedObjectResult": {
+                            "fields": {
+                                "requestedPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "operStatus": {
+                                    "type": "OperationStatus",
+                                    "id": 2
+                                }
+                            },
+                            "nested": {
+                                "OperationStatus": {
+                                    "oneofs": {
+                                        "operStatus": {
+                                            "oneof": [
+                                                "operFailure",
+                                                "operSuccess"
+                                            ]
+                                        }
+                                    },
+                                    "fields": {
+                                        "operFailure": {
+                                            "type": "OperationFailure",
+                                            "id": 1
+                                        },
+                                        "operSuccess": {
+                                            "type": "OperationSuccess",
+                                            "id": 2
+                                        }
+                                    },
+                                    "nested": {
+                                        "OperationFailure": {
+                                            "fields": {
+                                                "errCode": {
+                                                    "type": "fixed32",
+                                                    "id": 1
+                                                },
+                                                "errMsg": {
+                                                    "type": "string",
+                                                    "id": 2
+                                                }
+                                            }
+                                        },
+                                        "OperationSuccess": {
+                                            "fields": {
+                                                "affectedPaths": {
+                                                    "rule": "repeated",
+                                                    "type": "string",
+                                                    "id": 1
+                                                },
+                                                "unaffectedPathErrs": {
+                                                    "rule": "repeated",
+                                                    "type": "UnaffectedPathError",
+                                                    "id": 2
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "UnaffectedPathError": {
+                            "fields": {
+                                "unaffectedPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "errCode": {
+                                    "type": "fixed32",
+                                    "id": 2
+                                },
+                                "errMsg": {
+                                    "type": "string",
+                                    "id": 3
+                                }
+                            }
+                        }
+                    }
+                },
+                "Set": {
+                    "fields": {
+                        "allowPartial": {
+                            "type": "bool",
+                            "id": 1
+                        },
+                        "updateObjs": {
+                            "rule": "repeated",
+                            "type": "UpdateObject",
+                            "id": 2
+                        }
+                    },
+                    "nested": {
+                        "UpdateObject": {
+                            "fields": {
+                                "objPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "paramSettings": {
+                                    "rule": "repeated",
+                                    "type": "UpdateParamSetting",
+                                    "id": 2
+                                }
+                            }
+                        },
+                        "UpdateParamSetting": {
+                            "fields": {
+                                "param": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "value": {
+                                    "type": "string",
+                                    "id": 2
+                                },
+                                "required": {
+                                    "type": "bool",
+                                    "id": 3
+                                }
+                            }
+                        }
+                    }
+                },
+                "SetResp": {
+                    "fields": {
+                        "updatedObjResults": {
+                            "rule": "repeated",
+                            "type": "UpdatedObjectResult",
+                            "id": 1
+                        }
+                    },
+                    "nested": {
+                        "UpdatedObjectResult": {
+                            "fields": {
+                                "requestedPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "operStatus": {
+                                    "type": "OperationStatus",
+                                    "id": 2
+                                }
+                            },
+                            "nested": {
+                                "OperationStatus": {
+                                    "oneofs": {
+                                        "operStatus": {
+                                            "oneof": [
+                                                "operFailure",
+                                                "operSuccess"
+                                            ]
+                                        }
+                                    },
+                                    "fields": {
+                                        "operFailure": {
+                                            "type": "OperationFailure",
+                                            "id": 1
+                                        },
+                                        "operSuccess": {
+                                            "type": "OperationSuccess",
+                                            "id": 2
+                                        }
+                                    },
+                                    "nested": {
+                                        "OperationFailure": {
+                                            "fields": {
+                                                "errCode": {
+                                                    "type": "fixed32",
+                                                    "id": 1
+                                                },
+                                                "errMsg": {
+                                                    "type": "string",
+                                                    "id": 2
+                                                },
+                                                "updatedInstFailures": {
+                                                    "rule": "repeated",
+                                                    "type": "UpdatedInstanceFailure",
+                                                    "id": 3
+                                                }
+                                            }
+                                        },
+                                        "OperationSuccess": {
+                                            "fields": {
+                                                "updatedInstResults": {
+                                                    "rule": "repeated",
+                                                    "type": "UpdatedInstanceResult",
+                                                    "id": 1
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "UpdatedInstanceFailure": {
+                            "fields": {
+                                "affectedPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "paramErrs": {
+                                    "rule": "repeated",
+                                    "type": "ParameterError",
+                                    "id": 2
+                                }
+                            }
+                        },
+                        "UpdatedInstanceResult": {
+                            "fields": {
+                                "affectedPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "paramErrs": {
+                                    "rule": "repeated",
+                                    "type": "ParameterError",
+                                    "id": 2
+                                },
+                                "updatedParams": {
+                                    "keyType": "string",
+                                    "type": "string",
+                                    "id": 3
+                                }
+                            }
+                        },
+                        "ParameterError": {
+                            "fields": {
+                                "param": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "errCode": {
+                                    "type": "fixed32",
+                                    "id": 2
+                                },
+                                "errMsg": {
+                                    "type": "string",
+                                    "id": 3
+                                }
+                            }
+                        }
+                    }
+                },
+                "Operate": {
+                    "fields": {
+                        "command": {
+                            "type": "string",
+                            "id": 1
+                        },
+                        "commandKey": {
+                            "type": "string",
+                            "id": 2
+                        },
+                        "sendResp": {
+                            "type": "bool",
+                            "id": 3
+                        },
+                        "inputArgs": {
+                            "keyType": "string",
+                            "type": "string",
+                            "id": 4
+                        }
+                    }
+                },
+                "OperateResp": {
+                    "fields": {
+                        "operationResults": {
+                            "rule": "repeated",
+                            "type": "OperationResult",
+                            "id": 1
+                        }
+                    },
+                    "nested": {
+                        "OperationResult": {
+                            "oneofs": {
+                                "operationResp": {
+                                    "oneof": [
+                                        "reqObjPath",
+                                        "reqOutputArgs",
+                                        "cmdFailure"
+                                    ]
+                                }
+                            },
+                            "fields": {
+                                "executedCommand": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "reqObjPath": {
+                                    "type": "string",
+                                    "id": 2
+                                },
+                                "reqOutputArgs": {
+                                    "type": "OutputArgs",
+                                    "id": 3
+                                },
+                                "cmdFailure": {
+                                    "type": "CommandFailure",
+                                    "id": 4
+                                }
+                            },
+                            "nested": {
+                                "OutputArgs": {
+                                    "fields": {
+                                        "outputArgs": {
+                                            "keyType": "string",
+                                            "type": "string",
+                                            "id": 1
+                                        }
+                                    }
+                                },
+                                "CommandFailure": {
+                                    "fields": {
+                                        "errCode": {
+                                            "type": "fixed32",
+                                            "id": 1
+                                        },
+                                        "errMsg": {
+                                            "type": "string",
+                                            "id": 2
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                },
+                "Notify": {
+                    "oneofs": {
+                        "notification": {
+                            "oneof": [
+                                "event",
+                                "valueChange",
+                                "objCreation",
+                                "objDeletion",
+                                "operComplete",
+                                "onBoardReq"
+                            ]
+                        }
+                    },
+                    "fields": {
+                        "subscriptionId": {
+                            "type": "string",
+                            "id": 1
+                        },
+                        "sendResp": {
+                            "type": "bool",
+                            "id": 2
+                        },
+                        "event": {
+                            "type": "Event",
+                            "id": 3
+                        },
+                        "valueChange": {
+                            "type": "ValueChange",
+                            "id": 4
+                        },
+                        "objCreation": {
+                            "type": "ObjectCreation",
+                            "id": 5
+                        },
+                        "objDeletion": {
+                            "type": "ObjectDeletion",
+                            "id": 6
+                        },
+                        "operComplete": {
+                            "type": "OperationComplete",
+                            "id": 7
+                        },
+                        "onBoardReq": {
+                            "type": "OnBoardRequest",
+                            "id": 8
+                        }
+                    },
+                    "nested": {
+                        "Event": {
+                            "fields": {
+                                "objPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "eventName": {
+                                    "type": "string",
+                                    "id": 2
+                                },
+                                "params": {
+                                    "keyType": "string",
+                                    "type": "string",
+                                    "id": 3
+                                }
+                            }
+                        },
+                        "ValueChange": {
+                            "fields": {
+                                "paramPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "paramValue": {
+                                    "type": "string",
+                                    "id": 2
+                                }
+                            }
+                        },
+                        "ObjectCreation": {
+                            "fields": {
+                                "objPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "uniqueKeys": {
+                                    "keyType": "string",
+                                    "type": "string",
+                                    "id": 2
+                                }
+                            }
+                        },
+                        "ObjectDeletion": {
+                            "fields": {
+                                "objPath": {
+                                    "type": "string",
+                                    "id": 1
+                                }
+                            }
+                        },
+                        "OperationComplete": {
+                            "oneofs": {
+                                "operationResp": {
+                                    "oneof": [
+                                        "reqOutputArgs",
+                                        "cmdFailure"
+                                    ]
+                                }
+                            },
+                            "fields": {
+                                "objPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "commandName": {
+                                    "type": "string",
+                                    "id": 2
+                                },
+                                "commandKey": {
+                                    "type": "string",
+                                    "id": 3
+                                },
+                                "reqOutputArgs": {
+                                    "type": "OutputArgs",
+                                    "id": 4
+                                },
+                                "cmdFailure": {
+                                    "type": "CommandFailure",
+                                    "id": 5
+                                }
+                            },
+                            "nested": {
+                                "OutputArgs": {
+                                    "fields": {
+                                        "outputArgs": {
+                                            "keyType": "string",
+                                            "type": "string",
+                                            "id": 1
+                                        }
+                                    }
+                                },
+                                "CommandFailure": {
+                                    "fields": {
+                                        "errCode": {
+                                            "type": "fixed32",
+                                            "id": 1
+                                        },
+                                        "errMsg": {
+                                            "type": "string",
+                                            "id": 2
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "OnBoardRequest": {
+                            "fields": {
+                                "oui": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "productClass": {
+                                    "type": "string",
+                                    "id": 2
+                                },
+                                "serialNumber": {
+                                    "type": "string",
+                                    "id": 3
+                                },
+                                "agentSupportedProtocolVersions": {
+                                    "type": "string",
+                                    "id": 4
+                                }
+                            }
+                        }
+                    }
+                },
+                "NotifyResp": {
+                    "fields": {
+                        "subscriptionId": {
+                            "type": "string",
+                            "id": 1
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/node/specs/usp-record-1-1.json b/node/specs/usp-record-1-1.json
new file mode 100644
index 0000000000000000000000000000000000000000..c4a15e8deb763e4102476f1b96664ca80a2faea6
--- /dev/null
+++ b/node/specs/usp-record-1-1.json
@@ -0,0 +1,111 @@
+{
+    "nested": {
+        "usp_record": {
+            "nested": {
+                "Record": {
+                    "oneofs": {
+                        "recordType": {
+                            "oneof": [
+                                "noSessionContext",
+                                "sessionContext"
+                            ]
+                        }
+                    },
+                    "fields": {
+                        "version": {
+                            "type": "string",
+                            "id": 1
+                        },
+                        "toId": {
+                            "type": "string",
+                            "id": 2
+                        },
+                        "fromId": {
+                            "type": "string",
+                            "id": 3
+                        },
+                        "payloadSecurity": {
+                            "type": "PayloadSecurity",
+                            "id": 4
+                        },
+                        "macSignature": {
+                            "type": "bytes",
+                            "id": 5
+                        },
+                        "senderCert": {
+                            "type": "bytes",
+                            "id": 6
+                        },
+                        "noSessionContext": {
+                            "type": "NoSessionContextRecord",
+                            "id": 7
+                        },
+                        "sessionContext": {
+                            "type": "SessionContextRecord",
+                            "id": 8
+                        }
+                    },
+                    "nested": {
+                        "PayloadSecurity": {
+                            "values": {
+                                "PLAINTEXT": 0,
+                                "TLS12": 1
+                            }
+                        }
+                    }
+                },
+                "NoSessionContextRecord": {
+                    "fields": {
+                        "payload": {
+                            "type": "bytes",
+                            "id": 2
+                        }
+                    }
+                },
+                "SessionContextRecord": {
+                    "fields": {
+                        "sessionId": {
+                            "type": "uint64",
+                            "id": 1
+                        },
+                        "sequenceId": {
+                            "type": "uint64",
+                            "id": 2
+                        },
+                        "expectedId": {
+                            "type": "uint64",
+                            "id": 3
+                        },
+                        "retransmitId": {
+                            "type": "uint64",
+                            "id": 4
+                        },
+                        "payloadSarState": {
+                            "type": "PayloadSARState",
+                            "id": 5
+                        },
+                        "payloadrecSarState": {
+                            "type": "PayloadSARState",
+                            "id": 6
+                        },
+                        "payload": {
+                            "rule": "repeated",
+                            "type": "bytes",
+                            "id": 7
+                        }
+                    },
+                    "nested": {
+                        "PayloadSARState": {
+                            "values": {
+                                "NONE": 0,
+                                "BEGIN": 1,
+                                "INPROCESS": 2,
+                                "COMPLETE": 3
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/node/testy.d.ts b/node/testy.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cb0ff5c3b541f646105198ee23ac0fc3d805023e
--- /dev/null
+++ b/node/testy.d.ts
@@ -0,0 +1 @@
+export {};
diff --git a/node/testy.js b/node/testy.js
new file mode 100644
index 0000000000000000000000000000000000000000..7566c29d74cdbfa5d8a598531f91752b2ef10154
--- /dev/null
+++ b/node/testy.js
@@ -0,0 +1,143 @@
+"use strict";
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+    return new (P || (P = Promise))(function (resolve, reject) {
+        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+        step((generator = generator.apply(thisArg, _arguments || [])).next());
+    });
+};
+var __generator = (this && this.__generator) || function (thisArg, body) {
+    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
+    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
+    function verb(n) { return function (v) { return step([n, v]); }; }
+    function step(op) {
+        if (f) throw new TypeError("Generator is already executing.");
+        while (_) try {
+            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
+            if (y = 0, t) op = [op[0] & 2, t.value];
+            switch (op[0]) {
+                case 0: case 1: t = op; break;
+                case 4: _.label++; return { value: op[1], done: false };
+                case 5: _.label++; y = op[1]; op = [0]; continue;
+                case 7: op = _.ops.pop(); _.trys.pop(); continue;
+                default:
+                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
+                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
+                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
+                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
+                    if (t[2]) _.ops.pop();
+                    _.trys.pop(); continue;
+            }
+            op = body.call(thisArg, _);
+        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
+        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
+    }
+};
+var __importDefault = (this && this.__importDefault) || function (mod) {
+    return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var _1 = __importDefault(require("."));
+var run = function () { return __awaiter(void 0, void 0, void 0, function () {
+    var usp, err_1;
+    return __generator(this, function (_a) {
+        switch (_a.label) {
+            case 0: return [4 /*yield*/, _1.default({
+                    host: '192.168.1.1',
+                    port: 9001,
+                    protocol: 'ws',
+                    username: 'admin',
+                    password: 'admin',
+                    publishEndpoint: '/usp/endpoint',
+                    subscribeEndpoint: '/usp/controller'
+                }, {
+                    onError: console.log,
+                })];
+            case 1:
+                usp = _a.sent();
+                // console.log("CONNECTED!")
+                // await usp.supportedDM("Device.WiFi.").then(r => console.log(JSON.stringify(r, null, 2))).catch(console.log)
+                // await usp.instances("Device.WiFi.").then(r => console.log(JSON.stringify(r, null, 2))).catch(console.log)
+                // await usp.instances("Device.WiFi.",  {firstLevelOnly: true} ).then(r => console.log(JSON.stringify(r, null, 2))).catch(console.log)
+                // await usp.supportedProto("").then(console.log).catch(console.log)
+                // await usp.get("Device.Hosts.Host.").then(usp.resolve).then(j => console.log(JSON.stringify(j, null, 2))).catch(console.error)
+                // await usp.get(["Device.WiFi.Radio.1.", "Device.WiFi.Radio.2."]).then(() => console.log('get successful')).catch(console.error)
+                // await usp.get("CAT.WiFi.Radio.1.").then(() => console.log('get successful')).catch(console.error)
+                // await usp.get(["Device.WiFi.Radio.1.", "CAT.WiFi.Radio.2."]).then(() => console.log('get successful')).catch(console.error)
+                return [4 /*yield*/, usp.set("Device.Users.User.3.Language", "").then(console.log).catch(console.error)];
+            case 2:
+                // console.log("CONNECTED!")
+                // await usp.supportedDM("Device.WiFi.").then(r => console.log(JSON.stringify(r, null, 2))).catch(console.log)
+                // await usp.instances("Device.WiFi.").then(r => console.log(JSON.stringify(r, null, 2))).catch(console.log)
+                // await usp.instances("Device.WiFi.",  {firstLevelOnly: true} ).then(r => console.log(JSON.stringify(r, null, 2))).catch(console.log)
+                // await usp.supportedProto("").then(console.log).catch(console.log)
+                // await usp.get("Device.Hosts.Host.").then(usp.resolve).then(j => console.log(JSON.stringify(j, null, 2))).catch(console.error)
+                // await usp.get(["Device.WiFi.Radio.1.", "Device.WiFi.Radio.2."]).then(() => console.log('get successful')).catch(console.error)
+                // await usp.get("CAT.WiFi.Radio.1.").then(() => console.log('get successful')).catch(console.error)
+                // await usp.get(["Device.WiFi.Radio.1.", "CAT.WiFi.Radio.2."]).then(() => console.log('get successful')).catch(console.error)
+                _a.sent();
+                _a.label = 3;
+            case 3:
+                _a.trys.push([3, 5, , 6]);
+                return [4 /*yield*/, usp._operate('Device.IP.Diagnostics.TraceRoute()', 'NOTIFY@Device.IP.Diagnostics.TraceRoute()KMDBWMFZCAGUV', { Host: 'google.com' }).then(console.log)
+                    // const [op, clear] = await usp.operate("Device.IP.Diagnostics.TraceRoute()")
+                    // await op({ Host: 'google.com' }).then(console.log).catch(console.log)
+                    // await clear()
+                ];
+            case 4:
+                _a.sent();
+                return [3 /*break*/, 6];
+            case 5:
+                err_1 = _a.sent();
+                console.log(err_1);
+                return [3 /*break*/, 6];
+            case 6: 
+            // const clear = usp.on(/.*/, (_, r) => {
+            //   console.log("==========================================================");
+            //   console.log(JSON.stringify(r, null, 2));
+            //   console.log("==========================================================");
+            // });
+            // await usp
+            //   .supportedDM("Device.IP.", {
+            //     firstLevelOnly: false,
+            //     returnCommands: true,
+            //     returnEvents: true,
+            //     returnParams: true,
+            //   })
+            //   .then((r) => console.log(JSON.stringify(r, null, 2)));
+            // await usp.subscribe({ notif: 'ObjectCreation', reference: 'Device.Hosts.Host.' }, console.log)
+            // await usp.subscribe({ notif: 'ValueChange', reference: 'Device.Hosts.Host.*.Active' }, console.log)
+            // await usp.add('Device.NAT.PortMapping.')
+            // clearSub()
+            // await usp.add('Device.NAT.PortMapping.')
+            // await usp.del('Device.NAT.PortMapping.45.').then(console.log)
+            return [4 /*yield*/, usp.disconnect()];
+            case 7:
+                // const clear = usp.on(/.*/, (_, r) => {
+                //   console.log("==========================================================");
+                //   console.log(JSON.stringify(r, null, 2));
+                //   console.log("==========================================================");
+                // });
+                // await usp
+                //   .supportedDM("Device.IP.", {
+                //     firstLevelOnly: false,
+                //     returnCommands: true,
+                //     returnEvents: true,
+                //     returnParams: true,
+                //   })
+                //   .then((r) => console.log(JSON.stringify(r, null, 2)));
+                // await usp.subscribe({ notif: 'ObjectCreation', reference: 'Device.Hosts.Host.' }, console.log)
+                // await usp.subscribe({ notif: 'ValueChange', reference: 'Device.Hosts.Host.*.Active' }, console.log)
+                // await usp.add('Device.NAT.PortMapping.')
+                // clearSub()
+                // await usp.add('Device.NAT.PortMapping.')
+                // await usp.del('Device.NAT.PortMapping.45.').then(console.log)
+                _a.sent();
+                return [2 /*return*/];
+        }
+    });
+}); };
+run();
+//# sourceMappingURL=testy.js.map
\ No newline at end of file
diff --git a/node/testy.js.map b/node/testy.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..87eb36fb28606a5843feab517960a3c1997311e5
--- /dev/null
+++ b/node/testy.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"testy.js","sourceRoot":"","sources":["../src/testy.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uCAAwB;AAExB,IAAM,GAAG,GAAG;;;;oBAUE,qBAAM,UAAO,CACvB;oBACE,IAAI,EAAE,aAAa;oBACnB,IAAI,EAAE,IAAI;oBACV,QAAQ,EAAE,IAAI;oBACd,QAAQ,EAAE,OAAO;oBACjB,QAAQ,EAAE,OAAO;oBACjB,eAAe,EAAE,eAAe;oBAChC,iBAAiB,EAAE,iBAAiB;iBACrC,EACD;oBACE,OAAO,EAAE,OAAO,CAAC,GAAG;iBACrB,CACF,EAAA;;gBAbK,GAAG,GAAG,SAaX;gBACD,4BAA4B;gBAC5B,8GAA8G;gBAC9G,4GAA4G;gBAC5G,sIAAsI;gBACtI,oEAAoE;gBAEpE,gIAAgI;gBAChI,iIAAiI;gBACjI,oGAAoG;gBACpG,8HAA8H;gBAE9H,qBAAM,GAAG,CAAC,GAAG,CAAC,8BAA8B,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAA;;gBAXxF,4BAA4B;gBAC5B,8GAA8G;gBAC9G,4GAA4G;gBAC5G,sIAAsI;gBACtI,oEAAoE;gBAEpE,gIAAgI;gBAChI,iIAAiI;gBACjI,oGAAoG;gBACpG,8HAA8H;gBAE9H,SAAwF,CAAC;;;;gBAsCvF,qBAAM,GAAG,CAAC,QAAQ,CAAC,oCAAoC,EAAE,wDAAwD,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC;oBAC5J,8EAA8E;oBAC9E,wEAAwE;oBACxE,gBAAgB;kBAH4I;;gBAA5J,SAA4J,CAAA;;;;gBAK5J,OAAO,CAAC,GAAG,CAAC,KAAG,CAAC,CAAA;;;YAGlB,yCAAyC;YACzC,+EAA+E;YAC/E,6CAA6C;YAC7C,+EAA+E;YAC/E,MAAM;YACN,YAAY;YACZ,iCAAiC;YACjC,6BAA6B;YAC7B,4BAA4B;YAC5B,0BAA0B;YAC1B,0BAA0B;YAC1B,OAAO;YACP,2DAA2D;YAE3D,iGAAiG;YACjG,sGAAsG;YACtG,2CAA2C;YAC3C,aAAa;YACb,2CAA2C;YAC3C,gEAAgE;YAEhE,qBAAM,GAAG,CAAC,UAAU,EAAE,EAAA;;gBArBtB,yCAAyC;gBACzC,+EAA+E;gBAC/E,6CAA6C;gBAC7C,+EAA+E;gBAC/E,MAAM;gBACN,YAAY;gBACZ,iCAAiC;gBACjC,6BAA6B;gBAC7B,4BAA4B;gBAC5B,0BAA0B;gBAC1B,0BAA0B;gBAC1B,OAAO;gBACP,2DAA2D;gBAE3D,iGAAiG;gBACjG,sGAAsG;gBACtG,2CAA2C;gBAC3C,aAAa;gBACb,2CAA2C;gBAC3C,gEAAgE;gBAEhE,SAAsB,CAAC;;;;KACxB,CAAC;AAEF,GAAG,EAAE,CAAC"}
\ No newline at end of file
diff --git a/node/types.d.ts b/node/types.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a930008e69693ee9f39ddf50f70320a3063ebb5c
--- /dev/null
+++ b/node/types.d.ts
@@ -0,0 +1,321 @@
+/// <reference types="node" />
+export declare type CommandType = "GET" | "SET" | "ADD" | "DELETE" | "OPERATE" | "NOTIFY" | "GET_SUPPORTED_DM" | "GET_INSTANCES" | "GET_SUPPORTED_PROTO";
+export declare type GetReturn = string | Record<string, any> | Record<string, any>[];
+export declare type GetCommand = (paths: string | string[]) => Promise<GetReturn>;
+export declare type SetCommand = (path: string, value: JSValue | JSValue[] | InputRecord) => Promise<void>;
+export declare type AddCommand = (path: string, value?: InputRecord) => Promise<string | string[]>;
+export declare type DelCommand = (path: string, allowPartial?: boolean) => Promise<void>;
+export declare type OperateFn = (input?: Record<string, any>) => Promise<any>;
+export declare type OperateClearFn = () => Promise<void>;
+export declare type OperateRecipe = (path: string, opts?: OperateOptions) => Promise<[OperateFn, OperateClearFn]>;
+export declare type OperateCommand = (path: string, id: string, input?: Record<string, any>) => Promise<any>;
+export declare type SupportedDMCommand = (paths: string | string[], opts?: SuportedCommandOpts) => Promise<Record<string, any>>;
+export declare type InstancesCommand = (paths: string | string[], opts?: {
+    firstLevelOnly?: boolean;
+}) => Promise<Record<string, any>>;
+export declare type SupportedProtoCommand = (versions: string) => Promise<string>;
+export declare type SubscribeRecipe = (opts: SubscriptionOptions, callback: SubscriptionCallback) => Promise<PromiseClearFn>;
+export declare type PromiseClearFn = () => Promise<void>;
+export declare type Command = GetCommand | SetCommand | AddCommand | DelCommand | OperateCommand | OperateRecipe | SubscribeRecipe | SupportedDMCommand | InstancesCommand | SupportedProtoCommand;
+export declare type PbRequestCommand = PbRequestCommandGet | PbRequestCommandSet | PbRequestCommandAdd | PbRequestCommandDel | PbRequestCommandOperate | PbRequestCommandSupport | PbRequestCommandInstance | PbRequestCommandSupportProto;
+export interface SuportedCommandOpts {
+    firstLevelOnly?: boolean;
+    returnCommands?: boolean;
+    returnEvents?: boolean;
+    returnParams?: boolean;
+}
+export declare type InputRecord = {
+    [k: string]: {
+        required: boolean;
+        value: any;
+    } | string | number | boolean;
+} & {
+    allowPartial?: boolean;
+};
+export declare type PbRequestCommandSupportProto = {
+    getSupportedProtocol: {
+        controllerSupportedProtocolVersions: string;
+    };
+};
+export declare type PbRequestCommandInstance = {
+    getInstances: {
+        objPaths: string[];
+        firstLevelOnly: boolean;
+    };
+};
+export declare type PbRequestCommandSupport = {
+    getSupportedDm: {
+        objPaths: string[];
+        firstLevelOnly: boolean;
+        returnCommands: boolean;
+        returnEvents: boolean;
+        returnParams: boolean;
+    };
+};
+export declare type PbRequestCommandOperate = {
+    operate: {
+        command: string;
+        commandKey: string;
+        sendResp: boolean;
+        inputArgs: Record<string, any>;
+    };
+};
+export declare type PbRequestCommandDel = {
+    delete: {
+        allowPartial: boolean;
+        objPaths: string[];
+    };
+};
+export declare type PbRequestCommandGet = {
+    get: {
+        paramPaths: string[];
+    };
+};
+export declare type PbRequestCommandSet = {
+    set: {
+        allowPartial: boolean;
+        updateObjs: {
+            objPath: string;
+            lookup: "Set.UpdateObject";
+            paramSettings: {
+                lookup: "Set.UpdateParamSetting";
+                param: string;
+                value: any;
+                required: boolean;
+            }[];
+        }[];
+    };
+};
+export declare type PbRequestCommandAdd = {
+    add: {
+        allowPartial: boolean;
+        createObjs: {
+            lookup: "Add.CreateObject";
+            objPath: string;
+            paramSettings: {
+                param: string;
+                value: any;
+                required: boolean;
+                lookup: "Add.CreateParamSetting";
+            }[];
+        }[];
+    };
+};
+export declare type Recipe = ResolveRecipe;
+export declare type ResolveRecipe = (msg: GetReturn, level?: number) => Promise<GetReturn>;
+/** Device API */
+export interface USP {
+    /**
+     * Get value at path
+     * @param path Location of value (e.g. "Device.DeviceInfo.")
+     * ```
+     * await usp.get("Device.WiFi.Radio.1.")
+     * // or
+     * await usp.get(["Device.WiFi.Radio.1.", "Device.WiFi.Radio.2."])
+     * ```
+     */
+    get: GetCommand;
+    /**
+     * Set value at path
+     * @param path Location of value (e.g. "Device.DeviceInfo.")
+     * @param value Value to assign
+     * ```
+     * await usp.set("Device.WiFi.Radio.1.", { Name: "radio-1" })
+     * // or
+     * await usp.set("Device.WiFi.Radio.1.Name", "radio-1")
+     * ```
+     */
+    set: SetCommand;
+    /**
+     * Create a command
+     * @param path Full path of command (e.g. "Device.IP.Diagnostics.IPPing()")
+     * @param opts Subscription options (not required)
+     * @returns Function that executes command
+     * ```
+     * const [ping, cleanPing] = await usp.operate("Device.IP.Diagnostics.IPPing()")
+     * const results = await ping({ Host: "iopsys.eu" })
+     * await cleanPing()
+     * ```
+     */
+    operate: OperateRecipe;
+    /**
+     * Directly call operate without creating a subscription (avoid using unless certain subsctiption exists)
+     * @param path Full path of command (e.g. "Device.IP.Diagnostics.IPPing()")
+     * @param id Full id of subscription (can be found in Device.LocalAgent.Subscription.)
+     * @param input Optional arguments for command
+     * @returns Command results
+     * ```
+     * await usp._operate("Device.IP.Diagnostics.IPPing()", 'command-id', { Host: "iopsys.eu" })
+     * ```
+     */
+    _operate: OperateCommand;
+    /**
+     * Add object to path
+     * @param path Path to add to (e.g. "Device.NAT.PortMapping.")
+     * @param values Optional object to add (if skipped will use default values)
+     * @returns Full path of new object
+     * ```
+     * await usp.add("Device.NAT.PortMapping.")
+     * await usp.add("Device.NAT.PortMapping.", { Description: "cpe-1", allowPartial: true })
+     * ```
+     */
+    add: AddCommand;
+    /**
+     * Delete object at path
+     * @param path Full path to delete (e.g. "Device.NAT.PortMapping.1.")
+     * @param allowPartial [Optional] Allow partial (defaults to false)
+     * ```
+     * await usp.del("Device.NAT.PortMapping.1.")
+     * await usp.del("Device.NAT.PortMapping.1.", true)
+     * ```
+     */
+    del: DelCommand;
+    /**
+     * Resolve references in message
+     * @param msg Message with reference in it
+     * @param level Optional level of nesting to resolve to (avoid using high numbers)
+     * ```
+     * await usp.get("Device.WiFi.Radio.1.").then(device.resolve)
+     * ```
+     */
+    resolve: ResolveRecipe;
+    /**
+     * Get Supported DM
+     * @param paths Path(s)
+     * @param opts [Optional] Response options
+     * ```
+     * await usp.supportedDM("Device.WiFi.")
+     * ```
+     */
+    supportedDM: SupportedDMCommand;
+    /**
+     * Get Supported Protocol
+     * @param versions Controller supported protocol versions
+     * ```
+     * await usp.supportedProto("1.0")
+     * ```
+     */
+    supportedProto: SupportedProtoCommand;
+    /**
+     * Get instances
+     * @param paths Path(s)
+     * @param firstLevelOnly [Optional] Return only first level
+     * ```
+     * await usp.instances("Device.WiFi.")
+     * ```
+     */
+    instances: SupportedDMCommand;
+    /**
+     * Subscribe to event
+     * @param options Subscription options
+     * @param callback Callback on relevant message
+     * @returns Returns function to clear subscription
+     * ```
+     * const clearSub = await usp.subscribe({ id: '1234', notif: 'ObjectCreation', reference: 'Device.NAT.PortMapping.' }, console.log)
+     * ```
+     */
+    subscribe: SubscribeRecipe;
+    /**
+     * Add handler for messages
+     * @param ident Message identifier (identifies by id, can be a string or regexp)
+     * @param callback Callback on relevant message
+     * @returns Returns function to clear handler
+     * ```
+     * const clear = usp.on("error", () => console.log('An error!'))
+     * ```
+     */
+    on: OnFn;
+    /**
+     * Disconnect from device
+     * ```
+     * await usp.disconnect()
+     * ```
+     */
+    disconnect: () => Promise<void>;
+}
+export declare type Connect = (options: ConnectionOptions, events?: ConnectionEvents) => Promise<USP>;
+declare type NotifType = "Event" | "ValueChange" | "ObjectCreation" | "ObjectDeletion" | "OperationComplete" | "OnBoardRequest";
+export interface SubscriptionOptions {
+    id?: string;
+    notif: NotifType;
+    reference: string | string[];
+}
+export declare type SubscriptionCallback = (msg: Response, fullMsg?: Record<string, any>) => void;
+export interface OperateOptions {
+    ID?: string;
+    Persistent?: boolean;
+}
+export interface PbRequestHeader {
+    msgId: string;
+    msgType: CommandType;
+    lookup: "Header";
+}
+export interface PbRequestBody {
+    lookup: "Body";
+    request: {
+        lookup: "Request";
+    } & PbRequestCommand;
+}
+export interface PbRequestMessage {
+    header: PbRequestHeader;
+    body: PbRequestBody;
+    lookup: "Msg";
+}
+export declare type URLConnectionOptions = {
+    url: string;
+} & OtherConnectionOptions;
+export declare type HostConnectionOptions = {
+    host: string;
+    port: number;
+    protocol: "wss" | "ws" | "mqtt" | "mqtts" | "tcp" | "ssl" | "wx" | "wxs";
+} & OtherConnectionOptions;
+export declare type CertType = string | string[] | Buffer | Buffer[];
+export interface OtherConnectionOptions {
+    username: string;
+    password: string;
+    fromId?: string;
+    toId?: string;
+    idEndpoint?: string;
+    publishEndpoint?: string;
+    subscribeEndpoint?: string;
+    ca?: CertType | Object[];
+    key?: CertType;
+    cert?: CertType;
+}
+export declare type ConnectionOptions = URLConnectionOptions | HostConnectionOptions;
+export declare type Response = string | Record<string, any>;
+export declare type DecodeFn = (msg: Record<string, any>) => DecodeResponse | [any];
+export declare type DecodeResponse = [any, ResponseID | null, null | Response] | [any];
+export declare type EncodeArgs = {
+    rootMsg: protobuf.Root;
+    rootRecord: protobuf.Root;
+    header: any;
+    options: Record<string, string>;
+    args: Record<string, any>;
+};
+export declare type OnIdent = string | RegExp;
+export declare type EncodeFn = (args: Record<string, any>) => PbRequestMessage;
+export declare type CallArgs = Record<string, any>;
+export declare type ClearFn = () => void;
+export declare type OnFn = (ident: OnIdent, callback: SubscriptionCallback) => ClearFn;
+export declare type MakeFn = (call: CallFn, on: OnFn) => Command;
+export declare type MakeRecipeFn = (call: CallFn) => Recipe;
+export declare type CommandTrigger = {
+    decode: string | ((msg: Record<string, string>) => boolean);
+    encode: string;
+};
+export declare type CommandObject = {
+    encode: EncodeFn;
+    decode: DecodeFn;
+};
+export interface ConnectionEvents {
+    onError?: (err: string) => void;
+}
+export interface RecipeObject {
+    name: string;
+    make: MakeFn;
+}
+export declare type ResponseID = "ignore" | "error" | string;
+export declare type JSValue = string | number | boolean;
+export declare type CallFn = (cmd: CommandType, args: Record<string, any>) => any;
+export {};
diff --git a/node/types.js b/node/types.js
new file mode 100644
index 0000000000000000000000000000000000000000..11e638d1ee44ae0dcb1feb5aef676ea74a7d4df3
--- /dev/null
+++ b/node/types.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=types.js.map
\ No newline at end of file
diff --git a/node/types.js.map b/node/types.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..c768b79002615c0e69cc6efdcad6a509c1abaaec
--- /dev/null
+++ b/node/types.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
\ No newline at end of file
diff --git a/node/util.d.ts b/node/util.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f85f7aeae3e7d2636fd42d1597843ab321d06fd1
--- /dev/null
+++ b/node/util.d.ts
@@ -0,0 +1,13 @@
+import { OnIdent, SubscriptionCallback } from "./types";
+/**
+ * Makes a router for storing resolve/reject for a message
+ */
+export declare const makeRouter: () => {
+    get: (id: string) => any;
+    add: (id: string, data: any) => void;
+};
+export declare const makeCallbackRouter: () => {
+    get: (id: string) => SubscriptionCallback[];
+    add: (ident: OnIdent, callback: SubscriptionCallback) => void;
+    del: (id: OnIdent) => boolean;
+};
diff --git a/node/util.js b/node/util.js
new file mode 100644
index 0000000000000000000000000000000000000000..fd32884dccc74a6b5cfefe373e0e3aa905c6706b
--- /dev/null
+++ b/node/util.js
@@ -0,0 +1,55 @@
+"use strict";
+var __assign = (this && this.__assign) || function () {
+    __assign = Object.assign || function(t) {
+        for (var s, i = 1, n = arguments.length; i < n; i++) {
+            s = arguments[i];
+            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
+                t[p] = s[p];
+        }
+        return t;
+    };
+    return __assign.apply(this, arguments);
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.makeCallbackRouter = exports.makeRouter = void 0;
+/**
+ * Makes a router for storing resolve/reject for a message
+ */
+var makeRouter = function () {
+    var routes = new Map();
+    return {
+        get: function (id) {
+            var res = __assign({}, routes.get(id));
+            routes.delete(id);
+            return res;
+        },
+        add: function (id, data) {
+            routes.set(id, __assign({}, data));
+        },
+    };
+};
+exports.makeRouter = makeRouter;
+var toId = function (id) { return id.toString(); };
+var isRegExp = function (v) { return typeof v === "object"; };
+var satisfies = function (id, matches) {
+    return isRegExp(id) ? matches.match(id) !== null : id === matches;
+};
+var makeCallbackRouter = function () {
+    var routes = new Map();
+    return {
+        get: function (id) {
+            return Array.from(routes.values())
+                .filter(function (_a) {
+                var ident = _a.ident;
+                return satisfies(ident, id);
+            })
+                .map(function (v) { return v.callback; });
+        },
+        add: function (ident, callback) {
+            routes.set(toId(ident), { callback: callback, ident: ident });
+        },
+        del: function (id) { return routes.delete(toId(id)); },
+    };
+};
+exports.makeCallbackRouter = makeCallbackRouter;
+//# sourceMappingURL=util.js.map
\ No newline at end of file
diff --git a/node/util.js.map b/node/util.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..a5f0a9264f1c5bb8ba95ce1e277a83084a8be12e
--- /dev/null
+++ b/node/util.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AAEA;;GAEG;AACI,IAAM,UAAU,GAAG;IACxB,IAAM,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;IACzB,OAAO;QACL,GAAG,EAAE,UAAC,EAAU;YACd,IAAM,GAAG,gBAAQ,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAE,CAAC;YAClC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAClB,OAAO,GAAG,CAAC;QACb,CAAC;QACD,GAAG,EAAE,UAAC,EAAU,EAAE,IAAS;YACzB,MAAM,CAAC,GAAG,CAAC,EAAE,eAAO,IAAI,EAAG,CAAC;QAC9B,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAZW,QAAA,UAAU,cAYrB;AAEF,IAAM,IAAI,GAAG,UAAC,EAAW,IAAa,OAAA,EAAE,CAAC,QAAQ,EAAE,EAAb,CAAa,CAAC;AACpD,IAAM,QAAQ,GAAG,UAAC,CAAU,IAAkB,OAAA,OAAO,CAAC,KAAK,QAAQ,EAArB,CAAqB,CAAC;AACpE,IAAM,SAAS,GAAG,UAAC,EAAW,EAAE,OAAe;IAC7C,OAAA,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO;AAA1D,CAA0D,CAAC;AACtD,IAAM,kBAAkB,GAAG;IAChC,IAAM,MAAM,GAAG,IAAI,GAAG,EAGnB,CAAC;IACJ,OAAO;QACL,GAAG,EAAE,UAAC,EAAU;YACd,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;iBAC/B,MAAM,CAAC,UAAC,EAAS;oBAAP,KAAK,WAAA;gBAAO,OAAA,SAAS,CAAC,KAAK,EAAE,EAAE,CAAC;YAApB,CAAoB,CAAC;iBAC3C,GAAG,CAAC,UAAC,CAAC,IAAK,OAAA,CAAC,CAAC,QAAQ,EAAV,CAAU,CAAC,CAAC;QAC5B,CAAC;QACD,GAAG,EAAE,UAAC,KAAc,EAAE,QAA8B;YAClD,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,QAAQ,UAAA,EAAE,KAAK,OAAA,EAAE,CAAC,CAAC;QAC/C,CAAC;QACD,GAAG,EAAE,UAAC,EAAW,IAAK,OAAA,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAvB,CAAuB;KAC9C,CAAC;AACJ,CAAC,CAAC;AAhBW,QAAA,kBAAkB,sBAgB7B"}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 49388f3128e47ecaf949c039892a5468e234a71e..8b0af1062ae5b147e9574bcfc78ceb70b2b2ed4a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "usp-js",
-  "version": "0.1.15",
+  "version": "0.1.17",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -80,6 +80,9 @@
       "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==",
       "dev": true
     },
+    "@usp-js/web": {
+      "version": "file:web"
+    },
     "@webassemblyjs/ast": {
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
diff --git a/package.json b/package.json
index 8df1719cb884fee66555d4e7b7f2230c1dbfecdb..f0baa7042e81f94b8c5590943c4d2721e040039d 100644
--- a/package.json
+++ b/package.json
@@ -1,14 +1,14 @@
 {
   "name": "usp-js",
-  "version": "0.1.17",
+  "version": "0.1.18",
   "description": "Helper library for easy usp communication using mqtt over tcp or ws.",
-  "main": "lib/index.js",
-  "web": "_bundles/index.min.js",
+  "main": "node/index.js",
+  "browser": "web/index.js",
   "scripts": {
-    "clean": "shx rm -rf _bundles lib lib-esm",
-    "ts": "tsc && tsc -m es6 --outDir lib-esm",
-    "wb": "webpack --mode=production",
-    "build": "npm run clean && npm run ts && npm run wb",
+    "node": "tsc",
+    "web": "webpack --mode=production",
+    "web-types": "tsc --outDir web --declaration",
+    "build": "npm run node && npm run web && npm run web-types",
     "dev": "tsc --watch",
     "test": "mocha tests/integration",
     "docs": "npx typedoc",
@@ -32,5 +32,5 @@
     "webpack-cli": "^4.6.0"
   },
   "type": "commonjs",
-  "types": "lib-esm/index.d.ts"
+  "types": "node/index.d.ts"
 }
diff --git a/tsconfig.json b/tsconfig.json
index d476505933960c2112547c3c49ccaf9ffc11568d..9a9afb1ba336656af6af0068b64277049b918603 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,19 +1,18 @@
 {
   "exclude": [
     "node_modules",
-    "dist",
     "tests",
-    "docs",
     "public",
     "webpack.config.js",
-    "lib"
+    "node",
+    "web"
   ],
   "compilerOptions": {
     "target": "ES5",
     "module": "CommonJS",
     "lib": ["ESNext"],
     "allowJs": true,
-    "outDir": "lib",
+    "outDir": "node",
 
     "strict": true,
     "noImplicitAny": false,
@@ -29,6 +28,6 @@
   "compileOnSave": true,
   "typedocOptions": {
     "out": "public",
-    "entryPoints": ["./src"],
+    "entryPoints": ["./src"]
   }
 }
diff --git a/web/commands/add.d.ts b/web/commands/add.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5e00b673fef4d5d0922d1ef792b1d08ca25a83fa
--- /dev/null
+++ b/web/commands/add.d.ts
@@ -0,0 +1,6 @@
+import { DecodeFn, EncodeFn } from "../types";
+declare const _default: {
+    decode: DecodeFn;
+    encode: EncodeFn;
+};
+export default _default;
diff --git a/web/commands/add.js b/web/commands/add.js
new file mode 100644
index 0000000000000000000000000000000000000000..b2c01260afa76f0f26017173546c686751b1d547
--- /dev/null
+++ b/web/commands/add.js
@@ -0,0 +1,85 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var util = __importStar(require("./util"));
+var decode = function (msg) {
+    var paths = util.searchAll(msg, "instantiatedPath");
+    if (paths && paths.length === 1)
+        return [paths[0]];
+    return [paths];
+};
+var isObj = function (v) {
+    return typeof v === "object" && v.required !== undefined && v.value !== undefined;
+};
+var encode = function (_a) {
+    var value = _a.value, path = _a.path;
+    var allowPartial = value && value.allowPartial;
+    var pairs = value
+        ? Object.entries(value).map(function (_a) {
+            var k = _a[0], v = _a[1];
+            return isObj(v)
+                ? [k, v.value.toString(), v.required]
+                : [k, v.toString(), false];
+        })
+        : [];
+    return {
+        lookup: "Msg",
+        header: {
+            lookup: "Header",
+            msgId: util.uniq("ADD@"),
+            msgType: "ADD",
+        },
+        body: {
+            lookup: "Body",
+            request: {
+                lookup: "Request",
+                add: {
+                    allowPartial: allowPartial,
+                    createObjs: [
+                        {
+                            lookup: "Add.CreateObject",
+                            objPath: path,
+                            paramSettings: pairs
+                                .filter(function (_a) {
+                                var k = _a[0];
+                                return k !== "allowPartial";
+                            })
+                                .map(function (_a) {
+                                var param = _a[0], value = _a[1], required = _a[2];
+                                return ({
+                                    lookup: "Add.CreateParamSetting",
+                                    param: param,
+                                    value: value,
+                                    required: required,
+                                });
+                            }),
+                        },
+                    ],
+                },
+            },
+        },
+    };
+};
+exports.default = {
+    decode: decode,
+    encode: encode
+};
+//# sourceMappingURL=add.js.map
\ No newline at end of file
diff --git a/web/commands/add.js.map b/web/commands/add.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..bde2dafde0ba19f4a84bef4f9e887bece6efab83
--- /dev/null
+++ b/web/commands/add.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"add.js","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AACA,2CAA+B;AAE/B,IAAM,MAAM,GAAa,UAAC,GAAG;IAC3B,IAAM,KAAK,GAAyB,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;IAC5E,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;IAClD,OAAO,CAAC,KAAK,CAAC,CAAC;AACjB,CAAC,CAAC;AAEF,IAAM,KAAK,GAAG,UAAC,CAAC;IACd,OAAA,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS;AAA1E,CAA0E,CAAC;AAE7E,IAAM,MAAM,GAAa,UAAC,EAAe;QAAb,KAAK,WAAA,EAAE,IAAI,UAAA;IACrC,IAAM,YAAY,GAAG,KAAK,IAAI,KAAK,CAAC,YAAY,CAAC;IACjD,IAAM,KAAK,GAA6B,KAAK;QAC3C,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAc,CAAC,CAAC,GAAG,CAAC,UAAC,EAAM;gBAAL,CAAC,QAAA,EAAE,CAAC,QAAA;YACvC,OAAA,KAAK,CAAC,CAAC,CAAC;gBACN,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC;gBACrC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC;QAF5B,CAE4B,CAC7B;QACH,CAAC,CAAC,EAAE,CAAC;IACP,OAAO;QACL,MAAM,EAAE,KAAK;QACb,MAAM,EAAE;YACN,MAAM,EAAE,QAAQ;YAChB,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;YACxB,OAAO,EAAE,KAAK;SACf;QACD,IAAI,EAAE;YACJ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,MAAM,EAAE,SAAS;gBACjB,GAAG,EAAE;oBACH,YAAY,cAAA;oBACZ,UAAU,EAAE;wBACV;4BACE,MAAM,EAAE,kBAAkB;4BAC1B,OAAO,EAAE,IAAI;4BACb,aAAa,EAAE,KAAK;iCACjB,MAAM,CAAC,UAAC,EAAG;oCAAF,CAAC,QAAA;gCAAM,OAAA,CAAC,KAAK,cAAc;4BAApB,CAAoB,CAAC;iCACrC,GAAG,CAAC,UAAC,EAAwB;oCAAvB,KAAK,QAAA,EAAE,KAAK,QAAA,EAAE,QAAQ,QAAA;gCAAM,OAAA,CAAC;oCAClC,MAAM,EAAE,wBAAwB;oCAChC,KAAK,OAAA;oCACL,KAAK,OAAA;oCACL,QAAQ,UAAA;iCACT,CAAC;4BALiC,CAKjC,CAAC;yBACN;qBACF;iBACF;aACF;SACF;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,kBAAe;IACb,MAAM,QAAA;IACN,MAAM,QAAA;CACP,CAAC"}
\ No newline at end of file
diff --git a/web/commands/del.d.ts b/web/commands/del.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5e00b673fef4d5d0922d1ef792b1d08ca25a83fa
--- /dev/null
+++ b/web/commands/del.d.ts
@@ -0,0 +1,6 @@
+import { DecodeFn, EncodeFn } from "../types";
+declare const _default: {
+    decode: DecodeFn;
+    encode: EncodeFn;
+};
+export default _default;
diff --git a/web/commands/del.js b/web/commands/del.js
new file mode 100644
index 0000000000000000000000000000000000000000..5310addc49e71c458c59cecd6f450583e95b81e8
--- /dev/null
+++ b/web/commands/del.js
@@ -0,0 +1,32 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+var util_1 = require("./util");
+var decode = function (_msg) {
+    return [null];
+};
+var encode = function (_a) {
+    var paths = _a.paths, allowPartial = _a.allowPartial;
+    return ({
+        lookup: "Msg",
+        header: {
+            msgId: util_1.uniq("DELETE@"),
+            msgType: "DELETE",
+            lookup: "Header",
+        },
+        body: {
+            lookup: "Body",
+            request: {
+                lookup: "Request",
+                delete: {
+                    objPaths: Array.isArray(paths) ? paths : [paths],
+                    allowPartial: allowPartial || false
+                },
+            },
+        },
+    });
+};
+exports.default = {
+    decode: decode,
+    encode: encode
+};
+//# sourceMappingURL=del.js.map
\ No newline at end of file
diff --git a/web/commands/del.js.map b/web/commands/del.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..e23e0150e23edabb55bc5c915df4ad9d9c81e6cf
--- /dev/null
+++ b/web/commands/del.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"del.js","sourceRoot":"","sources":["../../src/commands/del.ts"],"names":[],"mappings":";;AACA,+BAA8B;AAE9B,IAAM,MAAM,GAAa,UAAC,IAAI;IAC5B,OAAO,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC,CAAC;AAEF,IAAM,MAAM,GAAa,UAAC,EAAuB;QAArB,KAAK,WAAA,EAAE,YAAY,kBAAA;IAAO,OAAA,CAAC;QACrD,MAAM,EAAE,KAAK;QACb,MAAM,EAAE;YACN,KAAK,EAAE,WAAI,CAAC,SAAS,CAAC;YACtB,OAAO,EAAE,QAAQ;YACjB,MAAM,EAAE,QAAQ;SACjB;QACD,IAAI,EAAE;YACJ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,MAAM,EAAE,SAAS;gBACjB,MAAM,EAAE;oBACN,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;oBAChD,YAAY,EAAE,YAAY,IAAI,KAAK;iBACpC;aACF;SACF;KACF,CAAC;AAjBoD,CAiBpD,CAAC;AAEH,kBAAe;IACb,MAAM,QAAA;IACN,MAAM,QAAA;CACP,CAAC"}
\ No newline at end of file
diff --git a/web/commands/get.d.ts b/web/commands/get.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5e00b673fef4d5d0922d1ef792b1d08ca25a83fa
--- /dev/null
+++ b/web/commands/get.d.ts
@@ -0,0 +1,6 @@
+import { DecodeFn, EncodeFn } from "../types";
+declare const _default: {
+    decode: DecodeFn;
+    encode: EncodeFn;
+};
+export default _default;
diff --git a/web/commands/get.js b/web/commands/get.js
new file mode 100644
index 0000000000000000000000000000000000000000..8dd178b2941c34f4d2f6746595b5617f95dd546e
--- /dev/null
+++ b/web/commands/get.js
@@ -0,0 +1,58 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var util = __importStar(require("./util"));
+var decode = function (msg) {
+    var resolvedPathResultsArr = util.searchAll(msg, "resolvedPathResults");
+    if (resolvedPathResultsArr) {
+        var unflattened = resolvedPathResultsArr.map(function (v) {
+            return util.unflatten(v.resultParams);
+        });
+        var data = util.unwrapObject(util.unwrapArray(unflattened));
+        return [data];
+    }
+    return [null];
+};
+var encode = function (_a) {
+    var paths = _a.paths;
+    return ({
+        lookup: "Msg",
+        header: {
+            msgId: util.uniq("GET@"),
+            msgType: "GET",
+            lookup: "Header",
+        },
+        body: {
+            lookup: "Body",
+            request: {
+                lookup: "Request",
+                get: {
+                    paramPaths: Array.isArray(paths) ? paths : [paths],
+                },
+            },
+        },
+    });
+};
+exports.default = {
+    decode: decode,
+    encode: encode
+};
+//# sourceMappingURL=get.js.map
\ No newline at end of file
diff --git a/web/commands/get.js.map b/web/commands/get.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..08d043894d034e19467840a03645a4c9c7043228
--- /dev/null
+++ b/web/commands/get.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"get.js","sourceRoot":"","sources":["../../src/commands/get.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AACA,2CAA+B;AAE/B,IAAM,MAAM,GAAa,UAAC,GAAG;IAC3B,IAAM,sBAAsB,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,qBAAqB,CAAC,CAAC;IAC1E,IAAI,sBAAsB,EAAE;QAC1B,IAAM,WAAW,GAAG,sBAAsB,CAAC,GAAG,CAAC,UAAC,CAAC;YAC/C,OAAA,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC;QAA9B,CAA8B,CAC/B,CAAC;QACF,IAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC;QAC9D,OAAO,CAAC,IAAI,CAAC,CAAC;KACf;IACD,OAAO,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC,CAAC;AAEF,IAAM,MAAM,GAAa,UAAC,EAAS;QAAP,KAAK,WAAA;IAAO,OAAA,CAAC;QACvC,MAAM,EAAE,KAAK;QACb,MAAM,EAAE;YACN,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;YACxB,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,QAAQ;SACjB;QACD,IAAI,EAAE;YACJ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,MAAM,EAAE,SAAS;gBACjB,GAAG,EAAE;oBACH,UAAU,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;iBACnD;aACF;SACF;KACF,CAAC;AAhBsC,CAgBtC,CAAC;AAEH,kBAAe;IACb,MAAM,QAAA;IACN,MAAM,QAAA;CACP,CAAC"}
\ No newline at end of file
diff --git a/web/commands/index.d.ts b/web/commands/index.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..99f0c783d1d726bdf6cb38859ad436408c31b04e
--- /dev/null
+++ b/web/commands/index.d.ts
@@ -0,0 +1,10 @@
+import protobuf from "protobufjs";
+import { CommandType, CallFn, OnFn, DecodeResponse } from "../types";
+export declare const rootRecord: protobuf.Root;
+export declare const rootMsg: protobuf.Root;
+export declare const header: any;
+export declare const makeRecipes: (call: CallFn, on: OnFn) => any;
+export declare const decodeId: (data: any) => string;
+export declare const readMsg: (data: any) => Record<string, any>;
+export declare const decode: (parsedMsg: any) => DecodeResponse;
+export declare const makeEncode: (options?: Record<string, string> | undefined) => (command: CommandType, args: Record<string, any>) => [string, any, string | null];
diff --git a/web/commands/index.js b/web/commands/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..f680c5948c51fe59e0e82a6ebc0583fabbfb3a43
--- /dev/null
+++ b/web/commands/index.js
@@ -0,0 +1,157 @@
+"use strict";
+var __assign = (this && this.__assign) || function () {
+    __assign = Object.assign || function(t) {
+        for (var s, i = 1, n = arguments.length; i < n; i++) {
+            s = arguments[i];
+            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
+                t[p] = s[p];
+        }
+        return t;
+    };
+    return __assign.apply(this, arguments);
+};
+var __spreadArray = (this && this.__spreadArray) || function (to, from) {
+    for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
+        to[j] = from[i];
+    return to;
+};
+var __importDefault = (this && this.__importDefault) || function (mod) {
+    return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.makeEncode = exports.decode = exports.readMsg = exports.decodeId = exports.makeRecipes = exports.header = exports.rootMsg = exports.rootRecord = void 0;
+var protobufjs_1 = __importDefault(require("protobufjs"));
+var usp_record_1_1_json_1 = __importDefault(require("../specs/usp-record-1-1.json"));
+var usp_msg_1_1_json_1 = __importDefault(require("../specs/usp-msg-1-1.json"));
+var util_1 = require("./util");
+exports.rootRecord = protobufjs_1.default.Root.fromJSON(usp_record_1_1_json_1.default);
+exports.rootMsg = protobufjs_1.default.Root.fromJSON(usp_msg_1_1_json_1.default);
+exports.header = exports.rootMsg.lookupType("usp.Header");
+var get_1 = __importDefault(require("./get"));
+var set_1 = __importDefault(require("./set"));
+var add_1 = __importDefault(require("./add"));
+var del_1 = __importDefault(require("./del"));
+var operate_1 = __importDefault(require("./operate"));
+var supported_1 = __importDefault(require("./supported"));
+var proto_1 = __importDefault(require("./proto"));
+var instances_1 = __importDefault(require("./instances"));
+var notify_1 = __importDefault(require("./notify"));
+var resolve_1 = __importDefault(require("./recipes/resolve"));
+var operate_2 = __importDefault(require("./recipes/operate"));
+var subscribe_1 = __importDefault(require("./recipes/subscribe"));
+var commands = {
+    GET: get_1.default,
+    ADD: add_1.default,
+    DELETE: del_1.default,
+    GET_INSTANCES: instances_1.default,
+    GET_SUPPORTED_DM: supported_1.default,
+    GET_SUPPORTED_PROTO: proto_1.default,
+    NOTIFY: notify_1.default,
+    OPERATE: operate_1.default,
+    SET: set_1.default,
+};
+var recipes = [resolve_1.default, operate_2.default, subscribe_1.default];
+var makeRecipes = function (call, on) {
+    return recipes.reduce(function (acc, _a) {
+        var _b;
+        var make = _a.make, name = _a.name;
+        return (__assign(__assign({}, acc), (_b = {}, _b[name] = make(call, on), _b)));
+    }, {});
+};
+exports.makeRecipes = makeRecipes;
+var decodeId = function (data) { return String(data); };
+exports.decodeId = decodeId;
+var unkownErr = function (msg) { return ["error", "", msg]; };
+var readMsg = function (data) {
+    var record = exports.rootRecord.lookupType("usp_record.Record");
+    var decodedRecord = record.decode(data);
+    var msg = exports.rootMsg.lookupType("usp.Msg");
+    var decodedMsg = msg.decode(decodedRecord.noSessionContext.payload);
+    return JSON.parse(JSON.stringify(decodedMsg)); // forces conversions
+};
+exports.readMsg = readMsg;
+var decode = function (parsedMsg) {
+    var id = util_1.search(parsedMsg, "msgId") || null;
+    var err = util_1.searchParent(parsedMsg, "errMsg") || null;
+    if (err)
+        return [id, null, err];
+    var command = util_1.extractCommand(parsedMsg);
+    if (!command)
+        return unkownErr(parsedMsg);
+    var cmd = commands[command.replace("_RESP", "")] || null;
+    if (!cmd)
+        return unkownErr(parsedMsg);
+    var _a = cmd.decode(parsedMsg), ddata = _a[0], did = _a[1], derr = _a[2];
+    return [did || id, ddata, derr || err];
+};
+exports.decode = decode;
+var makeEncode = function (options) { return function (command, args) {
+    var cmd = commands[command] || null;
+    if (!cmd)
+        return ["error", null, "Uknown command: " + command];
+    return __spreadArray([], convert(cmd.encode(args), options));
+}; };
+exports.makeEncode = makeEncode;
+var convert = function (msg, bufferOptions) {
+    var id = msg.header.msgId;
+    msg.header.msgType = exports.header.MsgType[msg.header.msgType];
+    var converted = _convert(msg);
+    if (isError(converted))
+        return [id, null, converted];
+    var encoded = exports.rootMsg.lookupType("usp.Msg").encode(converted).finish();
+    var buffer = util_1.makeBuffer(exports.rootRecord, encoded, bufferOptions || {});
+    return [id, buffer, null];
+};
+var isError = function (o) { return typeof o == "string"; };
+var internalKeys = ["lookup"];
+var isInternal = function (key) { return internalKeys.includes(key); };
+var makePayload = function (items, isArr) {
+    return items
+        .filter(function (_a) {
+        var k = _a[0];
+        return !isInternal(k);
+    })
+        .reduce(function (acc, _a) {
+        var _b;
+        var k = _a[0], v = _a[1];
+        return isArr
+            ? __spreadArray(__spreadArray([], acc), [v]) : __assign(__assign({}, acc), (_b = {}, _b[k] = v, _b));
+    }, isArr ? [] : {});
+};
+var isStringArray = function (obj) {
+    return Array.isArray(obj) && obj.every(function (v) { return typeof v === "string"; });
+};
+var needsConversion = function (v) { return typeof v === "object" && !isStringArray(v); };
+var _convert = function (value) {
+    var skip = value.lookup === undefined;
+    var lookup = "usp." + value.lookup;
+    var item = skip ? null : exports.rootMsg.lookupType(lookup);
+    var simpleValues = Object.entries(value).filter(function (_a) {
+        var v = _a[1];
+        return !needsConversion(v);
+    });
+    var toConvert = Object.entries(value).filter(function (_a) {
+        var v = _a[1];
+        return needsConversion(v);
+    });
+    var converted = toConvert.map(function (_a) {
+        var k = _a[0], v = _a[1];
+        return [
+            k,
+            _convert(v),
+        ];
+    });
+    var err = converted.find(function (_a) {
+        var v = _a[1];
+        return isError(v);
+    });
+    if (err)
+        return err[1];
+    var total = converted.concat(simpleValues);
+    var payload = makePayload(total, Array.isArray(value));
+    var payloadErr = item === null || item === void 0 ? void 0 : item.verify(payload);
+    if (payloadErr)
+        return payloadErr;
+    return item ? item.create(payload) : payload;
+};
+//# sourceMappingURL=index.js.map
\ No newline at end of file
diff --git a/web/commands/index.js.map b/web/commands/index.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..0ac63606999294142094d9c40e5e6eff70e284c6
--- /dev/null
+++ b/web/commands/index.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/web/commands/instances.d.ts b/web/commands/instances.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5e00b673fef4d5d0922d1ef792b1d08ca25a83fa
--- /dev/null
+++ b/web/commands/instances.d.ts
@@ -0,0 +1,6 @@
+import { DecodeFn, EncodeFn } from "../types";
+declare const _default: {
+    decode: DecodeFn;
+    encode: EncodeFn;
+};
+export default _default;
diff --git a/web/commands/instances.js b/web/commands/instances.js
new file mode 100644
index 0000000000000000000000000000000000000000..1741c9219256d0f57c2df74b186c0c9e8673f205
--- /dev/null
+++ b/web/commands/instances.js
@@ -0,0 +1,52 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var util = __importStar(require("./util"));
+var decode = function (msg) {
+    var results = util.search(msg, "reqPathResults");
+    return [results];
+};
+var encode = function (_a) {
+    var paths = _a.paths, _b = _a.opts, opts = _b === void 0 ? {} : _b;
+    return ({
+        lookup: "Msg",
+        header: {
+            msgId: util.uniq("GET_INSTANCES@"),
+            msgType: "GET_INSTANCES",
+            lookup: "Header",
+        },
+        body: {
+            lookup: "Body",
+            request: {
+                lookup: "Request",
+                getInstances: {
+                    objPaths: Array.isArray(paths) ? paths : [paths],
+                    firstLevelOnly: opts.firstLevelOnly || false
+                },
+            },
+        },
+    });
+};
+exports.default = {
+    decode: decode,
+    encode: encode
+};
+//# sourceMappingURL=instances.js.map
\ No newline at end of file
diff --git a/web/commands/instances.js.map b/web/commands/instances.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..61fd3fca0ecbcd682b20d02aeca5258c687ba6b3
--- /dev/null
+++ b/web/commands/instances.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"instances.js","sourceRoot":"","sources":["../../src/commands/instances.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AACA,2CAA+B;AAE/B,IAAM,MAAM,GAAa,UAAC,GAAG;IAC3B,IAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;IACnD,OAAO,CAAC,OAAO,CAAC,CAAC;AACnB,CAAC,CAAC;AAEF,IAAM,MAAM,GAAa,UAAC,EAAoB;QAAlB,KAAK,WAAA,EAAE,YAAS,EAAT,IAAI,mBAAG,EAAE,KAAA;IAAO,OAAA,CAAC;QAClD,MAAM,EAAE,KAAK;QACb,MAAM,EAAE;YACN,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC;YAClC,OAAO,EAAE,eAAe;YACxB,MAAM,EAAE,QAAQ;SACjB;QACD,IAAI,EAAE;YACJ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,MAAM,EAAE,SAAS;gBACjB,YAAY,EAAE;oBACZ,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;oBAChD,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,KAAK;iBAC7C;aACF;SACF;KACF,CAAC;AAjBiD,CAiBjD,CAAC;AAEH,kBAAe;IACb,MAAM,QAAA;IACN,MAAM,QAAA;CACP,CAAC"}
\ No newline at end of file
diff --git a/web/commands/notify.d.ts b/web/commands/notify.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f30f242ea34768f4f96f6add2d06deb0363cc423
--- /dev/null
+++ b/web/commands/notify.d.ts
@@ -0,0 +1,12 @@
+import { DecodeFn, EncodeFn, MakeFn } from "../types";
+declare const _default: {
+    decode: DecodeFn;
+    encode: EncodeFn;
+    make: MakeFn;
+    name: string;
+    trigger: {
+        encode: string;
+        decode: string;
+    };
+};
+export default _default;
diff --git a/web/commands/notify.js b/web/commands/notify.js
new file mode 100644
index 0000000000000000000000000000000000000000..3f22041a4cfbb750fde8d2cbea51924ba20a9d96
--- /dev/null
+++ b/web/commands/notify.js
@@ -0,0 +1,64 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var util = __importStar(require("./util"));
+var parseInfo = function (key, data) { return util.unflatten(util.search(data, key === "operComplete" ? "outputArgs" : key)); };
+var decode = function (msg) {
+    var parent = util.searchParent(msg, 'subscriptionId');
+    if (parent) {
+        var id = parent.subscriptionId;
+        var relField = Object.keys(parent).find(function (k) { return k !== "subscriptionId"; });
+        return (id && relField) ? [parseInfo(relField, msg), id, null] : [null, id, null];
+    }
+    return [null];
+};
+var encode = function (_a) {
+    var paths = _a.paths;
+    return ({
+        lookup: "Msg",
+        header: {
+            msgId: util.uniq("NOTIFY@"),
+            msgType: "GET",
+            lookup: "Header",
+        },
+        body: {
+            lookup: "Body",
+            request: {
+                lookup: "Request",
+                get: {
+                    paramPaths: Array.isArray(paths) ? paths : [paths],
+                },
+            },
+        },
+    });
+};
+var make = function (call) { return function (paths) { return call("GET", { paths: paths }); }; };
+exports.default = {
+    decode: decode,
+    encode: encode,
+    make: make,
+    name: "get",
+    trigger: {
+        encode: "GET",
+        decode: "GET_RESP"
+    }
+};
+//# sourceMappingURL=notify.js.map
\ No newline at end of file
diff --git a/web/commands/notify.js.map b/web/commands/notify.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..4a2f6b01a484cded7fe2ba67711a3673c9425ddf
--- /dev/null
+++ b/web/commands/notify.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"notify.js","sourceRoot":"","sources":["../../src/commands/notify.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AACA,2CAA+B;AAE/B,IAAM,SAAS,GAAG,UAAC,GAAW,EAAE,IAAyB,IAAK,OAAA,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,KAAK,cAAc,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAA9E,CAA8E,CAAA;AAE5I,IAAM,MAAM,GAAa,UAAC,GAAG;IAC3B,IAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAA;IACvD,IAAI,MAAM,EAAE;QACV,IAAM,EAAE,GAAG,MAAM,CAAC,cAAc,CAAA;QAChC,IAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,UAAA,CAAC,IAAI,OAAA,CAAC,KAAK,gBAAgB,EAAtB,CAAsB,CAAC,CAAA;QACtE,OAAO,CAAC,EAAE,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;KACnF;IACD,OAAO,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC,CAAC;AAEF,IAAM,MAAM,GAAa,UAAC,EAAS;QAAP,KAAK,WAAA;IAAO,OAAA,CAAC;QACvC,MAAM,EAAE,KAAK;QACb,MAAM,EAAE;YACN,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC;YAC3B,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,QAAQ;SACjB;QACD,IAAI,EAAE;YACJ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,MAAM,EAAE,SAAS;gBACjB,GAAG,EAAE;oBACH,UAAU,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;iBACnD;aACF;SACF;KACF,CAAC;AAhBsC,CAgBtC,CAAC;AAEH,IAAM,IAAI,GAAW,UAAC,IAAI,IAAiB,OAAA,UAAC,KAAK,IAAK,OAAA,IAAI,CAAC,KAAK,EAAE,EAAE,KAAK,OAAA,EAAE,CAAC,EAAtB,CAAsB,EAAjC,CAAiC,CAAC;AAE7E,kBAAe;IACb,MAAM,QAAA;IACN,MAAM,QAAA;IACN,IAAI,MAAA;IACJ,IAAI,EAAE,KAAK;IACX,OAAO,EAAE;QACP,MAAM,EAAE,KAAK;QACb,MAAM,EAAE,UAAU;KACnB;CACF,CAAC"}
\ No newline at end of file
diff --git a/web/commands/operate.d.ts b/web/commands/operate.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5e00b673fef4d5d0922d1ef792b1d08ca25a83fa
--- /dev/null
+++ b/web/commands/operate.d.ts
@@ -0,0 +1,6 @@
+import { DecodeFn, EncodeFn } from "../types";
+declare const _default: {
+    decode: DecodeFn;
+    encode: EncodeFn;
+};
+export default _default;
diff --git a/web/commands/operate.js b/web/commands/operate.js
new file mode 100644
index 0000000000000000000000000000000000000000..3f129dc53a1c82ab0cebb0aba964d04d0be95f38
--- /dev/null
+++ b/web/commands/operate.js
@@ -0,0 +1,57 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var util = __importStar(require("./util"));
+var decode = function (msg) {
+    var data = util.searchAll(msg, "operationResults");
+    if (data && data.length === 1)
+        return [util.unflatten(data[0])];
+    var unflattened = data.map(function (v) { return util.unflatten(v); });
+    return [unflattened];
+};
+var encode = function (_a) {
+    var path = _a.path, input = _a.input, id = _a.id;
+    return ({
+        lookup: "Msg",
+        header: {
+            msgId: id,
+            msgType: "OPERATE",
+            lookup: "Header",
+        },
+        body: {
+            lookup: "Body",
+            request: {
+                lookup: "Request",
+                operate: {
+                    command: path,
+                    commandKey: "",
+                    sendResp: false,
+                    inputArgs: input || {},
+                },
+            },
+        },
+    });
+};
+exports.default = {
+    decode: decode,
+    encode: encode
+};
+//# sourceMappingURL=operate.js.map
\ No newline at end of file
diff --git a/web/commands/operate.js.map b/web/commands/operate.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..5c6e56cba1e411d6340be3ab67adfac6a8950f7c
--- /dev/null
+++ b/web/commands/operate.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"operate.js","sourceRoot":"","sources":["../../src/commands/operate.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AAIA,2CAA+B;AAE/B,IAAM,MAAM,GAAa,UAAC,GAAG;IAC3B,IAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;IACrD,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAChE,IAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,UAAC,CAAC,IAAK,OAAA,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAjB,CAAiB,CAAC,CAAC;IACvD,OAAO,CAAC,WAAW,CAAC,CAAC;AACvB,CAAC,CAAC;AAEF,IAAM,MAAM,GAAa,UAAC,EAAmB;QAAjB,IAAI,UAAA,EAAE,KAAK,WAAA,EAAE,EAAE,QAAA;IAAO,OAAA,CAAC;QACjD,MAAM,EAAE,KAAK;QACb,MAAM,EAAE;YACN,KAAK,EAAE,EAAE;YACT,OAAO,EAAE,SAAS;YAClB,MAAM,EAAE,QAAQ;SACjB;QACD,IAAI,EAAE;YACJ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,MAAM,EAAE,SAAS;gBACjB,OAAO,EAAE;oBACP,OAAO,EAAE,IAAI;oBACb,UAAU,EAAE,EAAE;oBACd,QAAQ,EAAE,KAAK;oBACf,SAAS,EAAE,KAAK,IAAI,EAAE;iBACvB;aACF;SACF;KACF,CAAC;AAnBgD,CAmBhD,CAAC;AAEH,kBAAe;IACb,MAAM,QAAA;IACN,MAAM,QAAA;CACP,CAAC"}
\ No newline at end of file
diff --git a/web/commands/proto.d.ts b/web/commands/proto.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5e00b673fef4d5d0922d1ef792b1d08ca25a83fa
--- /dev/null
+++ b/web/commands/proto.d.ts
@@ -0,0 +1,6 @@
+import { DecodeFn, EncodeFn } from "../types";
+declare const _default: {
+    decode: DecodeFn;
+    encode: EncodeFn;
+};
+export default _default;
diff --git a/web/commands/proto.js b/web/commands/proto.js
new file mode 100644
index 0000000000000000000000000000000000000000..df7569cffd0b8de956b287a813cc52adde0cbe5f
--- /dev/null
+++ b/web/commands/proto.js
@@ -0,0 +1,51 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var util = __importStar(require("./util"));
+var decode = function (msg) {
+    var results = util.search(msg, "agentSupportedProtocolVersions");
+    return [results];
+};
+var encode = function (_a) {
+    var versions = _a.versions;
+    return ({
+        lookup: "Msg",
+        header: {
+            msgId: util.uniq("GET_SUPPORTED_PROTO@"),
+            msgType: "GET_SUPPORTED_PROTO",
+            lookup: "Header",
+        },
+        body: {
+            lookup: "Body",
+            request: {
+                lookup: "Request",
+                getSupportedProtocol: {
+                    controllerSupportedProtocolVersions: versions,
+                },
+            },
+        },
+    });
+};
+exports.default = {
+    decode: decode,
+    encode: encode
+};
+//# sourceMappingURL=proto.js.map
\ No newline at end of file
diff --git a/web/commands/proto.js.map b/web/commands/proto.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..c08521588a8c9b75857a59f984a6810a466106ac
--- /dev/null
+++ b/web/commands/proto.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"proto.js","sourceRoot":"","sources":["../../src/commands/proto.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AACA,2CAA+B;AAE/B,IAAM,MAAM,GAAa,UAAC,GAAG;IAC3B,IAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,gCAAgC,CAAC,CAAC;IACnE,OAAO,CAAC,OAAO,CAAC,CAAC;AACnB,CAAC,CAAC;AAEF,IAAM,MAAM,GAAa,UAAC,EAAY;QAAV,QAAQ,cAAA;IAAO,OAAA,CAAC;QAC1C,MAAM,EAAE,KAAK;QACb,MAAM,EAAE;YACN,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,sBAAsB,CAAC;YACxC,OAAO,EAAE,qBAAqB;YAC9B,MAAM,EAAE,QAAQ;SACjB;QACD,IAAI,EAAE;YACJ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,MAAM,EAAE,SAAS;gBACjB,oBAAoB,EAAE;oBACpB,mCAAmC,EAAE,QAAQ;iBAC9C;aACF;SACF;KACF,CAAC;AAhByC,CAgBzC,CAAC;AAEH,kBAAe;IACb,MAAM,QAAA;IACN,MAAM,QAAA;CACP,CAAC"}
\ No newline at end of file
diff --git a/web/commands/recipes/operate.d.ts b/web/commands/recipes/operate.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5ce835a2fe8690c2913a7d7ef49b60be818210ed
--- /dev/null
+++ b/web/commands/recipes/operate.d.ts
@@ -0,0 +1,6 @@
+import { MakeFn } from "../../types";
+declare const _default: {
+    name: string;
+    make: MakeFn;
+};
+export default _default;
diff --git a/web/commands/recipes/operate.js b/web/commands/recipes/operate.js
new file mode 100644
index 0000000000000000000000000000000000000000..122533b69b7bd7e01042cd89768f6cd0426f1877
--- /dev/null
+++ b/web/commands/recipes/operate.js
@@ -0,0 +1,77 @@
+"use strict";
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+    return new (P || (P = Promise))(function (resolve, reject) {
+        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+        step((generator = generator.apply(thisArg, _arguments || [])).next());
+    });
+};
+var __generator = (this && this.__generator) || function (thisArg, body) {
+    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
+    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
+    function verb(n) { return function (v) { return step([n, v]); }; }
+    function step(op) {
+        if (f) throw new TypeError("Generator is already executing.");
+        while (_) try {
+            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
+            if (y = 0, t) op = [op[0] & 2, t.value];
+            switch (op[0]) {
+                case 0: case 1: t = op; break;
+                case 4: _.label++; return { value: op[1], done: false };
+                case 5: _.label++; y = op[1]; op = [0]; continue;
+                case 7: op = _.ops.pop(); _.trys.pop(); continue;
+                default:
+                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
+                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
+                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
+                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
+                    if (t[2]) _.ops.pop();
+                    _.trys.pop(); continue;
+            }
+            op = body.call(thisArg, _);
+        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
+        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
+    }
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var util_1 = require("../util");
+var operateSubscriptionPath = "Device.LocalAgent.Subscription.";
+var make = function (call) { return function (path, opts) { return __awaiter(void 0, void 0, void 0, function () {
+    var Persistent, id, operateInput, newSubPath, command, cleanup;
+    return __generator(this, function (_a) {
+        switch (_a.label) {
+            case 0:
+                Persistent = (opts === null || opts === void 0 ? void 0 : opts.Persistent) === undefined ? false : opts.Persistent;
+                id = "NOTIFY@" + ((opts === null || opts === void 0 ? void 0 : opts.ID) || util_1.uniq(path));
+                operateInput = {
+                    Enable: true,
+                    ID: id,
+                    NotifType: "OperationComplete",
+                    ReferenceList: path,
+                    Persistent: Persistent,
+                };
+                return [4 /*yield*/, call("ADD", {
+                        path: operateSubscriptionPath,
+                        value: operateInput,
+                    })];
+            case 1:
+                newSubPath = _a.sent();
+                command = function (input) {
+                    return call("OPERATE", {
+                        path: path,
+                        input: input,
+                        id: id,
+                    });
+                };
+                cleanup = function () { return call("DELETE", { paths: newSubPath }); };
+                return [2 /*return*/, [command, cleanup]];
+        }
+    });
+}); }; };
+exports.default = {
+    name: "operate",
+    make: make,
+};
+//# sourceMappingURL=operate.js.map
\ No newline at end of file
diff --git a/web/commands/recipes/operate.js.map b/web/commands/recipes/operate.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..7ffbd0afa171400fa9e1d3d2839094ee0fbc1251
--- /dev/null
+++ b/web/commands/recipes/operate.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"operate.js","sourceRoot":"","sources":["../../../src/commands/recipes/operate.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,gCAA+B;AAE/B,IAAM,uBAAuB,GAAG,iCAAiC,CAAC;AAElE,IAAM,IAAI,GAAW,UAAC,IAAI,IAAoB,OAAA,UAAO,IAAI,EAAE,IAAI;;;;;gBACvD,UAAU,GAAG,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,UAAU,MAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC;gBACtE,EAAE,GAAG,SAAS,GAAG,CAAC,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,EAAE,KAAI,WAAI,CAAC,IAAI,CAAC,CAAC,CAAC;gBAC1C,YAAY,GAAG;oBACnB,MAAM,EAAE,IAAI;oBACZ,EAAE,EAAE,EAAE;oBACN,SAAS,EAAE,mBAAmB;oBAC9B,aAAa,EAAE,IAAI;oBACnB,UAAU,YAAA;iBACX,CAAC;gBAEiB,qBAAM,IAAI,CAAC,KAAK,EAAE;wBACnC,IAAI,EAAE,uBAAuB;wBAC7B,KAAK,EAAE,YAAY;qBACpB,CAAC,EAAA;;gBAHI,UAAU,GAAG,SAGjB;gBAEI,OAAO,GAAc,UAAC,KAA2B;oBACrD,OAAA,IAAI,CAAC,SAAS,EAAE;wBACd,IAAI,MAAA;wBACJ,KAAK,OAAA;wBACL,EAAE,IAAA;qBACH,CAAC;gBAJF,CAIE,CAAC;gBAEC,OAAO,GAAmB,cAAM,OAAA,IAAI,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,EAArC,CAAqC,CAAC;gBAE5E,sBAAO,CAAC,OAAO,EAAE,OAAO,CAAC,EAAC;;;KAC3B,EA1B6C,CA0B7C,CAAC;AAEF,kBAAe;IACb,IAAI,EAAE,SAAS;IACf,IAAI,MAAA;CACL,CAAC"}
\ No newline at end of file
diff --git a/web/commands/recipes/resolve.d.ts b/web/commands/recipes/resolve.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a8a21edf32e6e0b10c4c46ea9186de7bb369dc13
--- /dev/null
+++ b/web/commands/recipes/resolve.d.ts
@@ -0,0 +1,6 @@
+import { MakeRecipeFn } from "../../types";
+declare const _default: {
+    name: string;
+    make: MakeRecipeFn;
+};
+export default _default;
diff --git a/web/commands/recipes/resolve.js b/web/commands/recipes/resolve.js
new file mode 100644
index 0000000000000000000000000000000000000000..0172b12f25ff56f25ef3eae7405fad71791076ec
--- /dev/null
+++ b/web/commands/recipes/resolve.js
@@ -0,0 +1,110 @@
+"use strict";
+var __assign = (this && this.__assign) || function () {
+    __assign = Object.assign || function(t) {
+        for (var s, i = 1, n = arguments.length; i < n; i++) {
+            s = arguments[i];
+            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
+                t[p] = s[p];
+        }
+        return t;
+    };
+    return __assign.apply(this, arguments);
+};
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+    return new (P || (P = Promise))(function (resolve, reject) {
+        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+        step((generator = generator.apply(thisArg, _arguments || [])).next());
+    });
+};
+var __generator = (this && this.__generator) || function (thisArg, body) {
+    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
+    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
+    function verb(n) { return function (v) { return step([n, v]); }; }
+    function step(op) {
+        if (f) throw new TypeError("Generator is already executing.");
+        while (_) try {
+            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
+            if (y = 0, t) op = [op[0] & 2, t.value];
+            switch (op[0]) {
+                case 0: case 1: t = op; break;
+                case 4: _.label++; return { value: op[1], done: false };
+                case 5: _.label++; y = op[1]; op = [0]; continue;
+                case 7: op = _.ops.pop(); _.trys.pop(); continue;
+                default:
+                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
+                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
+                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
+                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
+                    if (t[2]) _.ops.pop();
+                    _.trys.pop(); continue;
+            }
+            op = body.call(thisArg, _);
+        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
+        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
+    }
+};
+var __spreadArray = (this && this.__spreadArray) || function (to, from) {
+    for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
+        to[j] = from[i];
+    return to;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var splitReference = function (s) { return s.split(","); };
+var isReference = function (s) { return s.startsWith("Device."); };
+var addDot = function (s) { return (s.endsWith(".") ? s : s + "."); };
+var resolveReferences = function (message, call, level) { return __awaiter(void 0, void 0, void 0, function () {
+    var msg, msg, _i, _a, _b, key, val, item, _c, _d, _e, _f;
+    return __generator(this, function (_g) {
+        switch (_g.label) {
+            case 0:
+                if (level === 0 || !["string", "object"].includes(typeof message))
+                    return [2 /*return*/, message];
+                if (!(typeof message === "string")) return [3 /*break*/, 3];
+                return [4 /*yield*/, call("GET", { paths: splitReference(message).map(addDot) })];
+            case 1:
+                msg = _g.sent();
+                return [4 /*yield*/, resolveReferences(msg, call, level - 1)];
+            case 2: return [2 /*return*/, _g.sent()];
+            case 3:
+                if (!(typeof message === "object")) return [3 /*break*/, 11];
+                msg = Array.isArray(message) ? __spreadArray([], message) : __assign({}, message);
+                _i = 0, _a = Object.entries(message);
+                _g.label = 4;
+            case 4:
+                if (!(_i < _a.length)) return [3 /*break*/, 10];
+                _b = _a[_i], key = _b[0], val = _b[1];
+                if (!(typeof val === "string" && isReference(val))) return [3 /*break*/, 7];
+                return [4 /*yield*/, call("GET", { paths: splitReference(val).map(addDot) })];
+            case 5:
+                item = _g.sent();
+                _c = msg;
+                _d = key;
+                return [4 /*yield*/, resolveReferences(item, call, level - 1)];
+            case 6:
+                _c[_d] = _g.sent();
+                return [3 /*break*/, 9];
+            case 7:
+                if (!(typeof val === "object")) return [3 /*break*/, 9];
+                _e = msg;
+                _f = key;
+                return [4 /*yield*/, resolveReferences(msg[key], call, level)];
+            case 8:
+                _e[_f] = _g.sent();
+                _g.label = 9;
+            case 9:
+                _i++;
+                return [3 /*break*/, 4];
+            case 10: return [2 /*return*/, msg];
+            case 11: return [2 /*return*/, message];
+        }
+    });
+}); };
+var make = function (call) { return function (msg, level) { return resolveReferences(msg, call, level || 1); }; };
+exports.default = {
+    name: "resolve",
+    make: make
+};
+//# sourceMappingURL=resolve.js.map
\ No newline at end of file
diff --git a/web/commands/recipes/resolve.js.map b/web/commands/recipes/resolve.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..61b4547a2486001873d8097e8c537c1c5fa44df1
--- /dev/null
+++ b/web/commands/recipes/resolve.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../../src/commands/recipes/resolve.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,IAAM,cAAc,GAAG,UAAC,CAAS,IAAK,OAAA,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAZ,CAAY,CAAC;AACnD,IAAM,WAAW,GAAG,UAAC,CAAS,IAAc,OAAA,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,EAAvB,CAAuB,CAAC;AACpE,IAAM,MAAM,GAAG,UAAC,CAAS,IAAK,OAAA,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,EAA/B,CAA+B,CAAC;AAE9D,IAAM,iBAAiB,GAAG,UACxB,OAAkB,EAClB,IAAY,EACZ,KAAa;;;;;gBAEb,IAAI,KAAK,KAAK,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,OAAO,OAAO,CAAC;oBAAE,sBAAO,OAAO,EAAC;qBAC9E,CAAA,OAAO,OAAO,KAAK,QAAQ,CAAA,EAA3B,wBAA2B;gBACjB,qBAAM,IAAI,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAA;;gBAAvE,GAAG,GAAG,SAAiE;gBACtE,qBAAM,iBAAiB,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,EAAA;oBAApD,sBAAO,SAA6C,EAAC;;qBAC5C,CAAA,OAAO,OAAO,KAAK,QAAQ,CAAA,EAA3B,yBAA2B;gBAC9B,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,mBAAK,OAAO,EAAE,CAAC,cAAM,OAAO,CAAE,CAAC;sBACnB,EAAvB,KAAA,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;;;qBAAvB,CAAA,cAAuB,CAAA;gBAArC,WAAU,EAAT,GAAG,QAAA,EAAE,GAAG,QAAA;qBACd,CAAA,OAAO,GAAG,KAAK,QAAQ,IAAI,WAAW,CAAC,GAAG,CAAC,CAAA,EAA3C,wBAA2C;gBAChC,qBAAM,IAAI,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAA;;gBAApE,IAAI,GAAG,SAA6D;gBAC1E,KAAA,GAAG,CAAA;gBAAC,KAAA,GAAG,CAAA;gBAAI,qBAAM,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,EAAA;;gBAAzD,MAAQ,GAAG,SAA8C,CAAC;;;qBACjD,CAAA,OAAO,GAAG,KAAK,QAAQ,CAAA,EAAvB,wBAAuB;gBAChC,KAAA,GAAG,CAAA;gBAAC,KAAA,GAAG,CAAA;gBAAI,qBAAM,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,EAAA;;gBAAzD,MAAQ,GAAG,SAA8C,CAAC;;;gBALrC,IAAuB,CAAA;;qBAOhD,sBAAO,GAAG,EAAC;qBAEb,sBAAO,OAAO,EAAC;;;KAChB,CAAC;AAEF,IAAM,IAAI,GAAiB,UAAC,IAAI,IAAoB,OAAA,UAAC,GAAG,EAAE,KAAK,IAAK,OAAA,iBAAiB,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC,CAAC,EAAxC,CAAwC,EAAxD,CAAwD,CAAA;AAE5G,kBAAe;IACb,IAAI,EAAE,SAAS;IACf,IAAI,MAAA;CACL,CAAA"}
\ No newline at end of file
diff --git a/web/commands/recipes/subscribe.d.ts b/web/commands/recipes/subscribe.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5ce835a2fe8690c2913a7d7ef49b60be818210ed
--- /dev/null
+++ b/web/commands/recipes/subscribe.d.ts
@@ -0,0 +1,6 @@
+import { MakeFn } from "../../types";
+declare const _default: {
+    name: string;
+    make: MakeFn;
+};
+export default _default;
diff --git a/web/commands/recipes/subscribe.js b/web/commands/recipes/subscribe.js
new file mode 100644
index 0000000000000000000000000000000000000000..d41ae8b6c6a26c09c941049b8afef01b16a6c391
--- /dev/null
+++ b/web/commands/recipes/subscribe.js
@@ -0,0 +1,72 @@
+"use strict";
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+    return new (P || (P = Promise))(function (resolve, reject) {
+        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+        step((generator = generator.apply(thisArg, _arguments || [])).next());
+    });
+};
+var __generator = (this && this.__generator) || function (thisArg, body) {
+    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
+    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
+    function verb(n) { return function (v) { return step([n, v]); }; }
+    function step(op) {
+        if (f) throw new TypeError("Generator is already executing.");
+        while (_) try {
+            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
+            if (y = 0, t) op = [op[0] & 2, t.value];
+            switch (op[0]) {
+                case 0: case 1: t = op; break;
+                case 4: _.label++; return { value: op[1], done: false };
+                case 5: _.label++; y = op[1]; op = [0]; continue;
+                case 7: op = _.ops.pop(); _.trys.pop(); continue;
+                default:
+                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
+                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
+                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
+                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
+                    if (t[2]) _.ops.pop();
+                    _.trys.pop(); continue;
+            }
+            op = body.call(thisArg, _);
+        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
+        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
+    }
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var util_1 = require("../util");
+var subscriptionPath = "Device.LocalAgent.Subscription.";
+var make = function (call, on) { return function (opts, callback) { return __awaiter(void 0, void 0, void 0, function () {
+    var id, refList, newSubPath, clear;
+    return __generator(this, function (_a) {
+        switch (_a.label) {
+            case 0:
+                id = "NOTIFY@" + (opts.id || util_1.uniq());
+                refList = Array.isArray(opts.reference) ? opts.reference.join(",") : opts.reference;
+                return [4 /*yield*/, call("ADD", {
+                        path: subscriptionPath,
+                        value: {
+                            Enable: true,
+                            ID: id,
+                            NotifType: opts.notif,
+                            ReferenceList: refList,
+                            Persistent: false,
+                        },
+                    })];
+            case 1:
+                newSubPath = _a.sent();
+                clear = on(id, callback);
+                return [2 /*return*/, function () {
+                        clear();
+                        return call("DELETE", { paths: newSubPath });
+                    }];
+        }
+    });
+}); }; };
+exports.default = {
+    name: "subscribe",
+    make: make,
+};
+//# sourceMappingURL=subscribe.js.map
\ No newline at end of file
diff --git a/web/commands/recipes/subscribe.js.map b/web/commands/recipes/subscribe.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..86ac014cfe04c7ccba38d858e1d898925f46c4ed
--- /dev/null
+++ b/web/commands/recipes/subscribe.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"subscribe.js","sourceRoot":"","sources":["../../../src/commands/recipes/subscribe.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,gCAA+B;AAE/B,IAAM,gBAAgB,GAAG,iCAAiC,CAAC;AAE3D,IAAM,IAAI,GAAW,UAAC,IAAI,EAAE,EAAE,IAAsB,OAAA,UAAO,IAAI,EAAE,QAAQ;;;;;gBACjE,EAAE,GAAG,SAAS,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,WAAI,EAAE,CAAC,CAAC;gBACrC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAA;gBAEtE,qBAAM,IAAI,CAAC,KAAK,EAAE;wBACnC,IAAI,EAAE,gBAAgB;wBACtB,KAAK,EAAE;4BACL,MAAM,EAAE,IAAI;4BACZ,EAAE,EAAE,EAAE;4BACN,SAAS,EAAE,IAAI,CAAC,KAAK;4BACrB,aAAa,EAAE,OAAO;4BACtB,UAAU,EAAE,KAAK;yBAClB;qBACF,CAAC,EAAA;;gBATI,UAAU,GAAG,SASjB;gBAEI,KAAK,GAAG,EAAE,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;gBAC/B,sBAAO;wBACL,KAAK,EAAE,CAAC;wBACR,OAAO,IAAI,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAA;oBAC9C,CAAC,EAAA;;;KACF,EApBmD,CAoBnD,CAAC;AAEF,kBAAe;IACb,IAAI,EAAE,WAAW;IACjB,IAAI,MAAA;CACL,CAAC"}
\ No newline at end of file
diff --git a/web/commands/set.d.ts b/web/commands/set.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5e00b673fef4d5d0922d1ef792b1d08ca25a83fa
--- /dev/null
+++ b/web/commands/set.d.ts
@@ -0,0 +1,6 @@
+import { DecodeFn, EncodeFn } from "../types";
+declare const _default: {
+    decode: DecodeFn;
+    encode: EncodeFn;
+};
+export default _default;
diff --git a/web/commands/set.js b/web/commands/set.js
new file mode 100644
index 0000000000000000000000000000000000000000..ef39cba0206efa53c61718be957b2616c7fdea75
--- /dev/null
+++ b/web/commands/set.js
@@ -0,0 +1,85 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var util = __importStar(require("./util"));
+var decode = function (_msg) {
+    return [null];
+};
+var isObject = function (v) {
+    return typeof v === "object" && v.required !== undefined && v.value !== undefined;
+};
+var encode = function (_a) {
+    var value = _a.value, initialPath = _a.path;
+    var isObj = typeof value === "object";
+    var allowPartial = isObj && value.allowPartial !== undefined ? value.allowPartial : false;
+    var attr = initialPath.split(".").pop() || "";
+    var pairs = isObj
+        ? Object.entries(value).map(function (_a) {
+            var k = _a[0], v = _a[1];
+            return isObject(v)
+                ? [k, v.value.toString(), v.required]
+                : [k, v.toString(), false];
+        })
+        : [[attr, value]];
+    var path = initialPath.endsWith(".") ? initialPath : initialPath.slice(0, initialPath.lastIndexOf('.') + 1);
+    return {
+        lookup: "Msg",
+        header: {
+            lookup: "Header",
+            msgId: util.uniq("SET@"),
+            msgType: "SET",
+        },
+        body: {
+            lookup: "Body",
+            request: {
+                lookup: "Request",
+                set: {
+                    allowPartial: allowPartial,
+                    updateObjs: [
+                        {
+                            lookup: "Set.UpdateObject",
+                            objPath: path,
+                            paramSettings: pairs
+                                .filter(function (_a) {
+                                var k = _a[0];
+                                return k !== "allowPartial";
+                            })
+                                .map(function (_a) {
+                                var param = _a[0], value = _a[1], required = _a[2];
+                                return ({
+                                    lookup: "Set.UpdateParamSetting",
+                                    param: param,
+                                    value: value,
+                                    required: required,
+                                });
+                            }),
+                        },
+                    ],
+                },
+            },
+        },
+    };
+};
+exports.default = {
+    decode: decode,
+    encode: encode
+};
+//# sourceMappingURL=set.js.map
\ No newline at end of file
diff --git a/web/commands/set.js.map b/web/commands/set.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..00087436d047e9bf1e991977e636e11a632b60a3
--- /dev/null
+++ b/web/commands/set.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"set.js","sourceRoot":"","sources":["../../src/commands/set.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AACA,2CAA+B;AAE/B,IAAM,MAAM,GAAa,UAAC,IAAI;IAC5B,OAAO,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC,CAAC;AAEF,IAAM,QAAQ,GAAG,UAAC,CAAC;IACjB,OAAA,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS;AAA1E,CAA0E,CAAC;AAE7E,IAAM,MAAM,GAAa,UAAC,EAA4B;QAA1B,KAAK,WAAA,EAAQ,WAAW,UAAA;IAClD,IAAM,KAAK,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC;IACxC,IAAM,YAAY,GAAG,KAAK,IAAI,KAAK,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC;IAC5F,IAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;IAChD,IAAM,KAAK,GAAG,KAAK;QACjB,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAc,CAAC,CAAC,GAAG,CAAC,UAAC,EAAM;gBAAL,CAAC,QAAA,EAAE,CAAC,QAAA;YACvC,OAAA,QAAQ,CAAC,CAAC,CAAC;gBACT,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC;gBACrC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC;QAF5B,CAE4B,CAC7B;QACH,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;IACpB,IAAM,IAAI,GAAG,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IAE9G,OAAO;QACL,MAAM,EAAE,KAAK;QACb,MAAM,EAAE;YACN,MAAM,EAAE,QAAQ;YAChB,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;YACxB,OAAO,EAAE,KAAK;SACf;QACD,IAAI,EAAE;YACJ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,MAAM,EAAE,SAAS;gBACjB,GAAG,EAAE;oBACH,YAAY,cAAA;oBACZ,UAAU,EAAE;wBACV;4BACE,MAAM,EAAE,kBAAkB;4BAC1B,OAAO,EAAE,IAAI;4BACb,aAAa,EAAE,KAAK;iCACjB,MAAM,CAAC,UAAC,EAAG;oCAAF,CAAC,QAAA;gCAAM,OAAA,CAAC,KAAK,cAAc;4BAApB,CAAoB,CAAC;iCACrC,GAAG,CAAC,UAAC,EAAwB;oCAAvB,KAAK,QAAA,EAAE,KAAK,QAAA,EAAE,QAAQ,QAAA;gCAAM,OAAA,CAAC;oCAClC,MAAM,EAAE,wBAAwB;oCAChC,KAAK,OAAA;oCACL,KAAK,OAAA;oCACL,QAAQ,UAAA;iCACT,CAAC;4BALiC,CAKjC,CAAC;yBACN;qBACF;iBACF;aACF;SACF;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,kBAAe;IACb,MAAM,QAAA;IACN,MAAM,QAAA;CACP,CAAC"}
\ No newline at end of file
diff --git a/web/commands/supported.d.ts b/web/commands/supported.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5e00b673fef4d5d0922d1ef792b1d08ca25a83fa
--- /dev/null
+++ b/web/commands/supported.d.ts
@@ -0,0 +1,6 @@
+import { DecodeFn, EncodeFn } from "../types";
+declare const _default: {
+    decode: DecodeFn;
+    encode: EncodeFn;
+};
+export default _default;
diff --git a/web/commands/supported.js b/web/commands/supported.js
new file mode 100644
index 0000000000000000000000000000000000000000..28c7b8ccd2c59da772097a7357bfee69cbdeb992
--- /dev/null
+++ b/web/commands/supported.js
@@ -0,0 +1,55 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var util = __importStar(require("./util"));
+var decode = function (msg) {
+    var results = util.search(msg, "reqObjResults");
+    return [results];
+};
+var encode = function (_a) {
+    var paths = _a.paths, _b = _a.opts, opts = _b === void 0 ? {} : _b;
+    return ({
+        lookup: "Msg",
+        header: {
+            msgId: util.uniq("GET_SUPPORTED_DM@"),
+            msgType: "GET_SUPPORTED_DM",
+            lookup: "Header",
+        },
+        body: {
+            lookup: "Body",
+            request: {
+                lookup: "Request",
+                getSupportedDm: {
+                    objPaths: Array.isArray(paths) ? paths : [paths],
+                    firstLevelOnly: opts.firstLevelOnly || false,
+                    returnCommands: opts.returnCommands || false,
+                    returnEvents: opts.returnEvents || false,
+                    returnParams: opts.returnParams || false,
+                },
+            },
+        },
+    });
+};
+exports.default = {
+    decode: decode,
+    encode: encode
+};
+//# sourceMappingURL=supported.js.map
\ No newline at end of file
diff --git a/web/commands/supported.js.map b/web/commands/supported.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..630139da915b2695a4db408ef314cf5ef6d4bca7
--- /dev/null
+++ b/web/commands/supported.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"supported.js","sourceRoot":"","sources":["../../src/commands/supported.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AACA,2CAA+B;AAE/B,IAAM,MAAM,GAAa,UAAC,GAAG;IAC3B,IAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;IAClD,OAAO,CAAC,OAAO,CAAC,CAAC;AACnB,CAAC,CAAC;AAEF,IAAM,MAAM,GAAa,UAAC,EAAoB;QAAlB,KAAK,WAAA,EAAE,YAAS,EAAT,IAAI,mBAAG,EAAE,KAAA;IAAO,OAAA,CAAC;QAClD,MAAM,EAAE,KAAK;QACb,MAAM,EAAE;YACN,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC;YACrC,OAAO,EAAE,kBAAkB;YAC3B,MAAM,EAAE,QAAQ;SACjB;QACD,IAAI,EAAE;YACJ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,MAAM,EAAE,SAAS;gBACjB,cAAc,EAAE;oBACd,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;oBAChD,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,KAAK;oBAC5C,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,KAAK;oBAC5C,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,KAAK;oBACxC,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,KAAK;iBACzC;aACF;SACF;KACF,CAAC;AApBiD,CAoBjD,CAAC;AAEH,kBAAe;IACb,MAAM,QAAA;IACN,MAAM,QAAA;CACP,CAAC"}
\ No newline at end of file
diff --git a/web/commands/util.d.ts b/web/commands/util.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..867f5de4d6cfb6baebcba9204dcdf86044f9b44e
--- /dev/null
+++ b/web/commands/util.d.ts
@@ -0,0 +1,13 @@
+export declare const unflatten: (obj: any) => {};
+export declare const search: (obj: any, key: string) => any;
+export declare const searchParent: (obj: any, key: string) => Record<string, any> | undefined;
+export declare const searchAll: (obj: any, key: string) => any[];
+export declare const extractCommand: (msg: {
+    [key: string]: any;
+}) => string | undefined;
+/** Unwraps object with single key */
+export declare const unwrapObject: (data: any) => any;
+/** Unwraps array with single item */
+export declare const unwrapArray: (arr: any) => any;
+export declare function makeBuffer(rootRecord: protobuf.Root, payload: any, options: Record<string, string>): any;
+export declare const uniq: (initial?: string | undefined) => string;
diff --git a/web/commands/util.js b/web/commands/util.js
new file mode 100644
index 0000000000000000000000000000000000000000..57b80e4bb68bd1b4f60cea6efb8c7111ae8fdaf1
--- /dev/null
+++ b/web/commands/util.js
@@ -0,0 +1,119 @@
+"use strict";
+var __assign = (this && this.__assign) || function () {
+    __assign = Object.assign || function(t) {
+        for (var s, i = 1, n = arguments.length; i < n; i++) {
+            s = arguments[i];
+            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
+                t[p] = s[p];
+        }
+        return t;
+    };
+    return __assign.apply(this, arguments);
+};
+var __spreadArray = (this && this.__spreadArray) || function (to, from) {
+    for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
+        to[j] = from[i];
+    return to;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.uniq = exports.makeBuffer = exports.unwrapArray = exports.unwrapObject = exports.extractCommand = exports.searchAll = exports.searchParent = exports.search = exports.unflatten = void 0;
+var digitRe = /^\d+$/;
+var digitDotRe = /^\d+\..*$/;
+var isDigit = function (v) { return digitRe.test(v); };
+var firstIsIndex = function (s) { return digitDotRe.test(s); };
+// Based on: https://www.30secondsofcode.org/js/s/unflatten-object
+var unflatten = function (obj) {
+    return Object.keys(obj).reduce(function (res, k) {
+        k.split(".")
+            .map(function (v) { return (isDigit(v) ? parseInt(v) - 1 : v); })
+            .reduce(function (acc, e, i, keys) {
+            return acc[e] ||
+                (acc[e] = isNaN(Number(keys[i + 1]))
+                    ? keys.length - 1 === i
+                        ? obj[k]
+                        : {}
+                    : []);
+        }, res);
+        return res;
+    }, firstIsIndex(Object.keys(obj)[0]) ? [] : {});
+};
+exports.unflatten = unflatten;
+var search = function (obj, key) {
+    if (typeof obj !== "object")
+        return null;
+    if (obj[key])
+        return obj[key];
+    for (var _i = 0, _a = Object.values(obj); _i < _a.length; _i++) {
+        var val = _a[_i];
+        var s = exports.search(val, key);
+        if (s)
+            return s;
+    }
+};
+exports.search = search;
+var searchParent = function (obj, key) {
+    if (typeof obj !== "object")
+        return;
+    if (obj[key])
+        return obj;
+    for (var _i = 0, _a = Object.values(obj); _i < _a.length; _i++) {
+        var val = _a[_i];
+        var s = exports.searchParent(val, key);
+        if (s)
+            return s;
+    }
+};
+exports.searchParent = searchParent;
+var _searchAll = function (obj, key) {
+    return typeof obj !== "object"
+        ? []
+        : Object.entries(obj).reduce(function (acc, _a) {
+            var k = _a[0], v = _a[1];
+            return __spreadArray(__spreadArray([], acc), [k === key ? v : _searchAll(v, key)]);
+        }, []);
+};
+var searchAll = function (obj, key) {
+    return _searchAll(obj, key).flat(Infinity);
+};
+exports.searchAll = searchAll;
+var extractCommand = function (msg) {
+    var msgType = exports.search(msg, "msgType");
+    if (!msgType) {
+        var id = exports.search(msg, "msgId");
+        var frst = (id ? id.split("@") : [""])[0];
+        return frst.toUpperCase();
+    }
+    return msgType;
+};
+exports.extractCommand = extractCommand;
+/** Unwraps object with single key */
+var unwrapObject = function (data) {
+    return !Array.isArray(data) &&
+        typeof data === "object" &&
+        Object.keys(data).length === 1
+        ? Object.values(data)[0]
+        : data;
+};
+exports.unwrapObject = unwrapObject;
+/** Unwraps array with single item */
+var unwrapArray = function (arr) {
+    return Array.isArray(arr) && arr.length === 1 ? arr[0] : arr;
+};
+exports.unwrapArray = unwrapArray;
+function makeBuffer(rootRecord, payload, options) {
+    var NoSessionContextRecord = rootRecord.lookupType("usp_record.NoSessionContextRecord");
+    var noSessionContextRecordMsg = NoSessionContextRecord.create({
+        payload: payload,
+    });
+    var record = rootRecord.lookupType("usp_record.Record");
+    var recordMsg = record.create(__assign({ version: "1.0", PayloadSecurity: record.PayloadSecurity.PLAINTEXT, noSessionContext: noSessionContextRecordMsg }, options));
+    var buffer = record.encode(recordMsg).finish();
+    return buffer;
+}
+exports.makeBuffer = makeBuffer;
+var uniq = function (initial) {
+    return (initial || "") +
+        (Date.now().toString(36) + Math.random().toString(36).substr(2, 5)).toUpperCase();
+};
+exports.uniq = uniq;
+//# sourceMappingURL=util.js.map
\ No newline at end of file
diff --git a/web/commands/util.js.map b/web/commands/util.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..b44edfd5c16f5ec39082724630f28dc746b2fce5
--- /dev/null
+++ b/web/commands/util.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/commands/util.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/web/index.d.ts b/web/index.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..88e4ae2e92477d409cb5bc098bf5d8a8544a279d
--- /dev/null
+++ b/web/index.d.ts
@@ -0,0 +1,9 @@
+import { Connect } from "./types";
+/**
+ * Connect to device
+ * @param opts - Connection options
+ * @param events - Optional event handlers
+ * @returns A set of functions for interacting with the device
+ */
+declare const connect: Connect;
+export default connect;
diff --git a/web/index.js b/web/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..8f4e4e8a505567ddeb90ed48ef93e33a8e4fdd5f
--- /dev/null
+++ b/web/index.js
@@ -0,0 +1,166 @@
+"use strict";
+var __assign = (this && this.__assign) || function () {
+    __assign = Object.assign || function(t) {
+        for (var s, i = 1, n = arguments.length; i < n; i++) {
+            s = arguments[i];
+            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
+                t[p] = s[p];
+        }
+        return t;
+    };
+    return __assign.apply(this, arguments);
+};
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+    return new (P || (P = Promise))(function (resolve, reject) {
+        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+        step((generator = generator.apply(thisArg, _arguments || [])).next());
+    });
+};
+var __generator = (this && this.__generator) || function (thisArg, body) {
+    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
+    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
+    function verb(n) { return function (v) { return step([n, v]); }; }
+    function step(op) {
+        if (f) throw new TypeError("Generator is already executing.");
+        while (_) try {
+            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
+            if (y = 0, t) op = [op[0] & 2, t.value];
+            switch (op[0]) {
+                case 0: case 1: t = op; break;
+                case 4: _.label++; return { value: op[1], done: false };
+                case 5: _.label++; y = op[1]; op = [0]; continue;
+                case 7: op = _.ops.pop(); _.trys.pop(); continue;
+                default:
+                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
+                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
+                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
+                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
+                    if (t[2]) _.ops.pop();
+                    _.trys.pop(); continue;
+            }
+            op = body.call(thisArg, _);
+        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
+        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
+    }
+};
+var __importDefault = (this && this.__importDefault) || function (mod) {
+    return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var async_mqtt_1 = __importDefault(require("async-mqtt"));
+var commands_1 = require("./commands");
+var util_1 = require("./util");
+var defaultPublishEndpoint = "/usp/endpoint";
+var defaultSubscribeEndpoint = "/usp/controller";
+var defaultIdEndpoint = "obuspa/EndpointID";
+var defaultFromId = "proto::interop-usp-controller";
+var idResolveTimeout = 5000;
+var isURL = function (opts) {
+    return "url" in opts;
+};
+var _connect = function (opts) {
+    var _a;
+    if (isURL(opts))
+        return async_mqtt_1.default.connectAsync(opts.url, opts);
+    else
+        return ((_a = opts.protocol) === null || _a === void 0 ? void 0 : _a.startsWith("ws"))
+            ? async_mqtt_1.default.connectAsync(opts.protocol + "://" + opts.host + ":" + opts.port, opts)
+            : async_mqtt_1.default.connectAsync(opts);
+};
+var fixId = function (s) { return s.split("+").join("%2B"); };
+/**
+ * Connect to device
+ * @param opts - Connection options
+ * @param events - Optional event handlers
+ * @returns A set of functions for interacting with the device
+ */
+var connect = function (options, events) { return __awaiter(void 0, void 0, void 0, function () {
+    var subscribeEndpoint, publishEndpoint, idEndpoint, router, callbackRouter, handleError, client, handleInit, toId, _a, _b, fromId, on, encode, call;
+    return __generator(this, function (_c) {
+        switch (_c.label) {
+            case 0:
+                subscribeEndpoint = options.subscribeEndpoint || defaultSubscribeEndpoint;
+                publishEndpoint = options.publishEndpoint || defaultPublishEndpoint;
+                idEndpoint = options.idEndpoint || defaultIdEndpoint;
+                router = util_1.makeRouter();
+                callbackRouter = util_1.makeCallbackRouter();
+                handleError = function (err) {
+                    return events && events.onError && events.onError(err);
+                };
+                callbackRouter.add("error", handleError);
+                return [4 /*yield*/, _connect(options)];
+            case 1:
+                client = _c.sent();
+                handleInit = function () {
+                    return new Promise(function (resolve, reject) {
+                        var id = setTimeout(function () { return reject({ errMsg: "toId was not received within timeout(" + idResolveTimeout + ")" }); }, idResolveTimeout);
+                        client.on("message", function (_topic, data) {
+                            clearTimeout(id);
+                            client.unsubscribe(idEndpoint);
+                            resolve(commands_1.decodeId(data));
+                        });
+                        client.subscribe(idEndpoint);
+                    });
+                };
+                _a = fixId;
+                _b = options.toId;
+                if (_b) return [3 /*break*/, 3];
+                return [4 /*yield*/, handleInit()];
+            case 2:
+                _b = (_c.sent());
+                _c.label = 3;
+            case 3:
+                toId = _a.apply(void 0, [_b]);
+                fromId = options.fromId || defaultFromId;
+                client.on("message", function (_topic, data) {
+                    var parsedMsg = commands_1.readMsg(data);
+                    var _a = commands_1.decode(parsedMsg), id = _a[0], message = _a[1], err = _a[2];
+                    var call = router.get(id);
+                    if (call && call.resolve && call.resolve) {
+                        if (err)
+                            call.reject(err);
+                        else
+                            call.resolve(message);
+                    }
+                    var cbs = callbackRouter.get(id);
+                    cbs.forEach(function (cb) {
+                        if (message)
+                            cb(message, parsedMsg);
+                        else if (err)
+                            cb(err, parsedMsg);
+                    });
+                });
+                client.on("error", function (err) {
+                    callbackRouter.get("error").forEach(function (cb) { return cb(err); });
+                    handleError(JSON.stringify(err, null, 2));
+                });
+                on = function (ident, callback) {
+                    callbackRouter.add(ident, callback);
+                    return function () {
+                        callbackRouter.del(ident);
+                    };
+                };
+                encode = commands_1.makeEncode({ fromId: fromId, toId: toId });
+                call = function (command, args) {
+                    return new Promise(function (resolve, reject) {
+                        var _a = encode(command, args), id = _a[0], msg = _a[1], err = _a[2];
+                        if (err)
+                            reject(err);
+                        else {
+                            router.add(id, { resolve: resolve, reject: reject });
+                            client.publish(publishEndpoint, msg);
+                        }
+                    });
+                };
+                return [4 /*yield*/, client.subscribe(subscribeEndpoint)];
+            case 4:
+                _c.sent();
+                return [2 /*return*/, __assign(__assign({ get: function (paths) { return call("GET", { paths: paths }); }, set: function (path, value) { return call("SET", { path: path, value: value }); }, add: function (path, value) { return call("ADD", { path: path, value: value }); }, del: function (paths, allowPartial) { return call("DELETE", { paths: paths, allowPartial: allowPartial }); }, instances: function (paths, opts) { return call("GET_INSTANCES", { paths: paths, opts: opts }); }, supportedDM: function (paths, opts) { return call("GET_SUPPORTED_DM", { paths: paths, opts: opts }); }, supportedProto: function (versions) { return call("GET_SUPPORTED_PROTO", { versions: versions }); }, _operate: function (path, id, input) { return call("OPERATE", { path: path, input: input, id: id }); }, on: on }, commands_1.makeRecipes(call, on)), { disconnect: function () { return client.end(); } })];
+        }
+    });
+}); };
+exports.default = connect;
+//# sourceMappingURL=index.js.map
\ No newline at end of file
diff --git a/web/index.js.map b/web/index.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..a61b4276799343a3a6bfc02afdb0c9f722dc9a96
--- /dev/null
+++ b/web/index.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/web/specs/usp-msg-1-1.json b/web/specs/usp-msg-1-1.json
new file mode 100644
index 0000000000000000000000000000000000000000..7f528a8e6aab83341084cc0a651029e532472244
--- /dev/null
+++ b/web/specs/usp-msg-1-1.json
@@ -0,0 +1,1175 @@
+{
+    "nested": {
+        "usp": {
+            "nested": {
+                "Msg": {
+                    "fields": {
+                        "header": {
+                            "type": "Header",
+                            "id": 1
+                        },
+                        "body": {
+                            "type": "Body",
+                            "id": 2
+                        }
+                    }
+                },
+                "Header": {
+                    "fields": {
+                        "msgId": {
+                            "type": "string",
+                            "id": 1
+                        },
+                        "msgType": {
+                            "type": "MsgType",
+                            "id": 2
+                        }
+                    },
+                    "nested": {
+                        "MsgType": {
+                            "values": {
+                                "ERROR": 0,
+                                "GET": 1,
+                                "GET_RESP": 2,
+                                "NOTIFY": 3,
+                                "SET": 4,
+                                "SET_RESP": 5,
+                                "OPERATE": 6,
+                                "OPERATE_RESP": 7,
+                                "ADD": 8,
+                                "ADD_RESP": 9,
+                                "DELETE": 10,
+                                "DELETE_RESP": 11,
+                                "GET_SUPPORTED_DM": 12,
+                                "GET_SUPPORTED_DM_RESP": 13,
+                                "GET_INSTANCES": 14,
+                                "GET_INSTANCES_RESP": 15,
+                                "NOTIFY_RESP": 16,
+                                "GET_SUPPORTED_PROTO": 17,
+                                "GET_SUPPORTED_PROTO_RESP": 18
+                            }
+                        }
+                    }
+                },
+                "Body": {
+                    "oneofs": {
+                        "msgBody": {
+                            "oneof": [
+                                "request",
+                                "response",
+                                "error"
+                            ]
+                        }
+                    },
+                    "fields": {
+                        "request": {
+                            "type": "Request",
+                            "id": 1
+                        },
+                        "response": {
+                            "type": "Response",
+                            "id": 2
+                        },
+                        "error": {
+                            "type": "Error",
+                            "id": 3
+                        }
+                    }
+                },
+                "Request": {
+                    "oneofs": {
+                        "reqType": {
+                            "oneof": [
+                                "get",
+                                "getSupportedDm",
+                                "getInstances",
+                                "set",
+                                "add",
+                                "delete",
+                                "operate",
+                                "notify",
+                                "getSupportedProtocol"
+                            ]
+                        }
+                    },
+                    "fields": {
+                        "get": {
+                            "type": "Get",
+                            "id": 1
+                        },
+                        "getSupportedDm": {
+                            "type": "GetSupportedDM",
+                            "id": 2
+                        },
+                        "getInstances": {
+                            "type": "GetInstances",
+                            "id": 3
+                        },
+                        "set": {
+                            "type": "Set",
+                            "id": 4
+                        },
+                        "add": {
+                            "type": "Add",
+                            "id": 5
+                        },
+                        "delete": {
+                            "type": "Delete",
+                            "id": 6
+                        },
+                        "operate": {
+                            "type": "Operate",
+                            "id": 7
+                        },
+                        "notify": {
+                            "type": "Notify",
+                            "id": 8
+                        },
+                        "getSupportedProtocol": {
+                            "type": "GetSupportedProtocol",
+                            "id": 9
+                        }
+                    }
+                },
+                "Response": {
+                    "oneofs": {
+                        "respType": {
+                            "oneof": [
+                                "getResp",
+                                "getSupportedDmResp",
+                                "getInstancesResp",
+                                "setResp",
+                                "addResp",
+                                "deleteResp",
+                                "operateResp",
+                                "notifyResp",
+                                "getSupportedProtocolResp"
+                            ]
+                        }
+                    },
+                    "fields": {
+                        "getResp": {
+                            "type": "GetResp",
+                            "id": 1
+                        },
+                        "getSupportedDmResp": {
+                            "type": "GetSupportedDMResp",
+                            "id": 2
+                        },
+                        "getInstancesResp": {
+                            "type": "GetInstancesResp",
+                            "id": 3
+                        },
+                        "setResp": {
+                            "type": "SetResp",
+                            "id": 4
+                        },
+                        "addResp": {
+                            "type": "AddResp",
+                            "id": 5
+                        },
+                        "deleteResp": {
+                            "type": "DeleteResp",
+                            "id": 6
+                        },
+                        "operateResp": {
+                            "type": "OperateResp",
+                            "id": 7
+                        },
+                        "notifyResp": {
+                            "type": "NotifyResp",
+                            "id": 8
+                        },
+                        "getSupportedProtocolResp": {
+                            "type": "GetSupportedProtocolResp",
+                            "id": 9
+                        }
+                    }
+                },
+                "Error": {
+                    "fields": {
+                        "errCode": {
+                            "type": "fixed32",
+                            "id": 1
+                        },
+                        "errMsg": {
+                            "type": "string",
+                            "id": 2
+                        },
+                        "paramErrs": {
+                            "rule": "repeated",
+                            "type": "ParamError",
+                            "id": 3
+                        }
+                    },
+                    "nested": {
+                        "ParamError": {
+                            "fields": {
+                                "paramPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "errCode": {
+                                    "type": "fixed32",
+                                    "id": 2
+                                },
+                                "errMsg": {
+                                    "type": "string",
+                                    "id": 3
+                                }
+                            }
+                        }
+                    }
+                },
+                "Get": {
+                    "fields": {
+                        "paramPaths": {
+                            "rule": "repeated",
+                            "type": "string",
+                            "id": 1
+                        }
+                    }
+                },
+                "GetResp": {
+                    "fields": {
+                        "reqPathResults": {
+                            "rule": "repeated",
+                            "type": "RequestedPathResult",
+                            "id": 1
+                        }
+                    },
+                    "nested": {
+                        "RequestedPathResult": {
+                            "fields": {
+                                "requestedPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "errCode": {
+                                    "type": "fixed32",
+                                    "id": 2
+                                },
+                                "errMsg": {
+                                    "type": "string",
+                                    "id": 3
+                                },
+                                "resolvedPathResults": {
+                                    "rule": "repeated",
+                                    "type": "ResolvedPathResult",
+                                    "id": 4
+                                }
+                            }
+                        },
+                        "ResolvedPathResult": {
+                            "fields": {
+                                "resolvedPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "resultParams": {
+                                    "keyType": "string",
+                                    "type": "string",
+                                    "id": 2
+                                }
+                            }
+                        }
+                    }
+                },
+                "GetSupportedDM": {
+                    "fields": {
+                        "objPaths": {
+                            "rule": "repeated",
+                            "type": "string",
+                            "id": 1
+                        },
+                        "firstLevelOnly": {
+                            "type": "bool",
+                            "id": 2
+                        },
+                        "returnCommands": {
+                            "type": "bool",
+                            "id": 3
+                        },
+                        "returnEvents": {
+                            "type": "bool",
+                            "id": 4
+                        },
+                        "returnParams": {
+                            "type": "bool",
+                            "id": 5
+                        }
+                    }
+                },
+                "GetSupportedDMResp": {
+                    "fields": {
+                        "reqObjResults": {
+                            "rule": "repeated",
+                            "type": "RequestedObjectResult",
+                            "id": 1
+                        }
+                    },
+                    "nested": {
+                        "RequestedObjectResult": {
+                            "fields": {
+                                "reqObjPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "errCode": {
+                                    "type": "fixed32",
+                                    "id": 2
+                                },
+                                "errMsg": {
+                                    "type": "string",
+                                    "id": 3
+                                },
+                                "dataModelInstUri": {
+                                    "type": "string",
+                                    "id": 4
+                                },
+                                "supportedObjs": {
+                                    "rule": "repeated",
+                                    "type": "SupportedObjectResult",
+                                    "id": 5
+                                }
+                            }
+                        },
+                        "SupportedObjectResult": {
+                            "fields": {
+                                "supportedObjPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "access": {
+                                    "type": "ObjAccessType",
+                                    "id": 2
+                                },
+                                "isMultiInstance": {
+                                    "type": "bool",
+                                    "id": 3
+                                },
+                                "supportedCommands": {
+                                    "rule": "repeated",
+                                    "type": "SupportedCommandResult",
+                                    "id": 4
+                                },
+                                "supportedEvents": {
+                                    "rule": "repeated",
+                                    "type": "SupportedEventResult",
+                                    "id": 5
+                                },
+                                "supportedParams": {
+                                    "rule": "repeated",
+                                    "type": "SupportedParamResult",
+                                    "id": 6
+                                }
+                            }
+                        },
+                        "SupportedParamResult": {
+                            "fields": {
+                                "paramName": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "access": {
+                                    "type": "ParamAccessType",
+                                    "id": 2
+                                }
+                            }
+                        },
+                        "SupportedCommandResult": {
+                            "fields": {
+                                "commandName": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "inputArgNames": {
+                                    "rule": "repeated",
+                                    "type": "string",
+                                    "id": 2
+                                },
+                                "outputArgNames": {
+                                    "rule": "repeated",
+                                    "type": "string",
+                                    "id": 3
+                                }
+                            }
+                        },
+                        "SupportedEventResult": {
+                            "fields": {
+                                "eventName": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "argNames": {
+                                    "rule": "repeated",
+                                    "type": "string",
+                                    "id": 2
+                                }
+                            }
+                        },
+                        "ParamAccessType": {
+                            "values": {
+                                "PARAM_READ_ONLY": 0,
+                                "PARAM_READ_WRITE": 1,
+                                "PARAM_WRITE_ONLY": 2
+                            }
+                        },
+                        "ObjAccessType": {
+                            "values": {
+                                "OBJ_READ_ONLY": 0,
+                                "OBJ_ADD_DELETE": 1,
+                                "OBJ_ADD_ONLY": 2,
+                                "OBJ_DELETE_ONLY": 3
+                            }
+                        }
+                    }
+                },
+                "GetInstances": {
+                    "fields": {
+                        "objPaths": {
+                            "rule": "repeated",
+                            "type": "string",
+                            "id": 1
+                        },
+                        "firstLevelOnly": {
+                            "type": "bool",
+                            "id": 2
+                        }
+                    }
+                },
+                "GetInstancesResp": {
+                    "fields": {
+                        "reqPathResults": {
+                            "rule": "repeated",
+                            "type": "RequestedPathResult",
+                            "id": 1
+                        }
+                    },
+                    "nested": {
+                        "RequestedPathResult": {
+                            "fields": {
+                                "requestedPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "errCode": {
+                                    "type": "fixed32",
+                                    "id": 2
+                                },
+                                "errMsg": {
+                                    "type": "string",
+                                    "id": 3
+                                },
+                                "currInsts": {
+                                    "rule": "repeated",
+                                    "type": "CurrInstance",
+                                    "id": 4
+                                }
+                            }
+                        },
+                        "CurrInstance": {
+                            "fields": {
+                                "instantiatedObjPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "uniqueKeys": {
+                                    "keyType": "string",
+                                    "type": "string",
+                                    "id": 2
+                                }
+                            }
+                        }
+                    }
+                },
+                "GetSupportedProtocol": {
+                    "fields": {
+                        "controllerSupportedProtocolVersions": {
+                            "type": "string",
+                            "id": 1
+                        }
+                    }
+                },
+                "GetSupportedProtocolResp": {
+                    "fields": {
+                        "agentSupportedProtocolVersions": {
+                            "type": "string",
+                            "id": 1
+                        }
+                    }
+                },
+                "Add": {
+                    "fields": {
+                        "allowPartial": {
+                            "type": "bool",
+                            "id": 1
+                        },
+                        "createObjs": {
+                            "rule": "repeated",
+                            "type": "CreateObject",
+                            "id": 2
+                        }
+                    },
+                    "nested": {
+                        "CreateObject": {
+                            "fields": {
+                                "objPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "paramSettings": {
+                                    "rule": "repeated",
+                                    "type": "CreateParamSetting",
+                                    "id": 2
+                                }
+                            }
+                        },
+                        "CreateParamSetting": {
+                            "fields": {
+                                "param": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "value": {
+                                    "type": "string",
+                                    "id": 2
+                                },
+                                "required": {
+                                    "type": "bool",
+                                    "id": 3
+                                }
+                            }
+                        }
+                    }
+                },
+                "AddResp": {
+                    "fields": {
+                        "createdObjResults": {
+                            "rule": "repeated",
+                            "type": "CreatedObjectResult",
+                            "id": 1
+                        }
+                    },
+                    "nested": {
+                        "CreatedObjectResult": {
+                            "fields": {
+                                "requestedPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "operStatus": {
+                                    "type": "OperationStatus",
+                                    "id": 2
+                                }
+                            },
+                            "nested": {
+                                "OperationStatus": {
+                                    "oneofs": {
+                                        "operStatus": {
+                                            "oneof": [
+                                                "operFailure",
+                                                "operSuccess"
+                                            ]
+                                        }
+                                    },
+                                    "fields": {
+                                        "operFailure": {
+                                            "type": "OperationFailure",
+                                            "id": 1
+                                        },
+                                        "operSuccess": {
+                                            "type": "OperationSuccess",
+                                            "id": 2
+                                        }
+                                    },
+                                    "nested": {
+                                        "OperationFailure": {
+                                            "fields": {
+                                                "errCode": {
+                                                    "type": "fixed32",
+                                                    "id": 1
+                                                },
+                                                "errMsg": {
+                                                    "type": "string",
+                                                    "id": 2
+                                                }
+                                            }
+                                        },
+                                        "OperationSuccess": {
+                                            "fields": {
+                                                "instantiatedPath": {
+                                                    "type": "string",
+                                                    "id": 1
+                                                },
+                                                "paramErrs": {
+                                                    "rule": "repeated",
+                                                    "type": "ParameterError",
+                                                    "id": 2
+                                                },
+                                                "uniqueKeys": {
+                                                    "keyType": "string",
+                                                    "type": "string",
+                                                    "id": 3
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "ParameterError": {
+                            "fields": {
+                                "param": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "errCode": {
+                                    "type": "fixed32",
+                                    "id": 2
+                                },
+                                "errMsg": {
+                                    "type": "string",
+                                    "id": 3
+                                }
+                            }
+                        }
+                    }
+                },
+                "Delete": {
+                    "fields": {
+                        "allowPartial": {
+                            "type": "bool",
+                            "id": 1
+                        },
+                        "objPaths": {
+                            "rule": "repeated",
+                            "type": "string",
+                            "id": 2
+                        }
+                    }
+                },
+                "DeleteResp": {
+                    "fields": {
+                        "deletedObjResults": {
+                            "rule": "repeated",
+                            "type": "DeletedObjectResult",
+                            "id": 1
+                        }
+                    },
+                    "nested": {
+                        "DeletedObjectResult": {
+                            "fields": {
+                                "requestedPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "operStatus": {
+                                    "type": "OperationStatus",
+                                    "id": 2
+                                }
+                            },
+                            "nested": {
+                                "OperationStatus": {
+                                    "oneofs": {
+                                        "operStatus": {
+                                            "oneof": [
+                                                "operFailure",
+                                                "operSuccess"
+                                            ]
+                                        }
+                                    },
+                                    "fields": {
+                                        "operFailure": {
+                                            "type": "OperationFailure",
+                                            "id": 1
+                                        },
+                                        "operSuccess": {
+                                            "type": "OperationSuccess",
+                                            "id": 2
+                                        }
+                                    },
+                                    "nested": {
+                                        "OperationFailure": {
+                                            "fields": {
+                                                "errCode": {
+                                                    "type": "fixed32",
+                                                    "id": 1
+                                                },
+                                                "errMsg": {
+                                                    "type": "string",
+                                                    "id": 2
+                                                }
+                                            }
+                                        },
+                                        "OperationSuccess": {
+                                            "fields": {
+                                                "affectedPaths": {
+                                                    "rule": "repeated",
+                                                    "type": "string",
+                                                    "id": 1
+                                                },
+                                                "unaffectedPathErrs": {
+                                                    "rule": "repeated",
+                                                    "type": "UnaffectedPathError",
+                                                    "id": 2
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "UnaffectedPathError": {
+                            "fields": {
+                                "unaffectedPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "errCode": {
+                                    "type": "fixed32",
+                                    "id": 2
+                                },
+                                "errMsg": {
+                                    "type": "string",
+                                    "id": 3
+                                }
+                            }
+                        }
+                    }
+                },
+                "Set": {
+                    "fields": {
+                        "allowPartial": {
+                            "type": "bool",
+                            "id": 1
+                        },
+                        "updateObjs": {
+                            "rule": "repeated",
+                            "type": "UpdateObject",
+                            "id": 2
+                        }
+                    },
+                    "nested": {
+                        "UpdateObject": {
+                            "fields": {
+                                "objPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "paramSettings": {
+                                    "rule": "repeated",
+                                    "type": "UpdateParamSetting",
+                                    "id": 2
+                                }
+                            }
+                        },
+                        "UpdateParamSetting": {
+                            "fields": {
+                                "param": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "value": {
+                                    "type": "string",
+                                    "id": 2
+                                },
+                                "required": {
+                                    "type": "bool",
+                                    "id": 3
+                                }
+                            }
+                        }
+                    }
+                },
+                "SetResp": {
+                    "fields": {
+                        "updatedObjResults": {
+                            "rule": "repeated",
+                            "type": "UpdatedObjectResult",
+                            "id": 1
+                        }
+                    },
+                    "nested": {
+                        "UpdatedObjectResult": {
+                            "fields": {
+                                "requestedPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "operStatus": {
+                                    "type": "OperationStatus",
+                                    "id": 2
+                                }
+                            },
+                            "nested": {
+                                "OperationStatus": {
+                                    "oneofs": {
+                                        "operStatus": {
+                                            "oneof": [
+                                                "operFailure",
+                                                "operSuccess"
+                                            ]
+                                        }
+                                    },
+                                    "fields": {
+                                        "operFailure": {
+                                            "type": "OperationFailure",
+                                            "id": 1
+                                        },
+                                        "operSuccess": {
+                                            "type": "OperationSuccess",
+                                            "id": 2
+                                        }
+                                    },
+                                    "nested": {
+                                        "OperationFailure": {
+                                            "fields": {
+                                                "errCode": {
+                                                    "type": "fixed32",
+                                                    "id": 1
+                                                },
+                                                "errMsg": {
+                                                    "type": "string",
+                                                    "id": 2
+                                                },
+                                                "updatedInstFailures": {
+                                                    "rule": "repeated",
+                                                    "type": "UpdatedInstanceFailure",
+                                                    "id": 3
+                                                }
+                                            }
+                                        },
+                                        "OperationSuccess": {
+                                            "fields": {
+                                                "updatedInstResults": {
+                                                    "rule": "repeated",
+                                                    "type": "UpdatedInstanceResult",
+                                                    "id": 1
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "UpdatedInstanceFailure": {
+                            "fields": {
+                                "affectedPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "paramErrs": {
+                                    "rule": "repeated",
+                                    "type": "ParameterError",
+                                    "id": 2
+                                }
+                            }
+                        },
+                        "UpdatedInstanceResult": {
+                            "fields": {
+                                "affectedPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "paramErrs": {
+                                    "rule": "repeated",
+                                    "type": "ParameterError",
+                                    "id": 2
+                                },
+                                "updatedParams": {
+                                    "keyType": "string",
+                                    "type": "string",
+                                    "id": 3
+                                }
+                            }
+                        },
+                        "ParameterError": {
+                            "fields": {
+                                "param": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "errCode": {
+                                    "type": "fixed32",
+                                    "id": 2
+                                },
+                                "errMsg": {
+                                    "type": "string",
+                                    "id": 3
+                                }
+                            }
+                        }
+                    }
+                },
+                "Operate": {
+                    "fields": {
+                        "command": {
+                            "type": "string",
+                            "id": 1
+                        },
+                        "commandKey": {
+                            "type": "string",
+                            "id": 2
+                        },
+                        "sendResp": {
+                            "type": "bool",
+                            "id": 3
+                        },
+                        "inputArgs": {
+                            "keyType": "string",
+                            "type": "string",
+                            "id": 4
+                        }
+                    }
+                },
+                "OperateResp": {
+                    "fields": {
+                        "operationResults": {
+                            "rule": "repeated",
+                            "type": "OperationResult",
+                            "id": 1
+                        }
+                    },
+                    "nested": {
+                        "OperationResult": {
+                            "oneofs": {
+                                "operationResp": {
+                                    "oneof": [
+                                        "reqObjPath",
+                                        "reqOutputArgs",
+                                        "cmdFailure"
+                                    ]
+                                }
+                            },
+                            "fields": {
+                                "executedCommand": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "reqObjPath": {
+                                    "type": "string",
+                                    "id": 2
+                                },
+                                "reqOutputArgs": {
+                                    "type": "OutputArgs",
+                                    "id": 3
+                                },
+                                "cmdFailure": {
+                                    "type": "CommandFailure",
+                                    "id": 4
+                                }
+                            },
+                            "nested": {
+                                "OutputArgs": {
+                                    "fields": {
+                                        "outputArgs": {
+                                            "keyType": "string",
+                                            "type": "string",
+                                            "id": 1
+                                        }
+                                    }
+                                },
+                                "CommandFailure": {
+                                    "fields": {
+                                        "errCode": {
+                                            "type": "fixed32",
+                                            "id": 1
+                                        },
+                                        "errMsg": {
+                                            "type": "string",
+                                            "id": 2
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                },
+                "Notify": {
+                    "oneofs": {
+                        "notification": {
+                            "oneof": [
+                                "event",
+                                "valueChange",
+                                "objCreation",
+                                "objDeletion",
+                                "operComplete",
+                                "onBoardReq"
+                            ]
+                        }
+                    },
+                    "fields": {
+                        "subscriptionId": {
+                            "type": "string",
+                            "id": 1
+                        },
+                        "sendResp": {
+                            "type": "bool",
+                            "id": 2
+                        },
+                        "event": {
+                            "type": "Event",
+                            "id": 3
+                        },
+                        "valueChange": {
+                            "type": "ValueChange",
+                            "id": 4
+                        },
+                        "objCreation": {
+                            "type": "ObjectCreation",
+                            "id": 5
+                        },
+                        "objDeletion": {
+                            "type": "ObjectDeletion",
+                            "id": 6
+                        },
+                        "operComplete": {
+                            "type": "OperationComplete",
+                            "id": 7
+                        },
+                        "onBoardReq": {
+                            "type": "OnBoardRequest",
+                            "id": 8
+                        }
+                    },
+                    "nested": {
+                        "Event": {
+                            "fields": {
+                                "objPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "eventName": {
+                                    "type": "string",
+                                    "id": 2
+                                },
+                                "params": {
+                                    "keyType": "string",
+                                    "type": "string",
+                                    "id": 3
+                                }
+                            }
+                        },
+                        "ValueChange": {
+                            "fields": {
+                                "paramPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "paramValue": {
+                                    "type": "string",
+                                    "id": 2
+                                }
+                            }
+                        },
+                        "ObjectCreation": {
+                            "fields": {
+                                "objPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "uniqueKeys": {
+                                    "keyType": "string",
+                                    "type": "string",
+                                    "id": 2
+                                }
+                            }
+                        },
+                        "ObjectDeletion": {
+                            "fields": {
+                                "objPath": {
+                                    "type": "string",
+                                    "id": 1
+                                }
+                            }
+                        },
+                        "OperationComplete": {
+                            "oneofs": {
+                                "operationResp": {
+                                    "oneof": [
+                                        "reqOutputArgs",
+                                        "cmdFailure"
+                                    ]
+                                }
+                            },
+                            "fields": {
+                                "objPath": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "commandName": {
+                                    "type": "string",
+                                    "id": 2
+                                },
+                                "commandKey": {
+                                    "type": "string",
+                                    "id": 3
+                                },
+                                "reqOutputArgs": {
+                                    "type": "OutputArgs",
+                                    "id": 4
+                                },
+                                "cmdFailure": {
+                                    "type": "CommandFailure",
+                                    "id": 5
+                                }
+                            },
+                            "nested": {
+                                "OutputArgs": {
+                                    "fields": {
+                                        "outputArgs": {
+                                            "keyType": "string",
+                                            "type": "string",
+                                            "id": 1
+                                        }
+                                    }
+                                },
+                                "CommandFailure": {
+                                    "fields": {
+                                        "errCode": {
+                                            "type": "fixed32",
+                                            "id": 1
+                                        },
+                                        "errMsg": {
+                                            "type": "string",
+                                            "id": 2
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "OnBoardRequest": {
+                            "fields": {
+                                "oui": {
+                                    "type": "string",
+                                    "id": 1
+                                },
+                                "productClass": {
+                                    "type": "string",
+                                    "id": 2
+                                },
+                                "serialNumber": {
+                                    "type": "string",
+                                    "id": 3
+                                },
+                                "agentSupportedProtocolVersions": {
+                                    "type": "string",
+                                    "id": 4
+                                }
+                            }
+                        }
+                    }
+                },
+                "NotifyResp": {
+                    "fields": {
+                        "subscriptionId": {
+                            "type": "string",
+                            "id": 1
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/web/specs/usp-record-1-1.json b/web/specs/usp-record-1-1.json
new file mode 100644
index 0000000000000000000000000000000000000000..c4a15e8deb763e4102476f1b96664ca80a2faea6
--- /dev/null
+++ b/web/specs/usp-record-1-1.json
@@ -0,0 +1,111 @@
+{
+    "nested": {
+        "usp_record": {
+            "nested": {
+                "Record": {
+                    "oneofs": {
+                        "recordType": {
+                            "oneof": [
+                                "noSessionContext",
+                                "sessionContext"
+                            ]
+                        }
+                    },
+                    "fields": {
+                        "version": {
+                            "type": "string",
+                            "id": 1
+                        },
+                        "toId": {
+                            "type": "string",
+                            "id": 2
+                        },
+                        "fromId": {
+                            "type": "string",
+                            "id": 3
+                        },
+                        "payloadSecurity": {
+                            "type": "PayloadSecurity",
+                            "id": 4
+                        },
+                        "macSignature": {
+                            "type": "bytes",
+                            "id": 5
+                        },
+                        "senderCert": {
+                            "type": "bytes",
+                            "id": 6
+                        },
+                        "noSessionContext": {
+                            "type": "NoSessionContextRecord",
+                            "id": 7
+                        },
+                        "sessionContext": {
+                            "type": "SessionContextRecord",
+                            "id": 8
+                        }
+                    },
+                    "nested": {
+                        "PayloadSecurity": {
+                            "values": {
+                                "PLAINTEXT": 0,
+                                "TLS12": 1
+                            }
+                        }
+                    }
+                },
+                "NoSessionContextRecord": {
+                    "fields": {
+                        "payload": {
+                            "type": "bytes",
+                            "id": 2
+                        }
+                    }
+                },
+                "SessionContextRecord": {
+                    "fields": {
+                        "sessionId": {
+                            "type": "uint64",
+                            "id": 1
+                        },
+                        "sequenceId": {
+                            "type": "uint64",
+                            "id": 2
+                        },
+                        "expectedId": {
+                            "type": "uint64",
+                            "id": 3
+                        },
+                        "retransmitId": {
+                            "type": "uint64",
+                            "id": 4
+                        },
+                        "payloadSarState": {
+                            "type": "PayloadSARState",
+                            "id": 5
+                        },
+                        "payloadrecSarState": {
+                            "type": "PayloadSARState",
+                            "id": 6
+                        },
+                        "payload": {
+                            "rule": "repeated",
+                            "type": "bytes",
+                            "id": 7
+                        }
+                    },
+                    "nested": {
+                        "PayloadSARState": {
+                            "values": {
+                                "NONE": 0,
+                                "BEGIN": 1,
+                                "INPROCESS": 2,
+                                "COMPLETE": 3
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/web/testy.d.ts b/web/testy.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cb0ff5c3b541f646105198ee23ac0fc3d805023e
--- /dev/null
+++ b/web/testy.d.ts
@@ -0,0 +1 @@
+export {};
diff --git a/web/testy.js b/web/testy.js
new file mode 100644
index 0000000000000000000000000000000000000000..7566c29d74cdbfa5d8a598531f91752b2ef10154
--- /dev/null
+++ b/web/testy.js
@@ -0,0 +1,143 @@
+"use strict";
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+    return new (P || (P = Promise))(function (resolve, reject) {
+        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+        step((generator = generator.apply(thisArg, _arguments || [])).next());
+    });
+};
+var __generator = (this && this.__generator) || function (thisArg, body) {
+    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
+    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
+    function verb(n) { return function (v) { return step([n, v]); }; }
+    function step(op) {
+        if (f) throw new TypeError("Generator is already executing.");
+        while (_) try {
+            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
+            if (y = 0, t) op = [op[0] & 2, t.value];
+            switch (op[0]) {
+                case 0: case 1: t = op; break;
+                case 4: _.label++; return { value: op[1], done: false };
+                case 5: _.label++; y = op[1]; op = [0]; continue;
+                case 7: op = _.ops.pop(); _.trys.pop(); continue;
+                default:
+                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
+                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
+                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
+                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
+                    if (t[2]) _.ops.pop();
+                    _.trys.pop(); continue;
+            }
+            op = body.call(thisArg, _);
+        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
+        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
+    }
+};
+var __importDefault = (this && this.__importDefault) || function (mod) {
+    return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var _1 = __importDefault(require("."));
+var run = function () { return __awaiter(void 0, void 0, void 0, function () {
+    var usp, err_1;
+    return __generator(this, function (_a) {
+        switch (_a.label) {
+            case 0: return [4 /*yield*/, _1.default({
+                    host: '192.168.1.1',
+                    port: 9001,
+                    protocol: 'ws',
+                    username: 'admin',
+                    password: 'admin',
+                    publishEndpoint: '/usp/endpoint',
+                    subscribeEndpoint: '/usp/controller'
+                }, {
+                    onError: console.log,
+                })];
+            case 1:
+                usp = _a.sent();
+                // console.log("CONNECTED!")
+                // await usp.supportedDM("Device.WiFi.").then(r => console.log(JSON.stringify(r, null, 2))).catch(console.log)
+                // await usp.instances("Device.WiFi.").then(r => console.log(JSON.stringify(r, null, 2))).catch(console.log)
+                // await usp.instances("Device.WiFi.",  {firstLevelOnly: true} ).then(r => console.log(JSON.stringify(r, null, 2))).catch(console.log)
+                // await usp.supportedProto("").then(console.log).catch(console.log)
+                // await usp.get("Device.Hosts.Host.").then(usp.resolve).then(j => console.log(JSON.stringify(j, null, 2))).catch(console.error)
+                // await usp.get(["Device.WiFi.Radio.1.", "Device.WiFi.Radio.2."]).then(() => console.log('get successful')).catch(console.error)
+                // await usp.get("CAT.WiFi.Radio.1.").then(() => console.log('get successful')).catch(console.error)
+                // await usp.get(["Device.WiFi.Radio.1.", "CAT.WiFi.Radio.2."]).then(() => console.log('get successful')).catch(console.error)
+                return [4 /*yield*/, usp.set("Device.Users.User.3.Language", "").then(console.log).catch(console.error)];
+            case 2:
+                // console.log("CONNECTED!")
+                // await usp.supportedDM("Device.WiFi.").then(r => console.log(JSON.stringify(r, null, 2))).catch(console.log)
+                // await usp.instances("Device.WiFi.").then(r => console.log(JSON.stringify(r, null, 2))).catch(console.log)
+                // await usp.instances("Device.WiFi.",  {firstLevelOnly: true} ).then(r => console.log(JSON.stringify(r, null, 2))).catch(console.log)
+                // await usp.supportedProto("").then(console.log).catch(console.log)
+                // await usp.get("Device.Hosts.Host.").then(usp.resolve).then(j => console.log(JSON.stringify(j, null, 2))).catch(console.error)
+                // await usp.get(["Device.WiFi.Radio.1.", "Device.WiFi.Radio.2."]).then(() => console.log('get successful')).catch(console.error)
+                // await usp.get("CAT.WiFi.Radio.1.").then(() => console.log('get successful')).catch(console.error)
+                // await usp.get(["Device.WiFi.Radio.1.", "CAT.WiFi.Radio.2."]).then(() => console.log('get successful')).catch(console.error)
+                _a.sent();
+                _a.label = 3;
+            case 3:
+                _a.trys.push([3, 5, , 6]);
+                return [4 /*yield*/, usp._operate('Device.IP.Diagnostics.TraceRoute()', 'NOTIFY@Device.IP.Diagnostics.TraceRoute()KMDBWMFZCAGUV', { Host: 'google.com' }).then(console.log)
+                    // const [op, clear] = await usp.operate("Device.IP.Diagnostics.TraceRoute()")
+                    // await op({ Host: 'google.com' }).then(console.log).catch(console.log)
+                    // await clear()
+                ];
+            case 4:
+                _a.sent();
+                return [3 /*break*/, 6];
+            case 5:
+                err_1 = _a.sent();
+                console.log(err_1);
+                return [3 /*break*/, 6];
+            case 6: 
+            // const clear = usp.on(/.*/, (_, r) => {
+            //   console.log("==========================================================");
+            //   console.log(JSON.stringify(r, null, 2));
+            //   console.log("==========================================================");
+            // });
+            // await usp
+            //   .supportedDM("Device.IP.", {
+            //     firstLevelOnly: false,
+            //     returnCommands: true,
+            //     returnEvents: true,
+            //     returnParams: true,
+            //   })
+            //   .then((r) => console.log(JSON.stringify(r, null, 2)));
+            // await usp.subscribe({ notif: 'ObjectCreation', reference: 'Device.Hosts.Host.' }, console.log)
+            // await usp.subscribe({ notif: 'ValueChange', reference: 'Device.Hosts.Host.*.Active' }, console.log)
+            // await usp.add('Device.NAT.PortMapping.')
+            // clearSub()
+            // await usp.add('Device.NAT.PortMapping.')
+            // await usp.del('Device.NAT.PortMapping.45.').then(console.log)
+            return [4 /*yield*/, usp.disconnect()];
+            case 7:
+                // const clear = usp.on(/.*/, (_, r) => {
+                //   console.log("==========================================================");
+                //   console.log(JSON.stringify(r, null, 2));
+                //   console.log("==========================================================");
+                // });
+                // await usp
+                //   .supportedDM("Device.IP.", {
+                //     firstLevelOnly: false,
+                //     returnCommands: true,
+                //     returnEvents: true,
+                //     returnParams: true,
+                //   })
+                //   .then((r) => console.log(JSON.stringify(r, null, 2)));
+                // await usp.subscribe({ notif: 'ObjectCreation', reference: 'Device.Hosts.Host.' }, console.log)
+                // await usp.subscribe({ notif: 'ValueChange', reference: 'Device.Hosts.Host.*.Active' }, console.log)
+                // await usp.add('Device.NAT.PortMapping.')
+                // clearSub()
+                // await usp.add('Device.NAT.PortMapping.')
+                // await usp.del('Device.NAT.PortMapping.45.').then(console.log)
+                _a.sent();
+                return [2 /*return*/];
+        }
+    });
+}); };
+run();
+//# sourceMappingURL=testy.js.map
\ No newline at end of file
diff --git a/web/testy.js.map b/web/testy.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..87eb36fb28606a5843feab517960a3c1997311e5
--- /dev/null
+++ b/web/testy.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"testy.js","sourceRoot":"","sources":["../src/testy.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uCAAwB;AAExB,IAAM,GAAG,GAAG;;;;oBAUE,qBAAM,UAAO,CACvB;oBACE,IAAI,EAAE,aAAa;oBACnB,IAAI,EAAE,IAAI;oBACV,QAAQ,EAAE,IAAI;oBACd,QAAQ,EAAE,OAAO;oBACjB,QAAQ,EAAE,OAAO;oBACjB,eAAe,EAAE,eAAe;oBAChC,iBAAiB,EAAE,iBAAiB;iBACrC,EACD;oBACE,OAAO,EAAE,OAAO,CAAC,GAAG;iBACrB,CACF,EAAA;;gBAbK,GAAG,GAAG,SAaX;gBACD,4BAA4B;gBAC5B,8GAA8G;gBAC9G,4GAA4G;gBAC5G,sIAAsI;gBACtI,oEAAoE;gBAEpE,gIAAgI;gBAChI,iIAAiI;gBACjI,oGAAoG;gBACpG,8HAA8H;gBAE9H,qBAAM,GAAG,CAAC,GAAG,CAAC,8BAA8B,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAA;;gBAXxF,4BAA4B;gBAC5B,8GAA8G;gBAC9G,4GAA4G;gBAC5G,sIAAsI;gBACtI,oEAAoE;gBAEpE,gIAAgI;gBAChI,iIAAiI;gBACjI,oGAAoG;gBACpG,8HAA8H;gBAE9H,SAAwF,CAAC;;;;gBAsCvF,qBAAM,GAAG,CAAC,QAAQ,CAAC,oCAAoC,EAAE,wDAAwD,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC;oBAC5J,8EAA8E;oBAC9E,wEAAwE;oBACxE,gBAAgB;kBAH4I;;gBAA5J,SAA4J,CAAA;;;;gBAK5J,OAAO,CAAC,GAAG,CAAC,KAAG,CAAC,CAAA;;;YAGlB,yCAAyC;YACzC,+EAA+E;YAC/E,6CAA6C;YAC7C,+EAA+E;YAC/E,MAAM;YACN,YAAY;YACZ,iCAAiC;YACjC,6BAA6B;YAC7B,4BAA4B;YAC5B,0BAA0B;YAC1B,0BAA0B;YAC1B,OAAO;YACP,2DAA2D;YAE3D,iGAAiG;YACjG,sGAAsG;YACtG,2CAA2C;YAC3C,aAAa;YACb,2CAA2C;YAC3C,gEAAgE;YAEhE,qBAAM,GAAG,CAAC,UAAU,EAAE,EAAA;;gBArBtB,yCAAyC;gBACzC,+EAA+E;gBAC/E,6CAA6C;gBAC7C,+EAA+E;gBAC/E,MAAM;gBACN,YAAY;gBACZ,iCAAiC;gBACjC,6BAA6B;gBAC7B,4BAA4B;gBAC5B,0BAA0B;gBAC1B,0BAA0B;gBAC1B,OAAO;gBACP,2DAA2D;gBAE3D,iGAAiG;gBACjG,sGAAsG;gBACtG,2CAA2C;gBAC3C,aAAa;gBACb,2CAA2C;gBAC3C,gEAAgE;gBAEhE,SAAsB,CAAC;;;;KACxB,CAAC;AAEF,GAAG,EAAE,CAAC"}
\ No newline at end of file
diff --git a/web/types.d.ts b/web/types.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a930008e69693ee9f39ddf50f70320a3063ebb5c
--- /dev/null
+++ b/web/types.d.ts
@@ -0,0 +1,321 @@
+/// <reference types="node" />
+export declare type CommandType = "GET" | "SET" | "ADD" | "DELETE" | "OPERATE" | "NOTIFY" | "GET_SUPPORTED_DM" | "GET_INSTANCES" | "GET_SUPPORTED_PROTO";
+export declare type GetReturn = string | Record<string, any> | Record<string, any>[];
+export declare type GetCommand = (paths: string | string[]) => Promise<GetReturn>;
+export declare type SetCommand = (path: string, value: JSValue | JSValue[] | InputRecord) => Promise<void>;
+export declare type AddCommand = (path: string, value?: InputRecord) => Promise<string | string[]>;
+export declare type DelCommand = (path: string, allowPartial?: boolean) => Promise<void>;
+export declare type OperateFn = (input?: Record<string, any>) => Promise<any>;
+export declare type OperateClearFn = () => Promise<void>;
+export declare type OperateRecipe = (path: string, opts?: OperateOptions) => Promise<[OperateFn, OperateClearFn]>;
+export declare type OperateCommand = (path: string, id: string, input?: Record<string, any>) => Promise<any>;
+export declare type SupportedDMCommand = (paths: string | string[], opts?: SuportedCommandOpts) => Promise<Record<string, any>>;
+export declare type InstancesCommand = (paths: string | string[], opts?: {
+    firstLevelOnly?: boolean;
+}) => Promise<Record<string, any>>;
+export declare type SupportedProtoCommand = (versions: string) => Promise<string>;
+export declare type SubscribeRecipe = (opts: SubscriptionOptions, callback: SubscriptionCallback) => Promise<PromiseClearFn>;
+export declare type PromiseClearFn = () => Promise<void>;
+export declare type Command = GetCommand | SetCommand | AddCommand | DelCommand | OperateCommand | OperateRecipe | SubscribeRecipe | SupportedDMCommand | InstancesCommand | SupportedProtoCommand;
+export declare type PbRequestCommand = PbRequestCommandGet | PbRequestCommandSet | PbRequestCommandAdd | PbRequestCommandDel | PbRequestCommandOperate | PbRequestCommandSupport | PbRequestCommandInstance | PbRequestCommandSupportProto;
+export interface SuportedCommandOpts {
+    firstLevelOnly?: boolean;
+    returnCommands?: boolean;
+    returnEvents?: boolean;
+    returnParams?: boolean;
+}
+export declare type InputRecord = {
+    [k: string]: {
+        required: boolean;
+        value: any;
+    } | string | number | boolean;
+} & {
+    allowPartial?: boolean;
+};
+export declare type PbRequestCommandSupportProto = {
+    getSupportedProtocol: {
+        controllerSupportedProtocolVersions: string;
+    };
+};
+export declare type PbRequestCommandInstance = {
+    getInstances: {
+        objPaths: string[];
+        firstLevelOnly: boolean;
+    };
+};
+export declare type PbRequestCommandSupport = {
+    getSupportedDm: {
+        objPaths: string[];
+        firstLevelOnly: boolean;
+        returnCommands: boolean;
+        returnEvents: boolean;
+        returnParams: boolean;
+    };
+};
+export declare type PbRequestCommandOperate = {
+    operate: {
+        command: string;
+        commandKey: string;
+        sendResp: boolean;
+        inputArgs: Record<string, any>;
+    };
+};
+export declare type PbRequestCommandDel = {
+    delete: {
+        allowPartial: boolean;
+        objPaths: string[];
+    };
+};
+export declare type PbRequestCommandGet = {
+    get: {
+        paramPaths: string[];
+    };
+};
+export declare type PbRequestCommandSet = {
+    set: {
+        allowPartial: boolean;
+        updateObjs: {
+            objPath: string;
+            lookup: "Set.UpdateObject";
+            paramSettings: {
+                lookup: "Set.UpdateParamSetting";
+                param: string;
+                value: any;
+                required: boolean;
+            }[];
+        }[];
+    };
+};
+export declare type PbRequestCommandAdd = {
+    add: {
+        allowPartial: boolean;
+        createObjs: {
+            lookup: "Add.CreateObject";
+            objPath: string;
+            paramSettings: {
+                param: string;
+                value: any;
+                required: boolean;
+                lookup: "Add.CreateParamSetting";
+            }[];
+        }[];
+    };
+};
+export declare type Recipe = ResolveRecipe;
+export declare type ResolveRecipe = (msg: GetReturn, level?: number) => Promise<GetReturn>;
+/** Device API */
+export interface USP {
+    /**
+     * Get value at path
+     * @param path Location of value (e.g. "Device.DeviceInfo.")
+     * ```
+     * await usp.get("Device.WiFi.Radio.1.")
+     * // or
+     * await usp.get(["Device.WiFi.Radio.1.", "Device.WiFi.Radio.2."])
+     * ```
+     */
+    get: GetCommand;
+    /**
+     * Set value at path
+     * @param path Location of value (e.g. "Device.DeviceInfo.")
+     * @param value Value to assign
+     * ```
+     * await usp.set("Device.WiFi.Radio.1.", { Name: "radio-1" })
+     * // or
+     * await usp.set("Device.WiFi.Radio.1.Name", "radio-1")
+     * ```
+     */
+    set: SetCommand;
+    /**
+     * Create a command
+     * @param path Full path of command (e.g. "Device.IP.Diagnostics.IPPing()")
+     * @param opts Subscription options (not required)
+     * @returns Function that executes command
+     * ```
+     * const [ping, cleanPing] = await usp.operate("Device.IP.Diagnostics.IPPing()")
+     * const results = await ping({ Host: "iopsys.eu" })
+     * await cleanPing()
+     * ```
+     */
+    operate: OperateRecipe;
+    /**
+     * Directly call operate without creating a subscription (avoid using unless certain subsctiption exists)
+     * @param path Full path of command (e.g. "Device.IP.Diagnostics.IPPing()")
+     * @param id Full id of subscription (can be found in Device.LocalAgent.Subscription.)
+     * @param input Optional arguments for command
+     * @returns Command results
+     * ```
+     * await usp._operate("Device.IP.Diagnostics.IPPing()", 'command-id', { Host: "iopsys.eu" })
+     * ```
+     */
+    _operate: OperateCommand;
+    /**
+     * Add object to path
+     * @param path Path to add to (e.g. "Device.NAT.PortMapping.")
+     * @param values Optional object to add (if skipped will use default values)
+     * @returns Full path of new object
+     * ```
+     * await usp.add("Device.NAT.PortMapping.")
+     * await usp.add("Device.NAT.PortMapping.", { Description: "cpe-1", allowPartial: true })
+     * ```
+     */
+    add: AddCommand;
+    /**
+     * Delete object at path
+     * @param path Full path to delete (e.g. "Device.NAT.PortMapping.1.")
+     * @param allowPartial [Optional] Allow partial (defaults to false)
+     * ```
+     * await usp.del("Device.NAT.PortMapping.1.")
+     * await usp.del("Device.NAT.PortMapping.1.", true)
+     * ```
+     */
+    del: DelCommand;
+    /**
+     * Resolve references in message
+     * @param msg Message with reference in it
+     * @param level Optional level of nesting to resolve to (avoid using high numbers)
+     * ```
+     * await usp.get("Device.WiFi.Radio.1.").then(device.resolve)
+     * ```
+     */
+    resolve: ResolveRecipe;
+    /**
+     * Get Supported DM
+     * @param paths Path(s)
+     * @param opts [Optional] Response options
+     * ```
+     * await usp.supportedDM("Device.WiFi.")
+     * ```
+     */
+    supportedDM: SupportedDMCommand;
+    /**
+     * Get Supported Protocol
+     * @param versions Controller supported protocol versions
+     * ```
+     * await usp.supportedProto("1.0")
+     * ```
+     */
+    supportedProto: SupportedProtoCommand;
+    /**
+     * Get instances
+     * @param paths Path(s)
+     * @param firstLevelOnly [Optional] Return only first level
+     * ```
+     * await usp.instances("Device.WiFi.")
+     * ```
+     */
+    instances: SupportedDMCommand;
+    /**
+     * Subscribe to event
+     * @param options Subscription options
+     * @param callback Callback on relevant message
+     * @returns Returns function to clear subscription
+     * ```
+     * const clearSub = await usp.subscribe({ id: '1234', notif: 'ObjectCreation', reference: 'Device.NAT.PortMapping.' }, console.log)
+     * ```
+     */
+    subscribe: SubscribeRecipe;
+    /**
+     * Add handler for messages
+     * @param ident Message identifier (identifies by id, can be a string or regexp)
+     * @param callback Callback on relevant message
+     * @returns Returns function to clear handler
+     * ```
+     * const clear = usp.on("error", () => console.log('An error!'))
+     * ```
+     */
+    on: OnFn;
+    /**
+     * Disconnect from device
+     * ```
+     * await usp.disconnect()
+     * ```
+     */
+    disconnect: () => Promise<void>;
+}
+export declare type Connect = (options: ConnectionOptions, events?: ConnectionEvents) => Promise<USP>;
+declare type NotifType = "Event" | "ValueChange" | "ObjectCreation" | "ObjectDeletion" | "OperationComplete" | "OnBoardRequest";
+export interface SubscriptionOptions {
+    id?: string;
+    notif: NotifType;
+    reference: string | string[];
+}
+export declare type SubscriptionCallback = (msg: Response, fullMsg?: Record<string, any>) => void;
+export interface OperateOptions {
+    ID?: string;
+    Persistent?: boolean;
+}
+export interface PbRequestHeader {
+    msgId: string;
+    msgType: CommandType;
+    lookup: "Header";
+}
+export interface PbRequestBody {
+    lookup: "Body";
+    request: {
+        lookup: "Request";
+    } & PbRequestCommand;
+}
+export interface PbRequestMessage {
+    header: PbRequestHeader;
+    body: PbRequestBody;
+    lookup: "Msg";
+}
+export declare type URLConnectionOptions = {
+    url: string;
+} & OtherConnectionOptions;
+export declare type HostConnectionOptions = {
+    host: string;
+    port: number;
+    protocol: "wss" | "ws" | "mqtt" | "mqtts" | "tcp" | "ssl" | "wx" | "wxs";
+} & OtherConnectionOptions;
+export declare type CertType = string | string[] | Buffer | Buffer[];
+export interface OtherConnectionOptions {
+    username: string;
+    password: string;
+    fromId?: string;
+    toId?: string;
+    idEndpoint?: string;
+    publishEndpoint?: string;
+    subscribeEndpoint?: string;
+    ca?: CertType | Object[];
+    key?: CertType;
+    cert?: CertType;
+}
+export declare type ConnectionOptions = URLConnectionOptions | HostConnectionOptions;
+export declare type Response = string | Record<string, any>;
+export declare type DecodeFn = (msg: Record<string, any>) => DecodeResponse | [any];
+export declare type DecodeResponse = [any, ResponseID | null, null | Response] | [any];
+export declare type EncodeArgs = {
+    rootMsg: protobuf.Root;
+    rootRecord: protobuf.Root;
+    header: any;
+    options: Record<string, string>;
+    args: Record<string, any>;
+};
+export declare type OnIdent = string | RegExp;
+export declare type EncodeFn = (args: Record<string, any>) => PbRequestMessage;
+export declare type CallArgs = Record<string, any>;
+export declare type ClearFn = () => void;
+export declare type OnFn = (ident: OnIdent, callback: SubscriptionCallback) => ClearFn;
+export declare type MakeFn = (call: CallFn, on: OnFn) => Command;
+export declare type MakeRecipeFn = (call: CallFn) => Recipe;
+export declare type CommandTrigger = {
+    decode: string | ((msg: Record<string, string>) => boolean);
+    encode: string;
+};
+export declare type CommandObject = {
+    encode: EncodeFn;
+    decode: DecodeFn;
+};
+export interface ConnectionEvents {
+    onError?: (err: string) => void;
+}
+export interface RecipeObject {
+    name: string;
+    make: MakeFn;
+}
+export declare type ResponseID = "ignore" | "error" | string;
+export declare type JSValue = string | number | boolean;
+export declare type CallFn = (cmd: CommandType, args: Record<string, any>) => any;
+export {};
diff --git a/web/types.js b/web/types.js
new file mode 100644
index 0000000000000000000000000000000000000000..11e638d1ee44ae0dcb1feb5aef676ea74a7d4df3
--- /dev/null
+++ b/web/types.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=types.js.map
\ No newline at end of file
diff --git a/web/types.js.map b/web/types.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..c768b79002615c0e69cc6efdcad6a509c1abaaec
--- /dev/null
+++ b/web/types.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
\ No newline at end of file
diff --git a/web/util.d.ts b/web/util.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f85f7aeae3e7d2636fd42d1597843ab321d06fd1
--- /dev/null
+++ b/web/util.d.ts
@@ -0,0 +1,13 @@
+import { OnIdent, SubscriptionCallback } from "./types";
+/**
+ * Makes a router for storing resolve/reject for a message
+ */
+export declare const makeRouter: () => {
+    get: (id: string) => any;
+    add: (id: string, data: any) => void;
+};
+export declare const makeCallbackRouter: () => {
+    get: (id: string) => SubscriptionCallback[];
+    add: (ident: OnIdent, callback: SubscriptionCallback) => void;
+    del: (id: OnIdent) => boolean;
+};
diff --git a/web/util.js b/web/util.js
new file mode 100644
index 0000000000000000000000000000000000000000..fd32884dccc74a6b5cfefe373e0e3aa905c6706b
--- /dev/null
+++ b/web/util.js
@@ -0,0 +1,55 @@
+"use strict";
+var __assign = (this && this.__assign) || function () {
+    __assign = Object.assign || function(t) {
+        for (var s, i = 1, n = arguments.length; i < n; i++) {
+            s = arguments[i];
+            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
+                t[p] = s[p];
+        }
+        return t;
+    };
+    return __assign.apply(this, arguments);
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.makeCallbackRouter = exports.makeRouter = void 0;
+/**
+ * Makes a router for storing resolve/reject for a message
+ */
+var makeRouter = function () {
+    var routes = new Map();
+    return {
+        get: function (id) {
+            var res = __assign({}, routes.get(id));
+            routes.delete(id);
+            return res;
+        },
+        add: function (id, data) {
+            routes.set(id, __assign({}, data));
+        },
+    };
+};
+exports.makeRouter = makeRouter;
+var toId = function (id) { return id.toString(); };
+var isRegExp = function (v) { return typeof v === "object"; };
+var satisfies = function (id, matches) {
+    return isRegExp(id) ? matches.match(id) !== null : id === matches;
+};
+var makeCallbackRouter = function () {
+    var routes = new Map();
+    return {
+        get: function (id) {
+            return Array.from(routes.values())
+                .filter(function (_a) {
+                var ident = _a.ident;
+                return satisfies(ident, id);
+            })
+                .map(function (v) { return v.callback; });
+        },
+        add: function (ident, callback) {
+            routes.set(toId(ident), { callback: callback, ident: ident });
+        },
+        del: function (id) { return routes.delete(toId(id)); },
+    };
+};
+exports.makeCallbackRouter = makeCallbackRouter;
+//# sourceMappingURL=util.js.map
\ No newline at end of file
diff --git a/web/util.js.map b/web/util.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..a5f0a9264f1c5bb8ba95ce1e277a83084a8be12e
--- /dev/null
+++ b/web/util.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AAEA;;GAEG;AACI,IAAM,UAAU,GAAG;IACxB,IAAM,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;IACzB,OAAO;QACL,GAAG,EAAE,UAAC,EAAU;YACd,IAAM,GAAG,gBAAQ,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAE,CAAC;YAClC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAClB,OAAO,GAAG,CAAC;QACb,CAAC;QACD,GAAG,EAAE,UAAC,EAAU,EAAE,IAAS;YACzB,MAAM,CAAC,GAAG,CAAC,EAAE,eAAO,IAAI,EAAG,CAAC;QAC9B,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAZW,QAAA,UAAU,cAYrB;AAEF,IAAM,IAAI,GAAG,UAAC,EAAW,IAAa,OAAA,EAAE,CAAC,QAAQ,EAAE,EAAb,CAAa,CAAC;AACpD,IAAM,QAAQ,GAAG,UAAC,CAAU,IAAkB,OAAA,OAAO,CAAC,KAAK,QAAQ,EAArB,CAAqB,CAAC;AACpE,IAAM,SAAS,GAAG,UAAC,EAAW,EAAE,OAAe;IAC7C,OAAA,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO;AAA1D,CAA0D,CAAC;AACtD,IAAM,kBAAkB,GAAG;IAChC,IAAM,MAAM,GAAG,IAAI,GAAG,EAGnB,CAAC;IACJ,OAAO;QACL,GAAG,EAAE,UAAC,EAAU;YACd,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;iBAC/B,MAAM,CAAC,UAAC,EAAS;oBAAP,KAAK,WAAA;gBAAO,OAAA,SAAS,CAAC,KAAK,EAAE,EAAE,CAAC;YAApB,CAAoB,CAAC;iBAC3C,GAAG,CAAC,UAAC,CAAC,IAAK,OAAA,CAAC,CAAC,QAAQ,EAAV,CAAU,CAAC,CAAC;QAC5B,CAAC;QACD,GAAG,EAAE,UAAC,KAAc,EAAE,QAA8B;YAClD,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,QAAQ,UAAA,EAAE,KAAK,OAAA,EAAE,CAAC,CAAC;QAC/C,CAAC;QACD,GAAG,EAAE,UAAC,EAAW,IAAK,OAAA,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAvB,CAAuB;KAC9C,CAAC;AACJ,CAAC,CAAC;AAhBW,QAAA,kBAAkB,sBAgB7B"}
\ No newline at end of file
diff --git a/webpack.config.js b/webpack.config.js
index 0520d76045b666298e949d8494a2cfac2592346e..aaa1d24e61ca4d107ed7227c1eac81bb52af7144 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -6,7 +6,7 @@ module.exports = {
     "index": "./src/index.ts",
   },
   output: {
-    path: path.resolve(__dirname, "_bundles"),
+    path: path.resolve(__dirname, "web"),
     filename: "[name].js",
     libraryTarget: "umd",
     library: "UspJs",