@switchbot/openapi-cli 3.3.3 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -8
- package/dist/index.js +1185 -334
- package/dist/policy/schema/v0.2.json +59 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -989,8 +989,8 @@ var require_command = __commonJS({
|
|
|
989
989
|
init_cjs_shim();
|
|
990
990
|
var EventEmitter = __require("node:events").EventEmitter;
|
|
991
991
|
var childProcess = __require("node:child_process");
|
|
992
|
-
var
|
|
993
|
-
var
|
|
992
|
+
var path29 = __require("node:path");
|
|
993
|
+
var fs33 = __require("node:fs");
|
|
994
994
|
var process4 = __require("node:process");
|
|
995
995
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
996
996
|
var { CommanderError: CommanderError2 } = require_error();
|
|
@@ -1922,11 +1922,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1922
1922
|
let launchWithNode = false;
|
|
1923
1923
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
1924
1924
|
function findFile(baseDir, baseName) {
|
|
1925
|
-
const localBin =
|
|
1926
|
-
if (
|
|
1927
|
-
if (sourceExt.includes(
|
|
1925
|
+
const localBin = path29.resolve(baseDir, baseName);
|
|
1926
|
+
if (fs33.existsSync(localBin)) return localBin;
|
|
1927
|
+
if (sourceExt.includes(path29.extname(baseName))) return void 0;
|
|
1928
1928
|
const foundExt = sourceExt.find(
|
|
1929
|
-
(ext) =>
|
|
1929
|
+
(ext) => fs33.existsSync(`${localBin}${ext}`)
|
|
1930
1930
|
);
|
|
1931
1931
|
if (foundExt) return `${localBin}${foundExt}`;
|
|
1932
1932
|
return void 0;
|
|
@@ -1938,21 +1938,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1938
1938
|
if (this._scriptPath) {
|
|
1939
1939
|
let resolvedScriptPath;
|
|
1940
1940
|
try {
|
|
1941
|
-
resolvedScriptPath =
|
|
1941
|
+
resolvedScriptPath = fs33.realpathSync(this._scriptPath);
|
|
1942
1942
|
} catch (err) {
|
|
1943
1943
|
resolvedScriptPath = this._scriptPath;
|
|
1944
1944
|
}
|
|
1945
|
-
executableDir =
|
|
1946
|
-
|
|
1945
|
+
executableDir = path29.resolve(
|
|
1946
|
+
path29.dirname(resolvedScriptPath),
|
|
1947
1947
|
executableDir
|
|
1948
1948
|
);
|
|
1949
1949
|
}
|
|
1950
1950
|
if (executableDir) {
|
|
1951
1951
|
let localFile = findFile(executableDir, executableFile);
|
|
1952
1952
|
if (!localFile && !subcommand._executableFile && this._scriptPath) {
|
|
1953
|
-
const legacyName =
|
|
1953
|
+
const legacyName = path29.basename(
|
|
1954
1954
|
this._scriptPath,
|
|
1955
|
-
|
|
1955
|
+
path29.extname(this._scriptPath)
|
|
1956
1956
|
);
|
|
1957
1957
|
if (legacyName !== this._name) {
|
|
1958
1958
|
localFile = findFile(
|
|
@@ -1963,7 +1963,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1963
1963
|
}
|
|
1964
1964
|
executableFile = localFile || executableFile;
|
|
1965
1965
|
}
|
|
1966
|
-
launchWithNode = sourceExt.includes(
|
|
1966
|
+
launchWithNode = sourceExt.includes(path29.extname(executableFile));
|
|
1967
1967
|
let proc;
|
|
1968
1968
|
if (process4.platform !== "win32") {
|
|
1969
1969
|
if (launchWithNode) {
|
|
@@ -2803,7 +2803,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2803
2803
|
* @return {Command}
|
|
2804
2804
|
*/
|
|
2805
2805
|
nameFromFilename(filename) {
|
|
2806
|
-
this._name =
|
|
2806
|
+
this._name = path29.basename(filename, path29.extname(filename));
|
|
2807
2807
|
return this;
|
|
2808
2808
|
}
|
|
2809
2809
|
/**
|
|
@@ -2817,9 +2817,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2817
2817
|
* @param {string} [path]
|
|
2818
2818
|
* @return {(string|null|Command)}
|
|
2819
2819
|
*/
|
|
2820
|
-
executableDir(
|
|
2821
|
-
if (
|
|
2822
|
-
this._executableDir =
|
|
2820
|
+
executableDir(path30) {
|
|
2821
|
+
if (path30 === void 0) return this._executableDir;
|
|
2822
|
+
this._executableDir = path30;
|
|
2823
2823
|
return this;
|
|
2824
2824
|
}
|
|
2825
2825
|
/**
|
|
@@ -4340,7 +4340,7 @@ var require_supports_colors = __commonJS({
|
|
|
4340
4340
|
"node_modules/@colors/colors/lib/system/supports-colors.js"(exports, module) {
|
|
4341
4341
|
"use strict";
|
|
4342
4342
|
init_cjs_shim();
|
|
4343
|
-
var
|
|
4343
|
+
var os26 = __require("os");
|
|
4344
4344
|
var hasFlag2 = require_has_flag();
|
|
4345
4345
|
var env2 = process.env;
|
|
4346
4346
|
var forceColor = void 0;
|
|
@@ -4378,7 +4378,7 @@ var require_supports_colors = __commonJS({
|
|
|
4378
4378
|
}
|
|
4379
4379
|
var min = forceColor ? 1 : 0;
|
|
4380
4380
|
if (process.platform === "win32") {
|
|
4381
|
-
var osRelease =
|
|
4381
|
+
var osRelease = os26.release().split(".");
|
|
4382
4382
|
if (Number(process.versions.node.split(".")[0]) >= 8 && Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
4383
4383
|
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
4384
4384
|
}
|
|
@@ -8676,6 +8676,18 @@ function writeAudit(entry) {
|
|
|
8676
8676
|
} catch {
|
|
8677
8677
|
}
|
|
8678
8678
|
}
|
|
8679
|
+
function writeEvaluateTrace(record2, filePath) {
|
|
8680
|
+
const file2 = filePath ?? resolveAuditPath();
|
|
8681
|
+
if (!file2) return;
|
|
8682
|
+
const dir = path8.dirname(file2);
|
|
8683
|
+
try {
|
|
8684
|
+
if (!fs8.existsSync(dir)) {
|
|
8685
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
8686
|
+
}
|
|
8687
|
+
fs8.appendFileSync(file2, JSON.stringify({ auditVersion: AUDIT_VERSION, ...record2 }) + "\n");
|
|
8688
|
+
} catch {
|
|
8689
|
+
}
|
|
8690
|
+
}
|
|
8679
8691
|
function readAudit(file2) {
|
|
8680
8692
|
if (!fs8.existsSync(file2)) return [];
|
|
8681
8693
|
const raw = fs8.readFileSync(file2, "utf-8");
|
|
@@ -9428,17 +9440,17 @@ var require_visit = __commonJS({
|
|
|
9428
9440
|
visit.BREAK = BREAK;
|
|
9429
9441
|
visit.SKIP = SKIP;
|
|
9430
9442
|
visit.REMOVE = REMOVE;
|
|
9431
|
-
function visit_(key, node, visitor,
|
|
9432
|
-
const ctrl = callVisitor(key, node, visitor,
|
|
9443
|
+
function visit_(key, node, visitor, path29) {
|
|
9444
|
+
const ctrl = callVisitor(key, node, visitor, path29);
|
|
9433
9445
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
9434
|
-
replaceNode(key,
|
|
9435
|
-
return visit_(key, ctrl, visitor,
|
|
9446
|
+
replaceNode(key, path29, ctrl);
|
|
9447
|
+
return visit_(key, ctrl, visitor, path29);
|
|
9436
9448
|
}
|
|
9437
9449
|
if (typeof ctrl !== "symbol") {
|
|
9438
9450
|
if (identity.isCollection(node)) {
|
|
9439
|
-
|
|
9451
|
+
path29 = Object.freeze(path29.concat(node));
|
|
9440
9452
|
for (let i = 0; i < node.items.length; ++i) {
|
|
9441
|
-
const ci = visit_(i, node.items[i], visitor,
|
|
9453
|
+
const ci = visit_(i, node.items[i], visitor, path29);
|
|
9442
9454
|
if (typeof ci === "number")
|
|
9443
9455
|
i = ci - 1;
|
|
9444
9456
|
else if (ci === BREAK)
|
|
@@ -9449,13 +9461,13 @@ var require_visit = __commonJS({
|
|
|
9449
9461
|
}
|
|
9450
9462
|
}
|
|
9451
9463
|
} else if (identity.isPair(node)) {
|
|
9452
|
-
|
|
9453
|
-
const ck = visit_("key", node.key, visitor,
|
|
9464
|
+
path29 = Object.freeze(path29.concat(node));
|
|
9465
|
+
const ck = visit_("key", node.key, visitor, path29);
|
|
9454
9466
|
if (ck === BREAK)
|
|
9455
9467
|
return BREAK;
|
|
9456
9468
|
else if (ck === REMOVE)
|
|
9457
9469
|
node.key = null;
|
|
9458
|
-
const cv = visit_("value", node.value, visitor,
|
|
9470
|
+
const cv = visit_("value", node.value, visitor, path29);
|
|
9459
9471
|
if (cv === BREAK)
|
|
9460
9472
|
return BREAK;
|
|
9461
9473
|
else if (cv === REMOVE)
|
|
@@ -9476,17 +9488,17 @@ var require_visit = __commonJS({
|
|
|
9476
9488
|
visitAsync.BREAK = BREAK;
|
|
9477
9489
|
visitAsync.SKIP = SKIP;
|
|
9478
9490
|
visitAsync.REMOVE = REMOVE;
|
|
9479
|
-
async function visitAsync_(key, node, visitor,
|
|
9480
|
-
const ctrl = await callVisitor(key, node, visitor,
|
|
9491
|
+
async function visitAsync_(key, node, visitor, path29) {
|
|
9492
|
+
const ctrl = await callVisitor(key, node, visitor, path29);
|
|
9481
9493
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
9482
|
-
replaceNode(key,
|
|
9483
|
-
return visitAsync_(key, ctrl, visitor,
|
|
9494
|
+
replaceNode(key, path29, ctrl);
|
|
9495
|
+
return visitAsync_(key, ctrl, visitor, path29);
|
|
9484
9496
|
}
|
|
9485
9497
|
if (typeof ctrl !== "symbol") {
|
|
9486
9498
|
if (identity.isCollection(node)) {
|
|
9487
|
-
|
|
9499
|
+
path29 = Object.freeze(path29.concat(node));
|
|
9488
9500
|
for (let i = 0; i < node.items.length; ++i) {
|
|
9489
|
-
const ci = await visitAsync_(i, node.items[i], visitor,
|
|
9501
|
+
const ci = await visitAsync_(i, node.items[i], visitor, path29);
|
|
9490
9502
|
if (typeof ci === "number")
|
|
9491
9503
|
i = ci - 1;
|
|
9492
9504
|
else if (ci === BREAK)
|
|
@@ -9497,13 +9509,13 @@ var require_visit = __commonJS({
|
|
|
9497
9509
|
}
|
|
9498
9510
|
}
|
|
9499
9511
|
} else if (identity.isPair(node)) {
|
|
9500
|
-
|
|
9501
|
-
const ck = await visitAsync_("key", node.key, visitor,
|
|
9512
|
+
path29 = Object.freeze(path29.concat(node));
|
|
9513
|
+
const ck = await visitAsync_("key", node.key, visitor, path29);
|
|
9502
9514
|
if (ck === BREAK)
|
|
9503
9515
|
return BREAK;
|
|
9504
9516
|
else if (ck === REMOVE)
|
|
9505
9517
|
node.key = null;
|
|
9506
|
-
const cv = await visitAsync_("value", node.value, visitor,
|
|
9518
|
+
const cv = await visitAsync_("value", node.value, visitor, path29);
|
|
9507
9519
|
if (cv === BREAK)
|
|
9508
9520
|
return BREAK;
|
|
9509
9521
|
else if (cv === REMOVE)
|
|
@@ -9530,23 +9542,23 @@ var require_visit = __commonJS({
|
|
|
9530
9542
|
}
|
|
9531
9543
|
return visitor;
|
|
9532
9544
|
}
|
|
9533
|
-
function callVisitor(key, node, visitor,
|
|
9545
|
+
function callVisitor(key, node, visitor, path29) {
|
|
9534
9546
|
if (typeof visitor === "function")
|
|
9535
|
-
return visitor(key, node,
|
|
9547
|
+
return visitor(key, node, path29);
|
|
9536
9548
|
if (identity.isMap(node))
|
|
9537
|
-
return visitor.Map?.(key, node,
|
|
9549
|
+
return visitor.Map?.(key, node, path29);
|
|
9538
9550
|
if (identity.isSeq(node))
|
|
9539
|
-
return visitor.Seq?.(key, node,
|
|
9551
|
+
return visitor.Seq?.(key, node, path29);
|
|
9540
9552
|
if (identity.isPair(node))
|
|
9541
|
-
return visitor.Pair?.(key, node,
|
|
9553
|
+
return visitor.Pair?.(key, node, path29);
|
|
9542
9554
|
if (identity.isScalar(node))
|
|
9543
|
-
return visitor.Scalar?.(key, node,
|
|
9555
|
+
return visitor.Scalar?.(key, node, path29);
|
|
9544
9556
|
if (identity.isAlias(node))
|
|
9545
|
-
return visitor.Alias?.(key, node,
|
|
9557
|
+
return visitor.Alias?.(key, node, path29);
|
|
9546
9558
|
return void 0;
|
|
9547
9559
|
}
|
|
9548
|
-
function replaceNode(key,
|
|
9549
|
-
const parent =
|
|
9560
|
+
function replaceNode(key, path29, node) {
|
|
9561
|
+
const parent = path29[path29.length - 1];
|
|
9550
9562
|
if (identity.isCollection(parent)) {
|
|
9551
9563
|
parent.items[key] = node;
|
|
9552
9564
|
} else if (identity.isPair(parent)) {
|
|
@@ -10163,10 +10175,10 @@ var require_Collection = __commonJS({
|
|
|
10163
10175
|
var createNode = require_createNode();
|
|
10164
10176
|
var identity = require_identity();
|
|
10165
10177
|
var Node = require_Node();
|
|
10166
|
-
function collectionFromPath(schema2,
|
|
10178
|
+
function collectionFromPath(schema2, path29, value) {
|
|
10167
10179
|
let v2 = value;
|
|
10168
|
-
for (let i =
|
|
10169
|
-
const k2 =
|
|
10180
|
+
for (let i = path29.length - 1; i >= 0; --i) {
|
|
10181
|
+
const k2 = path29[i];
|
|
10170
10182
|
if (typeof k2 === "number" && Number.isInteger(k2) && k2 >= 0) {
|
|
10171
10183
|
const a = [];
|
|
10172
10184
|
a[k2] = v2;
|
|
@@ -10185,7 +10197,7 @@ var require_Collection = __commonJS({
|
|
|
10185
10197
|
sourceObjects: /* @__PURE__ */ new Map()
|
|
10186
10198
|
});
|
|
10187
10199
|
}
|
|
10188
|
-
var isEmptyPath = (
|
|
10200
|
+
var isEmptyPath = (path29) => path29 == null || typeof path29 === "object" && !!path29[Symbol.iterator]().next().done;
|
|
10189
10201
|
var Collection = class extends Node.NodeBase {
|
|
10190
10202
|
constructor(type2, schema2) {
|
|
10191
10203
|
super(type2);
|
|
@@ -10215,11 +10227,11 @@ var require_Collection = __commonJS({
|
|
|
10215
10227
|
* be a Pair instance or a `{ key, value }` object, which may not have a key
|
|
10216
10228
|
* that already exists in the map.
|
|
10217
10229
|
*/
|
|
10218
|
-
addIn(
|
|
10219
|
-
if (isEmptyPath(
|
|
10230
|
+
addIn(path29, value) {
|
|
10231
|
+
if (isEmptyPath(path29))
|
|
10220
10232
|
this.add(value);
|
|
10221
10233
|
else {
|
|
10222
|
-
const [key, ...rest] =
|
|
10234
|
+
const [key, ...rest] = path29;
|
|
10223
10235
|
const node = this.get(key, true);
|
|
10224
10236
|
if (identity.isCollection(node))
|
|
10225
10237
|
node.addIn(rest, value);
|
|
@@ -10233,8 +10245,8 @@ var require_Collection = __commonJS({
|
|
|
10233
10245
|
* Removes a value from the collection.
|
|
10234
10246
|
* @returns `true` if the item was found and removed.
|
|
10235
10247
|
*/
|
|
10236
|
-
deleteIn(
|
|
10237
|
-
const [key, ...rest] =
|
|
10248
|
+
deleteIn(path29) {
|
|
10249
|
+
const [key, ...rest] = path29;
|
|
10238
10250
|
if (rest.length === 0)
|
|
10239
10251
|
return this.delete(key);
|
|
10240
10252
|
const node = this.get(key, true);
|
|
@@ -10248,8 +10260,8 @@ var require_Collection = __commonJS({
|
|
|
10248
10260
|
* scalar values from their surrounding node; to disable set `keepScalar` to
|
|
10249
10261
|
* `true` (collections are always returned intact).
|
|
10250
10262
|
*/
|
|
10251
|
-
getIn(
|
|
10252
|
-
const [key, ...rest] =
|
|
10263
|
+
getIn(path29, keepScalar) {
|
|
10264
|
+
const [key, ...rest] = path29;
|
|
10253
10265
|
const node = this.get(key, true);
|
|
10254
10266
|
if (rest.length === 0)
|
|
10255
10267
|
return !keepScalar && identity.isScalar(node) ? node.value : node;
|
|
@@ -10267,8 +10279,8 @@ var require_Collection = __commonJS({
|
|
|
10267
10279
|
/**
|
|
10268
10280
|
* Checks if the collection includes a value with the key `key`.
|
|
10269
10281
|
*/
|
|
10270
|
-
hasIn(
|
|
10271
|
-
const [key, ...rest] =
|
|
10282
|
+
hasIn(path29) {
|
|
10283
|
+
const [key, ...rest] = path29;
|
|
10272
10284
|
if (rest.length === 0)
|
|
10273
10285
|
return this.has(key);
|
|
10274
10286
|
const node = this.get(key, true);
|
|
@@ -10278,8 +10290,8 @@ var require_Collection = __commonJS({
|
|
|
10278
10290
|
* Sets a value in this collection. For `!!set`, `value` needs to be a
|
|
10279
10291
|
* boolean to add/remove the item from the set.
|
|
10280
10292
|
*/
|
|
10281
|
-
setIn(
|
|
10282
|
-
const [key, ...rest] =
|
|
10293
|
+
setIn(path29, value) {
|
|
10294
|
+
const [key, ...rest] = path29;
|
|
10283
10295
|
if (rest.length === 0) {
|
|
10284
10296
|
this.set(key, value);
|
|
10285
10297
|
} else {
|
|
@@ -12826,9 +12838,9 @@ var require_Document = __commonJS({
|
|
|
12826
12838
|
this.contents.add(value);
|
|
12827
12839
|
}
|
|
12828
12840
|
/** Adds a value to the document. */
|
|
12829
|
-
addIn(
|
|
12841
|
+
addIn(path29, value) {
|
|
12830
12842
|
if (assertCollection(this.contents))
|
|
12831
|
-
this.contents.addIn(
|
|
12843
|
+
this.contents.addIn(path29, value);
|
|
12832
12844
|
}
|
|
12833
12845
|
/**
|
|
12834
12846
|
* Create a new `Alias` node, ensuring that the target `node` has the required anchor.
|
|
@@ -12903,14 +12915,14 @@ var require_Document = __commonJS({
|
|
|
12903
12915
|
* Removes a value from the document.
|
|
12904
12916
|
* @returns `true` if the item was found and removed.
|
|
12905
12917
|
*/
|
|
12906
|
-
deleteIn(
|
|
12907
|
-
if (Collection.isEmptyPath(
|
|
12918
|
+
deleteIn(path29) {
|
|
12919
|
+
if (Collection.isEmptyPath(path29)) {
|
|
12908
12920
|
if (this.contents == null)
|
|
12909
12921
|
return false;
|
|
12910
12922
|
this.contents = null;
|
|
12911
12923
|
return true;
|
|
12912
12924
|
}
|
|
12913
|
-
return assertCollection(this.contents) ? this.contents.deleteIn(
|
|
12925
|
+
return assertCollection(this.contents) ? this.contents.deleteIn(path29) : false;
|
|
12914
12926
|
}
|
|
12915
12927
|
/**
|
|
12916
12928
|
* Returns item at `key`, or `undefined` if not found. By default unwraps
|
|
@@ -12925,10 +12937,10 @@ var require_Document = __commonJS({
|
|
|
12925
12937
|
* scalar values from their surrounding node; to disable set `keepScalar` to
|
|
12926
12938
|
* `true` (collections are always returned intact).
|
|
12927
12939
|
*/
|
|
12928
|
-
getIn(
|
|
12929
|
-
if (Collection.isEmptyPath(
|
|
12940
|
+
getIn(path29, keepScalar) {
|
|
12941
|
+
if (Collection.isEmptyPath(path29))
|
|
12930
12942
|
return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
|
|
12931
|
-
return identity.isCollection(this.contents) ? this.contents.getIn(
|
|
12943
|
+
return identity.isCollection(this.contents) ? this.contents.getIn(path29, keepScalar) : void 0;
|
|
12932
12944
|
}
|
|
12933
12945
|
/**
|
|
12934
12946
|
* Checks if the document includes a value with the key `key`.
|
|
@@ -12939,10 +12951,10 @@ var require_Document = __commonJS({
|
|
|
12939
12951
|
/**
|
|
12940
12952
|
* Checks if the document includes a value at `path`.
|
|
12941
12953
|
*/
|
|
12942
|
-
hasIn(
|
|
12943
|
-
if (Collection.isEmptyPath(
|
|
12954
|
+
hasIn(path29) {
|
|
12955
|
+
if (Collection.isEmptyPath(path29))
|
|
12944
12956
|
return this.contents !== void 0;
|
|
12945
|
-
return identity.isCollection(this.contents) ? this.contents.hasIn(
|
|
12957
|
+
return identity.isCollection(this.contents) ? this.contents.hasIn(path29) : false;
|
|
12946
12958
|
}
|
|
12947
12959
|
/**
|
|
12948
12960
|
* Sets a value in this document. For `!!set`, `value` needs to be a
|
|
@@ -12959,13 +12971,13 @@ var require_Document = __commonJS({
|
|
|
12959
12971
|
* Sets a value in this document. For `!!set`, `value` needs to be a
|
|
12960
12972
|
* boolean to add/remove the item from the set.
|
|
12961
12973
|
*/
|
|
12962
|
-
setIn(
|
|
12963
|
-
if (Collection.isEmptyPath(
|
|
12974
|
+
setIn(path29, value) {
|
|
12975
|
+
if (Collection.isEmptyPath(path29)) {
|
|
12964
12976
|
this.contents = value;
|
|
12965
12977
|
} else if (this.contents == null) {
|
|
12966
|
-
this.contents = Collection.collectionFromPath(this.schema, Array.from(
|
|
12978
|
+
this.contents = Collection.collectionFromPath(this.schema, Array.from(path29), value);
|
|
12967
12979
|
} else if (assertCollection(this.contents)) {
|
|
12968
|
-
this.contents.setIn(
|
|
12980
|
+
this.contents.setIn(path29, value);
|
|
12969
12981
|
}
|
|
12970
12982
|
}
|
|
12971
12983
|
/**
|
|
@@ -14942,9 +14954,9 @@ var require_cst_visit = __commonJS({
|
|
|
14942
14954
|
visit.BREAK = BREAK;
|
|
14943
14955
|
visit.SKIP = SKIP;
|
|
14944
14956
|
visit.REMOVE = REMOVE;
|
|
14945
|
-
visit.itemAtPath = (cst,
|
|
14957
|
+
visit.itemAtPath = (cst, path29) => {
|
|
14946
14958
|
let item = cst;
|
|
14947
|
-
for (const [field, index] of
|
|
14959
|
+
for (const [field, index] of path29) {
|
|
14948
14960
|
const tok = item?.[field];
|
|
14949
14961
|
if (tok && "items" in tok) {
|
|
14950
14962
|
item = tok.items[index];
|
|
@@ -14953,23 +14965,23 @@ var require_cst_visit = __commonJS({
|
|
|
14953
14965
|
}
|
|
14954
14966
|
return item;
|
|
14955
14967
|
};
|
|
14956
|
-
visit.parentCollection = (cst,
|
|
14957
|
-
const parent = visit.itemAtPath(cst,
|
|
14958
|
-
const field =
|
|
14968
|
+
visit.parentCollection = (cst, path29) => {
|
|
14969
|
+
const parent = visit.itemAtPath(cst, path29.slice(0, -1));
|
|
14970
|
+
const field = path29[path29.length - 1][0];
|
|
14959
14971
|
const coll = parent?.[field];
|
|
14960
14972
|
if (coll && "items" in coll)
|
|
14961
14973
|
return coll;
|
|
14962
14974
|
throw new Error("Parent collection not found");
|
|
14963
14975
|
};
|
|
14964
|
-
function _visit(
|
|
14965
|
-
let ctrl = visitor(item,
|
|
14976
|
+
function _visit(path29, item, visitor) {
|
|
14977
|
+
let ctrl = visitor(item, path29);
|
|
14966
14978
|
if (typeof ctrl === "symbol")
|
|
14967
14979
|
return ctrl;
|
|
14968
14980
|
for (const field of ["key", "value"]) {
|
|
14969
14981
|
const token = item[field];
|
|
14970
14982
|
if (token && "items" in token) {
|
|
14971
14983
|
for (let i = 0; i < token.items.length; ++i) {
|
|
14972
|
-
const ci = _visit(Object.freeze(
|
|
14984
|
+
const ci = _visit(Object.freeze(path29.concat([[field, i]])), token.items[i], visitor);
|
|
14973
14985
|
if (typeof ci === "number")
|
|
14974
14986
|
i = ci - 1;
|
|
14975
14987
|
else if (ci === BREAK)
|
|
@@ -14980,10 +14992,10 @@ var require_cst_visit = __commonJS({
|
|
|
14980
14992
|
}
|
|
14981
14993
|
}
|
|
14982
14994
|
if (typeof ctrl === "function" && field === "key")
|
|
14983
|
-
ctrl = ctrl(item,
|
|
14995
|
+
ctrl = ctrl(item, path29);
|
|
14984
14996
|
}
|
|
14985
14997
|
}
|
|
14986
|
-
return typeof ctrl === "function" ? ctrl(item,
|
|
14998
|
+
return typeof ctrl === "function" ? ctrl(item, path29) : ctrl;
|
|
14987
14999
|
}
|
|
14988
15000
|
exports.visit = visit;
|
|
14989
15001
|
}
|
|
@@ -16272,14 +16284,14 @@ var require_parser = __commonJS({
|
|
|
16272
16284
|
case "scalar":
|
|
16273
16285
|
case "single-quoted-scalar":
|
|
16274
16286
|
case "double-quoted-scalar": {
|
|
16275
|
-
const
|
|
16287
|
+
const fs33 = this.flowScalar(this.type);
|
|
16276
16288
|
if (atNextItem || it.value) {
|
|
16277
|
-
map3.items.push({ start, key:
|
|
16289
|
+
map3.items.push({ start, key: fs33, sep: [] });
|
|
16278
16290
|
this.onKeyLine = true;
|
|
16279
16291
|
} else if (it.sep) {
|
|
16280
|
-
this.stack.push(
|
|
16292
|
+
this.stack.push(fs33);
|
|
16281
16293
|
} else {
|
|
16282
|
-
Object.assign(it, { key:
|
|
16294
|
+
Object.assign(it, { key: fs33, sep: [] });
|
|
16283
16295
|
this.onKeyLine = true;
|
|
16284
16296
|
}
|
|
16285
16297
|
return;
|
|
@@ -16407,13 +16419,13 @@ var require_parser = __commonJS({
|
|
|
16407
16419
|
case "scalar":
|
|
16408
16420
|
case "single-quoted-scalar":
|
|
16409
16421
|
case "double-quoted-scalar": {
|
|
16410
|
-
const
|
|
16422
|
+
const fs33 = this.flowScalar(this.type);
|
|
16411
16423
|
if (!it || it.value)
|
|
16412
|
-
fc.items.push({ start: [], key:
|
|
16424
|
+
fc.items.push({ start: [], key: fs33, sep: [] });
|
|
16413
16425
|
else if (it.sep)
|
|
16414
|
-
this.stack.push(
|
|
16426
|
+
this.stack.push(fs33);
|
|
16415
16427
|
else
|
|
16416
|
-
Object.assign(it, { key:
|
|
16428
|
+
Object.assign(it, { key: fs33, sep: [] });
|
|
16417
16429
|
return;
|
|
16418
16430
|
}
|
|
16419
16431
|
case "flow-map-end":
|
|
@@ -16724,6 +16736,14 @@ var require_dist = __commonJS({
|
|
|
16724
16736
|
});
|
|
16725
16737
|
|
|
16726
16738
|
// src/policy/load.ts
|
|
16739
|
+
var load_exports = {};
|
|
16740
|
+
__export(load_exports, {
|
|
16741
|
+
DEFAULT_POLICY_PATH: () => DEFAULT_POLICY_PATH,
|
|
16742
|
+
PolicyFileNotFoundError: () => PolicyFileNotFoundError,
|
|
16743
|
+
PolicyYamlParseError: () => PolicyYamlParseError,
|
|
16744
|
+
loadPolicyFile: () => loadPolicyFile,
|
|
16745
|
+
resolvePolicyPath: () => resolvePolicyPath
|
|
16746
|
+
});
|
|
16727
16747
|
import { readFileSync } from "node:fs";
|
|
16728
16748
|
import { homedir } from "node:os";
|
|
16729
16749
|
import { join, resolve } from "node:path";
|
|
@@ -20004,8 +20024,8 @@ var require_utils2 = __commonJS({
|
|
|
20004
20024
|
}
|
|
20005
20025
|
return ind;
|
|
20006
20026
|
}
|
|
20007
|
-
function removeDotSegments(
|
|
20008
|
-
let input =
|
|
20027
|
+
function removeDotSegments(path29) {
|
|
20028
|
+
let input = path29;
|
|
20009
20029
|
const output = [];
|
|
20010
20030
|
let nextSlash = -1;
|
|
20011
20031
|
let len = 0;
|
|
@@ -20205,8 +20225,8 @@ var require_schemes = __commonJS({
|
|
|
20205
20225
|
wsComponent.secure = void 0;
|
|
20206
20226
|
}
|
|
20207
20227
|
if (wsComponent.resourceName) {
|
|
20208
|
-
const [
|
|
20209
|
-
wsComponent.path =
|
|
20228
|
+
const [path29, query] = wsComponent.resourceName.split("?");
|
|
20229
|
+
wsComponent.path = path29 && path29 !== "/" ? path29 : void 0;
|
|
20210
20230
|
wsComponent.query = query;
|
|
20211
20231
|
wsComponent.resourceName = void 0;
|
|
20212
20232
|
}
|
|
@@ -24010,6 +24030,9 @@ function isAnyCondition(c) {
|
|
|
24010
24030
|
function isNotCondition(c) {
|
|
24011
24031
|
return c.not !== void 0 && !Array.isArray(c.not);
|
|
24012
24032
|
}
|
|
24033
|
+
function isLlmCondition(c) {
|
|
24034
|
+
return c.llm !== void 0 && typeof c.llm === "object";
|
|
24035
|
+
}
|
|
24013
24036
|
var init_types = __esm({
|
|
24014
24037
|
"src/rules/types.ts"() {
|
|
24015
24038
|
"use strict";
|
|
@@ -24293,7 +24316,7 @@ function locateError(doc, lineCounter, err) {
|
|
|
24293
24316
|
return { line: pos.line, col: pos.col };
|
|
24294
24317
|
}
|
|
24295
24318
|
function humanMessage(err) {
|
|
24296
|
-
const
|
|
24319
|
+
const path29 = err.instancePath || "(root)";
|
|
24297
24320
|
switch (err.keyword) {
|
|
24298
24321
|
case "required":
|
|
24299
24322
|
return `missing required property "${err.params.missingProperty}"`;
|
|
@@ -24301,21 +24324,21 @@ function humanMessage(err) {
|
|
|
24301
24324
|
return `unknown property "${err.params.additionalProperty}"`;
|
|
24302
24325
|
case "dependentRequired": {
|
|
24303
24326
|
const { property, missingProperty } = err.params;
|
|
24304
|
-
const parent =
|
|
24327
|
+
const parent = path29 === "(root)" ? "" : `${path29}: `;
|
|
24305
24328
|
return `${parent}when "${property}" is set, "${missingProperty}" is also required`;
|
|
24306
24329
|
}
|
|
24307
24330
|
case "pattern":
|
|
24308
|
-
return `${
|
|
24331
|
+
return `${path29} does not match pattern ${err.params.pattern}`;
|
|
24309
24332
|
case "const":
|
|
24310
|
-
return `${
|
|
24333
|
+
return `${path29} must be exactly ${JSON.stringify(err.params.allowedValue)}`;
|
|
24311
24334
|
case "enum":
|
|
24312
|
-
return `${
|
|
24335
|
+
return `${path29} must be one of ${JSON.stringify(err.params.allowedValues)}`;
|
|
24313
24336
|
case "type":
|
|
24314
|
-
return `${
|
|
24337
|
+
return `${path29} must be ${err.params.type}`;
|
|
24315
24338
|
case "not":
|
|
24316
|
-
return `${
|
|
24339
|
+
return `${path29} is not allowed here`;
|
|
24317
24340
|
default:
|
|
24318
|
-
return `${
|
|
24341
|
+
return `${path29} ${err.message ?? "is invalid"}`;
|
|
24319
24342
|
}
|
|
24320
24343
|
}
|
|
24321
24344
|
function hintFor(err) {
|
|
@@ -24371,8 +24394,8 @@ function escapeJsonPointerSegment(segment) {
|
|
|
24371
24394
|
function isPlausibleDeviceId(value) {
|
|
24372
24395
|
return HEX_MAC_DEVICE_ID_RE.test(value) || HYPHENATED_DEVICE_ID_RE.test(value);
|
|
24373
24396
|
}
|
|
24374
|
-
function hasErrorAtPath(errors,
|
|
24375
|
-
return errors.some((err) => err.path ===
|
|
24397
|
+
function hasErrorAtPath(errors, path29) {
|
|
24398
|
+
return errors.some((err) => err.path === path29);
|
|
24376
24399
|
}
|
|
24377
24400
|
function resolvePolicyDeviceRef(raw, aliases) {
|
|
24378
24401
|
if (!raw) return { ok: false, reason: "missing-device" };
|
|
@@ -24395,25 +24418,25 @@ function isDeviceStateConditionLike(value) {
|
|
|
24395
24418
|
const candidate = value;
|
|
24396
24419
|
return typeof candidate.device === "string" && typeof candidate.field === "string" && typeof candidate.op === "string";
|
|
24397
24420
|
}
|
|
24398
|
-
function collectConditionDeviceRefs(condition,
|
|
24421
|
+
function collectConditionDeviceRefs(condition, path29) {
|
|
24399
24422
|
if (!condition || typeof condition !== "object" || Array.isArray(condition)) return [];
|
|
24400
24423
|
const out = [];
|
|
24401
24424
|
if (isDeviceStateConditionLike(condition)) {
|
|
24402
|
-
out.push({ path: `${
|
|
24425
|
+
out.push({ path: `${path29}/device`, ref: condition.device });
|
|
24403
24426
|
}
|
|
24404
24427
|
const candidate = condition;
|
|
24405
24428
|
if (Array.isArray(candidate.all)) {
|
|
24406
24429
|
for (let i = 0; i < candidate.all.length; i++) {
|
|
24407
|
-
out.push(...collectConditionDeviceRefs(candidate.all[i], `${
|
|
24430
|
+
out.push(...collectConditionDeviceRefs(candidate.all[i], `${path29}/all/${i}`));
|
|
24408
24431
|
}
|
|
24409
24432
|
}
|
|
24410
24433
|
if (Array.isArray(candidate.any)) {
|
|
24411
24434
|
for (let i = 0; i < candidate.any.length; i++) {
|
|
24412
|
-
out.push(...collectConditionDeviceRefs(candidate.any[i], `${
|
|
24435
|
+
out.push(...collectConditionDeviceRefs(candidate.any[i], `${path29}/any/${i}`));
|
|
24413
24436
|
}
|
|
24414
24437
|
}
|
|
24415
24438
|
if (candidate.not !== void 0) {
|
|
24416
|
-
out.push(...collectConditionDeviceRefs(candidate.not, `${
|
|
24439
|
+
out.push(...collectConditionDeviceRefs(candidate.not, `${path29}/not`));
|
|
24417
24440
|
}
|
|
24418
24441
|
return out;
|
|
24419
24442
|
}
|
|
@@ -24460,12 +24483,12 @@ function collectOfflineSemanticErrors(loaded, existingErrors) {
|
|
|
24460
24483
|
const out = [];
|
|
24461
24484
|
const aliases = collectAliasMap(data);
|
|
24462
24485
|
for (const [aliasName, deviceId] of Object.entries(aliases)) {
|
|
24463
|
-
const
|
|
24464
|
-
if (hasErrorAtPath(existingErrors,
|
|
24486
|
+
const path29 = `/aliases/${escapeJsonPointerSegment(aliasName)}`;
|
|
24487
|
+
if (hasErrorAtPath(existingErrors, path29)) continue;
|
|
24465
24488
|
if (isPlausibleDeviceId(deviceId)) continue;
|
|
24466
|
-
const { line, col } = locateInstancePath(loaded.doc, loaded.lineCounter,
|
|
24489
|
+
const { line, col } = locateInstancePath(loaded.doc, loaded.lineCounter, path29);
|
|
24467
24490
|
out.push({
|
|
24468
|
-
path:
|
|
24491
|
+
path: path29,
|
|
24469
24492
|
line,
|
|
24470
24493
|
col,
|
|
24471
24494
|
keyword: "alias-device-id",
|
|
@@ -24599,12 +24622,12 @@ function validateLoadedPolicyAgainstInventory(loaded, inventory) {
|
|
|
24599
24622
|
inventoryById.set(remote.deviceId, { typeName: remote.remoteType });
|
|
24600
24623
|
}
|
|
24601
24624
|
for (const [aliasName, deviceId] of Object.entries(aliases)) {
|
|
24602
|
-
const
|
|
24603
|
-
if (hasErrorAtPath(errors,
|
|
24625
|
+
const path29 = `/aliases/${escapeJsonPointerSegment(aliasName)}`;
|
|
24626
|
+
if (hasErrorAtPath(errors, path29)) continue;
|
|
24604
24627
|
if (!inventoryById.has(deviceId)) {
|
|
24605
|
-
const { line, col } = locateInstancePath(loaded.doc, loaded.lineCounter,
|
|
24628
|
+
const { line, col } = locateInstancePath(loaded.doc, loaded.lineCounter, path29);
|
|
24606
24629
|
errors.push({
|
|
24607
|
-
path:
|
|
24630
|
+
path: path29,
|
|
24608
24631
|
line,
|
|
24609
24632
|
col,
|
|
24610
24633
|
keyword: "alias-live-device-not-found",
|
|
@@ -24668,10 +24691,10 @@ function validateLoadedPolicyAgainstInventory(loaded, inventory) {
|
|
|
24668
24691
|
if (!effectiveDeviceId) continue;
|
|
24669
24692
|
const target = inventoryById.get(effectiveDeviceId);
|
|
24670
24693
|
if (!target) {
|
|
24671
|
-
const
|
|
24672
|
-
const { line: line2, col: col2 } = locateInstancePath(loaded.doc, loaded.lineCounter,
|
|
24694
|
+
const path29 = typeof action?.device === "string" ? devicePath : commandPath;
|
|
24695
|
+
const { line: line2, col: col2 } = locateInstancePath(loaded.doc, loaded.lineCounter, path29);
|
|
24673
24696
|
errors.push({
|
|
24674
|
-
path:
|
|
24697
|
+
path: path29,
|
|
24675
24698
|
line: line2,
|
|
24676
24699
|
col: col2,
|
|
24677
24700
|
keyword: "rule-live-device-not-found",
|
|
@@ -24850,6 +24873,70 @@ var init_openai = __esm({
|
|
|
24850
24873
|
if (!content) throw new Error("OpenAI returned empty content");
|
|
24851
24874
|
return content.replace(/^```ya?ml\n?/i, "").replace(/\n?```\s*$/i, "").trim();
|
|
24852
24875
|
}
|
|
24876
|
+
async decide(prompt2, opts = {}) {
|
|
24877
|
+
const timeoutMs = opts.timeoutMs ?? this.timeoutMs;
|
|
24878
|
+
const body = JSON.stringify({
|
|
24879
|
+
model: this.model,
|
|
24880
|
+
max_tokens: 256,
|
|
24881
|
+
tools: [{
|
|
24882
|
+
type: "function",
|
|
24883
|
+
function: {
|
|
24884
|
+
name: "decide",
|
|
24885
|
+
description: "Return a boolean pass/fail decision with a brief reason.",
|
|
24886
|
+
parameters: {
|
|
24887
|
+
type: "object",
|
|
24888
|
+
properties: {
|
|
24889
|
+
pass: { type: "boolean", description: "true if the condition passes, false otherwise." },
|
|
24890
|
+
reason: { type: "string", description: "Brief reason (\u2264200 chars)." }
|
|
24891
|
+
},
|
|
24892
|
+
required: ["pass", "reason"]
|
|
24893
|
+
}
|
|
24894
|
+
}
|
|
24895
|
+
}],
|
|
24896
|
+
tool_choice: { type: "function", function: { name: "decide" } },
|
|
24897
|
+
messages: [{ role: "user", content: prompt2 }]
|
|
24898
|
+
});
|
|
24899
|
+
const parsed = new URL(`${this.baseUrl}/v1/chat/completions`);
|
|
24900
|
+
const isHttps = parsed.protocol === "https:";
|
|
24901
|
+
const responseBody = await new Promise((resolve2, reject) => {
|
|
24902
|
+
const req = (isHttps ? https : http).request(
|
|
24903
|
+
{
|
|
24904
|
+
hostname: parsed.hostname,
|
|
24905
|
+
port: parsed.port || (isHttps ? 443 : 80),
|
|
24906
|
+
path: parsed.pathname,
|
|
24907
|
+
method: "POST",
|
|
24908
|
+
headers: {
|
|
24909
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
24910
|
+
"Content-Type": "application/json",
|
|
24911
|
+
"Content-Length": Buffer.byteLength(body)
|
|
24912
|
+
},
|
|
24913
|
+
timeout: timeoutMs
|
|
24914
|
+
},
|
|
24915
|
+
(res) => {
|
|
24916
|
+
const chunks = [];
|
|
24917
|
+
res.on("data", (c) => chunks.push(c));
|
|
24918
|
+
res.on("end", () => {
|
|
24919
|
+
const text = Buffer.concat(chunks).toString("utf-8");
|
|
24920
|
+
if (res.statusCode !== void 0 && res.statusCode >= 400) {
|
|
24921
|
+
reject(new Error(`OpenAI API error ${res.statusCode}: ${text.slice(0, 200)}`));
|
|
24922
|
+
} else {
|
|
24923
|
+
resolve2(text);
|
|
24924
|
+
}
|
|
24925
|
+
});
|
|
24926
|
+
}
|
|
24927
|
+
);
|
|
24928
|
+
req.on("error", reject);
|
|
24929
|
+
req.on("timeout", () => req.destroy(new Error("LLM request timeout")));
|
|
24930
|
+
req.write(body);
|
|
24931
|
+
req.end();
|
|
24932
|
+
});
|
|
24933
|
+
const json3 = JSON.parse(responseBody);
|
|
24934
|
+
const toolCall = json3.choices?.[0]?.message?.tool_calls?.find((tc) => tc.function.name === "decide");
|
|
24935
|
+
if (!toolCall) throw new Error("OpenAI decide: no tool call in response");
|
|
24936
|
+
const args = JSON.parse(toolCall.function.arguments);
|
|
24937
|
+
if (typeof args.pass !== "boolean") throw new Error("OpenAI decide: malformed function-call response");
|
|
24938
|
+
return { pass: args.pass, reason: String(args.reason ?? "").slice(0, 200) };
|
|
24939
|
+
}
|
|
24853
24940
|
};
|
|
24854
24941
|
}
|
|
24855
24942
|
});
|
|
@@ -24920,6 +25007,66 @@ var init_anthropic = __esm({
|
|
|
24920
25007
|
if (!content) throw new Error("Anthropic returned empty content");
|
|
24921
25008
|
return content.replace(/^```ya?ml\n?/i, "").replace(/\n?```\s*$/i, "").trim();
|
|
24922
25009
|
}
|
|
25010
|
+
async decide(prompt2, opts = {}) {
|
|
25011
|
+
const timeoutMs = opts.timeoutMs ?? this.timeoutMs;
|
|
25012
|
+
const body = JSON.stringify({
|
|
25013
|
+
model: this.model,
|
|
25014
|
+
max_tokens: 256,
|
|
25015
|
+
tools: [{
|
|
25016
|
+
name: "decide",
|
|
25017
|
+
description: "Return a boolean pass/fail decision with a brief reason.",
|
|
25018
|
+
input_schema: {
|
|
25019
|
+
type: "object",
|
|
25020
|
+
properties: {
|
|
25021
|
+
pass: { type: "boolean", description: "true if the condition passes, false otherwise." },
|
|
25022
|
+
reason: { type: "string", description: "Brief reason (\u2264200 chars)." }
|
|
25023
|
+
},
|
|
25024
|
+
required: ["pass", "reason"]
|
|
25025
|
+
}
|
|
25026
|
+
}],
|
|
25027
|
+
tool_choice: { type: "tool", name: "decide" },
|
|
25028
|
+
messages: [{ role: "user", content: prompt2 }]
|
|
25029
|
+
});
|
|
25030
|
+
const responseBody = await new Promise((resolve2, reject) => {
|
|
25031
|
+
const req = https2.request(
|
|
25032
|
+
{
|
|
25033
|
+
hostname: "api.anthropic.com",
|
|
25034
|
+
port: 443,
|
|
25035
|
+
path: "/v1/messages",
|
|
25036
|
+
method: "POST",
|
|
25037
|
+
headers: {
|
|
25038
|
+
"x-api-key": this.apiKey,
|
|
25039
|
+
"anthropic-version": "2023-06-01",
|
|
25040
|
+
"Content-Type": "application/json",
|
|
25041
|
+
"Content-Length": Buffer.byteLength(body)
|
|
25042
|
+
},
|
|
25043
|
+
timeout: timeoutMs
|
|
25044
|
+
},
|
|
25045
|
+
(res) => {
|
|
25046
|
+
const chunks = [];
|
|
25047
|
+
res.on("data", (c) => chunks.push(c));
|
|
25048
|
+
res.on("end", () => {
|
|
25049
|
+
const text = Buffer.concat(chunks).toString("utf-8");
|
|
25050
|
+
if (res.statusCode !== void 0 && res.statusCode >= 400) {
|
|
25051
|
+
reject(new Error(`Anthropic API error ${res.statusCode}: ${text.slice(0, 200)}`));
|
|
25052
|
+
} else {
|
|
25053
|
+
resolve2(text);
|
|
25054
|
+
}
|
|
25055
|
+
});
|
|
25056
|
+
}
|
|
25057
|
+
);
|
|
25058
|
+
req.on("error", reject);
|
|
25059
|
+
req.on("timeout", () => req.destroy(new Error("LLM request timeout")));
|
|
25060
|
+
req.write(body);
|
|
25061
|
+
req.end();
|
|
25062
|
+
});
|
|
25063
|
+
const json3 = JSON.parse(responseBody);
|
|
25064
|
+
const toolUse = json3.content?.find((c) => c.type === "tool_use" && c.name === "decide");
|
|
25065
|
+
if (!toolUse?.input || typeof toolUse.input.pass !== "boolean") {
|
|
25066
|
+
throw new Error("Anthropic decide: malformed tool-use response");
|
|
25067
|
+
}
|
|
25068
|
+
return { pass: toolUse.input.pass, reason: String(toolUse.input.reason ?? "").slice(0, 200) };
|
|
25069
|
+
}
|
|
24923
25070
|
};
|
|
24924
25071
|
}
|
|
24925
25072
|
});
|
|
@@ -25086,8 +25233,10 @@ function matchesMqttTrigger(trigger, event, resolvedTriggerDeviceId) {
|
|
|
25086
25233
|
async function evaluateConditions(conditions, now, ctx = {}) {
|
|
25087
25234
|
const result = { matched: true, failures: [], unsupported: [] };
|
|
25088
25235
|
if (!conditions || conditions.length === 0) return result;
|
|
25236
|
+
const evaluated = [];
|
|
25089
25237
|
for (const c of conditions) {
|
|
25090
25238
|
const sub = await evaluateSingle(c, now, ctx);
|
|
25239
|
+
evaluated.push({ c, sub });
|
|
25091
25240
|
if (!sub.matched) {
|
|
25092
25241
|
result.matched = false;
|
|
25093
25242
|
result.failures.push(...sub.failures);
|
|
@@ -25096,6 +25245,13 @@ async function evaluateConditions(conditions, now, ctx = {}) {
|
|
|
25096
25245
|
if (!sub.matched && result.unsupported.length > 0) {
|
|
25097
25246
|
}
|
|
25098
25247
|
}
|
|
25248
|
+
if (ctx.trace) {
|
|
25249
|
+
for (const { c, sub } of evaluated) {
|
|
25250
|
+
if (!isLlmCondition(c)) {
|
|
25251
|
+
pushConditionTrace(ctx.trace, c, sub);
|
|
25252
|
+
}
|
|
25253
|
+
}
|
|
25254
|
+
}
|
|
25099
25255
|
return result;
|
|
25100
25256
|
}
|
|
25101
25257
|
async function evaluateSingle(c, now, ctx) {
|
|
@@ -25157,6 +25313,29 @@ async function evaluateSingle(c, now, ctx) {
|
|
|
25157
25313
|
return fail(`device_state ${c.device}.${c.field}: fetch failed \u2014 ${err instanceof Error ? err.message : String(err)}`);
|
|
25158
25314
|
}
|
|
25159
25315
|
}
|
|
25316
|
+
if (isLlmCondition(c)) {
|
|
25317
|
+
if (!ctx.llmEvaluator || !ctx.event) {
|
|
25318
|
+
return {
|
|
25319
|
+
matched: false,
|
|
25320
|
+
failures: [],
|
|
25321
|
+
unsupported: [{ keyword: "llm", hint: "llm condition requires an LlmConditionEvaluator and event in context." }]
|
|
25322
|
+
};
|
|
25323
|
+
}
|
|
25324
|
+
try {
|
|
25325
|
+
const res = await ctx.llmEvaluator.evaluate(
|
|
25326
|
+
c.llm,
|
|
25327
|
+
{ event: ctx.event },
|
|
25328
|
+
ctx.ruleVersion ?? "unknown",
|
|
25329
|
+
ctx.globalLlmMaxCallsPerHour
|
|
25330
|
+
);
|
|
25331
|
+
if (ctx.trace) {
|
|
25332
|
+
ctx.trace.push({ kind: "llm", config: res.traceFields, passed: res.pass });
|
|
25333
|
+
}
|
|
25334
|
+
return res.pass ? ok : fail(`llm condition returned false: ${res.traceFields.reason}`);
|
|
25335
|
+
} catch (err) {
|
|
25336
|
+
return fail(`llm condition error: ${err instanceof Error ? err.message : String(err)}`);
|
|
25337
|
+
}
|
|
25338
|
+
}
|
|
25160
25339
|
return {
|
|
25161
25340
|
matched: false,
|
|
25162
25341
|
failures: [],
|
|
@@ -25216,6 +25395,28 @@ function formatValue(v2) {
|
|
|
25216
25395
|
if (typeof v2 === "string") return JSON.stringify(v2);
|
|
25217
25396
|
return String(v2);
|
|
25218
25397
|
}
|
|
25398
|
+
function conditionKind(c) {
|
|
25399
|
+
if (isAllCondition(c)) return "all";
|
|
25400
|
+
if (isAnyCondition(c)) return "any";
|
|
25401
|
+
if (isNotCondition(c)) return "not";
|
|
25402
|
+
if (isTimeBetween(c)) return "time_between";
|
|
25403
|
+
if (isDeviceState(c)) return "device_state";
|
|
25404
|
+
if (isLlmCondition(c)) return "llm";
|
|
25405
|
+
return "unknown";
|
|
25406
|
+
}
|
|
25407
|
+
function conditionConfig(c) {
|
|
25408
|
+
if (isTimeBetween(c)) return c.time_between;
|
|
25409
|
+
if (isDeviceState(c)) return { device: c.device, field: c.field, op: c.op, value: c.value };
|
|
25410
|
+
if (isLlmCondition(c)) return { prompt: c.llm.prompt.slice(0, 80) };
|
|
25411
|
+
return void 0;
|
|
25412
|
+
}
|
|
25413
|
+
function pushConditionTrace(trace, c, sub) {
|
|
25414
|
+
trace.push({
|
|
25415
|
+
kind: conditionKind(c),
|
|
25416
|
+
config: conditionConfig(c),
|
|
25417
|
+
passed: sub.unsupported.length > 0 ? false : sub.matched
|
|
25418
|
+
});
|
|
25419
|
+
}
|
|
25219
25420
|
var EVENT_CLASSIFIERS;
|
|
25220
25421
|
var init_matcher = __esm({
|
|
25221
25422
|
"src/rules/matcher.ts"() {
|
|
@@ -26529,6 +26730,85 @@ var init_notify = __esm({
|
|
|
26529
26730
|
}
|
|
26530
26731
|
});
|
|
26531
26732
|
|
|
26733
|
+
// src/rules/trace.ts
|
|
26734
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
26735
|
+
function shouldWriteTrace(mode, event, decision) {
|
|
26736
|
+
if (mode === "off") return false;
|
|
26737
|
+
if (mode === "full") return true;
|
|
26738
|
+
if (HIGH_FREQ_EVENTS.has(event.event) && decision === "blocked-by-condition") return false;
|
|
26739
|
+
return true;
|
|
26740
|
+
}
|
|
26741
|
+
function deepSortedJson(value) {
|
|
26742
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
26743
|
+
if (Array.isArray(value)) {
|
|
26744
|
+
return "[" + value.map(deepSortedJson).join(",") + "]";
|
|
26745
|
+
}
|
|
26746
|
+
const obj = value;
|
|
26747
|
+
const keys = Object.keys(obj).sort();
|
|
26748
|
+
return "{" + keys.map((k2) => JSON.stringify(k2) + ":" + deepSortedJson(obj[k2])).join(",") + "}";
|
|
26749
|
+
}
|
|
26750
|
+
function canonicalizeRule(rule) {
|
|
26751
|
+
return deepSortedJson(rule);
|
|
26752
|
+
}
|
|
26753
|
+
function ruleVersion(rule) {
|
|
26754
|
+
return createHash3("sha256").update(canonicalizeRule(rule)).digest("hex").slice(0, 8);
|
|
26755
|
+
}
|
|
26756
|
+
function filterTraceRecords(lines, opts = {}) {
|
|
26757
|
+
const sinceMs = opts.since ? new Date(opts.since).getTime() : void 0;
|
|
26758
|
+
const results = [];
|
|
26759
|
+
for (const line of lines) {
|
|
26760
|
+
const trimmed = line.trim();
|
|
26761
|
+
if (!trimmed) continue;
|
|
26762
|
+
let entry;
|
|
26763
|
+
try {
|
|
26764
|
+
entry = JSON.parse(trimmed);
|
|
26765
|
+
} catch {
|
|
26766
|
+
continue;
|
|
26767
|
+
}
|
|
26768
|
+
if (entry.kind !== "rule-evaluate") continue;
|
|
26769
|
+
if (opts.fireId && entry.fireId !== opts.fireId) continue;
|
|
26770
|
+
if (opts.ruleName && entry.rule.name !== opts.ruleName) continue;
|
|
26771
|
+
if (sinceMs !== void 0 && new Date(entry.t).getTime() < sinceMs) continue;
|
|
26772
|
+
if (opts.noFireOnly && (entry.decision === "fire" || entry.decision === "dry")) continue;
|
|
26773
|
+
results.push(entry);
|
|
26774
|
+
}
|
|
26775
|
+
return results;
|
|
26776
|
+
}
|
|
26777
|
+
var HIGH_FREQ_EVENTS, TraceBuilder;
|
|
26778
|
+
var init_trace = __esm({
|
|
26779
|
+
"src/rules/trace.ts"() {
|
|
26780
|
+
"use strict";
|
|
26781
|
+
init_cjs_shim();
|
|
26782
|
+
HIGH_FREQ_EVENTS = /* @__PURE__ */ new Set([
|
|
26783
|
+
"device.shadow",
|
|
26784
|
+
"motion.detected",
|
|
26785
|
+
"motion.cleared"
|
|
26786
|
+
]);
|
|
26787
|
+
TraceBuilder = class {
|
|
26788
|
+
conditions = [];
|
|
26789
|
+
startMs;
|
|
26790
|
+
constructor() {
|
|
26791
|
+
this.startMs = Date.now();
|
|
26792
|
+
}
|
|
26793
|
+
push(entry) {
|
|
26794
|
+
this.conditions.push(entry);
|
|
26795
|
+
}
|
|
26796
|
+
build(rule, event, fireId, decision) {
|
|
26797
|
+
return {
|
|
26798
|
+
t: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26799
|
+
kind: "rule-evaluate",
|
|
26800
|
+
rule: { name: rule.name, version: ruleVersion(rule) },
|
|
26801
|
+
trigger: { source: event.source, event: event.event, deviceId: event.deviceId },
|
|
26802
|
+
fireId,
|
|
26803
|
+
conditions: [...this.conditions],
|
|
26804
|
+
decision,
|
|
26805
|
+
evaluationMs: Date.now() - this.startMs
|
|
26806
|
+
};
|
|
26807
|
+
}
|
|
26808
|
+
};
|
|
26809
|
+
}
|
|
26810
|
+
});
|
|
26811
|
+
|
|
26532
26812
|
// src/rules/engine.ts
|
|
26533
26813
|
var engine_exports = {};
|
|
26534
26814
|
__export(engine_exports, {
|
|
@@ -26721,6 +27001,44 @@ function lintRules(automation) {
|
|
|
26721
27001
|
});
|
|
26722
27002
|
}
|
|
26723
27003
|
}
|
|
27004
|
+
for (const c of r.conditions ?? []) {
|
|
27005
|
+
if (!isLlmCondition(c)) continue;
|
|
27006
|
+
const llm = c.llm;
|
|
27007
|
+
if (!llm.provider || llm.provider === "auto") {
|
|
27008
|
+
if (!process.env.ANTHROPIC_API_KEY && !process.env.OPENAI_API_KEY && !process.env.LLM_API_KEY) {
|
|
27009
|
+
issues.push({
|
|
27010
|
+
rule: r.name,
|
|
27011
|
+
severity: "error",
|
|
27012
|
+
code: "condition-llm-no-provider",
|
|
27013
|
+
message: 'llm condition uses provider "auto" but no LLM API key env var is set (ANTHROPIC_API_KEY, OPENAI_API_KEY, or LLM_API_KEY).'
|
|
27014
|
+
});
|
|
27015
|
+
}
|
|
27016
|
+
}
|
|
27017
|
+
if (isMqttTrigger(r.when) && HIGH_FREQ_EVENTS.has(r.when.event) && !llm.cache_ttl) {
|
|
27018
|
+
issues.push({
|
|
27019
|
+
rule: r.name,
|
|
27020
|
+
severity: "warning",
|
|
27021
|
+
code: "condition-llm-no-cache-ttl-high-freq",
|
|
27022
|
+
message: `llm condition on high-frequency event "${r.when.event}" without explicit cache_ttl \u2014 consider setting cache_ttl to reduce LLM calls.`
|
|
27023
|
+
});
|
|
27024
|
+
}
|
|
27025
|
+
if (llm.budget?.max_calls_per_hour === 0) {
|
|
27026
|
+
issues.push({
|
|
27027
|
+
rule: r.name,
|
|
27028
|
+
severity: "warning",
|
|
27029
|
+
code: "condition-llm-budget-zero",
|
|
27030
|
+
message: "llm condition budget.max_calls_per_hour is 0 \u2014 condition will always take the on_error path."
|
|
27031
|
+
});
|
|
27032
|
+
}
|
|
27033
|
+
if (llm.on_error === "pass") {
|
|
27034
|
+
issues.push({
|
|
27035
|
+
rule: r.name,
|
|
27036
|
+
severity: "warning",
|
|
27037
|
+
code: "condition-llm-on-error-pass",
|
|
27038
|
+
message: 'llm condition on_error is "pass" \u2014 the condition will silently pass when the LLM is unavailable or over-budget.'
|
|
27039
|
+
});
|
|
27040
|
+
}
|
|
27041
|
+
}
|
|
26724
27042
|
const enabled = r.enabled !== false;
|
|
26725
27043
|
const hasError = issues.some((i) => i.severity === "error");
|
|
26726
27044
|
const hasUnsupported = issues.some((i) => i.code === "trigger-unsupported");
|
|
@@ -26749,6 +27067,7 @@ var init_engine = __esm({
|
|
|
26749
27067
|
init_notify();
|
|
26750
27068
|
init_croner();
|
|
26751
27069
|
init_audit();
|
|
27070
|
+
init_trace();
|
|
26752
27071
|
RulesEngine = class {
|
|
26753
27072
|
opts;
|
|
26754
27073
|
rules;
|
|
@@ -27079,6 +27398,13 @@ var init_engine = __esm({
|
|
|
27079
27398
|
}
|
|
27080
27399
|
async dispatchRule(rule, event) {
|
|
27081
27400
|
const fireId = randomUUID4();
|
|
27401
|
+
const traceMode = this.opts.automation?.audit?.evaluate_trace ?? "sampled";
|
|
27402
|
+
const trace = new TraceBuilder();
|
|
27403
|
+
const emitTrace = (decision) => {
|
|
27404
|
+
if (shouldWriteTrace(traceMode, event, decision)) {
|
|
27405
|
+
writeEvaluateTrace(trace.build(rule, event, fireId, decision));
|
|
27406
|
+
}
|
|
27407
|
+
};
|
|
27082
27408
|
const statusCache = /* @__PURE__ */ new Map();
|
|
27083
27409
|
const baseFetcher = this.opts.statusFetcher ?? ((id) => fetchDeviceStatus(id, this.opts.httpClient));
|
|
27084
27410
|
const fetchStatus = (deviceId) => {
|
|
@@ -27090,7 +27416,8 @@ var init_engine = __esm({
|
|
|
27090
27416
|
};
|
|
27091
27417
|
const cond = await evaluateConditions(rule.conditions, event.t, {
|
|
27092
27418
|
aliases: this.aliases,
|
|
27093
|
-
fetchStatus
|
|
27419
|
+
fetchStatus,
|
|
27420
|
+
trace
|
|
27094
27421
|
});
|
|
27095
27422
|
if (!cond.matched) {
|
|
27096
27423
|
const hasHysteresis = rule.hysteresis ?? rule.requires_stable_for;
|
|
@@ -27099,6 +27426,7 @@ var init_engine = __esm({
|
|
|
27099
27426
|
this.hysteresisFirstSeen.delete(hysteresisKey);
|
|
27100
27427
|
}
|
|
27101
27428
|
if (cond.unsupported.length > 0) {
|
|
27429
|
+
emitTrace("error");
|
|
27102
27430
|
writeAudit({
|
|
27103
27431
|
t: event.t.toISOString(),
|
|
27104
27432
|
kind: "rule-fire",
|
|
@@ -27121,6 +27449,7 @@ var init_engine = __esm({
|
|
|
27121
27449
|
return;
|
|
27122
27450
|
}
|
|
27123
27451
|
this.stats.conditionsFailed++;
|
|
27452
|
+
emitTrace("blocked-by-condition");
|
|
27124
27453
|
this.opts.onFire?.({ ruleName: rule.name, fireId, status: "conditions-failed", deviceId: event.deviceId, reason: cond.failures.join("; ") });
|
|
27125
27454
|
return;
|
|
27126
27455
|
}
|
|
@@ -27130,6 +27459,7 @@ var init_engine = __esm({
|
|
|
27130
27459
|
const check2 = this.throttle.check(rule.name, effectiveMaxPerMs, event.t.getTime(), throttleKey, dedupeWindowMs);
|
|
27131
27460
|
if (!check2.allowed) {
|
|
27132
27461
|
this.stats.throttled++;
|
|
27462
|
+
emitTrace("throttled");
|
|
27133
27463
|
writeAudit({
|
|
27134
27464
|
t: event.t.toISOString(),
|
|
27135
27465
|
kind: "rule-throttled",
|
|
@@ -27157,6 +27487,7 @@ var init_engine = __esm({
|
|
|
27157
27487
|
const now = event.t.getTime();
|
|
27158
27488
|
if (firstSeen === void 0) {
|
|
27159
27489
|
this.hysteresisFirstSeen.set(hysteresisKey, now);
|
|
27490
|
+
emitTrace("throttled");
|
|
27160
27491
|
writeAudit({
|
|
27161
27492
|
t: event.t.toISOString(),
|
|
27162
27493
|
kind: "rule-throttled",
|
|
@@ -27172,6 +27503,7 @@ var init_engine = __esm({
|
|
|
27172
27503
|
return;
|
|
27173
27504
|
}
|
|
27174
27505
|
if (now - firstSeen < hysteresisMs) {
|
|
27506
|
+
emitTrace("throttled");
|
|
27175
27507
|
writeAudit({
|
|
27176
27508
|
t: event.t.toISOString(),
|
|
27177
27509
|
kind: "rule-throttled",
|
|
@@ -27192,6 +27524,7 @@ var init_engine = __esm({
|
|
|
27192
27524
|
const countCheck = this.throttle.checkMaxFirings(rule.name, rule.maxFiringsPerHour, 36e5, event.t.getTime(), event.deviceId);
|
|
27193
27525
|
if (!countCheck.allowed) {
|
|
27194
27526
|
this.stats.throttled++;
|
|
27527
|
+
emitTrace("throttled");
|
|
27195
27528
|
writeAudit({
|
|
27196
27529
|
t: event.t.toISOString(),
|
|
27197
27530
|
kind: "rule-throttled",
|
|
@@ -27218,6 +27551,7 @@ var init_engine = __esm({
|
|
|
27218
27551
|
const deviceStatus = await fetchStatus(targetId);
|
|
27219
27552
|
const powerState = deviceStatus["powerState"];
|
|
27220
27553
|
if (verb === "turnOn" && powerState === "on" || verb === "turnOff" && powerState === "off") {
|
|
27554
|
+
emitTrace("throttled");
|
|
27221
27555
|
writeAudit({
|
|
27222
27556
|
t: event.t.toISOString(),
|
|
27223
27557
|
kind: "rule-throttled",
|
|
@@ -27269,6 +27603,8 @@ var init_engine = __esm({
|
|
|
27269
27603
|
if (!result.ok && (action.on_error ?? "continue") === "stop") break;
|
|
27270
27604
|
}
|
|
27271
27605
|
if (fired) {
|
|
27606
|
+
const decision = allDry ? "dry" : "fire";
|
|
27607
|
+
emitTrace(decision);
|
|
27272
27608
|
if (allDry) this.stats.dryFires++;
|
|
27273
27609
|
else this.stats.fires++;
|
|
27274
27610
|
this.throttle.record(rule.name, event.t.getTime(), throttleKey);
|
|
@@ -27870,6 +28206,8 @@ var init_capabilities = __esm({
|
|
|
27870
28206
|
"rules summary": READ_LOCAL,
|
|
27871
28207
|
"rules last-fired": READ_LOCAL,
|
|
27872
28208
|
"rules explain": READ_LOCAL,
|
|
28209
|
+
"rules trace-explain": READ_LOCAL,
|
|
28210
|
+
"rules simulate": READ_LOCAL,
|
|
27873
28211
|
"schema export": READ_LOCAL,
|
|
27874
28212
|
"scenes list": READ_REMOTE,
|
|
27875
28213
|
"scenes execute": ACTION_REMOTE,
|
|
@@ -35224,10 +35562,10 @@ function mergeDefs(...defs) {
|
|
|
35224
35562
|
function cloneDef(schema2) {
|
|
35225
35563
|
return mergeDefs(schema2._zod.def);
|
|
35226
35564
|
}
|
|
35227
|
-
function getElementAtPath(obj,
|
|
35228
|
-
if (!
|
|
35565
|
+
function getElementAtPath(obj, path29) {
|
|
35566
|
+
if (!path29)
|
|
35229
35567
|
return obj;
|
|
35230
|
-
return
|
|
35568
|
+
return path29.reduce((acc, key) => acc?.[key], obj);
|
|
35231
35569
|
}
|
|
35232
35570
|
function promiseAllObject(promisesObj) {
|
|
35233
35571
|
const keys = Object.keys(promisesObj);
|
|
@@ -35610,11 +35948,11 @@ function aborted(x2, startIndex = 0) {
|
|
|
35610
35948
|
}
|
|
35611
35949
|
return false;
|
|
35612
35950
|
}
|
|
35613
|
-
function prefixIssues(
|
|
35951
|
+
function prefixIssues(path29, issues) {
|
|
35614
35952
|
return issues.map((iss) => {
|
|
35615
35953
|
var _a2;
|
|
35616
35954
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
35617
|
-
iss.path.unshift(
|
|
35955
|
+
iss.path.unshift(path29);
|
|
35618
35956
|
return iss;
|
|
35619
35957
|
});
|
|
35620
35958
|
}
|
|
@@ -35797,7 +36135,7 @@ function formatError2(error48, mapper = (issue2) => issue2.message) {
|
|
|
35797
36135
|
}
|
|
35798
36136
|
function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
35799
36137
|
const result = { errors: [] };
|
|
35800
|
-
const processError = (error49,
|
|
36138
|
+
const processError = (error49, path29 = []) => {
|
|
35801
36139
|
var _a2, _b;
|
|
35802
36140
|
for (const issue2 of error49.issues) {
|
|
35803
36141
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -35807,7 +36145,7 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
|
35807
36145
|
} else if (issue2.code === "invalid_element") {
|
|
35808
36146
|
processError({ issues: issue2.issues }, issue2.path);
|
|
35809
36147
|
} else {
|
|
35810
|
-
const fullpath = [...
|
|
36148
|
+
const fullpath = [...path29, ...issue2.path];
|
|
35811
36149
|
if (fullpath.length === 0) {
|
|
35812
36150
|
result.errors.push(mapper(issue2));
|
|
35813
36151
|
continue;
|
|
@@ -35839,8 +36177,8 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
|
35839
36177
|
}
|
|
35840
36178
|
function toDotPath(_path) {
|
|
35841
36179
|
const segs = [];
|
|
35842
|
-
const
|
|
35843
|
-
for (const seg of
|
|
36180
|
+
const path29 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
36181
|
+
for (const seg of path29) {
|
|
35844
36182
|
if (typeof seg === "number")
|
|
35845
36183
|
segs.push(`[${seg}]`);
|
|
35846
36184
|
else if (typeof seg === "symbol")
|
|
@@ -47895,13 +48233,13 @@ function resolveRef(ref, ctx) {
|
|
|
47895
48233
|
if (!ref.startsWith("#")) {
|
|
47896
48234
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
47897
48235
|
}
|
|
47898
|
-
const
|
|
47899
|
-
if (
|
|
48236
|
+
const path29 = ref.slice(1).split("/").filter(Boolean);
|
|
48237
|
+
if (path29.length === 0) {
|
|
47900
48238
|
return ctx.rootSchema;
|
|
47901
48239
|
}
|
|
47902
48240
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
47903
|
-
if (
|
|
47904
|
-
const key =
|
|
48241
|
+
if (path29[0] === defsKey) {
|
|
48242
|
+
const key = path29[1];
|
|
47905
48243
|
if (!key || !ctx.defs[key]) {
|
|
47906
48244
|
throw new Error(`Reference not found: ${ref}`);
|
|
47907
48245
|
}
|
|
@@ -50027,6 +50365,282 @@ function addRuleToPolicyFile(opts) {
|
|
|
50027
50365
|
return { ...result, written: false };
|
|
50028
50366
|
}
|
|
50029
50367
|
|
|
50368
|
+
// src/rules/explain.ts
|
|
50369
|
+
init_cjs_shim();
|
|
50370
|
+
init_trace();
|
|
50371
|
+
import fs16 from "node:fs";
|
|
50372
|
+
function loadTraceRecords(auditFile, opts = {}) {
|
|
50373
|
+
if (!fs16.existsSync(auditFile)) return [];
|
|
50374
|
+
const lines = fs16.readFileSync(auditFile, "utf-8").split(/\r?\n/);
|
|
50375
|
+
return filterTraceRecords(lines, opts);
|
|
50376
|
+
}
|
|
50377
|
+
function loadRelatedAudit(auditFile, fireId) {
|
|
50378
|
+
if (!fs16.existsSync(auditFile)) return [];
|
|
50379
|
+
const raw = fs16.readFileSync(auditFile, "utf-8");
|
|
50380
|
+
const out = [];
|
|
50381
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
50382
|
+
const trimmed = line.trim();
|
|
50383
|
+
if (!trimmed) continue;
|
|
50384
|
+
try {
|
|
50385
|
+
const entry = JSON.parse(trimmed);
|
|
50386
|
+
const entryFireId = entry.rule?.fireId ?? entry["fireId"];
|
|
50387
|
+
if (entryFireId === fireId) out.push(entry);
|
|
50388
|
+
} catch {
|
|
50389
|
+
}
|
|
50390
|
+
}
|
|
50391
|
+
return out;
|
|
50392
|
+
}
|
|
50393
|
+
function conditionSymbol(passed) {
|
|
50394
|
+
if (passed === true) return "\u2713";
|
|
50395
|
+
if (passed === false) return "\u2717";
|
|
50396
|
+
return "\xB7";
|
|
50397
|
+
}
|
|
50398
|
+
function conditionSummary(c) {
|
|
50399
|
+
if (c.passed === null) {
|
|
50400
|
+
return `\xB7 ${c.kind} \u2192 not evaluated (short-circuited)`;
|
|
50401
|
+
}
|
|
50402
|
+
const sym = conditionSymbol(c.passed);
|
|
50403
|
+
let detail = c.kind;
|
|
50404
|
+
if (c.config !== void 0) {
|
|
50405
|
+
if (Array.isArray(c.config)) {
|
|
50406
|
+
detail += ` ${c.config.join("\u2013")}`;
|
|
50407
|
+
} else if (c.config && typeof c.config === "object") {
|
|
50408
|
+
const cfg = c.config;
|
|
50409
|
+
if ("device" in cfg) {
|
|
50410
|
+
detail += ` ${cfg["device"]}.${cfg["field"]} ${cfg["op"]} ${JSON.stringify(cfg["value"])}`;
|
|
50411
|
+
}
|
|
50412
|
+
}
|
|
50413
|
+
}
|
|
50414
|
+
const status = c.passed ? "passed" : "failed";
|
|
50415
|
+
return ` ${sym} ${detail.padEnd(36)} \u2192 ${status}`;
|
|
50416
|
+
}
|
|
50417
|
+
function formatTimestamp(iso) {
|
|
50418
|
+
const d = new Date(iso);
|
|
50419
|
+
return `${d.toISOString().slice(0, 10)} ${d.toISOString().slice(11, 19)}`;
|
|
50420
|
+
}
|
|
50421
|
+
function formatExplainText(record2, relatedAudit) {
|
|
50422
|
+
const lines = [];
|
|
50423
|
+
lines.push(`Rule: ${record2.rule.name} (version ${record2.rule.version})`);
|
|
50424
|
+
lines.push(`Evaluated: ${formatTimestamp(record2.t)} (${record2.evaluationMs}ms)`);
|
|
50425
|
+
const triggerDevice = record2.trigger.deviceId ? ` on ${record2.trigger.deviceId}` : "";
|
|
50426
|
+
lines.push(`Trigger: ${record2.trigger.source} ${record2.trigger.event}${triggerDevice}`);
|
|
50427
|
+
lines.push("");
|
|
50428
|
+
if (record2.conditions.length > 0) {
|
|
50429
|
+
lines.push("Conditions (evaluated in order):");
|
|
50430
|
+
for (const c of record2.conditions) {
|
|
50431
|
+
lines.push(conditionSummary(c));
|
|
50432
|
+
}
|
|
50433
|
+
lines.push("");
|
|
50434
|
+
}
|
|
50435
|
+
lines.push(`Decision: ${record2.decision}`);
|
|
50436
|
+
lines.push("");
|
|
50437
|
+
lines.push(`Related fireId: ${record2.fireId}`);
|
|
50438
|
+
const nonEval = relatedAudit.filter(
|
|
50439
|
+
(e) => e["kind"] !== "rule-evaluate"
|
|
50440
|
+
);
|
|
50441
|
+
if (nonEval.length > 0) {
|
|
50442
|
+
lines.push(`Audit trail (${nonEval.length} record${nonEval.length === 1 ? "" : "s"}):`);
|
|
50443
|
+
for (const e of nonEval) {
|
|
50444
|
+
const ts = formatTimestamp(e.t);
|
|
50445
|
+
lines.push(` ${e.kind.padEnd(20)} ${ts}`);
|
|
50446
|
+
}
|
|
50447
|
+
} else {
|
|
50448
|
+
lines.push(`Audit trail: (no related records${record2.decision === "blocked-by-condition" ? " \u2014 rule did not fire" : ""})`);
|
|
50449
|
+
}
|
|
50450
|
+
return lines.join("\n");
|
|
50451
|
+
}
|
|
50452
|
+
function formatExplainJson(record2, relatedAudit) {
|
|
50453
|
+
return JSON.stringify({ trace: record2, relatedAudit }, null, 2);
|
|
50454
|
+
}
|
|
50455
|
+
|
|
50456
|
+
// src/rules/simulate.ts
|
|
50457
|
+
init_cjs_shim();
|
|
50458
|
+
init_matcher();
|
|
50459
|
+
init_throttle();
|
|
50460
|
+
init_trace();
|
|
50461
|
+
init_trace();
|
|
50462
|
+
init_matcher();
|
|
50463
|
+
import fs17 from "node:fs";
|
|
50464
|
+
import path14 from "node:path";
|
|
50465
|
+
import os13 from "node:os";
|
|
50466
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
50467
|
+
var HOUR_MS = 60 * 60 * 1e3;
|
|
50468
|
+
var DEVICE_HISTORY_DIR = path14.join(os13.homedir(), ".switchbot", "device-history");
|
|
50469
|
+
async function simulateRule(opts) {
|
|
50470
|
+
const { rule, aliases = {}, liveLlm = false } = opts;
|
|
50471
|
+
const rv = ruleVersion(rule);
|
|
50472
|
+
const events = loadSourceEvents(opts);
|
|
50473
|
+
const windowStart = events.length > 0 ? new Date(Math.min(...events.map((e) => e.t.getTime()))) : new Date(Date.now() - 24 * HOUR_MS);
|
|
50474
|
+
const windowEnd = events.length > 0 ? new Date(Math.max(...events.map((e) => e.t.getTime()))) : /* @__PURE__ */ new Date();
|
|
50475
|
+
const counts = { wouldFire: 0, blocked: 0, throttled: 0, errored: 0, skippedLlm: 0 };
|
|
50476
|
+
const blockReasons = /* @__PURE__ */ new Map();
|
|
50477
|
+
const sampleFires = [];
|
|
50478
|
+
const traces = [];
|
|
50479
|
+
const throttle = new ThrottleGate();
|
|
50480
|
+
const cooldownMs = rule.cooldown ? parseMaxPerMs(rule.cooldown) : null;
|
|
50481
|
+
const throttleMs = rule.throttle ? parseMaxPerMs(rule.throttle.max_per) : null;
|
|
50482
|
+
const effectiveWindowMs = cooldownMs ?? throttleMs;
|
|
50483
|
+
for (const event of events) {
|
|
50484
|
+
const fireId = randomUUID5();
|
|
50485
|
+
const nowMs = event.t.getTime();
|
|
50486
|
+
if (rule.when.source === "mqtt") {
|
|
50487
|
+
const resolvedDevice = rule.when.device ? aliases[rule.when.device] ?? rule.when.device : void 0;
|
|
50488
|
+
if (!matchesMqttTrigger(rule.when, event, resolvedDevice)) continue;
|
|
50489
|
+
}
|
|
50490
|
+
const hasLlm = (rule.conditions ?? []).some((c) => c["llm"] !== void 0);
|
|
50491
|
+
if (hasLlm && !liveLlm) {
|
|
50492
|
+
counts.skippedLlm++;
|
|
50493
|
+
const fireEvent = {
|
|
50494
|
+
t: event.t.toISOString(),
|
|
50495
|
+
fireId,
|
|
50496
|
+
deviceId: event.deviceId,
|
|
50497
|
+
decision: "skipped-llm"
|
|
50498
|
+
};
|
|
50499
|
+
sampleFires.push(fireEvent);
|
|
50500
|
+
continue;
|
|
50501
|
+
}
|
|
50502
|
+
if (effectiveWindowMs !== null) {
|
|
50503
|
+
const check2 = throttle.check(rule.name, effectiveWindowMs, nowMs, event.deviceId);
|
|
50504
|
+
if (!check2.allowed) {
|
|
50505
|
+
counts.throttled++;
|
|
50506
|
+
const fireEvent = {
|
|
50507
|
+
t: event.t.toISOString(),
|
|
50508
|
+
fireId,
|
|
50509
|
+
deviceId: event.deviceId,
|
|
50510
|
+
decision: "throttled"
|
|
50511
|
+
};
|
|
50512
|
+
sampleFires.push(fireEvent);
|
|
50513
|
+
continue;
|
|
50514
|
+
}
|
|
50515
|
+
}
|
|
50516
|
+
const statusFetcher = buildStatusFetcher(event.t);
|
|
50517
|
+
let condResult;
|
|
50518
|
+
try {
|
|
50519
|
+
condResult = await evaluateConditions(rule.conditions, event.t, {
|
|
50520
|
+
aliases,
|
|
50521
|
+
fetchStatus: statusFetcher,
|
|
50522
|
+
event,
|
|
50523
|
+
ruleVersion: rv
|
|
50524
|
+
});
|
|
50525
|
+
} catch (err) {
|
|
50526
|
+
counts.errored++;
|
|
50527
|
+
sampleFires.push({ t: event.t.toISOString(), fireId, deviceId: event.deviceId, decision: "error", reason: String(err) });
|
|
50528
|
+
continue;
|
|
50529
|
+
}
|
|
50530
|
+
if (!condResult.matched) {
|
|
50531
|
+
counts.blocked++;
|
|
50532
|
+
const reason = condResult.failures[0] ?? "unknown";
|
|
50533
|
+
blockReasons.set(reason, (blockReasons.get(reason) ?? 0) + 1);
|
|
50534
|
+
sampleFires.push({ t: event.t.toISOString(), fireId, deviceId: event.deviceId, decision: "blocked-by-condition", reason });
|
|
50535
|
+
} else {
|
|
50536
|
+
counts.wouldFire++;
|
|
50537
|
+
throttle.record(rule.name, nowMs, event.deviceId);
|
|
50538
|
+
sampleFires.push({ t: event.t.toISOString(), fireId, deviceId: event.deviceId, decision: "would-fire" });
|
|
50539
|
+
}
|
|
50540
|
+
}
|
|
50541
|
+
let topBlockReason;
|
|
50542
|
+
let topBlockCount;
|
|
50543
|
+
if (blockReasons.size > 0) {
|
|
50544
|
+
let max = 0;
|
|
50545
|
+
for (const [reason, count] of blockReasons) {
|
|
50546
|
+
if (count > max) {
|
|
50547
|
+
max = count;
|
|
50548
|
+
topBlockReason = reason;
|
|
50549
|
+
topBlockCount = count;
|
|
50550
|
+
}
|
|
50551
|
+
}
|
|
50552
|
+
}
|
|
50553
|
+
return {
|
|
50554
|
+
ruleName: rule.name,
|
|
50555
|
+
ruleVersion: rv,
|
|
50556
|
+
windowStart,
|
|
50557
|
+
windowEnd,
|
|
50558
|
+
sourceEventCount: events.length,
|
|
50559
|
+
wouldFire: counts.wouldFire,
|
|
50560
|
+
blockedByCondition: counts.blocked,
|
|
50561
|
+
throttled: counts.throttled,
|
|
50562
|
+
errored: counts.errored,
|
|
50563
|
+
skippedLlm: counts.skippedLlm,
|
|
50564
|
+
topBlockReason,
|
|
50565
|
+
topBlockCount,
|
|
50566
|
+
sampleFires: sampleFires.slice(0, 20),
|
|
50567
|
+
traces
|
|
50568
|
+
};
|
|
50569
|
+
}
|
|
50570
|
+
function loadSourceEvents(opts) {
|
|
50571
|
+
if (opts.against) {
|
|
50572
|
+
if (!fs17.existsSync(opts.against)) return [];
|
|
50573
|
+
const lines2 = fs17.readFileSync(opts.against, "utf-8").split(/\r?\n/);
|
|
50574
|
+
const events = [];
|
|
50575
|
+
for (const line of lines2) {
|
|
50576
|
+
const trimmed = line.trim();
|
|
50577
|
+
if (!trimmed) continue;
|
|
50578
|
+
try {
|
|
50579
|
+
const raw = JSON.parse(trimmed);
|
|
50580
|
+
events.push({
|
|
50581
|
+
source: raw["source"] ?? "mqtt",
|
|
50582
|
+
event: String(raw["event"] ?? "device.shadow"),
|
|
50583
|
+
t: new Date(String(raw["t"] ?? (/* @__PURE__ */ new Date()).toISOString())),
|
|
50584
|
+
deviceId: raw["deviceId"],
|
|
50585
|
+
payload: raw["payload"]
|
|
50586
|
+
});
|
|
50587
|
+
} catch {
|
|
50588
|
+
}
|
|
50589
|
+
}
|
|
50590
|
+
return events;
|
|
50591
|
+
}
|
|
50592
|
+
const auditLog = opts.auditLog;
|
|
50593
|
+
if (!auditLog || !fs17.existsSync(auditLog)) return [];
|
|
50594
|
+
const sinceMs = opts.since ? parseSince(opts.since) : Date.now() - 24 * HOUR_MS;
|
|
50595
|
+
const sinceIso = new Date(sinceMs).toISOString();
|
|
50596
|
+
const lines = fs17.readFileSync(auditLog, "utf-8").split(/\r?\n/);
|
|
50597
|
+
const traceRecords = filterTraceRecords(lines, {
|
|
50598
|
+
ruleName: opts.rule.name,
|
|
50599
|
+
since: sinceIso
|
|
50600
|
+
});
|
|
50601
|
+
return traceRecords.map((r) => ({
|
|
50602
|
+
source: r.trigger.source,
|
|
50603
|
+
event: r.trigger.event,
|
|
50604
|
+
t: new Date(r.t),
|
|
50605
|
+
deviceId: r.trigger.deviceId
|
|
50606
|
+
}));
|
|
50607
|
+
}
|
|
50608
|
+
function parseSince(since) {
|
|
50609
|
+
if (since.includes("T") || since.includes("-")) {
|
|
50610
|
+
const d = new Date(since);
|
|
50611
|
+
if (!isNaN(d.getTime())) return d.getTime();
|
|
50612
|
+
}
|
|
50613
|
+
const m2 = /^(\d+)([smhd])$/.exec(since.trim());
|
|
50614
|
+
if (m2) {
|
|
50615
|
+
const n = parseInt(m2[1], 10);
|
|
50616
|
+
const unit = m2[2];
|
|
50617
|
+
const unitMs = unit === "s" ? 1e3 : unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5;
|
|
50618
|
+
return Date.now() - n * unitMs;
|
|
50619
|
+
}
|
|
50620
|
+
return Date.now() - 24 * HOUR_MS;
|
|
50621
|
+
}
|
|
50622
|
+
function buildStatusFetcher(asOf) {
|
|
50623
|
+
return async (deviceId) => {
|
|
50624
|
+
const histFile = path14.join(DEVICE_HISTORY_DIR, `${deviceId}.jsonl`);
|
|
50625
|
+
if (!fs17.existsSync(histFile)) return {};
|
|
50626
|
+
const lines = fs17.readFileSync(histFile, "utf-8").split(/\r?\n/);
|
|
50627
|
+
const asOfMs = asOf.getTime();
|
|
50628
|
+
let best;
|
|
50629
|
+
for (const line of lines) {
|
|
50630
|
+
const trimmed = line.trim();
|
|
50631
|
+
if (!trimmed) continue;
|
|
50632
|
+
try {
|
|
50633
|
+
const entry = JSON.parse(trimmed);
|
|
50634
|
+
const entryT = new Date(String(entry["t"] ?? 0)).getTime();
|
|
50635
|
+
if (entryT <= asOfMs) best = entry;
|
|
50636
|
+
else break;
|
|
50637
|
+
} catch {
|
|
50638
|
+
}
|
|
50639
|
+
}
|
|
50640
|
+
return best ?? {};
|
|
50641
|
+
};
|
|
50642
|
+
}
|
|
50643
|
+
|
|
50030
50644
|
// src/commands/mcp.ts
|
|
50031
50645
|
init_audit();
|
|
50032
50646
|
init_flags();
|
|
@@ -50045,13 +50659,13 @@ function collectPolicyDiff(left, right, at, out, limit) {
|
|
|
50045
50659
|
const maxLen = Math.max(left.length, right.length);
|
|
50046
50660
|
for (let i = 0; i < maxLen; i++) {
|
|
50047
50661
|
if (out.length >= limit) return;
|
|
50048
|
-
const
|
|
50662
|
+
const path29 = `${at}[${i}]`;
|
|
50049
50663
|
if (i >= left.length) {
|
|
50050
|
-
out.push({ path:
|
|
50664
|
+
out.push({ path: path29, kind: "added", after: right[i] });
|
|
50051
50665
|
} else if (i >= right.length) {
|
|
50052
|
-
out.push({ path:
|
|
50666
|
+
out.push({ path: path29, kind: "removed", before: left[i] });
|
|
50053
50667
|
} else {
|
|
50054
|
-
collectPolicyDiff(left[i], right[i],
|
|
50668
|
+
collectPolicyDiff(left[i], right[i], path29, out, limit);
|
|
50055
50669
|
}
|
|
50056
50670
|
}
|
|
50057
50671
|
return;
|
|
@@ -50060,15 +50674,15 @@ function collectPolicyDiff(left, right, at, out, limit) {
|
|
|
50060
50674
|
const keys = /* @__PURE__ */ new Set([...Object.keys(left), ...Object.keys(right)]);
|
|
50061
50675
|
for (const key of [...keys].sort()) {
|
|
50062
50676
|
if (out.length >= limit) return;
|
|
50063
|
-
const
|
|
50677
|
+
const path29 = at === "$" ? `$.${key}` : `${at}.${key}`;
|
|
50064
50678
|
const leftHas = Object.prototype.hasOwnProperty.call(left, key);
|
|
50065
50679
|
const rightHas = Object.prototype.hasOwnProperty.call(right, key);
|
|
50066
50680
|
if (!leftHas && rightHas) {
|
|
50067
|
-
out.push({ path:
|
|
50681
|
+
out.push({ path: path29, kind: "added", after: right[key] });
|
|
50068
50682
|
} else if (leftHas && !rightHas) {
|
|
50069
|
-
out.push({ path:
|
|
50683
|
+
out.push({ path: path29, kind: "removed", before: left[key] });
|
|
50070
50684
|
} else {
|
|
50071
|
-
collectPolicyDiff(left[key], right[key],
|
|
50685
|
+
collectPolicyDiff(left[key], right[key], path29, out, limit);
|
|
50072
50686
|
}
|
|
50073
50687
|
}
|
|
50074
50688
|
return;
|
|
@@ -50121,8 +50735,8 @@ function diffPolicyValues(leftDoc, rightDoc, leftSource, rightSource, maxChanges
|
|
|
50121
50735
|
// src/commands/mcp.ts
|
|
50122
50736
|
init_embedded_assets();
|
|
50123
50737
|
import { dirname as pathDirname, join as pathJoin } from "node:path";
|
|
50124
|
-
import
|
|
50125
|
-
import
|
|
50738
|
+
import os14 from "node:os";
|
|
50739
|
+
import fs18 from "node:fs";
|
|
50126
50740
|
var LATEST_SUPPORTED_VERSION = SUPPORTED_POLICY_SCHEMA_VERSIONS[SUPPORTED_POLICY_SCHEMA_VERSIONS.length - 1];
|
|
50127
50741
|
function mcpError(kind, code, message, options) {
|
|
50128
50742
|
const obj = { code, kind, message };
|
|
@@ -50151,7 +50765,7 @@ function apiErrorToMcpError(err) {
|
|
|
50151
50765
|
retryAfterMs: payload.retryAfterMs
|
|
50152
50766
|
});
|
|
50153
50767
|
}
|
|
50154
|
-
var DEFAULT_AUDIT_LOG_FILE = pathJoin(
|
|
50768
|
+
var DEFAULT_AUDIT_LOG_FILE = pathJoin(os14.homedir(), ".switchbot", "audit.log");
|
|
50155
50769
|
function resolveAuditRange(opts) {
|
|
50156
50770
|
if (opts.since && (opts.from || opts.to)) {
|
|
50157
50771
|
throw new Error("--since is mutually exclusive with --from/--to.");
|
|
@@ -51080,15 +51694,15 @@ API docs: https://github.com/OpenWonderLabs/SwitchBotAPI`
|
|
|
51080
51694
|
async ({ path: pathArg, force }) => {
|
|
51081
51695
|
const policyPath = resolvePolicyPath({ flag: pathArg });
|
|
51082
51696
|
const doForce = force === true;
|
|
51083
|
-
if (
|
|
51697
|
+
if (fs18.existsSync(policyPath) && !doForce) {
|
|
51084
51698
|
return mcpError("guard", 5, `refusing to overwrite existing policy at ${policyPath}`, {
|
|
51085
51699
|
hint: "pass force=true to overwrite, or choose a different path",
|
|
51086
51700
|
context: { policyPath }
|
|
51087
51701
|
});
|
|
51088
51702
|
}
|
|
51089
51703
|
const template = readPolicyExampleYaml();
|
|
51090
|
-
|
|
51091
|
-
|
|
51704
|
+
fs18.mkdirSync(pathDirname(policyPath), { recursive: true });
|
|
51705
|
+
fs18.writeFileSync(policyPath, template, { encoding: "utf-8" });
|
|
51092
51706
|
const structured = {
|
|
51093
51707
|
policyPath,
|
|
51094
51708
|
schemaVersion: CURRENT_POLICY_SCHEMA_VERSION,
|
|
@@ -51275,7 +51889,7 @@ API docs: https://github.com/OpenWonderLabs/SwitchBotAPI`
|
|
|
51275
51889
|
let leftSource = "";
|
|
51276
51890
|
let rightSource = "";
|
|
51277
51891
|
try {
|
|
51278
|
-
leftSource =
|
|
51892
|
+
leftSource = fs18.readFileSync(left_path, "utf-8");
|
|
51279
51893
|
} catch (err) {
|
|
51280
51894
|
if (err?.code === "ENOENT") {
|
|
51281
51895
|
return mcpError("usage", 2, `policy file not found: ${left_path}`, {
|
|
@@ -51285,7 +51899,7 @@ API docs: https://github.com/OpenWonderLabs/SwitchBotAPI`
|
|
|
51285
51899
|
return mcpError("runtime", 1, `failed to read ${left_path}: ${String(err)}`);
|
|
51286
51900
|
}
|
|
51287
51901
|
try {
|
|
51288
|
-
rightSource =
|
|
51902
|
+
rightSource = fs18.readFileSync(right_path, "utf-8");
|
|
51289
51903
|
} catch (err) {
|
|
51290
51904
|
if (err?.code === "ENOENT") {
|
|
51291
51905
|
return mcpError("usage", 2, `policy file not found: ${right_path}`, {
|
|
@@ -51725,6 +52339,130 @@ API docs: https://github.com/OpenWonderLabs/SwitchBotAPI`
|
|
|
51725
52339
|
}
|
|
51726
52340
|
}
|
|
51727
52341
|
);
|
|
52342
|
+
server.registerTool(
|
|
52343
|
+
"rules_explain",
|
|
52344
|
+
{
|
|
52345
|
+
title: "Show why a rule evaluation fired or was blocked",
|
|
52346
|
+
description: 'Read rule-evaluate trace records from the audit log and format them for inspection. Pass fire_id to explain a specific evaluation; or pass rule_name with last:true for the most recent evaluation; or pass rule_name + since for a window. Returns trace records only when automation.audit.evaluate_trace is "sampled" or "full".',
|
|
52347
|
+
_meta: { agentSafetyTier: "read" },
|
|
52348
|
+
inputSchema: external_exports.object({
|
|
52349
|
+
fire_id: external_exports.string().optional().describe("Specific fireId to explain."),
|
|
52350
|
+
rule_name: external_exports.string().optional().describe("Filter to this rule name."),
|
|
52351
|
+
since: external_exports.string().optional().describe("Duration string (e.g. 1h, 7d) \u2014 show evaluations in this window."),
|
|
52352
|
+
last: external_exports.boolean().optional().describe("Return only the most recent evaluation (requires rule_name)."),
|
|
52353
|
+
audit_log: external_exports.string().optional().describe(`Audit log path (default: ${pathJoin(os14.homedir(), ".switchbot", "audit.log")}).`)
|
|
52354
|
+
}).strict(),
|
|
52355
|
+
outputSchema: {
|
|
52356
|
+
records: external_exports.array(external_exports.unknown()).describe("Array of trace + relatedAudit objects."),
|
|
52357
|
+
count: external_exports.number().describe("Number of trace records returned.")
|
|
52358
|
+
}
|
|
52359
|
+
},
|
|
52360
|
+
async ({ fire_id, rule_name, since, last, audit_log }) => {
|
|
52361
|
+
const DEFAULT_AUDIT_PATH4 = pathJoin(os14.homedir(), ".switchbot", "audit.log");
|
|
52362
|
+
const auditFile = audit_log ?? DEFAULT_AUDIT_PATH4;
|
|
52363
|
+
const sinceIso = since ? new Date(Date.now() - (parseDurationToMs(since) ?? 0)).toISOString() : void 0;
|
|
52364
|
+
let records = loadTraceRecords(auditFile, {
|
|
52365
|
+
fireId: fire_id,
|
|
52366
|
+
ruleName: rule_name,
|
|
52367
|
+
since: sinceIso
|
|
52368
|
+
});
|
|
52369
|
+
if (records.length === 0) {
|
|
52370
|
+
return {
|
|
52371
|
+
content: [{ type: "text", text: 'No rule-evaluate trace records found. Check that automation.audit.evaluate_trace is "sampled" or "full".' }],
|
|
52372
|
+
structuredContent: { records: [], count: 0 }
|
|
52373
|
+
};
|
|
52374
|
+
}
|
|
52375
|
+
if (last) {
|
|
52376
|
+
records = [records[records.length - 1]];
|
|
52377
|
+
}
|
|
52378
|
+
const output = records.map((record2) => {
|
|
52379
|
+
const related = loadRelatedAudit(auditFile, record2.fireId);
|
|
52380
|
+
return JSON.parse(formatExplainJson(record2, related));
|
|
52381
|
+
});
|
|
52382
|
+
return {
|
|
52383
|
+
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
52384
|
+
structuredContent: { records: output, count: output.length }
|
|
52385
|
+
};
|
|
52386
|
+
}
|
|
52387
|
+
);
|
|
52388
|
+
server.registerTool(
|
|
52389
|
+
"rules_simulate",
|
|
52390
|
+
{
|
|
52391
|
+
title: "Simulate a rule against historical events",
|
|
52392
|
+
description: "Replay historical events from the audit log or a JSONL file against a rule definition and report would-fire / blocked-by-condition / throttled outcomes. Useful for validating a new or modified rule before deployment. Pass rule_yaml to test an unpublished rule, or rule_name + policy_path to test a deployed rule.",
|
|
52393
|
+
_meta: { agentSafetyTier: "read" },
|
|
52394
|
+
inputSchema: external_exports.object({
|
|
52395
|
+
rule_yaml: external_exports.string().optional().describe("Standalone rule YAML (takes precedence over policy_path + rule_name)."),
|
|
52396
|
+
policy_path: external_exports.string().optional().describe("Path to policy.yaml (defaults to ~/.switchbot/policy.yaml)."),
|
|
52397
|
+
rule_name: external_exports.string().optional().describe("Name of the rule in policy.yaml to simulate."),
|
|
52398
|
+
since: external_exports.string().optional().describe("Replay events from this window (e.g. 7d, 24h)."),
|
|
52399
|
+
against: external_exports.string().optional().describe("JSONL file path of EngineEvent objects to replay."),
|
|
52400
|
+
live_llm: external_exports.boolean().optional().describe("Allow live LLM calls for llm conditions (default: skip and report as would-call)."),
|
|
52401
|
+
audit_log: external_exports.string().optional().describe(`Audit log path (default: ${pathJoin(os14.homedir(), ".switchbot", "audit.log")}).`)
|
|
52402
|
+
}).strict(),
|
|
52403
|
+
outputSchema: {
|
|
52404
|
+
report: external_exports.unknown().describe("SimulateReport object.")
|
|
52405
|
+
}
|
|
52406
|
+
},
|
|
52407
|
+
async ({ rule_yaml, policy_path, rule_name, since, against, live_llm, audit_log }) => {
|
|
52408
|
+
const DEFAULT_AUDIT_PATH4 = pathJoin(os14.homedir(), ".switchbot", "audit.log");
|
|
52409
|
+
const auditFile = audit_log ?? DEFAULT_AUDIT_PATH4;
|
|
52410
|
+
let rule;
|
|
52411
|
+
if (rule_yaml) {
|
|
52412
|
+
try {
|
|
52413
|
+
rule = (0, import_yaml7.parse)(rule_yaml);
|
|
52414
|
+
} catch (err) {
|
|
52415
|
+
return {
|
|
52416
|
+
content: [{ type: "text", text: `Failed to parse rule_yaml: ${String(err)}` }],
|
|
52417
|
+
structuredContent: { report: null }
|
|
52418
|
+
};
|
|
52419
|
+
}
|
|
52420
|
+
} else if (policy_path || rule_name) {
|
|
52421
|
+
const { loadPolicyFile: loadPolicyFile2 } = await Promise.resolve().then(() => (init_load(), load_exports));
|
|
52422
|
+
const policyFile = policy_path ?? pathJoin(os14.homedir(), ".switchbot", "policy.yaml");
|
|
52423
|
+
try {
|
|
52424
|
+
const policy = loadPolicyFile2(policyFile);
|
|
52425
|
+
const data = policy.data ?? {};
|
|
52426
|
+
const found = data.automation?.rules?.find((r) => r.name === rule_name);
|
|
52427
|
+
if (!found) {
|
|
52428
|
+
return {
|
|
52429
|
+
content: [{ type: "text", text: `Rule "${rule_name}" not found in ${policyFile}.` }],
|
|
52430
|
+
structuredContent: { report: null }
|
|
52431
|
+
};
|
|
52432
|
+
}
|
|
52433
|
+
rule = found;
|
|
52434
|
+
} catch (err) {
|
|
52435
|
+
return {
|
|
52436
|
+
content: [{ type: "text", text: `Failed to load policy: ${String(err)}` }],
|
|
52437
|
+
structuredContent: { report: null }
|
|
52438
|
+
};
|
|
52439
|
+
}
|
|
52440
|
+
} else {
|
|
52441
|
+
return {
|
|
52442
|
+
content: [{ type: "text", text: "Provide rule_yaml or (policy_path + rule_name) to specify the rule to simulate." }],
|
|
52443
|
+
structuredContent: { report: null }
|
|
52444
|
+
};
|
|
52445
|
+
}
|
|
52446
|
+
try {
|
|
52447
|
+
const report = await simulateRule({
|
|
52448
|
+
rule,
|
|
52449
|
+
since,
|
|
52450
|
+
against,
|
|
52451
|
+
auditLog: auditFile,
|
|
52452
|
+
liveLlm: live_llm ?? false
|
|
52453
|
+
});
|
|
52454
|
+
return {
|
|
52455
|
+
content: [{ type: "text", text: JSON.stringify(report, null, 2) }],
|
|
52456
|
+
structuredContent: { report }
|
|
52457
|
+
};
|
|
52458
|
+
} catch (err) {
|
|
52459
|
+
return {
|
|
52460
|
+
content: [{ type: "text", text: `Simulate error: ${String(err)}` }],
|
|
52461
|
+
structuredContent: { report: null }
|
|
52462
|
+
};
|
|
52463
|
+
}
|
|
52464
|
+
}
|
|
52465
|
+
);
|
|
51728
52466
|
server.registerTool(
|
|
51729
52467
|
"policy_add_rule",
|
|
51730
52468
|
{
|
|
@@ -51995,7 +52733,7 @@ process_uptime_seconds ${Math.floor(process.uptime())}
|
|
|
51995
52733
|
}
|
|
51996
52734
|
if (profile) {
|
|
51997
52735
|
const envCredsPresent = !!(process.env.SWITCHBOT_TOKEN && process.env.SWITCHBOT_SECRET);
|
|
51998
|
-
if (!envCredsPresent && !
|
|
52736
|
+
if (!envCredsPresent && !fs18.existsSync(profileFilePath(profile))) {
|
|
51999
52737
|
res.writeHead(401, { "Content-Type": "application/json" });
|
|
52000
52738
|
res.end(JSON.stringify({
|
|
52001
52739
|
jsonrpc: "2.0",
|
|
@@ -52631,18 +53369,18 @@ var StdoutSink = class {
|
|
|
52631
53369
|
|
|
52632
53370
|
// src/sinks/file.ts
|
|
52633
53371
|
init_cjs_shim();
|
|
52634
|
-
import
|
|
52635
|
-
import
|
|
53372
|
+
import fs19 from "node:fs";
|
|
53373
|
+
import path15 from "node:path";
|
|
52636
53374
|
var FileSink = class {
|
|
52637
53375
|
filePath;
|
|
52638
53376
|
constructor(filePath) {
|
|
52639
|
-
this.filePath =
|
|
52640
|
-
const dir =
|
|
52641
|
-
if (!
|
|
53377
|
+
this.filePath = path15.resolve(filePath);
|
|
53378
|
+
const dir = path15.dirname(this.filePath);
|
|
53379
|
+
if (!fs19.existsSync(dir)) fs19.mkdirSync(dir, { recursive: true });
|
|
52642
53380
|
}
|
|
52643
53381
|
async write(event) {
|
|
52644
53382
|
try {
|
|
52645
|
-
|
|
53383
|
+
fs19.appendFileSync(this.filePath, JSON.stringify(event) + "\n", { encoding: "utf-8" });
|
|
52646
53384
|
} catch {
|
|
52647
53385
|
}
|
|
52648
53386
|
}
|
|
@@ -53311,9 +54049,9 @@ init_catalog();
|
|
|
53311
54049
|
init_config();
|
|
53312
54050
|
init_cache();
|
|
53313
54051
|
init_quota();
|
|
53314
|
-
import
|
|
53315
|
-
import
|
|
53316
|
-
import
|
|
54052
|
+
import fs22 from "node:fs";
|
|
54053
|
+
import os17 from "node:os";
|
|
54054
|
+
import path18 from "node:path";
|
|
53317
54055
|
import { execSync } from "node:child_process";
|
|
53318
54056
|
|
|
53319
54057
|
// src/commands/agent-bootstrap.ts
|
|
@@ -53549,38 +54287,38 @@ init_request_context();
|
|
|
53549
54287
|
|
|
53550
54288
|
// src/lib/daemon-state.ts
|
|
53551
54289
|
init_cjs_shim();
|
|
53552
|
-
import
|
|
53553
|
-
import
|
|
53554
|
-
import
|
|
54290
|
+
import fs20 from "node:fs";
|
|
54291
|
+
import os15 from "node:os";
|
|
54292
|
+
import path16 from "node:path";
|
|
53555
54293
|
function getStateDir() {
|
|
53556
|
-
return
|
|
54294
|
+
return path16.join(os15.homedir(), ".switchbot");
|
|
53557
54295
|
}
|
|
53558
54296
|
function getDaemonPidFile() {
|
|
53559
|
-
return
|
|
54297
|
+
return path16.join(getStateDir(), "daemon.pid");
|
|
53560
54298
|
}
|
|
53561
54299
|
function getDaemonLogFile() {
|
|
53562
|
-
return
|
|
54300
|
+
return path16.join(getStateDir(), "daemon.log");
|
|
53563
54301
|
}
|
|
53564
54302
|
function getDaemonStateFile() {
|
|
53565
|
-
return
|
|
54303
|
+
return path16.join(getStateDir(), "daemon.state.json");
|
|
53566
54304
|
}
|
|
53567
54305
|
function getHealthzPidFile() {
|
|
53568
|
-
return
|
|
54306
|
+
return path16.join(getStateDir(), "healthz.pid");
|
|
53569
54307
|
}
|
|
53570
54308
|
var DAEMON_PID_FILE = getDaemonPidFile();
|
|
53571
54309
|
var DAEMON_LOG_FILE = getDaemonLogFile();
|
|
53572
54310
|
var DAEMON_STATE_FILE = getDaemonStateFile();
|
|
53573
54311
|
var HEALTHZ_PID_FILE = getHealthzPidFile();
|
|
53574
54312
|
function ensureStateDir() {
|
|
53575
|
-
|
|
54313
|
+
fs20.mkdirSync(getStateDir(), { recursive: true, mode: 448 });
|
|
53576
54314
|
}
|
|
53577
54315
|
function writeDaemonState(state) {
|
|
53578
54316
|
ensureStateDir();
|
|
53579
|
-
|
|
54317
|
+
fs20.writeFileSync(getDaemonStateFile(), JSON.stringify(state, null, 2), { mode: 384 });
|
|
53580
54318
|
}
|
|
53581
54319
|
function readDaemonState() {
|
|
53582
54320
|
try {
|
|
53583
|
-
const raw =
|
|
54321
|
+
const raw = fs20.readFileSync(getDaemonStateFile(), "utf-8");
|
|
53584
54322
|
return JSON.parse(raw);
|
|
53585
54323
|
} catch {
|
|
53586
54324
|
return null;
|
|
@@ -53589,26 +54327,26 @@ function readDaemonState() {
|
|
|
53589
54327
|
|
|
53590
54328
|
// src/rules/pid-file.ts
|
|
53591
54329
|
init_cjs_shim();
|
|
53592
|
-
import
|
|
53593
|
-
import
|
|
53594
|
-
import
|
|
53595
|
-
var DEFAULT_DIR =
|
|
54330
|
+
import fs21 from "node:fs";
|
|
54331
|
+
import os16 from "node:os";
|
|
54332
|
+
import path17 from "node:path";
|
|
54333
|
+
var DEFAULT_DIR = path17.join(os16.homedir(), ".switchbot");
|
|
53596
54334
|
function getDefaultPidFilePaths() {
|
|
53597
54335
|
return {
|
|
53598
54336
|
dir: DEFAULT_DIR,
|
|
53599
|
-
pidFile:
|
|
53600
|
-
reloadFile:
|
|
54337
|
+
pidFile: path17.join(DEFAULT_DIR, "rules.pid"),
|
|
54338
|
+
reloadFile: path17.join(DEFAULT_DIR, "rules.reload")
|
|
53601
54339
|
};
|
|
53602
54340
|
}
|
|
53603
54341
|
function writePidFile(pidFile, pid = process.pid) {
|
|
53604
|
-
const dir =
|
|
53605
|
-
|
|
53606
|
-
|
|
54342
|
+
const dir = path17.dirname(pidFile);
|
|
54343
|
+
fs21.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
54344
|
+
fs21.writeFileSync(pidFile, `${pid}
|
|
53607
54345
|
`, { mode: 384 });
|
|
53608
54346
|
}
|
|
53609
54347
|
function readPidFile(pidFile) {
|
|
53610
54348
|
try {
|
|
53611
|
-
const raw =
|
|
54349
|
+
const raw = fs21.readFileSync(pidFile, "utf-8").trim();
|
|
53612
54350
|
const n = Number(raw);
|
|
53613
54351
|
return Number.isInteger(n) && n > 0 ? n : null;
|
|
53614
54352
|
} catch {
|
|
@@ -53618,20 +54356,20 @@ function readPidFile(pidFile) {
|
|
|
53618
54356
|
function clearPidFile(pidFile, pid = process.pid) {
|
|
53619
54357
|
try {
|
|
53620
54358
|
const existing = readPidFile(pidFile);
|
|
53621
|
-
if (existing === pid)
|
|
54359
|
+
if (existing === pid) fs21.unlinkSync(pidFile);
|
|
53622
54360
|
} catch {
|
|
53623
54361
|
}
|
|
53624
54362
|
}
|
|
53625
54363
|
function writeReloadSentinel(reloadFile) {
|
|
53626
|
-
const dir =
|
|
53627
|
-
|
|
53628
|
-
|
|
54364
|
+
const dir = path17.dirname(reloadFile);
|
|
54365
|
+
fs21.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
54366
|
+
fs21.writeFileSync(reloadFile, `${Date.now()}
|
|
53629
54367
|
`, { mode: 384 });
|
|
53630
54368
|
}
|
|
53631
54369
|
function consumeReloadSentinel(reloadFile) {
|
|
53632
54370
|
try {
|
|
53633
|
-
if (!
|
|
53634
|
-
|
|
54371
|
+
if (!fs21.existsSync(reloadFile)) return false;
|
|
54372
|
+
fs21.unlinkSync(reloadFile);
|
|
53635
54373
|
return true;
|
|
53636
54374
|
} catch {
|
|
53637
54375
|
return false;
|
|
@@ -53702,7 +54440,7 @@ async function checkCredentials() {
|
|
|
53702
54440
|
};
|
|
53703
54441
|
}
|
|
53704
54442
|
const file2 = configFilePath();
|
|
53705
|
-
if (!
|
|
54443
|
+
if (!fs22.existsSync(file2)) {
|
|
53706
54444
|
return {
|
|
53707
54445
|
name: "credentials",
|
|
53708
54446
|
status: "fail",
|
|
@@ -53717,7 +54455,7 @@ async function checkCredentials() {
|
|
|
53717
54455
|
};
|
|
53718
54456
|
}
|
|
53719
54457
|
try {
|
|
53720
|
-
const raw =
|
|
54458
|
+
const raw = fs22.readFileSync(file2, "utf-8");
|
|
53721
54459
|
const cfg = JSON.parse(raw);
|
|
53722
54460
|
if (!cfg.token || !cfg.secret) {
|
|
53723
54461
|
return {
|
|
@@ -53764,8 +54502,8 @@ async function checkCredentials() {
|
|
|
53764
54502
|
}
|
|
53765
54503
|
}
|
|
53766
54504
|
function checkProfiles() {
|
|
53767
|
-
const dir =
|
|
53768
|
-
if (!
|
|
54505
|
+
const dir = path18.join(os17.homedir(), ".switchbot", "profiles");
|
|
54506
|
+
if (!fs22.existsSync(dir)) {
|
|
53769
54507
|
return { name: "profiles", status: "ok", detail: "no profile dir (default profile only)" };
|
|
53770
54508
|
}
|
|
53771
54509
|
const profiles = listProfiles();
|
|
@@ -53872,8 +54610,8 @@ function checkCache() {
|
|
|
53872
54610
|
}
|
|
53873
54611
|
}
|
|
53874
54612
|
function checkQuotaFile() {
|
|
53875
|
-
const p2 =
|
|
53876
|
-
if (!
|
|
54613
|
+
const p2 = path18.join(os17.homedir(), ".switchbot", "quota.json");
|
|
54614
|
+
if (!fs22.existsSync(p2)) {
|
|
53877
54615
|
return {
|
|
53878
54616
|
name: "quota",
|
|
53879
54617
|
status: "ok",
|
|
@@ -53886,7 +54624,7 @@ function checkQuotaFile() {
|
|
|
53886
54624
|
};
|
|
53887
54625
|
}
|
|
53888
54626
|
try {
|
|
53889
|
-
const raw =
|
|
54627
|
+
const raw = fs22.readFileSync(p2, "utf-8");
|
|
53890
54628
|
JSON.parse(raw);
|
|
53891
54629
|
} catch {
|
|
53892
54630
|
return {
|
|
@@ -53966,8 +54704,8 @@ function checkInventoryConsistency() {
|
|
|
53966
54704
|
};
|
|
53967
54705
|
}
|
|
53968
54706
|
function checkAudit() {
|
|
53969
|
-
const p2 =
|
|
53970
|
-
if (!
|
|
54707
|
+
const p2 = path18.join(os17.homedir(), ".switchbot", "audit.log");
|
|
54708
|
+
if (!fs22.existsSync(p2)) {
|
|
53971
54709
|
return {
|
|
53972
54710
|
name: "audit",
|
|
53973
54711
|
status: "ok",
|
|
@@ -53979,7 +54717,7 @@ function checkAudit() {
|
|
|
53979
54717
|
};
|
|
53980
54718
|
}
|
|
53981
54719
|
try {
|
|
53982
|
-
const raw =
|
|
54720
|
+
const raw = fs22.readFileSync(p2, "utf-8");
|
|
53983
54721
|
const since = Date.now() - 24 * 60 * 60 * 1e3;
|
|
53984
54722
|
const recent = [];
|
|
53985
54723
|
let total = 0;
|
|
@@ -54179,7 +54917,7 @@ function checkPathDiscoverability() {
|
|
|
54179
54917
|
let npmBinDir = null;
|
|
54180
54918
|
try {
|
|
54181
54919
|
const prefix = execSync("npm prefix -g", { timeout: 4e3, encoding: "utf-8" }).trim();
|
|
54182
|
-
npmBinDir = isWindows ? prefix :
|
|
54920
|
+
npmBinDir = isWindows ? prefix : path18.join(prefix, "bin");
|
|
54183
54921
|
} catch {
|
|
54184
54922
|
}
|
|
54185
54923
|
let binaryOnPath = false;
|
|
@@ -54209,7 +54947,7 @@ function checkPathDiscoverability() {
|
|
|
54209
54947
|
};
|
|
54210
54948
|
}
|
|
54211
54949
|
const currentPath = process.env.PATH ?? "";
|
|
54212
|
-
const missingSegment = npmBinDir && !currentPath.split(
|
|
54950
|
+
const missingSegment = npmBinDir && !currentPath.split(path18.delimiter).includes(npmBinDir) ? npmBinDir : null;
|
|
54213
54951
|
const currentShell = detectShellFlavor();
|
|
54214
54952
|
const shellFix = buildPathFix(currentShell, missingSegment, npmBinDir);
|
|
54215
54953
|
return {
|
|
@@ -54310,9 +55048,9 @@ function checkMqtt() {
|
|
|
54310
55048
|
};
|
|
54311
55049
|
}
|
|
54312
55050
|
const file2 = configFilePath();
|
|
54313
|
-
if (
|
|
55051
|
+
if (fs22.existsSync(file2)) {
|
|
54314
55052
|
try {
|
|
54315
|
-
const cfg = JSON.parse(
|
|
55053
|
+
const cfg = JSON.parse(fs22.readFileSync(file2, "utf-8"));
|
|
54316
55054
|
if (cfg.token && cfg.secret) {
|
|
54317
55055
|
return {
|
|
54318
55056
|
name: "mqtt",
|
|
@@ -54339,9 +55077,9 @@ async function checkMqttProbe() {
|
|
|
54339
55077
|
creds = { token, secret };
|
|
54340
55078
|
} else {
|
|
54341
55079
|
const file2 = configFilePath();
|
|
54342
|
-
if (
|
|
55080
|
+
if (fs22.existsSync(file2)) {
|
|
54343
55081
|
try {
|
|
54344
|
-
const cfg = JSON.parse(
|
|
55082
|
+
const cfg = JSON.parse(fs22.readFileSync(file2, "utf-8"));
|
|
54345
55083
|
if (cfg.token && cfg.secret) {
|
|
54346
55084
|
creds = { token: cfg.token, secret: cfg.secret };
|
|
54347
55085
|
}
|
|
@@ -54435,10 +55173,12 @@ function checkNotifyConnectivity() {
|
|
|
54435
55173
|
return { name: "notify-connectivity", status: "ok", detail: { present: false, message: "policy file could not be loaded" } };
|
|
54436
55174
|
}
|
|
54437
55175
|
const policy = loaded.data;
|
|
54438
|
-
const
|
|
55176
|
+
const rawRules = policy?.automation?.rules;
|
|
55177
|
+
const rules = Array.isArray(rawRules) ? rawRules : [];
|
|
54439
55178
|
const webhookUrls = [];
|
|
54440
55179
|
for (const rule of rules) {
|
|
54441
|
-
|
|
55180
|
+
const then = rule.then;
|
|
55181
|
+
for (const action of Array.isArray(then) ? then : []) {
|
|
54442
55182
|
if (action.type === "notify" && (action.channel === "webhook" || action.channel === "openclaw") && action.to) {
|
|
54443
55183
|
webhookUrls.push(action.to);
|
|
54444
55184
|
}
|
|
@@ -54812,9 +55552,9 @@ init_arg_parsers();
|
|
|
54812
55552
|
init_output();
|
|
54813
55553
|
init_audit();
|
|
54814
55554
|
init_devices();
|
|
54815
|
-
import
|
|
54816
|
-
import
|
|
54817
|
-
var DEFAULT_AUDIT =
|
|
55555
|
+
import path19 from "node:path";
|
|
55556
|
+
import os18 from "node:os";
|
|
55557
|
+
var DEFAULT_AUDIT = path19.join(os18.homedir(), ".switchbot", "audit.log");
|
|
54818
55558
|
function registerHistoryCommand(program3) {
|
|
54819
55559
|
const history = program3.command("history").description("View and replay SwitchBot commands recorded via --audit-log").addHelpText("after", `
|
|
54820
55560
|
Every 'devices command' run with --audit-log is appended as JSONL to the
|
|
@@ -55672,9 +56412,9 @@ init_load();
|
|
|
55672
56412
|
init_validate();
|
|
55673
56413
|
init_types();
|
|
55674
56414
|
init_engine();
|
|
55675
|
-
import
|
|
55676
|
-
import
|
|
55677
|
-
import
|
|
56415
|
+
import fs24 from "node:fs";
|
|
56416
|
+
import os20 from "node:os";
|
|
56417
|
+
import path21 from "node:path";
|
|
55678
56418
|
|
|
55679
56419
|
// src/rules/conflict-analyzer.ts
|
|
55680
56420
|
init_cjs_shim();
|
|
@@ -55693,9 +56433,9 @@ var OPPOSING_PAIRS = [
|
|
|
55693
56433
|
["volumeUp", "volumeDown"],
|
|
55694
56434
|
["fanSpeedUp", "fanSpeedDown"]
|
|
55695
56435
|
];
|
|
55696
|
-
var
|
|
56436
|
+
var HIGH_FREQ_EVENTS2 = ["device.shadow", "*"];
|
|
55697
56437
|
function isHighFreqEvent(event) {
|
|
55698
|
-
return
|
|
56438
|
+
return HIGH_FREQ_EVENTS2.includes(event);
|
|
55699
56439
|
}
|
|
55700
56440
|
function commandsAreOpposing(a, b2) {
|
|
55701
56441
|
for (const [x2, y] of OPPOSING_PAIRS) {
|
|
@@ -55837,9 +56577,9 @@ init_client2();
|
|
|
55837
56577
|
|
|
55838
56578
|
// src/rules/webhook-token.ts
|
|
55839
56579
|
init_cjs_shim();
|
|
55840
|
-
import
|
|
55841
|
-
import
|
|
55842
|
-
import
|
|
56580
|
+
import fs23 from "node:fs";
|
|
56581
|
+
import os19 from "node:os";
|
|
56582
|
+
import path20 from "node:path";
|
|
55843
56583
|
import { randomBytes } from "node:crypto";
|
|
55844
56584
|
var ENV_TOKEN = "SWITCHBOT_WEBHOOK_TOKEN";
|
|
55845
56585
|
var DEFAULT_FILE = ".switchbot/webhook-token";
|
|
@@ -55847,7 +56587,7 @@ var WebhookTokenStore = class {
|
|
|
55847
56587
|
filePath;
|
|
55848
56588
|
envLookup;
|
|
55849
56589
|
constructor(opts = {}) {
|
|
55850
|
-
this.filePath = opts.filePath ??
|
|
56590
|
+
this.filePath = opts.filePath ?? path20.join(os19.homedir(), DEFAULT_FILE);
|
|
55851
56591
|
this.envLookup = opts.envLookup ?? (() => process.env[ENV_TOKEN]);
|
|
55852
56592
|
}
|
|
55853
56593
|
/**
|
|
@@ -55871,7 +56611,7 @@ var WebhookTokenStore = class {
|
|
|
55871
56611
|
*/
|
|
55872
56612
|
readFromDisk() {
|
|
55873
56613
|
try {
|
|
55874
|
-
const raw =
|
|
56614
|
+
const raw = fs23.readFileSync(this.filePath, "utf-8").trim();
|
|
55875
56615
|
return raw.length > 0 ? raw : null;
|
|
55876
56616
|
} catch (err) {
|
|
55877
56617
|
if (err.code === "ENOENT") return null;
|
|
@@ -55888,12 +56628,12 @@ var WebhookTokenStore = class {
|
|
|
55888
56628
|
return this.filePath;
|
|
55889
56629
|
}
|
|
55890
56630
|
writeToDisk(token) {
|
|
55891
|
-
const dir =
|
|
55892
|
-
|
|
55893
|
-
|
|
56631
|
+
const dir = path20.dirname(this.filePath);
|
|
56632
|
+
fs23.mkdirSync(dir, { recursive: true });
|
|
56633
|
+
fs23.writeFileSync(this.filePath, `${token}
|
|
55894
56634
|
`, { mode: 384 });
|
|
55895
56635
|
try {
|
|
55896
|
-
|
|
56636
|
+
fs23.chmodSync(this.filePath, 384);
|
|
55897
56637
|
} catch {
|
|
55898
56638
|
}
|
|
55899
56639
|
}
|
|
@@ -55978,18 +56718,18 @@ function aggregateRuleAudits(entries) {
|
|
|
55978
56718
|
}
|
|
55979
56719
|
|
|
55980
56720
|
// src/commands/rules.ts
|
|
55981
|
-
var DEFAULT_AUDIT_PATH2 =
|
|
56721
|
+
var DEFAULT_AUDIT_PATH2 = path21.join(os20.homedir(), ".switchbot", "audit.log");
|
|
55982
56722
|
function loadAutomation(policyPathFlag) {
|
|
55983
|
-
const
|
|
56723
|
+
const path29 = resolvePolicyPath({ flag: policyPathFlag });
|
|
55984
56724
|
let loaded;
|
|
55985
56725
|
try {
|
|
55986
|
-
loaded = loadPolicyFile(
|
|
56726
|
+
loaded = loadPolicyFile(path29);
|
|
55987
56727
|
} catch (err) {
|
|
55988
56728
|
if (err instanceof PolicyFileNotFoundError) {
|
|
55989
56729
|
exitWithError({
|
|
55990
56730
|
code: 2,
|
|
55991
56731
|
kind: "usage",
|
|
55992
|
-
message: `policy file not found: ${
|
|
56732
|
+
message: `policy file not found: ${path29}`,
|
|
55993
56733
|
extra: { subKind: "file-not-found" }
|
|
55994
56734
|
});
|
|
55995
56735
|
}
|
|
@@ -55997,7 +56737,7 @@ function loadAutomation(policyPathFlag) {
|
|
|
55997
56737
|
exitWithError({
|
|
55998
56738
|
code: 3,
|
|
55999
56739
|
kind: "runtime",
|
|
56000
|
-
message: `YAML parse error in ${
|
|
56740
|
+
message: `YAML parse error in ${path29}: ${err.message}`,
|
|
56001
56741
|
extra: { subKind: "yaml-parse", errors: err.yamlErrors }
|
|
56002
56742
|
});
|
|
56003
56743
|
}
|
|
@@ -56009,7 +56749,7 @@ function loadAutomation(policyPathFlag) {
|
|
|
56009
56749
|
code: 4,
|
|
56010
56750
|
kind: "runtime",
|
|
56011
56751
|
message: "policy file failed schema validation. Run `switchbot policy validate` for details.",
|
|
56012
|
-
extra: { subKind: "invalid-policy", path:
|
|
56752
|
+
extra: { subKind: "invalid-policy", path: path29 }
|
|
56013
56753
|
});
|
|
56014
56754
|
}
|
|
56015
56755
|
const data = loaded.data ?? {};
|
|
@@ -56023,7 +56763,7 @@ function loadAutomation(policyPathFlag) {
|
|
|
56023
56763
|
}
|
|
56024
56764
|
const rawQH = data.quiet_hours;
|
|
56025
56765
|
const quietHours = rawQH && typeof rawQH.start === "string" && typeof rawQH.end === "string" ? { start: rawQH.start, end: rawQH.end } : null;
|
|
56026
|
-
return { path:
|
|
56766
|
+
return { path: path29, automation, aliases, schemaVersion: result.schemaVersion, quietHours };
|
|
56027
56767
|
}
|
|
56028
56768
|
function describeTrigger(rule) {
|
|
56029
56769
|
const t = rule.when;
|
|
@@ -56287,7 +57027,7 @@ function registerTail(rules) {
|
|
|
56287
57027
|
rules.command("tail").description("Stream rule-* entries from the audit log.").option("--file <path>", `Audit log path (default ${DEFAULT_AUDIT_PATH2})`).option("--since <duration>", "Only entries newer than this window (e.g. 1h, 30m, 7d).").option("--rule <name>", "Filter to a single rule name.").option("-f, --follow", "Keep the process open and stream new lines as they arrive.").action(async (opts) => {
|
|
56288
57028
|
const file2 = opts.file ?? DEFAULT_AUDIT_PATH2;
|
|
56289
57029
|
const sinceMs = resolveSinceMs(opts.since);
|
|
56290
|
-
const existing =
|
|
57030
|
+
const existing = fs24.existsSync(file2) ? readAudit(file2) : [];
|
|
56291
57031
|
const filtered = filterRuleAudits(existing, { sinceMs, ruleName: opts.rule });
|
|
56292
57032
|
if (isJsonMode()) {
|
|
56293
57033
|
for (const e of filtered) console.log(JSON.stringify(e));
|
|
@@ -56299,7 +57039,7 @@ function registerTail(rules) {
|
|
|
56299
57039
|
for (const e of filtered) console.log(formatAuditLine(e));
|
|
56300
57040
|
}
|
|
56301
57041
|
if (!opts.follow) return;
|
|
56302
|
-
let offset =
|
|
57042
|
+
let offset = fs24.existsSync(file2) ? fs24.statSync(file2).size : 0;
|
|
56303
57043
|
let buffer = "";
|
|
56304
57044
|
const emit = (line) => {
|
|
56305
57045
|
const trimmed = line.trim();
|
|
@@ -56316,21 +57056,21 @@ function registerTail(rules) {
|
|
|
56316
57056
|
else console.log(formatAuditLine(entry));
|
|
56317
57057
|
};
|
|
56318
57058
|
const poll = setInterval(() => {
|
|
56319
|
-
if (!
|
|
56320
|
-
const size =
|
|
57059
|
+
if (!fs24.existsSync(file2)) return;
|
|
57060
|
+
const size = fs24.statSync(file2).size;
|
|
56321
57061
|
if (size < offset) {
|
|
56322
57062
|
offset = 0;
|
|
56323
57063
|
buffer = "";
|
|
56324
57064
|
}
|
|
56325
57065
|
if (size === offset) return;
|
|
56326
|
-
const fd =
|
|
57066
|
+
const fd = fs24.openSync(file2, "r");
|
|
56327
57067
|
try {
|
|
56328
57068
|
const chunk = Buffer.alloc(size - offset);
|
|
56329
|
-
|
|
57069
|
+
fs24.readSync(fd, chunk, 0, chunk.length, offset);
|
|
56330
57070
|
offset = size;
|
|
56331
57071
|
buffer += chunk.toString("utf-8");
|
|
56332
57072
|
} finally {
|
|
56333
|
-
|
|
57073
|
+
fs24.closeSync(fd);
|
|
56334
57074
|
}
|
|
56335
57075
|
let newline = buffer.indexOf("\n");
|
|
56336
57076
|
while (newline !== -1) {
|
|
@@ -56370,7 +57110,7 @@ function formatReplayTable(report) {
|
|
|
56370
57110
|
function registerReplay(rules) {
|
|
56371
57111
|
rules.command("replay").description("Aggregate rule-* audit entries per rule (fire/throttle/error counts).").option("--file <path>", `Audit log path (default ${DEFAULT_AUDIT_PATH2})`).option("--since <duration>", "Only entries newer than this window (e.g. 1h, 7d).").option("--rule <name>", "Filter to a single rule name.").action((opts) => {
|
|
56372
57112
|
const file2 = opts.file ?? DEFAULT_AUDIT_PATH2;
|
|
56373
|
-
const entries =
|
|
57113
|
+
const entries = fs24.existsSync(file2) ? readAudit(file2) : [];
|
|
56374
57114
|
const sinceMs = resolveSinceMs(opts.since);
|
|
56375
57115
|
const filtered = filterRuleAudits(entries, {
|
|
56376
57116
|
sinceMs,
|
|
@@ -56490,7 +57230,7 @@ function registerSuggest(rules) {
|
|
|
56490
57230
|
for (const w2 of warnings) process.stderr.write(`warning: ${w2}
|
|
56491
57231
|
`);
|
|
56492
57232
|
if (opts.out) {
|
|
56493
|
-
|
|
57233
|
+
fs24.writeFileSync(opts.out, ruleYaml, "utf8");
|
|
56494
57234
|
if (!isJsonMode()) console.log(`\u2713 rule YAML written to ${opts.out}`);
|
|
56495
57235
|
} else if (isJsonMode()) {
|
|
56496
57236
|
printJson({ rule, rule_yaml: ruleYaml, warnings });
|
|
@@ -56565,7 +57305,7 @@ overall: ${overall ? "ok" : "issues found"}`);
|
|
|
56565
57305
|
function registerSummary(rules) {
|
|
56566
57306
|
rules.command("summary").description("Aggregate rule-* audit entries per rule over a time window (fires, throttled, errors).").option("--file <path>", `Audit log path (default ${DEFAULT_AUDIT_PATH2})`).option("--since <duration>", "Only entries newer than this window (default: 24h). E.g. 1h, 7d.").option("--rule <name>", "Filter to a single rule name.").action((opts) => {
|
|
56567
57307
|
const file2 = opts.file ?? DEFAULT_AUDIT_PATH2;
|
|
56568
|
-
const entries =
|
|
57308
|
+
const entries = fs24.existsSync(file2) ? readAudit(file2) : [];
|
|
56569
57309
|
const sinceMs = resolveSinceMs(opts.since ?? "24h");
|
|
56570
57310
|
const filtered = filterRuleAudits(entries, { sinceMs, ruleName: opts.rule });
|
|
56571
57311
|
const report = aggregateRuleAudits(filtered);
|
|
@@ -56596,7 +57336,7 @@ function registerLastFired(rules) {
|
|
|
56596
57336
|
rules.command("last-fired").description("Show the N most recently fired rule-fire entries from the audit log.").option("--file <path>", `Audit log path (default ${DEFAULT_AUDIT_PATH2})`).option("--rule <name>", "Filter to a single rule name.").option("-n <count>", "Number of entries to show (default: 10).", (v2) => Number.parseInt(v2, 10)).action((opts) => {
|
|
56597
57337
|
const file2 = opts.file ?? DEFAULT_AUDIT_PATH2;
|
|
56598
57338
|
const n = opts.n ?? 10;
|
|
56599
|
-
const entries =
|
|
57339
|
+
const entries = fs24.existsSync(file2) ? readAudit(file2) : [];
|
|
56600
57340
|
const fires = filterRuleAudits(entries, {
|
|
56601
57341
|
ruleName: opts.rule,
|
|
56602
57342
|
kinds: ["rule-fire", "rule-fire-dry"]
|
|
@@ -56638,7 +57378,7 @@ function registerExplain(rules) {
|
|
|
56638
57378
|
return;
|
|
56639
57379
|
}
|
|
56640
57380
|
const auditFile = opts.file ?? DEFAULT_AUDIT_PATH2;
|
|
56641
|
-
const entries =
|
|
57381
|
+
const entries = fs24.existsSync(auditFile) ? readAudit(auditFile) : [];
|
|
56642
57382
|
const fires = filterRuleAudits(entries, { ruleName: name, kinds: ["rule-fire", "rule-fire-dry"] });
|
|
56643
57383
|
const lastFired = fires.length > 0 ? fires[fires.length - 1].t : null;
|
|
56644
57384
|
const detail = {
|
|
@@ -56675,6 +57415,115 @@ function registerExplain(rules) {
|
|
|
56675
57415
|
console.log(`last fired: ${detail.lastFired ?? "(never)"}`);
|
|
56676
57416
|
});
|
|
56677
57417
|
}
|
|
57418
|
+
function registerTraceExplain(rules) {
|
|
57419
|
+
rules.command("trace-explain [fireId]").description("Show why a rule evaluation fired or was blocked (reads rule-evaluate trace records).").option("--rule <name>", "Filter to a specific rule name.").option("--last", "Show the most recent evaluation for the rule (requires --rule).").option("--since <duration>", "Show evaluations in this window (e.g. 1h, 7d).").option("--all", "Include evaluations that fired (default: show all evaluations).").option("--file <path>", `Audit log path (default ${DEFAULT_AUDIT_PATH2}).`).action(
|
|
57420
|
+
(fireIdArg, opts) => {
|
|
57421
|
+
const auditFile = opts.file ?? DEFAULT_AUDIT_PATH2;
|
|
57422
|
+
if (!fs24.existsSync(auditFile)) {
|
|
57423
|
+
exitWithError({ code: 1, kind: "usage", message: `Audit log not found: ${auditFile}. Make sure trace recording is enabled (automation.audit.evaluate_trace: sampled or full).` });
|
|
57424
|
+
return;
|
|
57425
|
+
}
|
|
57426
|
+
const sinceIso = opts.since ? new Date(Date.now() - (parseDurationToMs2(opts.since) ?? 0)).toISOString() : void 0;
|
|
57427
|
+
let records = loadTraceRecords(auditFile, {
|
|
57428
|
+
fireId: fireIdArg,
|
|
57429
|
+
ruleName: opts.rule,
|
|
57430
|
+
since: sinceIso
|
|
57431
|
+
});
|
|
57432
|
+
if (records.length === 0) {
|
|
57433
|
+
const hint = 'Check that automation.audit.evaluate_trace is set to "sampled" or "full".';
|
|
57434
|
+
exitWithError({ code: 1, kind: "usage", message: `No rule-evaluate trace records found. ${hint}` });
|
|
57435
|
+
return;
|
|
57436
|
+
}
|
|
57437
|
+
if (opts.last) {
|
|
57438
|
+
records = [records[records.length - 1]];
|
|
57439
|
+
}
|
|
57440
|
+
for (const record2 of records) {
|
|
57441
|
+
const related = loadRelatedAudit(auditFile, record2.fireId);
|
|
57442
|
+
if (isJsonMode()) {
|
|
57443
|
+
console.log(formatExplainJson(record2, related));
|
|
57444
|
+
} else {
|
|
57445
|
+
console.log(formatExplainText(record2, related));
|
|
57446
|
+
if (records.length > 1) console.log("---");
|
|
57447
|
+
}
|
|
57448
|
+
}
|
|
57449
|
+
}
|
|
57450
|
+
);
|
|
57451
|
+
}
|
|
57452
|
+
function registerSimulate(rules) {
|
|
57453
|
+
rules.command("simulate <rule-or-policy>").description("Replay historical events against a rule and report would-fire / blocked outcomes.").option("--rule <name>", "Rule name to simulate (when <rule-or-policy> is a policy file).").option("--since <duration>", "Replay events from this window (e.g. 7d, 24h).").option("--against <file>", "Replay from a JSONL file of EngineEvent objects instead of the audit log.").option("--live-llm", "Allow live LLM calls for llm conditions (default: mark as would-call).").option("--audit-log <path>", `Audit log path (default ${DEFAULT_AUDIT_PATH2}).`).option("--report-out <path>", "Write the full JSON report to this file.").action(
|
|
57454
|
+
async (ruleOrPolicy, opts) => {
|
|
57455
|
+
let rule;
|
|
57456
|
+
const auditLog = opts.auditLog ?? DEFAULT_AUDIT_PATH2;
|
|
57457
|
+
if (!fs24.existsSync(ruleOrPolicy)) {
|
|
57458
|
+
exitWithError({ code: 2, kind: "usage", message: `File not found: ${ruleOrPolicy}` });
|
|
57459
|
+
return;
|
|
57460
|
+
}
|
|
57461
|
+
let parsed;
|
|
57462
|
+
try {
|
|
57463
|
+
const { parse: yamlParse5 } = await Promise.resolve().then(() => __toESM(require_dist(), 1));
|
|
57464
|
+
parsed = yamlParse5(fs24.readFileSync(ruleOrPolicy, "utf-8"));
|
|
57465
|
+
} catch {
|
|
57466
|
+
exitWithError({ code: 2, kind: "usage", message: `Could not parse YAML file: ${ruleOrPolicy}` });
|
|
57467
|
+
return;
|
|
57468
|
+
}
|
|
57469
|
+
const asRule = parsed;
|
|
57470
|
+
if (asRule["name"] && asRule["when"] && asRule["then"]) {
|
|
57471
|
+
rule = asRule;
|
|
57472
|
+
} else {
|
|
57473
|
+
const automation = loadAutomation(ruleOrPolicy);
|
|
57474
|
+
if (!automation) return;
|
|
57475
|
+
const ruleName = opts.rule;
|
|
57476
|
+
if (!ruleName) {
|
|
57477
|
+
exitWithError({ code: 1, kind: "usage", message: "Use --rule <name> to specify which rule to simulate from the policy file." });
|
|
57478
|
+
return;
|
|
57479
|
+
}
|
|
57480
|
+
rule = automation.automation?.rules?.find((r) => r.name === ruleName);
|
|
57481
|
+
if (!rule) {
|
|
57482
|
+
exitWithError({ code: 1, kind: "usage", message: `Rule "${ruleName}" not found in policy file.` });
|
|
57483
|
+
return;
|
|
57484
|
+
}
|
|
57485
|
+
}
|
|
57486
|
+
try {
|
|
57487
|
+
const report = await simulateRule({
|
|
57488
|
+
rule,
|
|
57489
|
+
since: opts.since,
|
|
57490
|
+
against: opts.against,
|
|
57491
|
+
auditLog,
|
|
57492
|
+
liveLlm: opts.liveLlm ?? false
|
|
57493
|
+
});
|
|
57494
|
+
if (opts.reportOut) {
|
|
57495
|
+
fs24.writeFileSync(opts.reportOut, JSON.stringify(report, null, 2));
|
|
57496
|
+
console.log(`Report written to ${opts.reportOut}`);
|
|
57497
|
+
}
|
|
57498
|
+
if (isJsonMode()) {
|
|
57499
|
+
printJson(report);
|
|
57500
|
+
} else {
|
|
57501
|
+
console.log(`Rule: ${report.ruleName} (version ${report.ruleVersion})`);
|
|
57502
|
+
console.log(`Window: ${report.windowStart.toISOString()} \u2192 ${report.windowEnd.toISOString()}`);
|
|
57503
|
+
console.log(`Source events: ${report.sourceEventCount}`);
|
|
57504
|
+
console.log("");
|
|
57505
|
+
console.log(` Would fire: ${report.wouldFire}`);
|
|
57506
|
+
console.log(` Blocked by condition:${report.blockedByCondition}`);
|
|
57507
|
+
console.log(` Throttled: ${report.throttled}`);
|
|
57508
|
+
console.log(` Errored: ${report.errored}`);
|
|
57509
|
+
if (report.skippedLlm > 0) {
|
|
57510
|
+
console.log(` Skipped (llm): ${report.skippedLlm} (use --live-llm to evaluate)`);
|
|
57511
|
+
}
|
|
57512
|
+
if (report.topBlockReason && report.topBlockCount !== void 0) {
|
|
57513
|
+
const total = report.blockedByCondition;
|
|
57514
|
+
const pct = total > 0 ? Math.round(report.topBlockCount / total * 100) : 0;
|
|
57515
|
+
if (pct >= 80) {
|
|
57516
|
+
console.log("");
|
|
57517
|
+
console.log(`Top block reason (${pct}%): ${report.topBlockReason}`);
|
|
57518
|
+
}
|
|
57519
|
+
}
|
|
57520
|
+
}
|
|
57521
|
+
} catch (err) {
|
|
57522
|
+
handleError(err);
|
|
57523
|
+
}
|
|
57524
|
+
}
|
|
57525
|
+
);
|
|
57526
|
+
}
|
|
56678
57527
|
function registerRulesCommand(program3) {
|
|
56679
57528
|
const rules = program3.command("rules").description("Run, list, and lint automation rules declared in policy.yaml (v0.2, preview).").addHelpText(
|
|
56680
57529
|
"after",
|
|
@@ -56722,6 +57571,8 @@ Exit codes (lint):
|
|
|
56722
57571
|
registerDoctor(rules);
|
|
56723
57572
|
registerSummary(rules);
|
|
56724
57573
|
registerLastFired(rules);
|
|
57574
|
+
registerTraceExplain(rules);
|
|
57575
|
+
registerSimulate(rules);
|
|
56725
57576
|
registerWebhookRotateToken(rules);
|
|
56726
57577
|
registerWebhookShowToken(rules);
|
|
56727
57578
|
}
|
|
@@ -56732,9 +57583,9 @@ init_output();
|
|
|
56732
57583
|
init_arg_parsers();
|
|
56733
57584
|
init_request_context();
|
|
56734
57585
|
init_keychain();
|
|
56735
|
-
import
|
|
56736
|
-
import
|
|
56737
|
-
import
|
|
57586
|
+
import fs25 from "node:fs";
|
|
57587
|
+
import path22 from "node:path";
|
|
57588
|
+
import os21 from "node:os";
|
|
56738
57589
|
import readline5 from "node:readline";
|
|
56739
57590
|
function activeProfile() {
|
|
56740
57591
|
return getActiveProfile() ?? "default";
|
|
@@ -56780,7 +57631,7 @@ async function promptSecret2(question) {
|
|
|
56780
57631
|
});
|
|
56781
57632
|
}
|
|
56782
57633
|
function readStdinFile(filePath) {
|
|
56783
|
-
if (!
|
|
57634
|
+
if (!fs25.existsSync(filePath)) {
|
|
56784
57635
|
exitWithError({
|
|
56785
57636
|
code: 2,
|
|
56786
57637
|
kind: "usage",
|
|
@@ -56789,7 +57640,7 @@ function readStdinFile(filePath) {
|
|
|
56789
57640
|
}
|
|
56790
57641
|
let parsed;
|
|
56791
57642
|
try {
|
|
56792
|
-
parsed = JSON.parse(
|
|
57643
|
+
parsed = JSON.parse(fs25.readFileSync(filePath, "utf-8"));
|
|
56793
57644
|
} catch (err) {
|
|
56794
57645
|
exitWithError({
|
|
56795
57646
|
code: 2,
|
|
@@ -56819,10 +57670,10 @@ function cleanupMigratedSourceFile(sourceFile, parsed) {
|
|
|
56819
57670
|
delete next.token;
|
|
56820
57671
|
delete next.secret;
|
|
56821
57672
|
if (Object.keys(next).length === 0) {
|
|
56822
|
-
|
|
57673
|
+
fs25.unlinkSync(sourceFile);
|
|
56823
57674
|
return "deleted";
|
|
56824
57675
|
}
|
|
56825
|
-
|
|
57676
|
+
fs25.writeFileSync(sourceFile, JSON.stringify(next, null, 2), { mode: 384 });
|
|
56826
57677
|
return "scrubbed";
|
|
56827
57678
|
}
|
|
56828
57679
|
function registerAuthCommand(program3) {
|
|
@@ -56953,8 +57804,8 @@ function registerAuthCommand(program3) {
|
|
|
56953
57804
|
message: `backend "${store.name}" is not writable on this machine`
|
|
56954
57805
|
});
|
|
56955
57806
|
}
|
|
56956
|
-
const sourceFile = profile === "default" ?
|
|
56957
|
-
if (!
|
|
57807
|
+
const sourceFile = profile === "default" ? path22.join(os21.homedir(), ".switchbot", "config.json") : path22.join(os21.homedir(), ".switchbot", "profiles", `${profile}.json`);
|
|
57808
|
+
if (!fs25.existsSync(sourceFile)) {
|
|
56958
57809
|
exitWithError({
|
|
56959
57810
|
code: 2,
|
|
56960
57811
|
kind: "usage",
|
|
@@ -56964,7 +57815,7 @@ function registerAuthCommand(program3) {
|
|
|
56964
57815
|
}
|
|
56965
57816
|
let parsed;
|
|
56966
57817
|
try {
|
|
56967
|
-
const raw = JSON.parse(
|
|
57818
|
+
const raw = JSON.parse(fs25.readFileSync(sourceFile, "utf-8"));
|
|
56968
57819
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
56969
57820
|
throw new Error("expected a JSON object");
|
|
56970
57821
|
}
|
|
@@ -57026,8 +57877,8 @@ function registerAuthCommand(program3) {
|
|
|
57026
57877
|
init_cjs_shim();
|
|
57027
57878
|
init_esm();
|
|
57028
57879
|
init_load();
|
|
57029
|
-
import
|
|
57030
|
-
import
|
|
57880
|
+
import fs28 from "node:fs";
|
|
57881
|
+
import path25 from "node:path";
|
|
57031
57882
|
|
|
57032
57883
|
// src/install/steps.ts
|
|
57033
57884
|
init_cjs_shim();
|
|
@@ -57079,9 +57930,9 @@ init_cjs_shim();
|
|
|
57079
57930
|
init_load();
|
|
57080
57931
|
init_validate();
|
|
57081
57932
|
init_keychain();
|
|
57082
|
-
import
|
|
57083
|
-
import
|
|
57084
|
-
import
|
|
57933
|
+
import fs26 from "node:fs";
|
|
57934
|
+
import path23 from "node:path";
|
|
57935
|
+
import os22 from "node:os";
|
|
57085
57936
|
function parseMajor(version2) {
|
|
57086
57937
|
const m2 = /^v?(\d+)\./.exec(version2);
|
|
57087
57938
|
if (!m2) return null;
|
|
@@ -57171,10 +58022,10 @@ async function checkKeychain2() {
|
|
|
57171
58022
|
}
|
|
57172
58023
|
}
|
|
57173
58024
|
function checkHomeDirWritable() {
|
|
57174
|
-
const home =
|
|
57175
|
-
const switchbotDir =
|
|
58025
|
+
const home = os22.homedir();
|
|
58026
|
+
const switchbotDir = path23.join(home, ".switchbot");
|
|
57176
58027
|
try {
|
|
57177
|
-
const homeStat =
|
|
58028
|
+
const homeStat = fs26.statSync(home);
|
|
57178
58029
|
if (!homeStat.isDirectory()) {
|
|
57179
58030
|
return {
|
|
57180
58031
|
name: "home",
|
|
@@ -57183,8 +58034,8 @@ function checkHomeDirWritable() {
|
|
|
57183
58034
|
hint: "check your HOME/USERPROFILE environment configuration"
|
|
57184
58035
|
};
|
|
57185
58036
|
}
|
|
57186
|
-
if (
|
|
57187
|
-
const sbStat =
|
|
58037
|
+
if (fs26.existsSync(switchbotDir)) {
|
|
58038
|
+
const sbStat = fs26.statSync(switchbotDir);
|
|
57188
58039
|
if (!sbStat.isDirectory()) {
|
|
57189
58040
|
return {
|
|
57190
58041
|
name: "home",
|
|
@@ -57193,10 +58044,10 @@ function checkHomeDirWritable() {
|
|
|
57193
58044
|
hint: "move the file aside and re-run install"
|
|
57194
58045
|
};
|
|
57195
58046
|
}
|
|
57196
|
-
|
|
58047
|
+
fs26.accessSync(switchbotDir, fs26.constants.W_OK);
|
|
57197
58048
|
return { name: "home", status: "ok", message: `writable: ${switchbotDir}` };
|
|
57198
58049
|
}
|
|
57199
|
-
|
|
58050
|
+
fs26.accessSync(home, fs26.constants.W_OK);
|
|
57200
58051
|
return { name: "home", status: "ok", message: `writable: ${home}` };
|
|
57201
58052
|
} catch (err) {
|
|
57202
58053
|
return {
|
|
@@ -57210,8 +58061,8 @@ function checkHomeDirWritable() {
|
|
|
57210
58061
|
function nearestExistingPath(target) {
|
|
57211
58062
|
let cur = target;
|
|
57212
58063
|
while (true) {
|
|
57213
|
-
if (
|
|
57214
|
-
const parent =
|
|
58064
|
+
if (fs26.existsSync(cur)) return cur;
|
|
58065
|
+
const parent = path23.dirname(cur);
|
|
57215
58066
|
if (parent === cur) return null;
|
|
57216
58067
|
cur = parent;
|
|
57217
58068
|
}
|
|
@@ -57219,8 +58070,8 @@ function nearestExistingPath(target) {
|
|
|
57219
58070
|
function checkAgentSkillDirWritable(opts) {
|
|
57220
58071
|
const shouldCheck = opts.agent === "claude-code" && (opts.expectSkillLink ?? true);
|
|
57221
58072
|
if (!shouldCheck) return null;
|
|
57222
|
-
const home =
|
|
57223
|
-
const target =
|
|
58073
|
+
const home = os22.homedir();
|
|
58074
|
+
const target = path23.join(home, ".claude", "skills");
|
|
57224
58075
|
try {
|
|
57225
58076
|
const existing = nearestExistingPath(target);
|
|
57226
58077
|
if (!existing) {
|
|
@@ -57231,7 +58082,7 @@ function checkAgentSkillDirWritable(opts) {
|
|
|
57231
58082
|
hint: "check your home directory path and permissions"
|
|
57232
58083
|
};
|
|
57233
58084
|
}
|
|
57234
|
-
const stat =
|
|
58085
|
+
const stat = fs26.statSync(existing);
|
|
57235
58086
|
if (!stat.isDirectory()) {
|
|
57236
58087
|
return {
|
|
57237
58088
|
name: "agent-skills-dir",
|
|
@@ -57240,7 +58091,7 @@ function checkAgentSkillDirWritable(opts) {
|
|
|
57240
58091
|
hint: "move the blocking file aside and re-run install"
|
|
57241
58092
|
};
|
|
57242
58093
|
}
|
|
57243
|
-
|
|
58094
|
+
fs26.accessSync(existing, fs26.constants.W_OK);
|
|
57244
58095
|
return { name: "agent-skills-dir", status: "ok", message: `writable: ${target}` };
|
|
57245
58096
|
} catch (err) {
|
|
57246
58097
|
return {
|
|
@@ -57265,9 +58116,9 @@ async function runPreflight(options = {}) {
|
|
|
57265
58116
|
|
|
57266
58117
|
// src/install/default-steps.ts
|
|
57267
58118
|
init_cjs_shim();
|
|
57268
|
-
import
|
|
57269
|
-
import
|
|
57270
|
-
import
|
|
58119
|
+
import fs27 from "node:fs";
|
|
58120
|
+
import path24 from "node:path";
|
|
58121
|
+
import os23 from "node:os";
|
|
57271
58122
|
import { spawnSync } from "node:child_process";
|
|
57272
58123
|
init_keychain();
|
|
57273
58124
|
function stepPromptCredentials() {
|
|
@@ -57342,15 +58193,15 @@ function stepScaffoldPolicy() {
|
|
|
57342
58193
|
const r = ctx.policyScaffoldResult;
|
|
57343
58194
|
if (!r || r.skipped) return;
|
|
57344
58195
|
try {
|
|
57345
|
-
|
|
58196
|
+
fs27.unlinkSync(r.policyPath);
|
|
57346
58197
|
} catch {
|
|
57347
58198
|
}
|
|
57348
58199
|
}
|
|
57349
58200
|
};
|
|
57350
58201
|
}
|
|
57351
|
-
function skillLinkPathFor(agent, home =
|
|
58202
|
+
function skillLinkPathFor(agent, home = os23.homedir()) {
|
|
57352
58203
|
if (agent === "claude-code") {
|
|
57353
|
-
return
|
|
58204
|
+
return path24.join(home, ".claude", "skills", "switchbot");
|
|
57354
58205
|
}
|
|
57355
58206
|
return null;
|
|
57356
58207
|
}
|
|
@@ -57364,11 +58215,11 @@ function stepSymlinkSkill(opts = {}) {
|
|
|
57364
58215
|
ctx.skillRecipePrinted = true;
|
|
57365
58216
|
return;
|
|
57366
58217
|
}
|
|
57367
|
-
const target =
|
|
57368
|
-
if (!
|
|
58218
|
+
const target = path24.resolve(ctx.skillPath);
|
|
58219
|
+
if (!fs27.existsSync(target)) {
|
|
57369
58220
|
throw new Error(`--skill-path does not exist: ${target}`);
|
|
57370
58221
|
}
|
|
57371
|
-
const stat =
|
|
58222
|
+
const stat = fs27.statSync(target);
|
|
57372
58223
|
if (!stat.isDirectory()) {
|
|
57373
58224
|
throw new Error(`--skill-path is not a directory: ${target}`);
|
|
57374
58225
|
}
|
|
@@ -57377,17 +58228,17 @@ function stepSymlinkSkill(opts = {}) {
|
|
|
57377
58228
|
ctx.skillRecipePrinted = true;
|
|
57378
58229
|
return;
|
|
57379
58230
|
}
|
|
57380
|
-
if (!opts.force && !
|
|
58231
|
+
if (!opts.force && !fs27.existsSync(path24.join(target, "SKILL.md"))) {
|
|
57381
58232
|
throw new Error(
|
|
57382
58233
|
`${target} does not look like a skill (no SKILL.md at the root). Pass --force if you really mean to link this directory.`
|
|
57383
58234
|
);
|
|
57384
58235
|
}
|
|
57385
|
-
if (
|
|
57386
|
-
const st =
|
|
58236
|
+
if (fs27.existsSync(linkPath)) {
|
|
58237
|
+
const st = fs27.lstatSync(linkPath);
|
|
57387
58238
|
if (st.isSymbolicLink()) {
|
|
57388
58239
|
let existingTarget = null;
|
|
57389
58240
|
try {
|
|
57390
|
-
existingTarget =
|
|
58241
|
+
existingTarget = path24.resolve(path24.dirname(linkPath), fs27.readlinkSync(linkPath));
|
|
57391
58242
|
} catch {
|
|
57392
58243
|
existingTarget = null;
|
|
57393
58244
|
}
|
|
@@ -57401,23 +58252,23 @@ function stepSymlinkSkill(opts = {}) {
|
|
|
57401
58252
|
`${linkPath} already links to ${existingTarget ?? "(unreadable)"}; pass --force to replace it, or run \`switchbot uninstall\` first.`
|
|
57402
58253
|
);
|
|
57403
58254
|
}
|
|
57404
|
-
|
|
58255
|
+
fs27.unlinkSync(linkPath);
|
|
57405
58256
|
} else {
|
|
57406
58257
|
throw new Error(
|
|
57407
58258
|
`${linkPath} exists and is not a symlink; refusing to clobber (move it aside and re-run)`
|
|
57408
58259
|
);
|
|
57409
58260
|
}
|
|
57410
58261
|
}
|
|
57411
|
-
|
|
58262
|
+
fs27.mkdirSync(path24.dirname(linkPath), { recursive: true });
|
|
57412
58263
|
const linkType = process.platform === "win32" ? "junction" : "dir";
|
|
57413
|
-
|
|
58264
|
+
fs27.symlinkSync(target, linkPath, linkType);
|
|
57414
58265
|
ctx.skillLinkPath = linkPath;
|
|
57415
58266
|
ctx.skillLinkCreated = true;
|
|
57416
58267
|
},
|
|
57417
58268
|
undo(ctx) {
|
|
57418
58269
|
if (!ctx.skillLinkCreated || !ctx.skillLinkPath) return;
|
|
57419
58270
|
try {
|
|
57420
|
-
|
|
58271
|
+
fs27.unlinkSync(ctx.skillLinkPath);
|
|
57421
58272
|
} catch {
|
|
57422
58273
|
}
|
|
57423
58274
|
}
|
|
@@ -57560,8 +58411,8 @@ Examples:
|
|
|
57560
58411
|
const agent = parseAgent(opts.agent);
|
|
57561
58412
|
const profile = getActiveProfile() ?? "default";
|
|
57562
58413
|
const skip = parseSkipList(opts.skip);
|
|
57563
|
-
const skillPath = opts.skillPath ?
|
|
57564
|
-
const tokenFile = opts.tokenFile ?
|
|
58414
|
+
const skillPath = opts.skillPath ? path25.resolve(opts.skillPath) : void 0;
|
|
58415
|
+
const tokenFile = opts.tokenFile ? path25.resolve(opts.tokenFile) : void 0;
|
|
57565
58416
|
const force = Boolean(opts.force);
|
|
57566
58417
|
const verify = Boolean(opts.verify);
|
|
57567
58418
|
const globalOpts = command.parent?.opts() ?? {};
|
|
@@ -57605,7 +58456,7 @@ Examples:
|
|
|
57605
58456
|
const report = await runInstall(steps, { context: ctx });
|
|
57606
58457
|
if (report.ok && tokenFile) {
|
|
57607
58458
|
try {
|
|
57608
|
-
|
|
58459
|
+
fs28.unlinkSync(tokenFile);
|
|
57609
58460
|
} catch {
|
|
57610
58461
|
}
|
|
57611
58462
|
}
|
|
@@ -57664,7 +58515,7 @@ Examples:
|
|
|
57664
58515
|
init_cjs_shim();
|
|
57665
58516
|
init_esm();
|
|
57666
58517
|
init_load();
|
|
57667
|
-
import
|
|
58518
|
+
import fs29 from "node:fs";
|
|
57668
58519
|
import readline6 from "node:readline";
|
|
57669
58520
|
init_keychain();
|
|
57670
58521
|
init_output();
|
|
@@ -57729,10 +58580,10 @@ Examples:
|
|
|
57729
58580
|
action: "remove-skill-link",
|
|
57730
58581
|
detail: skillLink,
|
|
57731
58582
|
run: async () => {
|
|
57732
|
-
if (!
|
|
58583
|
+
if (!fs29.existsSync(skillLink)) {
|
|
57733
58584
|
return { action: "remove-skill-link", status: "absent", detail: skillLink };
|
|
57734
58585
|
}
|
|
57735
|
-
const stat =
|
|
58586
|
+
const stat = fs29.lstatSync(skillLink);
|
|
57736
58587
|
if (!stat.isSymbolicLink()) {
|
|
57737
58588
|
return {
|
|
57738
58589
|
action: "remove-skill-link",
|
|
@@ -57743,7 +58594,7 @@ Examples:
|
|
|
57743
58594
|
const ok = yes ? true : await prompt(`Remove skill link ${skillLink}?`, true);
|
|
57744
58595
|
if (!ok) return { action: "remove-skill-link", status: "skipped", detail: skillLink };
|
|
57745
58596
|
try {
|
|
57746
|
-
|
|
58597
|
+
fs29.unlinkSync(skillLink);
|
|
57747
58598
|
return { action: "remove-skill-link", status: "removed", detail: skillLink };
|
|
57748
58599
|
} catch (err) {
|
|
57749
58600
|
return {
|
|
@@ -57798,13 +58649,13 @@ Examples:
|
|
|
57798
58649
|
detail: "pass --remove-policy to delete policy.yaml"
|
|
57799
58650
|
};
|
|
57800
58651
|
}
|
|
57801
|
-
if (!
|
|
58652
|
+
if (!fs29.existsSync(policyPath)) {
|
|
57802
58653
|
return { action: "remove-policy", status: "absent", detail: policyPath };
|
|
57803
58654
|
}
|
|
57804
58655
|
const ok = yes ? true : await prompt(`Delete policy file ${policyPath}?`, false);
|
|
57805
58656
|
if (!ok) return { action: "remove-policy", status: "skipped", detail: policyPath };
|
|
57806
58657
|
try {
|
|
57807
|
-
|
|
58658
|
+
fs29.unlinkSync(policyPath);
|
|
57808
58659
|
return { action: "remove-policy", status: "removed", detail: policyPath };
|
|
57809
58660
|
} catch (err) {
|
|
57810
58661
|
return {
|
|
@@ -57867,9 +58718,9 @@ init_request_context();
|
|
|
57867
58718
|
init_output();
|
|
57868
58719
|
init_flags();
|
|
57869
58720
|
import { spawn as spawn4, spawnSync as spawnSync2 } from "node:child_process";
|
|
57870
|
-
import
|
|
57871
|
-
import
|
|
57872
|
-
import
|
|
58721
|
+
import fs30 from "node:fs";
|
|
58722
|
+
import os24 from "node:os";
|
|
58723
|
+
import path26 from "node:path";
|
|
57873
58724
|
var DEFAULT_OPENCLAW_URL = "http://localhost:18789";
|
|
57874
58725
|
function resolveStatusSyncRuntime(options) {
|
|
57875
58726
|
if (!tryLoadConfig()) {
|
|
@@ -58003,14 +58854,14 @@ async function probeStatusSyncStart(options = {}) {
|
|
|
58003
58854
|
};
|
|
58004
58855
|
}
|
|
58005
58856
|
function resolveStatusSyncPaths(explicitStateDir) {
|
|
58006
|
-
const stateDir =
|
|
58007
|
-
explicitStateDir ?? process.env.SWITCHBOT_STATUS_SYNC_HOME ??
|
|
58857
|
+
const stateDir = path26.resolve(
|
|
58858
|
+
explicitStateDir ?? process.env.SWITCHBOT_STATUS_SYNC_HOME ?? path26.join(os24.homedir(), ".switchbot", "status-sync")
|
|
58008
58859
|
);
|
|
58009
58860
|
return {
|
|
58010
58861
|
stateDir,
|
|
58011
|
-
stateFile:
|
|
58012
|
-
stdoutLog:
|
|
58013
|
-
stderrLog:
|
|
58862
|
+
stateFile: path26.join(stateDir, "state.json"),
|
|
58863
|
+
stdoutLog: path26.join(stateDir, "stdout.log"),
|
|
58864
|
+
stderrLog: path26.join(stateDir, "stderr.log")
|
|
58014
58865
|
};
|
|
58015
58866
|
}
|
|
58016
58867
|
function buildStatusSyncChildArgs(options) {
|
|
@@ -58018,11 +58869,11 @@ function buildStatusSyncChildArgs(options) {
|
|
|
58018
58869
|
if (!scriptPath) {
|
|
58019
58870
|
throw new Error("Cannot determine the current CLI entrypoint path.");
|
|
58020
58871
|
}
|
|
58021
|
-
const args = [
|
|
58872
|
+
const args = [path26.resolve(scriptPath)];
|
|
58022
58873
|
const configPath = getConfigPath();
|
|
58023
58874
|
const profile = getActiveProfile();
|
|
58024
58875
|
if (configPath) {
|
|
58025
|
-
args.push("--config",
|
|
58876
|
+
args.push("--config", path26.resolve(configPath));
|
|
58026
58877
|
} else if (profile) {
|
|
58027
58878
|
args.push("--profile", profile);
|
|
58028
58879
|
}
|
|
@@ -58043,7 +58894,7 @@ function buildStatusSyncChildArgs(options) {
|
|
|
58043
58894
|
}
|
|
58044
58895
|
function safeUnlink(filePath) {
|
|
58045
58896
|
try {
|
|
58046
|
-
|
|
58897
|
+
fs30.unlinkSync(filePath);
|
|
58047
58898
|
} catch {
|
|
58048
58899
|
}
|
|
58049
58900
|
}
|
|
@@ -58058,9 +58909,9 @@ function isProcessRunning(pid) {
|
|
|
58058
58909
|
}
|
|
58059
58910
|
}
|
|
58060
58911
|
function readStateFile(paths) {
|
|
58061
|
-
if (!
|
|
58912
|
+
if (!fs30.existsSync(paths.stateFile)) return null;
|
|
58062
58913
|
try {
|
|
58063
|
-
const raw = JSON.parse(
|
|
58914
|
+
const raw = JSON.parse(fs30.readFileSync(paths.stateFile, "utf-8"));
|
|
58064
58915
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
58065
58916
|
safeUnlink(paths.stateFile);
|
|
58066
58917
|
return null;
|
|
@@ -58179,14 +59030,14 @@ function startStatusSync(options = {}) {
|
|
|
58179
59030
|
}
|
|
58180
59031
|
stopStatusSync({ stateDir: paths.stateDir });
|
|
58181
59032
|
}
|
|
58182
|
-
|
|
59033
|
+
fs30.mkdirSync(paths.stateDir, { recursive: true });
|
|
58183
59034
|
const configPath = getConfigPath();
|
|
58184
59035
|
const command = buildStatusSyncChildArgs(runtime);
|
|
58185
59036
|
let stdoutFd = null;
|
|
58186
59037
|
let stderrFd = null;
|
|
58187
59038
|
try {
|
|
58188
|
-
stdoutFd =
|
|
58189
|
-
stderrFd =
|
|
59039
|
+
stdoutFd = fs30.openSync(paths.stdoutLog, "a");
|
|
59040
|
+
stderrFd = fs30.openSync(paths.stderrLog, "a");
|
|
58190
59041
|
const child = spawn4(process.execPath, command, {
|
|
58191
59042
|
detached: true,
|
|
58192
59043
|
stdio: ["ignore", stdoutFd, stderrFd],
|
|
@@ -58204,16 +59055,16 @@ function startStatusSync(options = {}) {
|
|
|
58204
59055
|
openclawUrl: runtime.openclawUrl,
|
|
58205
59056
|
openclawModel: runtime.openclawModel,
|
|
58206
59057
|
topic: runtime.topic ?? null,
|
|
58207
|
-
configPath: configPath ?
|
|
59058
|
+
configPath: configPath ? path26.resolve(configPath) : null,
|
|
58208
59059
|
profile: configPath ? null : getActiveProfile() ?? null,
|
|
58209
59060
|
stdoutLog: paths.stdoutLog,
|
|
58210
59061
|
stderrLog: paths.stderrLog
|
|
58211
59062
|
};
|
|
58212
|
-
|
|
59063
|
+
fs30.writeFileSync(paths.stateFile, JSON.stringify(state, null, 2), { mode: 384 });
|
|
58213
59064
|
return toStatus(paths, state, true);
|
|
58214
59065
|
} finally {
|
|
58215
|
-
if (stdoutFd !== null)
|
|
58216
|
-
if (stderrFd !== null)
|
|
59066
|
+
if (stdoutFd !== null) fs30.closeSync(stdoutFd);
|
|
59067
|
+
if (stderrFd !== null) fs30.closeSync(stderrFd);
|
|
58217
59068
|
}
|
|
58218
59069
|
}
|
|
58219
59070
|
async function runStatusSyncForeground(options = {}) {
|
|
@@ -58360,10 +59211,10 @@ init_cjs_shim();
|
|
|
58360
59211
|
init_quota();
|
|
58361
59212
|
init_audit();
|
|
58362
59213
|
init_client();
|
|
58363
|
-
import
|
|
58364
|
-
import
|
|
58365
|
-
import
|
|
58366
|
-
var DEFAULT_AUDIT_PATH3 =
|
|
59214
|
+
import fs31 from "node:fs";
|
|
59215
|
+
import os25 from "node:os";
|
|
59216
|
+
import path27 from "node:path";
|
|
59217
|
+
var DEFAULT_AUDIT_PATH3 = path27.join(os25.homedir(), ".switchbot", "audit.log");
|
|
58367
59218
|
var AUDIT_ERROR_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
58368
59219
|
function getHealthReport(auditPath = DEFAULT_AUDIT_PATH3) {
|
|
58369
59220
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -58384,7 +59235,7 @@ function getHealthReport(auditPath = DEFAULT_AUDIT_PATH3) {
|
|
|
58384
59235
|
status: pct >= 90 ? "critical" : pct >= 70 ? "warn" : "ok"
|
|
58385
59236
|
};
|
|
58386
59237
|
let auditHealth;
|
|
58387
|
-
if (!
|
|
59238
|
+
if (!fs31.existsSync(auditPath)) {
|
|
58388
59239
|
auditHealth = { present: false, recentErrors: 0, recentTotal: 0, errorRatePercent: 0, status: "ok" };
|
|
58389
59240
|
} else {
|
|
58390
59241
|
const entries = readAudit(auditPath);
|
|
@@ -58654,8 +59505,8 @@ function registerUpgradeCheckCommand(program3) {
|
|
|
58654
59505
|
init_cjs_shim();
|
|
58655
59506
|
init_output();
|
|
58656
59507
|
import { spawn as spawn5 } from "node:child_process";
|
|
58657
|
-
import
|
|
58658
|
-
import
|
|
59508
|
+
import fs32 from "node:fs";
|
|
59509
|
+
import path28 from "node:path";
|
|
58659
59510
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
58660
59511
|
init_arg_parsers();
|
|
58661
59512
|
init_source();
|
|
@@ -58717,7 +59568,7 @@ function persistState(partial2) {
|
|
|
58717
59568
|
}
|
|
58718
59569
|
function readLastLines(filePath, n = 20) {
|
|
58719
59570
|
try {
|
|
58720
|
-
const content =
|
|
59571
|
+
const content = fs32.readFileSync(filePath, "utf-8");
|
|
58721
59572
|
const lines = content.split("\n");
|
|
58722
59573
|
return lines.slice(Math.max(0, lines.length - n)).join("\n").trim();
|
|
58723
59574
|
} catch {
|
|
@@ -58807,10 +59658,10 @@ The daemon reads the same policy file as \`switchbot rules run\`.
|
|
|
58807
59658
|
}
|
|
58808
59659
|
}
|
|
58809
59660
|
const thisFile = fileURLToPath3(import.meta.url);
|
|
58810
|
-
const cliEntry =
|
|
59661
|
+
const cliEntry = path28.resolve(path28.dirname(thisFile), "..", "index.js");
|
|
58811
59662
|
const args = ["rules", "run"];
|
|
58812
59663
|
if (opts.policy) args.push(opts.policy);
|
|
58813
|
-
|
|
59664
|
+
fs32.mkdirSync(path28.dirname(DAEMON_PID_FILE), { recursive: true, mode: 448 });
|
|
58814
59665
|
persistState({
|
|
58815
59666
|
status: "starting",
|
|
58816
59667
|
pid: null,
|
|
@@ -58822,14 +59673,14 @@ The daemon reads the same policy file as \`switchbot rules run\`.
|
|
|
58822
59673
|
healthzPid: null,
|
|
58823
59674
|
healthzPidFile: HEALTHZ_PID_FILE
|
|
58824
59675
|
});
|
|
58825
|
-
const logFd =
|
|
59676
|
+
const logFd = fs32.openSync(DAEMON_LOG_FILE, "a");
|
|
58826
59677
|
const child = spawn5(process.execPath, [cliEntry, ...args], {
|
|
58827
59678
|
detached: true,
|
|
58828
59679
|
stdio: ["ignore", logFd, logFd],
|
|
58829
59680
|
env: { ...process.env }
|
|
58830
59681
|
});
|
|
58831
59682
|
child.unref();
|
|
58832
|
-
|
|
59683
|
+
fs32.closeSync(logFd);
|
|
58833
59684
|
await probeLiveness({
|
|
58834
59685
|
child,
|
|
58835
59686
|
delayMs: 300,
|
|
@@ -58852,14 +59703,14 @@ The daemon reads the same policy file as \`switchbot rules run\`.
|
|
|
58852
59703
|
let healthzPort = opts.healthzPort ? Number.parseInt(opts.healthzPort, 10) : null;
|
|
58853
59704
|
if (healthzPort !== null) {
|
|
58854
59705
|
const healthArgs = ["health", "serve", "--port", String(healthzPort)];
|
|
58855
|
-
const healthLogFd =
|
|
59706
|
+
const healthLogFd = fs32.openSync(DAEMON_LOG_FILE, "a");
|
|
58856
59707
|
const healthChild = spawn5(process.execPath, [cliEntry, ...healthArgs], {
|
|
58857
59708
|
detached: true,
|
|
58858
59709
|
stdio: ["ignore", healthLogFd, healthLogFd],
|
|
58859
59710
|
env: { ...process.env }
|
|
58860
59711
|
});
|
|
58861
59712
|
healthChild.unref();
|
|
58862
|
-
|
|
59713
|
+
fs32.closeSync(healthLogFd);
|
|
58863
59714
|
if (healthChild.pid) {
|
|
58864
59715
|
const healthAlive = await probeLiveness({ child: healthChild, delayMs: 200, fatal: false });
|
|
58865
59716
|
if (healthAlive) {
|
|
@@ -58922,11 +59773,11 @@ The daemon reads the same policy file as \`switchbot rules run\`.
|
|
|
58922
59773
|
});
|
|
58923
59774
|
}
|
|
58924
59775
|
try {
|
|
58925
|
-
|
|
59776
|
+
fs32.unlinkSync(DAEMON_PID_FILE);
|
|
58926
59777
|
} catch {
|
|
58927
59778
|
}
|
|
58928
59779
|
try {
|
|
58929
|
-
|
|
59780
|
+
fs32.unlinkSync(HEALTHZ_PID_FILE);
|
|
58930
59781
|
} catch {
|
|
58931
59782
|
}
|
|
58932
59783
|
persistState({
|