homebridge-plugin-utils 1.35.0 → 2.0.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 +130 -2
- package/build/eslint-plugin/README.md +164 -0
- package/build/eslint-plugin/config.mjs +308 -0
- package/build/eslint-plugin/index.mjs +7 -0
- package/build/eslint-plugin/plugin.mjs +46 -0
- package/build/eslint-plugin/rules/blank-line-after-open-brace.mjs +137 -0
- package/build/eslint-plugin/rules/blank-line-after-open-brace.test.mjs +112 -0
- package/build/eslint-plugin/rules/comment-style.mjs +190 -0
- package/build/eslint-plugin/rules/comment-style.test.mjs +190 -0
- package/build/eslint-plugin/rules/enforce-node-protocol.mjs +114 -0
- package/build/eslint-plugin/rules/enforce-node-protocol.test.mjs +116 -0
- package/build/eslint-plugin/rules/paren-comparisons-in-logical.mjs +94 -0
- package/build/eslint-plugin/rules/paren-comparisons-in-logical.test.mjs +107 -0
- package/build/eslint-plugin/rules/split-type-imports.mjs +354 -0
- package/build/eslint-plugin/rules/split-type-imports.test.mjs +268 -0
- package/build/eslint-plugin/test-setup.mjs +21 -0
- package/build/fs-ops.mjs +184 -0
- package/build/tsconfig.json +18 -3
- package/dist/backpressure.d.ts +94 -45
- package/dist/backpressure.js +229 -93
- package/dist/backpressure.js.map +1 -1
- package/dist/cli/index.d.ts +95 -0
- package/dist/cli/index.js +355 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/clock-double.d.ts +93 -0
- package/dist/clock-double.js +141 -0
- package/dist/clock-double.js.map +1 -0
- package/dist/clock.d.ts +39 -0
- package/dist/clock.js +34 -0
- package/dist/clock.js.map +1 -0
- package/dist/eslint-plugin/config.d.mts +193 -0
- package/dist/eslint-plugin/index.d.mts +2 -0
- package/dist/eslint-plugin/plugin.d.mts +87 -0
- package/dist/eslint-plugin/rules/blank-line-after-open-brace.d.mts +19 -0
- package/dist/eslint-plugin/rules/comment-style.d.mts +15 -0
- package/dist/eslint-plugin/rules/enforce-node-protocol.d.mts +18 -0
- package/dist/eslint-plugin/rules/paren-comparisons-in-logical.d.mts +15 -0
- package/dist/eslint-plugin/rules/split-type-imports.d.mts +16 -0
- package/dist/featureOptions-docs.d.ts +96 -0
- package/dist/featureOptions-docs.js +193 -0
- package/dist/featureOptions-docs.js.map +1 -0
- package/dist/featureOptions.d.ts +674 -0
- package/dist/featureOptions.js +870 -0
- package/dist/featureOptions.js.map +1 -0
- package/dist/ffmpeg/codecs.d.ts +256 -72
- package/dist/ffmpeg/codecs.js +477 -262
- package/dist/ffmpeg/codecs.js.map +1 -1
- package/dist/ffmpeg/dgram-util.d.ts +46 -0
- package/dist/ffmpeg/dgram-util.js +38 -0
- package/dist/ffmpeg/dgram-util.js.map +1 -0
- package/dist/ffmpeg/exec.d.ts +83 -64
- package/dist/ffmpeg/exec.js +77 -86
- package/dist/ffmpeg/exec.js.map +1 -1
- package/dist/ffmpeg/fmp4.d.ts +49 -2
- package/dist/ffmpeg/fmp4.js +47 -11
- package/dist/ffmpeg/fmp4.js.map +1 -1
- package/dist/ffmpeg/hap-enums.d.ts +214 -0
- package/dist/ffmpeg/hap-enums.js +92 -0
- package/dist/ffmpeg/hap-enums.js.map +1 -0
- package/dist/ffmpeg/index.d.ts +16 -9
- package/dist/ffmpeg/index.js +6 -0
- package/dist/ffmpeg/index.js.map +1 -1
- package/dist/ffmpeg/mp4-assembler.d.ts +120 -0
- package/dist/ffmpeg/mp4-assembler.js +387 -0
- package/dist/ffmpeg/mp4-assembler.js.map +1 -0
- package/dist/ffmpeg/mp4-parser.d.ts +94 -0
- package/dist/ffmpeg/mp4-parser.js +130 -0
- package/dist/ffmpeg/mp4-parser.js.map +1 -0
- package/dist/ffmpeg/options.d.ts +62 -149
- package/dist/ffmpeg/options.js +608 -499
- package/dist/ffmpeg/options.js.map +1 -1
- package/dist/ffmpeg/process.d.ts +142 -96
- package/dist/ffmpeg/process.js +406 -278
- package/dist/ffmpeg/process.js.map +1 -1
- package/dist/ffmpeg/record.d.ts +302 -188
- package/dist/ffmpeg/record.js +408 -567
- package/dist/ffmpeg/record.js.map +1 -1
- package/dist/ffmpeg/recording-process-double.d.ts +144 -0
- package/dist/ffmpeg/recording-process-double.js +168 -0
- package/dist/ffmpeg/recording-process-double.js.map +1 -0
- package/dist/ffmpeg/rtp-parser.d.ts +70 -0
- package/dist/ffmpeg/rtp-parser.js +77 -0
- package/dist/ffmpeg/rtp-parser.js.map +1 -0
- package/dist/ffmpeg/rtp.d.ts +198 -141
- package/dist/ffmpeg/rtp.js +473 -251
- package/dist/ffmpeg/rtp.js.map +1 -1
- package/dist/ffmpeg/settings.d.ts +5 -2
- package/dist/ffmpeg/settings.js +20 -5
- package/dist/ffmpeg/settings.js.map +1 -1
- package/dist/ffmpeg/stream.d.ts +57 -107
- package/dist/ffmpeg/stream.js +121 -150
- package/dist/ffmpeg/stream.js.map +1 -1
- package/dist/formatters.d.ts +106 -0
- package/dist/formatters.js +174 -0
- package/dist/formatters.js.map +1 -0
- package/dist/homebridge-enums.d.ts +30 -0
- package/dist/homebridge-enums.js +17 -0
- package/dist/homebridge-enums.js.map +1 -0
- package/dist/index.d.ts +12 -6
- package/dist/index.js +7 -2
- package/dist/index.js.map +1 -1
- package/dist/logclient/auth.d.ts +114 -0
- package/dist/logclient/auth.js +199 -0
- package/dist/logclient/auth.js.map +1 -0
- package/dist/logclient/cli-run.d.ts +76 -0
- package/dist/logclient/cli-run.js +639 -0
- package/dist/logclient/cli-run.js.map +1 -0
- package/dist/logclient/cli.d.ts +3 -0
- package/dist/logclient/cli.js +97 -0
- package/dist/logclient/cli.js.map +1 -0
- package/dist/logclient/client.d.ts +145 -0
- package/dist/logclient/client.js +600 -0
- package/dist/logclient/client.js.map +1 -0
- package/dist/logclient/config.d.ts +173 -0
- package/dist/logclient/config.js +199 -0
- package/dist/logclient/config.js.map +1 -0
- package/dist/logclient/endpoints.d.ts +54 -0
- package/dist/logclient/endpoints.js +73 -0
- package/dist/logclient/endpoints.js.map +1 -0
- package/dist/logclient/filter.d.ts +45 -0
- package/dist/logclient/filter.js +51 -0
- package/dist/logclient/filter.js.map +1 -0
- package/dist/logclient/frame.d.ts +93 -0
- package/dist/logclient/frame.js +203 -0
- package/dist/logclient/frame.js.map +1 -0
- package/dist/logclient/index.d.ts +31 -0
- package/dist/logclient/index.js +12 -0
- package/dist/logclient/index.js.map +1 -0
- package/dist/logclient/parser.d.ts +211 -0
- package/dist/logclient/parser.js +393 -0
- package/dist/logclient/parser.js.map +1 -0
- package/dist/logclient/rest.d.ts +41 -0
- package/dist/logclient/rest.js +111 -0
- package/dist/logclient/rest.js.map +1 -0
- package/dist/logclient/settings.d.ts +15 -0
- package/dist/logclient/settings.js +64 -0
- package/dist/logclient/settings.js.map +1 -0
- package/dist/logclient/socket-double.d.ts +201 -0
- package/dist/logclient/socket-double.js +384 -0
- package/dist/logclient/socket-double.js.map +1 -0
- package/dist/logclient/socket.d.ts +257 -0
- package/dist/logclient/socket.js +620 -0
- package/dist/logclient/socket.js.map +1 -0
- package/dist/logclient/stitch.d.ts +83 -0
- package/dist/logclient/stitch.js +146 -0
- package/dist/logclient/stitch.js.map +1 -0
- package/dist/logclient/time-expression.d.ts +42 -0
- package/dist/logclient/time-expression.js +181 -0
- package/dist/logclient/time-expression.js.map +1 -0
- package/dist/logclient/time-window.d.ts +38 -0
- package/dist/logclient/time-window.js +53 -0
- package/dist/logclient/time-window.js.map +1 -0
- package/dist/logclient/types.d.ts +107 -0
- package/dist/logclient/types.js +6 -0
- package/dist/logclient/types.js.map +1 -0
- package/dist/mqttClient.d.ts +287 -0
- package/dist/mqttClient.js +433 -0
- package/dist/mqttClient.js.map +1 -0
- package/dist/service.d.ts +64 -15
- package/dist/service.js +93 -66
- package/dist/service.js.map +1 -1
- package/dist/ui/featureOptions.js +870 -0
- package/dist/ui/featureOptions.js.map +1 -0
- package/dist/ui/formatters.js +174 -0
- package/dist/ui/formatters.js.map +1 -0
- package/dist/ui/pluginConfigSession.mjs +141 -0
- package/dist/ui/webUi-featureOptions/categoryState.mjs +135 -0
- package/dist/ui/webUi-featureOptions/effects/keyboard.mjs +61 -0
- package/dist/ui/webUi-featureOptions/effects/persist.mjs +226 -0
- package/dist/ui/webUi-featureOptions/effects/theme.mjs +398 -0
- package/dist/ui/webUi-featureOptions/effects/tokens.mjs +152 -0
- package/dist/ui/webUi-featureOptions/rendering.mjs +431 -0
- package/dist/ui/webUi-featureOptions/selectors.mjs +360 -0
- package/dist/ui/webUi-featureOptions/state.mjs +319 -0
- package/dist/ui/webUi-featureOptions/store.mjs +181 -0
- package/dist/ui/webUi-featureOptions/utils.mjs +200 -0
- package/dist/ui/webUi-featureOptions/views/connectionError.mjs +152 -0
- package/dist/ui/webUi-featureOptions/views/deviceInfo.mjs +80 -0
- package/dist/ui/webUi-featureOptions/views/header.mjs +77 -0
- package/dist/ui/webUi-featureOptions/views/nav.mjs +327 -0
- package/dist/ui/webUi-featureOptions/views/options.mjs +521 -0
- package/dist/ui/webUi-featureOptions/views/search.mjs +395 -0
- package/dist/ui/webUi-featureOptions.mjs +677 -0
- package/dist/ui/webUi.mjs +192 -87
- package/dist/util.d.ts +602 -45
- package/dist/util.js +783 -78
- package/dist/util.js.map +1 -1
- package/package.json +33 -15
- package/build/eslint-rules.mjs +0 -511
- package/dist/featureoptions.d.ts +0 -264
- package/dist/featureoptions.js +0 -480
- package/dist/featureoptions.js.map +0 -1
- package/dist/mqttclient.d.ts +0 -178
- package/dist/mqttclient.js +0 -310
- package/dist/mqttclient.js.map +0 -1
- package/dist/ui/featureoptions.js +0 -480
- package/dist/ui/featureoptions.js.map +0 -1
- package/dist/ui/webUi-featureoptions.mjs +0 -3765
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mirrors homebridge-core const enum values that plugins need at value-side runtime. `verbatimModuleSyntax` disallows value imports of ambient const enums, so the
|
|
3
|
+
* string contracts from homebridge's `api.d.ts` are re-declared here at value-side. This is the homebridge-core counterpart of the HAP mirrors in `ffmpeg/hap-enums.ts`:
|
|
4
|
+
* every Homebridge plugin registers for the `api` lifecycle events, so centralizing the mirror gives every consumer a single import path and a single update point if
|
|
5
|
+
* upstream `homebridge` ever changes a value.
|
|
6
|
+
*
|
|
7
|
+
* Values MUST stay in lockstep with the upstream definitions in `homebridge/.../api.d.ts`. The matching type alias lets consumers import the canonical name from one
|
|
8
|
+
* place rather than re-declaring it.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
import type { APIEvent as APIEventEnum } from "homebridge";
|
|
13
|
+
/**
|
|
14
|
+
* String mirror of homebridge-core's `APIEvent` const enum, surfaced to plugins for `api.on(...)` lifecycle-event registration: `DID_FINISH_LAUNCHING` fires once
|
|
15
|
+
* homebridge has finished booting and initializing every plugin, and `SHUTDOWN` fires when homebridge shuts down (a regular shutdown or an unexpected crash).
|
|
16
|
+
*
|
|
17
|
+
* @category Homebridge
|
|
18
|
+
*/
|
|
19
|
+
export declare const APIEvent: {
|
|
20
|
+
readonly DID_FINISH_LAUNCHING: APIEventEnum.DID_FINISH_LAUNCHING;
|
|
21
|
+
readonly SHUTDOWN: APIEventEnum.SHUTDOWN;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Type alias re-exposing the homebridge enum under its canonical name so existing `event: APIEvent` annotations resolve through the shared module rather than a local
|
|
25
|
+
* redeclaration in each consumer.
|
|
26
|
+
*
|
|
27
|
+
* @category Homebridge
|
|
28
|
+
*/
|
|
29
|
+
export type APIEvent = APIEventEnum;
|
|
30
|
+
//# sourceMappingURL=homebridge-enums.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/* Copyright(C) 2017-2026, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
|
+
*
|
|
3
|
+
* homebridge-enums.ts: Single source of truth for the homebridge-core const enum mirrors that plugins need at runtime.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* String mirror of homebridge-core's `APIEvent` const enum, surfaced to plugins for `api.on(...)` lifecycle-event registration: `DID_FINISH_LAUNCHING` fires once
|
|
7
|
+
* homebridge has finished booting and initializing every plugin, and `SHUTDOWN` fires when homebridge shuts down (a regular shutdown or an unexpected crash).
|
|
8
|
+
*
|
|
9
|
+
* @category Homebridge
|
|
10
|
+
*/
|
|
11
|
+
export const APIEvent = {
|
|
12
|
+
// String const enum members are nominal in TypeScript: a raw string literal is not assignable to the enum member type without an explicit brand. The per-property
|
|
13
|
+
// assertions make the intent visible at each value, and the assertion fails fast if the upstream string ever changes out from under us.
|
|
14
|
+
DID_FINISH_LAUNCHING: "didFinishLaunching",
|
|
15
|
+
SHUTDOWN: "shutdown"
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=homebridge-enums.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"homebridge-enums.js","sourceRoot":"","sources":["../src/homebridge-enums.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAeH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAmH;IAEtI,kKAAkK;IAClK,wIAAwI;IACxI,oBAAoB,EAAE,oBAAyD;IAC/E,QAAQ,EAAE,UAAmC;CAC9C,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
export * from "./backpressure.
|
|
2
|
-
export * from "./
|
|
3
|
-
export * from "./
|
|
4
|
-
export * from "./
|
|
5
|
-
export * from "./
|
|
6
|
-
export * from "./
|
|
1
|
+
export * from "./backpressure.ts";
|
|
2
|
+
export * from "./clock-double.ts";
|
|
3
|
+
export * from "./clock.ts";
|
|
4
|
+
export * from "./featureOptions.ts";
|
|
5
|
+
export * from "./featureOptions-docs.ts";
|
|
6
|
+
export * from "./homebridge-enums.ts";
|
|
7
|
+
export * from "./mqttClient.ts";
|
|
8
|
+
export * from "./service.ts";
|
|
9
|
+
export * from "./util.ts";
|
|
10
|
+
export * from "./ffmpeg/index.ts";
|
|
11
|
+
export * from "./logclient/index.ts";
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -3,9 +3,14 @@
|
|
|
3
3
|
* index.ts: Homebridge plugin utilities.
|
|
4
4
|
*/
|
|
5
5
|
export * from "./backpressure.js";
|
|
6
|
-
export * from "./
|
|
7
|
-
export * from "./
|
|
6
|
+
export * from "./clock-double.js";
|
|
7
|
+
export * from "./clock.js";
|
|
8
|
+
export * from "./featureOptions.js";
|
|
9
|
+
export * from "./featureOptions-docs.js";
|
|
10
|
+
export * from "./homebridge-enums.js";
|
|
11
|
+
export * from "./mqttClient.js";
|
|
8
12
|
export * from "./service.js";
|
|
9
13
|
export * from "./util.js";
|
|
10
14
|
export * from "./ffmpeg/index.js";
|
|
15
|
+
export * from "./logclient/index.js";
|
|
11
16
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token acquisition for the Homebridge UI log client.
|
|
3
|
+
*
|
|
4
|
+
* {@link acquireToken} turns a {@link LogClientCredentials} discriminated union into a raw bearer token by talking to the homebridge-config-ui-x authentication API. Each
|
|
5
|
+
* credential arm maps to one of the server's authentication paths: a pre-acquired `token` is returned verbatim with no network call, a `password` arm posts to `POST
|
|
6
|
+
* /api/auth/login` (carrying an optional one-time passcode), and `noauth` posts to `POST /api/auth/noauth`, which the server honors only when its UI is configured with
|
|
7
|
+
* `auth: "none"`.
|
|
8
|
+
*
|
|
9
|
+
* The module's load-bearing concern beyond "get a token" is failure classification. The socket's reconnect loop re-authenticates on every reconnect, so it must be able
|
|
10
|
+
* to tell a transient fault (the server is briefly down or returned a 5xx) from a permanent one (the credentials are wrong, an OTP is required, or noauth is disabled).
|
|
11
|
+
* A transient fault should be retried with backoff; a permanent one must fail the reconnect fast so the user gets an actionable error rather than an endless retry loop
|
|
12
|
+
* against credentials that will never work. {@link acquireToken} therefore rejects with a {@link LogAuthError} whose `kind` discriminates `"permanent"` from
|
|
13
|
+
* `"transient"`, and the reconnect's `shouldRetry` predicate vetoes a retry on the permanent kind via {@link isPermanentAuthError}.
|
|
14
|
+
*
|
|
15
|
+
* The `fetch` implementation is injected (defaulting to the global `fetch`) so the whole module is exercised in tests without a live server.
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
import type { EndpointTarget } from "./endpoints.ts";
|
|
20
|
+
import type { LogClientCredentials } from "./types.ts";
|
|
21
|
+
/**
|
|
22
|
+
* The classification of an authentication failure, used by the reconnect loop to decide whether to retry.
|
|
23
|
+
*
|
|
24
|
+
* - `"permanent"` - the credentials are wrong, an OTP is required, or noauth is disabled. Retrying with the same credentials will keep failing, so the reconnect loop
|
|
25
|
+
* vetoes a retry and surfaces the error to the user.
|
|
26
|
+
* - `"transient"` - a network fault or a server-side 5xx/429. The condition may clear on its own, so the reconnect loop retries with backoff.
|
|
27
|
+
*
|
|
28
|
+
* @category Log Client
|
|
29
|
+
*/
|
|
30
|
+
export type LogAuthErrorKind = "permanent" | "transient";
|
|
31
|
+
/**
|
|
32
|
+
* Options accepted by {@link LogAuthError}'s constructor.
|
|
33
|
+
*
|
|
34
|
+
* @property cause - The underlying cause (a network error, or the HTTP response context), attached for diagnostics.
|
|
35
|
+
* @property kind - The failure classification. See {@link LogAuthErrorKind}.
|
|
36
|
+
*
|
|
37
|
+
* @category Log Client
|
|
38
|
+
*/
|
|
39
|
+
export interface LogAuthErrorOptions {
|
|
40
|
+
readonly cause?: unknown;
|
|
41
|
+
readonly kind: LogAuthErrorKind;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The error thrown by {@link acquireToken} when authentication fails.
|
|
45
|
+
*
|
|
46
|
+
* Carries a {@link LogAuthErrorKind} discriminator so a consumer (specifically the socket's reconnect `shouldRetry` predicate) can distinguish a permanent credential
|
|
47
|
+
* problem from a transient network/server fault without parsing the message text. The message itself is already actionable - it names the failing path and the reason -
|
|
48
|
+
* so it can be surfaced to the user directly.
|
|
49
|
+
*
|
|
50
|
+
* @category Log Client
|
|
51
|
+
*/
|
|
52
|
+
export declare class LogAuthError extends Error {
|
|
53
|
+
/**
|
|
54
|
+
* The failure classification. `"permanent"` failures must not be retried; `"transient"` failures may be.
|
|
55
|
+
*/
|
|
56
|
+
readonly kind: LogAuthErrorKind;
|
|
57
|
+
/**
|
|
58
|
+
* Construct a new authentication error.
|
|
59
|
+
*
|
|
60
|
+
* @param message - A human-readable, actionable description of the failure.
|
|
61
|
+
* @param options - The classification and optional underlying cause. See {@link LogAuthErrorOptions}.
|
|
62
|
+
*/
|
|
63
|
+
constructor(message: string, options: LogAuthErrorOptions);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Type guard: returns `true` when `error` is a {@link LogAuthError} classified `"permanent"`.
|
|
67
|
+
*
|
|
68
|
+
* The reconnect loop's `shouldRetry` predicate consults this to veto a retry the instant a permanent credential failure surfaces, so a wrong password or a missing OTP
|
|
69
|
+
* fails the reconnect fast rather than looping forever against credentials that cannot succeed.
|
|
70
|
+
*
|
|
71
|
+
* @param error - The value to test.
|
|
72
|
+
*
|
|
73
|
+
* @returns `true` when `error` is a permanent authentication failure.
|
|
74
|
+
*
|
|
75
|
+
* @category Log Client
|
|
76
|
+
*/
|
|
77
|
+
export declare function isPermanentAuthError(error: unknown): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Options accepted by {@link acquireToken}: the connection target plus an injectable `fetch` seam.
|
|
80
|
+
*
|
|
81
|
+
* @property fetch - The fetch implementation to use. Defaults to the global `fetch`. Injected so the auth flow is testable without a live server.
|
|
82
|
+
* @property host - The hostname or IP of the homebridge-config-ui-x server.
|
|
83
|
+
* @property port - The TCP port the server listens on.
|
|
84
|
+
* @property tls - When `true`, use the secure (`https`) scheme; when `false` or omitted, plaintext (`http`).
|
|
85
|
+
*
|
|
86
|
+
* @category Log Client
|
|
87
|
+
*/
|
|
88
|
+
export interface AcquireTokenOptions extends EndpointTarget {
|
|
89
|
+
readonly fetch?: typeof fetch;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Acquire a raw bearer token for the homebridge-config-ui-x API from the supplied credentials.
|
|
93
|
+
*
|
|
94
|
+
* Dispatches on the credential discriminated union:
|
|
95
|
+
*
|
|
96
|
+
* - `token` - returns the pre-acquired token verbatim, with no network call. A static token that has since expired is not detected here; the failure surfaces later when
|
|
97
|
+
* the socket handshake is rejected.
|
|
98
|
+
* - `password` - posts `{ username, password, otp? }` to `POST /api/auth/login`.
|
|
99
|
+
* - `noauth` - posts to `POST /api/auth/noauth`, which the server honors only when its UI is configured with `auth: "none"`.
|
|
100
|
+
*
|
|
101
|
+
* On failure it rejects with a {@link LogAuthError} whose `kind` classifies the failure as permanent (wrong credentials, OTP required, noauth disabled, broken success
|
|
102
|
+
* body) or transient (network fault, 5xx, 429), so the reconnect loop can fail fast on permanent failures and retry transient ones.
|
|
103
|
+
*
|
|
104
|
+
* @param credentials - The credentials to authenticate with. See {@link LogClientCredentials}.
|
|
105
|
+
* @param options - The connection target and the injectable `fetch` seam. See {@link AcquireTokenOptions}.
|
|
106
|
+
*
|
|
107
|
+
* @returns A promise resolving to the raw bearer token (the bare JWT, with no `Bearer` prefix).
|
|
108
|
+
*
|
|
109
|
+
* @throws {@link LogAuthError} on any authentication failure, classified permanent or transient.
|
|
110
|
+
*
|
|
111
|
+
* @category Log Client
|
|
112
|
+
*/
|
|
113
|
+
export declare function acquireToken(credentials: LogClientCredentials, options: AcquireTokenOptions): Promise<string>;
|
|
114
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/* Copyright(C) 2017-2026, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
|
+
*
|
|
3
|
+
* logclient/auth.ts: Token acquisition against the homebridge-config-ui-x authentication API.
|
|
4
|
+
*/
|
|
5
|
+
import { formatErrorMessage } from "../util.js";
|
|
6
|
+
import { httpBaseUrl } from "./endpoints.js";
|
|
7
|
+
// The auth API returns these status codes for a genuine credential or precondition problem: 401 (wrong username/password, or the no-auth path used against a server not
|
|
8
|
+
// in "none" mode), 403 (account disabled or action not permitted), and 412 (a one-time passcode is required but was not supplied). All three keep failing until the
|
|
9
|
+
// caller changes the credentials or supplies an OTP, so they are classified permanent and the reconnect loop must not retry them. A 400 is handled separately (see
|
|
10
|
+
// throwForStatus): it is a malformed-request / protocol mismatch, not a credential problem, so it carries a different message even though it too is permanent.
|
|
11
|
+
const CREDENTIAL_STATUS = new Set([401, 403, 412]);
|
|
12
|
+
/**
|
|
13
|
+
* The error thrown by {@link acquireToken} when authentication fails.
|
|
14
|
+
*
|
|
15
|
+
* Carries a {@link LogAuthErrorKind} discriminator so a consumer (specifically the socket's reconnect `shouldRetry` predicate) can distinguish a permanent credential
|
|
16
|
+
* problem from a transient network/server fault without parsing the message text. The message itself is already actionable - it names the failing path and the reason -
|
|
17
|
+
* so it can be surfaced to the user directly.
|
|
18
|
+
*
|
|
19
|
+
* @category Log Client
|
|
20
|
+
*/
|
|
21
|
+
export class LogAuthError extends Error {
|
|
22
|
+
/**
|
|
23
|
+
* The failure classification. `"permanent"` failures must not be retried; `"transient"` failures may be.
|
|
24
|
+
*/
|
|
25
|
+
kind;
|
|
26
|
+
/**
|
|
27
|
+
* Construct a new authentication error.
|
|
28
|
+
*
|
|
29
|
+
* @param message - A human-readable, actionable description of the failure.
|
|
30
|
+
* @param options - The classification and optional underlying cause. See {@link LogAuthErrorOptions}.
|
|
31
|
+
*/
|
|
32
|
+
constructor(message, options) {
|
|
33
|
+
super(message, { cause: options.cause });
|
|
34
|
+
this.kind = options.kind;
|
|
35
|
+
this.name = "LogAuthError";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Type guard: returns `true` when `error` is a {@link LogAuthError} classified `"permanent"`.
|
|
40
|
+
*
|
|
41
|
+
* The reconnect loop's `shouldRetry` predicate consults this to veto a retry the instant a permanent credential failure surfaces, so a wrong password or a missing OTP
|
|
42
|
+
* fails the reconnect fast rather than looping forever against credentials that cannot succeed.
|
|
43
|
+
*
|
|
44
|
+
* @param error - The value to test.
|
|
45
|
+
*
|
|
46
|
+
* @returns `true` when `error` is a permanent authentication failure.
|
|
47
|
+
*
|
|
48
|
+
* @category Log Client
|
|
49
|
+
*/
|
|
50
|
+
export function isPermanentAuthError(error) {
|
|
51
|
+
return (error instanceof LogAuthError) && (error.kind === "permanent");
|
|
52
|
+
}
|
|
53
|
+
// Read a token out of a successful auth response body. The server returns `{ access_token, token_type: "Bearer", expires_in }`; we need only `access_token`. A 2xx
|
|
54
|
+
// response whose body is missing or malformed is treated as a permanent failure, because a server that answers a valid request with an unparseable success body will
|
|
55
|
+
// not behave differently on retry - the contract is broken, not the moment.
|
|
56
|
+
async function readAccessToken(response, pathLabel) {
|
|
57
|
+
let body;
|
|
58
|
+
try {
|
|
59
|
+
body = await response.json();
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
throw new LogAuthError(pathLabel + " succeeded but returned an unreadable response body: " + formatErrorMessage(error) + ".", { cause: error, kind: "permanent" });
|
|
63
|
+
}
|
|
64
|
+
const token = isRecord(body) ? body["access_token"] : undefined;
|
|
65
|
+
if (typeof token !== "string") {
|
|
66
|
+
throw new LogAuthError(pathLabel + " succeeded but returned no access token.", { cause: body, kind: "permanent" });
|
|
67
|
+
}
|
|
68
|
+
return token;
|
|
69
|
+
}
|
|
70
|
+
// Narrow an unknown value to a plain record so a single top-level string property (such as `access_token` or `message`) can be read off a parsed JSON body without unsafe
|
|
71
|
+
// member access.
|
|
72
|
+
function isRecord(value) {
|
|
73
|
+
return (typeof value === "object") && (value !== null);
|
|
74
|
+
}
|
|
75
|
+
// Classify and throw for a non-2xx auth response. The status code is the discriminator: the codes in `CREDENTIAL_STATUS` describe a credential problem that will not
|
|
76
|
+
// clear on retry, so they raise a permanent failure with a path-specific, actionable message; a 400 is handled separately below as a permanent protocol error, and every
|
|
77
|
+
// other status (notably 5xx and 429) is treated as transient so the reconnect loop retries with backoff. The body is read best-effort for context but never required - a
|
|
78
|
+
// server returning an error status with no body still produces a clear message.
|
|
79
|
+
async function throwForStatus(response, pathLabel, permanentHint) {
|
|
80
|
+
const detail = await readErrorDetail(response);
|
|
81
|
+
const suffix = (detail.length > 0) ? (": " + detail) : "";
|
|
82
|
+
// A genuine credential or precondition rejection (401/403/412): the caller must change something, so surface the path-specific, actionable hint. Permanent - retrying
|
|
83
|
+
// the same credentials cannot succeed.
|
|
84
|
+
if (CREDENTIAL_STATUS.has(response.status)) {
|
|
85
|
+
throw new LogAuthError(pathLabel + " failed - " + permanentHint + suffix + ".", { cause: { status: response.status }, kind: "permanent" });
|
|
86
|
+
}
|
|
87
|
+
// A 400 is a malformed-request / protocol mismatch, not a credential problem - a server that merely is not in no-auth mode answers the no-auth path with 401, never
|
|
88
|
+
// 400. It is permanent (the identical request keeps failing) but it points at a request or homebridge-config-ui-x version mismatch rather than the credentials.
|
|
89
|
+
if (response.status === 400) {
|
|
90
|
+
throw new LogAuthError(pathLabel + " failed - the server rejected the request as malformed (HTTP 400)" + suffix +
|
|
91
|
+
"; this usually indicates a homebridge-config-ui-x version or protocol mismatch.", { cause: { status: 400 }, kind: "permanent" });
|
|
92
|
+
}
|
|
93
|
+
// Everything else (notably 5xx and 429) may clear on its own, so it is transient and the reconnect loop retries it with backoff.
|
|
94
|
+
throw new LogAuthError(pathLabel + " failed - the server returned HTTP " + response.status.toString() + " (" + response.statusText + ")" + suffix + ".", { cause: { status: response.status }, kind: "transient" });
|
|
95
|
+
}
|
|
96
|
+
// Best-effort read of an error response's human-readable detail. homebridge-config-ui-x typically returns `{ message, error, statusCode }` on a failure; we surface the
|
|
97
|
+
// `message` when present, falling back to the raw text. A body that cannot be read at all yields an empty string so the caller's message still reads cleanly without it.
|
|
98
|
+
async function readErrorDetail(response) {
|
|
99
|
+
let text;
|
|
100
|
+
try {
|
|
101
|
+
text = await response.text();
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return "";
|
|
105
|
+
}
|
|
106
|
+
if (text.length === 0) {
|
|
107
|
+
return "";
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const parsed = JSON.parse(text);
|
|
111
|
+
const message = isRecord(parsed) ? parsed["message"] : undefined;
|
|
112
|
+
if (typeof message === "string") {
|
|
113
|
+
return message;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Not JSON; fall through and surface the raw text trimmed of its trailing period so it composes cleanly with the caller's own trailing period.
|
|
118
|
+
}
|
|
119
|
+
return text.replace(/\.$/, "");
|
|
120
|
+
}
|
|
121
|
+
// Wrap a network-level fetch rejection (DNS failure, connection refused, TLS error) as a transient authentication failure. A connection that cannot be established may
|
|
122
|
+
// succeed on a later attempt, so the reconnect loop should retry rather than give up.
|
|
123
|
+
function networkFailure(pathLabel, error) {
|
|
124
|
+
return new LogAuthError(pathLabel + " could not reach the server: " + formatErrorMessage(error) + ".", { cause: error, kind: "transient" });
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Acquire a raw bearer token for the homebridge-config-ui-x API from the supplied credentials.
|
|
128
|
+
*
|
|
129
|
+
* Dispatches on the credential discriminated union:
|
|
130
|
+
*
|
|
131
|
+
* - `token` - returns the pre-acquired token verbatim, with no network call. A static token that has since expired is not detected here; the failure surfaces later when
|
|
132
|
+
* the socket handshake is rejected.
|
|
133
|
+
* - `password` - posts `{ username, password, otp? }` to `POST /api/auth/login`.
|
|
134
|
+
* - `noauth` - posts to `POST /api/auth/noauth`, which the server honors only when its UI is configured with `auth: "none"`.
|
|
135
|
+
*
|
|
136
|
+
* On failure it rejects with a {@link LogAuthError} whose `kind` classifies the failure as permanent (wrong credentials, OTP required, noauth disabled, broken success
|
|
137
|
+
* body) or transient (network fault, 5xx, 429), so the reconnect loop can fail fast on permanent failures and retry transient ones.
|
|
138
|
+
*
|
|
139
|
+
* @param credentials - The credentials to authenticate with. See {@link LogClientCredentials}.
|
|
140
|
+
* @param options - The connection target and the injectable `fetch` seam. See {@link AcquireTokenOptions}.
|
|
141
|
+
*
|
|
142
|
+
* @returns A promise resolving to the raw bearer token (the bare JWT, with no `Bearer` prefix).
|
|
143
|
+
*
|
|
144
|
+
* @throws {@link LogAuthError} on any authentication failure, classified permanent or transient.
|
|
145
|
+
*
|
|
146
|
+
* @category Log Client
|
|
147
|
+
*/
|
|
148
|
+
export async function acquireToken(credentials, options) {
|
|
149
|
+
// A pre-acquired token needs no network round-trip; return it verbatim. The token arm is handled first so the common "I already have a token" path costs nothing.
|
|
150
|
+
if (credentials.kind === "token") {
|
|
151
|
+
return credentials.token;
|
|
152
|
+
}
|
|
153
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
154
|
+
const base = httpBaseUrl(options);
|
|
155
|
+
switch (credentials.kind) {
|
|
156
|
+
case "noauth": {
|
|
157
|
+
// The no-authentication path. The server returns a token from `POST /api/auth/noauth` only when its UI is configured with `auth: "none"`; otherwise it rejects,
|
|
158
|
+
// which we classify permanent because the server's auth mode will not change between reconnect attempts.
|
|
159
|
+
return postForToken(fetchImpl, base + "/api/auth/noauth", undefined, "No-auth authentication", "the server does not have authentication disabled (set the UI auth mode to \"none\", or supply --user/--pass)");
|
|
160
|
+
}
|
|
161
|
+
case "password": {
|
|
162
|
+
// The interactive login path. We post the username, password, and optional OTP. A 401/403 means the credentials are wrong; a 412 means an OTP is required - both
|
|
163
|
+
// permanent until the caller supplies correct credentials or a passcode.
|
|
164
|
+
const body = { password: credentials.password, username: credentials.username };
|
|
165
|
+
if (credentials.otp !== undefined) {
|
|
166
|
+
body["otp"] = credentials.otp;
|
|
167
|
+
}
|
|
168
|
+
return postForToken(fetchImpl, base + "/api/auth/login", body, "Password authentication", "the username, password, or one-time passcode was rejected (a 2FA-enabled account requires --otp)");
|
|
169
|
+
}
|
|
170
|
+
default: {
|
|
171
|
+
// The union is exhausted above (the `token` arm returned earlier). This satisfies exhaustiveness and guards against a future credential arm being added without a
|
|
172
|
+
// handler here.
|
|
173
|
+
throw new LogAuthError("Unsupported credential kind.", { cause: credentials, kind: "permanent" });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Issue a single POST to an auth endpoint and extract the access token, mapping every failure shape to a classified {@link LogAuthError}. A JSON body is sent when one is
|
|
178
|
+
// supplied (the login path); the noauth path posts with no body. Network rejections are transient; non-2xx statuses are classified by `throwForStatus`.
|
|
179
|
+
async function postForToken(fetchImpl, url, body, pathLabel, permanentHint) {
|
|
180
|
+
// The JSON content-type header is a claim that a JSON body follows, so the two are derived together from a single condition: a request that carries a body
|
|
181
|
+
// declares `application/json` and serializes it, and a bodyless request (the no-auth path) sends neither. This coupling is the single source of truth that keeps
|
|
182
|
+
// every auth POST well-formed - declaring `application/json` with no body is incoherent, and Fastify (the homebridge-config-ui-x HTTP server) rejects a
|
|
183
|
+
// declared-JSON body that is empty.
|
|
184
|
+
const init = (body !== undefined) ?
|
|
185
|
+
{ body: JSON.stringify(body), headers: { "Content-Type": "application/json" }, method: "POST" } :
|
|
186
|
+
{ method: "POST" };
|
|
187
|
+
let response;
|
|
188
|
+
try {
|
|
189
|
+
response = await fetchImpl(url, init);
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
throw networkFailure(pathLabel, error);
|
|
193
|
+
}
|
|
194
|
+
if (!response.ok) {
|
|
195
|
+
return throwForStatus(response, pathLabel, permanentHint);
|
|
196
|
+
}
|
|
197
|
+
return readAccessToken(response, pathLabel);
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/logclient/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { LogSocketFactory } from "./socket.ts";
|
|
2
|
+
/**
|
|
3
|
+
* The event-hook shape {@link CliStream} exposes for the stream events the CLI observes. Modeled as an overloaded call signature - the same shape `EventEmitter.on`/
|
|
4
|
+
* `off` present - so each event's listener is typed to exactly what that event delivers: `"error"` hands the listener the failing error (an `EPIPE` broken pipe, or a
|
|
5
|
+
* genuine write fault such as `ENOSPC`), while `"drain"` delivers nothing and merely signals that the writable buffer has fallen back below its high-water mark.
|
|
6
|
+
*
|
|
7
|
+
* @category Log Client
|
|
8
|
+
*/
|
|
9
|
+
export interface CliStreamEventHook {
|
|
10
|
+
(event: "drain", listener: () => void): void;
|
|
11
|
+
(event: "error", listener: (error: NodeJS.ErrnoException) => void): void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A minimal output-stream surface {@link runHblog} writes to. Models the subset of a Node `WriteStream` the CLI actually uses: `write` for output, the optional `isTTY`
|
|
15
|
+
* flag that drives the auto-color decision, and the optional `on`/`off` event hooks used to trap a broken-pipe (`EPIPE`) error and to await `drain` when a write reports
|
|
16
|
+
* backpressure. The narrow interface keeps a test sink small (a `write` function is enough) while `process.stdout`/`process.stderr` satisfy it structurally.
|
|
17
|
+
*
|
|
18
|
+
* @category Log Client
|
|
19
|
+
*/
|
|
20
|
+
export interface CliStream {
|
|
21
|
+
isTTY?: boolean;
|
|
22
|
+
off?: CliStreamEventHook;
|
|
23
|
+
on?: CliStreamEventHook;
|
|
24
|
+
write: (chunk: string) => boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Options accepted by {@link runHblog}. Every external dependency the CLI touches is an injected seam, so the whole flow runs deterministically in tests.
|
|
28
|
+
*
|
|
29
|
+
* @property argv - The argument vector (typically `process.argv.slice(2)`).
|
|
30
|
+
* @property cwd - The current working directory. Reserved for future relative-path resolution; the home directory is the config-file anchor today.
|
|
31
|
+
* @property env - The environment map (typically `process.env`).
|
|
32
|
+
* @property fetch - Optional `fetch` seam forwarded to the engine's auth and REST transports. Defaults to the global `fetch`.
|
|
33
|
+
* @property homedir - The user's home directory, used to locate `~/.hblog.json` unless `HBLOG_CONFIG` overrides the path.
|
|
34
|
+
* @property now - Optional wall-clock epoch source (milliseconds) used to resolve the `--since`/`--until` time-range expressions against a single deterministic
|
|
35
|
+
* instant. Defaults to `systemClock.now`; a test injects a fixed clock so a windowed run's bounds are reproducible.
|
|
36
|
+
* @property readFile - Optional file-read seam forwarded to the config loader and used to read the package version. Defaults to `node:fs/promises` `readFile`.
|
|
37
|
+
* @property socketFactory - Optional socket-factory seam forwarded to the engine, so a test drives the live tail without a WebSocket. Defaults to the real factory.
|
|
38
|
+
* @property stat - Optional file-stat seam forwarded to the config loader for the permissions warning. Defaults to `node:fs/promises` `stat`.
|
|
39
|
+
* @property stderr - The diagnostics/warnings stream. Production passes `process.stderr`.
|
|
40
|
+
* @property stdout - The log-data stream. Production passes `process.stdout`.
|
|
41
|
+
*
|
|
42
|
+
* @category Log Client
|
|
43
|
+
*/
|
|
44
|
+
export interface RunHblogOptions {
|
|
45
|
+
readonly argv: readonly string[];
|
|
46
|
+
readonly cwd: string;
|
|
47
|
+
readonly env: NodeJS.ProcessEnv;
|
|
48
|
+
readonly fetch?: typeof fetch;
|
|
49
|
+
readonly homedir: string;
|
|
50
|
+
readonly now?: () => number;
|
|
51
|
+
readonly readFile?: (path: string) => Promise<string>;
|
|
52
|
+
readonly socketFactory?: LogSocketFactory;
|
|
53
|
+
readonly stat?: (path: string) => Promise<{
|
|
54
|
+
readonly mode: number;
|
|
55
|
+
}>;
|
|
56
|
+
readonly stderr: CliStream;
|
|
57
|
+
readonly stdout: CliStream;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Run the `hblog` command-line flow and return the process exit code.
|
|
61
|
+
*
|
|
62
|
+
* Parses {@link RunHblogOptions.argv}, handles `--help`/`--version` immediately, resolves the connection across flags / environment / `~/.hblog.json` (honoring
|
|
63
|
+
* `HBLOG_CONFIG`), maps the result into a {@link LogClientCredentials} and a {@link TailRequest}, builds a {@link HomebridgeLogClient}, runs the selected channel,
|
|
64
|
+
* applies the {@link createLogFilter} criteria, and writes log data to stdout (NDJSON for `--json`, raw/stripped lines otherwise) while routing diagnostics and warnings
|
|
65
|
+
* to stderr. A SIGINT/SIGTERM aborts the run cleanly (exit 0); a broken pipe (`EPIPE`) on stdout also ends cleanly (exit 0); a usage error returns 2; a connection or
|
|
66
|
+
* authentication failure returns 1. Token redaction is applied at the hard-error stderr writes (the setup failure, a captured stdout write error on either the
|
|
67
|
+
* normal-completion or the catch path, and the streaming catch's generic error); the usage and advisory writes never carry a token.
|
|
68
|
+
*
|
|
69
|
+
* @param options - The injected argument vector, environment, streams, directories, and seams. See {@link RunHblogOptions}.
|
|
70
|
+
*
|
|
71
|
+
* @returns The process exit code: 0 success / clean signal / help / version, 1 connection or auth failure, 2 usage error.
|
|
72
|
+
*
|
|
73
|
+
* @category Log Client
|
|
74
|
+
*/
|
|
75
|
+
export declare function runHblog(options: RunHblogOptions): Promise<number>;
|
|
76
|
+
//# sourceMappingURL=cli-run.d.ts.map
|