matterbridge-lg-thinq-plugin 0.1.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/.claude/CLAUDE.md +43 -0
- package/.claude/agents/compiler.md +91 -0
- package/.claude/agents/direct-executor.md +98 -0
- package/.claude/agents/documenter.md +78 -0
- package/.claude/agents/finalizer.md +233 -0
- package/.claude/agents/implementer.md +112 -0
- package/.claude/agents/investigator.md +100 -0
- package/.claude/agents/release-manager.md +203 -0
- package/.claude/agents/reviewer.md +131 -0
- package/.claude/agents/technical-architect.md +403 -0
- package/.claude/agents/test-writer.md +159 -0
- package/.claude/agents/wiki-manager.md +147 -0
- package/.claude/instructions/agent-prompts.md +90 -0
- package/.claude/instructions/shared-rules.md +11 -0
- package/.claude/instructions/team-orchestrator-policy.md +165 -0
- package/.claude/memory.md +186 -0
- package/.claude/output-styles/engineer-manager.md +38 -0
- package/.claude/settings.json +40 -0
- package/.claude/settings.local.json +30 -0
- package/.claude/skills/load-policy/SKILL.md +13 -0
- package/.claude/skills/ref-idea/SKILL.md +44 -0
- package/.claude/skills/status-of/SKILL.md +38 -0
- package/.claude/skills/workspace-console/SKILL.md +49 -0
- package/.claude/statusline-command.sh +130 -0
- package/.claude/statusline.md +53 -0
- package/.claude/templates/approval-packet.template.html +171 -0
- package/.claude/templates/reference-workspaces.md +16 -0
- package/.claude/tools/cli-runner-server.cjs +220 -0
- package/.claude/tools/discord-send-server.cjs +188 -0
- package/.claude/tools/github-release-server.cjs +181 -0
- package/.claude/tools/glob-grep-server.cjs +267 -0
- package/.claude/tools/read-log-server.cjs +175 -0
- package/.github/codeql/codeql-config.yml +14 -0
- package/.github/workflows/build.yml +54 -0
- package/.github/workflows/codeql.yml +59 -0
- package/.github/workflows/coverage.yml +70 -0
- package/.github/workflows/publish.yml +103 -0
- package/.mcp.json +34 -0
- package/CHANGELOG.md +195 -0
- package/LICENSE +21 -0
- package/dist/cli/commands/devices.js +52 -0
- package/dist/cli/commands/energy.js +122 -0
- package/dist/cli/commands/login.js +54 -0
- package/dist/cli/help.js +50 -0
- package/dist/cli/main.js +56 -0
- package/dist/cli/session.js +13 -0
- package/dist/cli/types.js +1 -0
- package/dist/cli/utils.js +29 -0
- package/dist/cli.js +2 -0
- package/dist/constants/index.js +1 -0
- package/dist/core/domain/entities/ThinqDevice.js +43 -0
- package/dist/core/domain/value-objects/AirConditionerCapabilities.js +22 -0
- package/dist/core/domain/value-objects/ThinqFilterState.js +35 -0
- package/dist/core/domain/value-objects/ThinqSnapshot.js +125 -0
- package/dist/errors/AuthenticationError.js +26 -0
- package/dist/errors/BaseError.js +23 -0
- package/dist/errors/index.js +2 -0
- package/dist/model/LgThinkqPluginPlatformConfig.js +23 -0
- package/dist/model/LgThinqPluginPlatformConfig.js +23 -0
- package/dist/module.js +212 -0
- package/dist/platform/deviceRegistry.js +26 -0
- package/dist/platform/platformConfigManager.js +89 -0
- package/dist/platform/platformState.js +9 -0
- package/dist/platform/thinq/thinqAirConditionerCommandHandlers.js +190 -0
- package/dist/platform/thinq/thinqAirConditionerEndpointFactory.js +53 -0
- package/dist/platform/thinq/thinqAirConditionerFilterResetCommandHandler.js +54 -0
- package/dist/platform/thinq/thinqAirConditionerFilterStateSync.js +19 -0
- package/dist/platform/thinq/thinqAirConditionerStateSync.js +205 -0
- package/dist/platform/thinq/thinqDeviceConfigReconciler.js +26 -0
- package/dist/platform/thinq/thinqDeviceConfigurator.js +124 -0
- package/dist/platform/thinq/thinqDeviceDiscovery.js +33 -0
- package/dist/platform/thinq/thinqWasherCommandHandlers.js +54 -0
- package/dist/platform/thinq/thinqWasherEndpointFactory.js +6 -0
- package/dist/platform/thinq/thinqWasherStateSync.js +31 -0
- package/dist/platform/thinq/thinqWasherStopCommandResolver.js +38 -0
- package/dist/services/authentication/AccountAuthStrategy.js +135 -0
- package/dist/services/authentication/AuthContext.js +1 -0
- package/dist/services/authentication/AuthenticationCoordinator.js +29 -0
- package/dist/services/authentication/IAuthStrategy.js +1 -0
- package/dist/services/authentication/TokenAuthStrategy.js +27 -0
- package/dist/services/authentication/UserDataRepository.js +32 -0
- package/dist/services/thinq/gateway.js +61 -0
- package/dist/services/thinq/mqtt/mqttCertificate.js +63 -0
- package/dist/services/thinq/mqtt/mqttConnectionEvents.js +37 -0
- package/dist/services/thinq/mqtt/mqttKeyRepository.js +33 -0
- package/dist/services/thinq/mqtt/mqttRetry.js +25 -0
- package/dist/services/thinq/mqtt/thinqMqttListener.js +97 -0
- package/dist/services/thinq/serviceContainer.js +73 -0
- package/dist/services/thinq/session.js +48 -0
- package/dist/services/thinq/thinqApiClient.js +258 -0
- package/dist/services/thinq/thinqDeviceService.js +75 -0
- package/dist/services/thinq/thinqFilterMonitoringService.js +40 -0
- package/dist/settings.js +9 -0
- package/dist/tests/helpers/testUtils.js +87 -0
- package/dist/utils/mask.js +39 -0
- package/docs/README_CLI.md +166 -0
- package/eslint.config.js +194 -0
- package/matterbridge-lg-thinq-plugin.config.json +27 -0
- package/matterbridge-lg-thinq-plugin.schema.json +520 -0
- package/package.json +107 -0
- package/prettier.config.js +49 -0
- package/scripts/parse-junit-failures.mjs +45 -0
- package/scripts/parse-tsc-failures.mjs +48 -0
- package/scripts/run-build-local-summary.mjs +29 -0
- package/scripts/run-format-summary.mjs +36 -0
- package/scripts/run-lint-fix-summary.mjs +38 -0
- package/scripts/run-precommit-summary.mjs +94 -0
- package/src/cli/commands/devices.ts +67 -0
- package/src/cli/commands/energy.ts +160 -0
- package/src/cli/commands/login.ts +74 -0
- package/src/cli/help.ts +53 -0
- package/src/cli/main.ts +66 -0
- package/src/cli/session.ts +15 -0
- package/src/cli/types.ts +10 -0
- package/src/cli/utils.ts +30 -0
- package/src/cli.ts +3 -0
- package/src/constants/index.ts +8 -0
- package/src/core/domain/entities/ThinqDevice.test.ts +224 -0
- package/src/core/domain/entities/ThinqDevice.ts +74 -0
- package/src/core/domain/value-objects/AirConditionerCapabilities.ts +50 -0
- package/src/core/domain/value-objects/ThinqFilterState.ts +50 -0
- package/src/core/domain/value-objects/ThinqSnapshot.ts +162 -0
- package/src/errors/AuthenticationError.ts +37 -0
- package/src/errors/BaseError.ts +38 -0
- package/src/errors/index.ts +24 -0
- package/src/model/LgThinqPluginPlatformConfig.ts +94 -0
- package/src/module.ts +298 -0
- package/src/platform/deviceRegistry.ts +58 -0
- package/src/platform/platformConfigManager.ts +133 -0
- package/src/platform/platformState.ts +16 -0
- package/src/platform/thinq/thinqAirConditionerCommandHandlers.ts +291 -0
- package/src/platform/thinq/thinqAirConditionerEndpointFactory.ts +145 -0
- package/src/platform/thinq/thinqAirConditionerFilterResetCommandHandler.ts +96 -0
- package/src/platform/thinq/thinqAirConditionerFilterStateSync.ts +42 -0
- package/src/platform/thinq/thinqAirConditionerStateSync.ts +343 -0
- package/src/platform/thinq/thinqDeviceConfigReconciler.ts +42 -0
- package/src/platform/thinq/thinqDeviceConfigurator.test.ts +90 -0
- package/src/platform/thinq/thinqDeviceConfigurator.ts +191 -0
- package/src/platform/thinq/thinqDeviceDiscovery.test.ts +173 -0
- package/src/platform/thinq/thinqDeviceDiscovery.ts +51 -0
- package/src/platform/thinq/thinqWasherCommandHandlers.ts +120 -0
- package/src/platform/thinq/thinqWasherEndpointFactory.ts +37 -0
- package/src/platform/thinq/thinqWasherStateSync.ts +60 -0
- package/src/platform/thinq/thinqWasherStopCommandResolver.ts +62 -0
- package/src/services/authentication/AccountAuthStrategy.test.ts +358 -0
- package/src/services/authentication/AccountAuthStrategy.ts +209 -0
- package/src/services/authentication/AuthContext.ts +19 -0
- package/src/services/authentication/AuthenticationCoordinator.test.ts +217 -0
- package/src/services/authentication/AuthenticationCoordinator.ts +59 -0
- package/src/services/authentication/IAuthStrategy.ts +10 -0
- package/src/services/authentication/TokenAuthStrategy.test.ts +167 -0
- package/src/services/authentication/TokenAuthStrategy.ts +40 -0
- package/src/services/authentication/UserDataRepository.test.ts +149 -0
- package/src/services/authentication/UserDataRepository.ts +46 -0
- package/src/services/thinq/gateway.ts +86 -0
- package/src/services/thinq/mqtt/mqttCertificate.test.ts +344 -0
- package/src/services/thinq/mqtt/mqttCertificate.ts +102 -0
- package/src/services/thinq/mqtt/mqttConnectionEvents.test.ts +276 -0
- package/src/services/thinq/mqtt/mqttConnectionEvents.ts +76 -0
- package/src/services/thinq/mqtt/mqttKeyRepository.test.ts +164 -0
- package/src/services/thinq/mqtt/mqttKeyRepository.ts +43 -0
- package/src/services/thinq/mqtt/mqttRetry.test.ts +239 -0
- package/src/services/thinq/mqtt/mqttRetry.ts +43 -0
- package/src/services/thinq/mqtt/thinqMqttListener.test.ts +349 -0
- package/src/services/thinq/mqtt/thinqMqttListener.ts +120 -0
- package/src/services/thinq/serviceContainer.test.ts +203 -0
- package/src/services/thinq/serviceContainer.ts +105 -0
- package/src/services/thinq/session.test.ts +287 -0
- package/src/services/thinq/session.ts +69 -0
- package/src/services/thinq/thinqApiClient.test.ts +702 -0
- package/src/services/thinq/thinqApiClient.ts +418 -0
- package/src/services/thinq/thinqDeviceService.test.ts +415 -0
- package/src/services/thinq/thinqDeviceService.ts +101 -0
- package/src/services/thinq/thinqFilterMonitoringService.test.ts +264 -0
- package/src/services/thinq/thinqFilterMonitoringService.ts +53 -0
- package/src/settings.ts +18 -0
- package/src/tests/cli/commands/devices.test.ts +293 -0
- package/src/tests/cli/commands/login.test.ts +240 -0
- package/src/tests/cli/help.test.ts +46 -0
- package/src/tests/cli/main.test.ts +214 -0
- package/src/tests/cli/session.test.ts +198 -0
- package/src/tests/cli/utils.test.ts +194 -0
- package/src/tests/core/domain/value-objects/AirConditionerCapabilities.test.ts +685 -0
- package/src/tests/core/domain/value-objects/ThinqFilterState.test.ts +210 -0
- package/src/tests/core/domain/value-objects/ThinqSnapshot.test.ts +1035 -0
- package/src/tests/helpers/testUtils.ts +118 -0
- package/src/tests/model/LgThinqPluginPlatformConfig.test.ts +81 -0
- package/src/tests/platform/deviceRegistry.test.ts +177 -0
- package/src/tests/platform/platformConfigManager.test.ts +790 -0
- package/src/tests/platform/platformState.test.ts +41 -0
- package/src/tests/platform/thinq/thinqAirConditionerCommandHandlers.test.ts +879 -0
- package/src/tests/platform/thinq/thinqAirConditionerEndpointFactory.test.ts +993 -0
- package/src/tests/platform/thinq/thinqAirConditionerFilterResetCommandHandler.test.ts +508 -0
- package/src/tests/platform/thinq/thinqAirConditionerFilterStateSync.test.ts +212 -0
- package/src/tests/platform/thinq/thinqAirConditionerStateSync.test.ts +1586 -0
- package/src/tests/platform/thinq/thinqDeviceConfigReconciler.test.ts +269 -0
- package/src/tests/platform/thinq/thinqDeviceConfigurator.test.ts +492 -0
- package/src/tests/platform/thinq/thinqWasherCommandHandlers.test.ts +594 -0
- package/src/tests/platform/thinq/thinqWasherEndpointFactory.test.ts +84 -0
- package/src/tests/platform/thinq/thinqWasherStateSync.test.ts +373 -0
- package/src/tests/platform/thinq/thinqWasherStopCommandResolver.test.ts +392 -0
- package/src/tests/settings.test.ts +33 -0
- package/src/utils/mask.ts +50 -0
- package/tsconfig.base.json +25 -0
- package/tsconfig.json +9 -0
- package/tsconfig.production.json +13 -0
- package/tsconfig.vitest.json +5 -0
- package/vite.config.ts +43 -0
- package/wiki/memory-archive.md +12 -0
- package/wiki/reference-workspaces.md +23 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/* eslint-disable no-console, n/no-process-exit */
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
const REPORT = 'test-report.junit.xml';
|
|
5
|
+
const MSG_MAX = 120;
|
|
6
|
+
|
|
7
|
+
function decodeXml(value) {
|
|
8
|
+
return value
|
|
9
|
+
.replace(/'/g, "'")
|
|
10
|
+
.replace(/"/g, '"')
|
|
11
|
+
.replace(/</g, '<')
|
|
12
|
+
.replace(/>/g, '>')
|
|
13
|
+
.replace(/&/g, '&');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!existsSync(REPORT)) {
|
|
17
|
+
console.error('TESTS FAIL: missing test-report.junit.xml');
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const xml = readFileSync(REPORT, 'utf8');
|
|
22
|
+
const failures = [];
|
|
23
|
+
|
|
24
|
+
for (const block of xml.matchAll(/<testcase[^>]*>[\s\S]*?<\/testcase>/g)) {
|
|
25
|
+
const testcase = block[0];
|
|
26
|
+
const failure = testcase.match(/<failure[^>]*message="([^"]*)"/);
|
|
27
|
+
if (!failure) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const openTag = testcase.match(/^<testcase\s+([^>]+)>/)?.[1] ?? '';
|
|
32
|
+
const classname = openTag.match(/classname="([^"]*)"/)?.[1] ?? '';
|
|
33
|
+
const name = decodeXml(openTag.match(/\bname="([^"]*)"/)?.[1] ?? '');
|
|
34
|
+
const message = decodeXml(failure[1]).split('\n')[0].slice(0, MSG_MAX);
|
|
35
|
+
failures.push(`${classname} > ${name}: ${message}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (failures.length === 0) {
|
|
39
|
+
console.log('TESTS PASS');
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log(`FAILED ${failures.length}:`);
|
|
44
|
+
console.log(failures.join('\n'));
|
|
45
|
+
process.exit(1);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/* eslint-disable no-console, n/no-process-exit */
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
const LOG = 'type-check.log';
|
|
5
|
+
const MSG_MAX = 120;
|
|
6
|
+
const MAX_LINES = 25;
|
|
7
|
+
|
|
8
|
+
if (!existsSync(LOG)) {
|
|
9
|
+
console.error('TYPE-CHECK FAIL: missing type-check.log');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const log = readFileSync(LOG, 'utf8');
|
|
14
|
+
const failures = [];
|
|
15
|
+
|
|
16
|
+
for (const line of log.split('\n')) {
|
|
17
|
+
const fileMatch = line.match(/^(.+?\(\d+,\d+\)): (error TS\d+: .+)$/);
|
|
18
|
+
if (fileMatch) {
|
|
19
|
+
failures.push(`${fileMatch[1]}: ${fileMatch[2].slice(0, MSG_MAX)}`);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const bareMatch = line.match(/^(error TS\d+: .+)$/);
|
|
24
|
+
if (bareMatch) {
|
|
25
|
+
failures.push(bareMatch[1].slice(0, MSG_MAX));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (failures.length === 0) {
|
|
30
|
+
if (/Found \d+ error/i.test(log) || /\berror TS\d+:/i.test(log)) {
|
|
31
|
+
const detail = log
|
|
32
|
+
.split('\n')
|
|
33
|
+
.map((line) => line.trimEnd())
|
|
34
|
+
.filter((line) => /error TS\d+:/i.test(line) || /Found \d+ error/i.test(line))
|
|
35
|
+
.slice(0, MAX_LINES)
|
|
36
|
+
.join('\n');
|
|
37
|
+
console.log('TYPE-CHECK FAIL');
|
|
38
|
+
console.log(detail || 'type-check failed');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log('TYPE-CHECK PASS');
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log(`FAILED ${failures.length}:`);
|
|
47
|
+
console.log(failures.slice(0, MAX_LINES).join('\n'));
|
|
48
|
+
process.exit(1);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/* eslint-disable no-console, n/no-process-exit */
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
|
|
4
|
+
const MAX_LINES = 30;
|
|
5
|
+
|
|
6
|
+
function run(command) {
|
|
7
|
+
return spawnSync(command, {
|
|
8
|
+
shell: true,
|
|
9
|
+
encoding: 'utf8',
|
|
10
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { status, stdout, stderr } = run('npm run build:local');
|
|
15
|
+
|
|
16
|
+
const code = status ?? 1;
|
|
17
|
+
if (code !== 0) {
|
|
18
|
+
console.log('BUILD LOCAL FAIL');
|
|
19
|
+
const detail = `${stderr}\n${stdout}`
|
|
20
|
+
.trim()
|
|
21
|
+
.split('\n')
|
|
22
|
+
.filter((line) => line.trim().length > 0)
|
|
23
|
+
.slice(-MAX_LINES)
|
|
24
|
+
.join('\n');
|
|
25
|
+
console.log(detail || 'build:local failed');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log('BUILD LOCAL PASS');
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/* eslint-disable no-console, n/no-process-exit */
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
|
|
4
|
+
const GLOB = '**/*.{js,jsx,ts,tsx,json,css,md}';
|
|
5
|
+
const MAX_FILES = 30;
|
|
6
|
+
|
|
7
|
+
const { status, stdout, stderr } = spawnSync(`npx prettier --write "${GLOB}"`, {
|
|
8
|
+
shell: true,
|
|
9
|
+
encoding: 'utf8',
|
|
10
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const code = status ?? 1;
|
|
14
|
+
if (code !== 0) {
|
|
15
|
+
console.log('FORMAT FAIL');
|
|
16
|
+
const detail = `${stderr}\n${stdout}`.trim().split('\n').slice(0, 10).join('\n');
|
|
17
|
+
console.log(detail || 'prettier failed');
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const reformatted = `${stdout}`
|
|
22
|
+
.split('\n')
|
|
23
|
+
.map((line) => line.trim())
|
|
24
|
+
.filter((line) => line.length > 0 && !line.includes('(unchanged)'))
|
|
25
|
+
.map((line) => line.replace(/\s+\d+ms$/, ''));
|
|
26
|
+
|
|
27
|
+
if (reformatted.length === 0) {
|
|
28
|
+
console.log('FORMAT PASS');
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const shown = reformatted.slice(0, MAX_FILES);
|
|
33
|
+
const suffix = reformatted.length > MAX_FILES ? `\n... +${reformatted.length - MAX_FILES} more` : '';
|
|
34
|
+
|
|
35
|
+
console.log(`FORMAT: ${reformatted.length} file(s)`);
|
|
36
|
+
console.log(shown.join('\n') + suffix);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/* eslint-disable no-console, n/no-process-exit */
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
|
|
4
|
+
const MAX_LINES = 25;
|
|
5
|
+
|
|
6
|
+
function run(command) {
|
|
7
|
+
return spawnSync(command, {
|
|
8
|
+
shell: true,
|
|
9
|
+
encoding: 'utf8',
|
|
10
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function compactLintErrors(output) {
|
|
15
|
+
const lines = output.split('\n').map((line) => line.trimEnd());
|
|
16
|
+
const matched = lines.filter((line) => /\berror\b/i.test(line) || /✖/.test(line) || /ESLint/.test(line));
|
|
17
|
+
return [...new Set(matched)].slice(0, MAX_LINES).join('\n');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const fix = run('npx eslint --fix --max-warnings=0 .');
|
|
21
|
+
const fixCode = fix.status ?? 1;
|
|
22
|
+
if (fixCode !== 0) {
|
|
23
|
+
console.log('LINT FIX FAIL');
|
|
24
|
+
const detail = compactLintErrors(`${fix.stdout ?? ''}\n${fix.stderr ?? ''}`);
|
|
25
|
+
console.log(detail || 'eslint --fix failed');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const verify = run('npm run lint');
|
|
30
|
+
const verifyCode = verify.status ?? 1;
|
|
31
|
+
if (verifyCode !== 0) {
|
|
32
|
+
console.log('LINT FIX FAIL');
|
|
33
|
+
const detail = compactLintErrors(`${verify.stdout ?? ''}\n${verify.stderr ?? ''}`);
|
|
34
|
+
console.log(detail || 'eslint verify failed after --fix');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.log('LINT FIX PASS');
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/* eslint-disable no-console, n/no-process-exit */
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
|
|
4
|
+
const MAX_LINES = 25;
|
|
5
|
+
|
|
6
|
+
const STEPS = [
|
|
7
|
+
{ name: 'lint', command: 'npm run lint' },
|
|
8
|
+
{ name: 'type-check', command: 'npm run type-check:ci' },
|
|
9
|
+
{ name: 'dup-check', command: 'npm run dup-check' },
|
|
10
|
+
{ name: 'format:check', command: 'npm run format:check' },
|
|
11
|
+
{ name: 'test', command: 'npm run test:ci' },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
function runStep(command) {
|
|
15
|
+
return spawnSync(command, {
|
|
16
|
+
shell: true,
|
|
17
|
+
encoding: 'utf8',
|
|
18
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function compactLines(output, patterns) {
|
|
23
|
+
const lines = output.split('\n').map((line) => line.trimEnd());
|
|
24
|
+
const matched = lines.filter((line) => patterns.some((re) => re.test(line)));
|
|
25
|
+
const unique = [...new Set(matched)];
|
|
26
|
+
return unique.slice(0, MAX_LINES);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function summarize(name, stdout, stderr, code) {
|
|
30
|
+
if (code === 0) {
|
|
31
|
+
const passLine = stdout.trim().split('\n').filter(Boolean).pop();
|
|
32
|
+
return { pass: true, detail: passLine && name === 'test' ? passLine : 'PASS' };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const output = `${stdout}\n${stderr}`;
|
|
36
|
+
|
|
37
|
+
switch (name) {
|
|
38
|
+
case 'lint':
|
|
39
|
+
return {
|
|
40
|
+
pass: false,
|
|
41
|
+
detail: compactLines(output, [/\berror\b/i, /✖/, /ESLint/]).join('\n') || 'lint failed',
|
|
42
|
+
};
|
|
43
|
+
case 'type-check':
|
|
44
|
+
return {
|
|
45
|
+
pass: false,
|
|
46
|
+
detail:
|
|
47
|
+
compactLines(output, [/TYPE-CHECK FAIL/, /FAILED \d+:/, /error TS\d+/, /Found \d+ error/]).join('\n') ||
|
|
48
|
+
'type-check failed',
|
|
49
|
+
};
|
|
50
|
+
case 'dup-check':
|
|
51
|
+
return {
|
|
52
|
+
pass: false,
|
|
53
|
+
detail: compactLines(output, [/Clone found/i, /duplication/i, /ERROR/i]).join('\n') || 'dup-check failed',
|
|
54
|
+
};
|
|
55
|
+
case 'format:check':
|
|
56
|
+
return {
|
|
57
|
+
pass: false,
|
|
58
|
+
detail:
|
|
59
|
+
compactLines(output, [/^\[warn\]/i, /Code style issue/, /\.(ts|tsx|js|md|json)/]).join('\n') ||
|
|
60
|
+
'format:check failed',
|
|
61
|
+
};
|
|
62
|
+
case 'test':
|
|
63
|
+
return {
|
|
64
|
+
pass: false,
|
|
65
|
+
detail: compactLines(output, [/TESTS FAIL/, /FAILED \d+:/, /^FAILED /, / > /]).join('\n') || 'test failed',
|
|
66
|
+
};
|
|
67
|
+
default:
|
|
68
|
+
return { pass: false, detail: output.split('\n').slice(0, MAX_LINES).join('\n') };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const results = [];
|
|
73
|
+
let failed = false;
|
|
74
|
+
|
|
75
|
+
for (const step of STEPS) {
|
|
76
|
+
const { status, stdout, stderr } = runStep(step.command);
|
|
77
|
+
const code = status ?? 1;
|
|
78
|
+
const summary = summarize(step.name, stdout ?? '', stderr ?? '', code);
|
|
79
|
+
results.push({ name: step.name, ...summary });
|
|
80
|
+
if (!summary.pass) {
|
|
81
|
+
failed = true;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log(failed ? 'PRECOMMIT FAIL' : 'PRECOMMIT PASS');
|
|
87
|
+
for (const result of results) {
|
|
88
|
+
console.log(`${result.name}: ${result.pass ? result.detail : 'FAIL'}`);
|
|
89
|
+
if (!result.pass) {
|
|
90
|
+
console.log(result.detail);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
process.exit(failed ? 1 : 0);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { AnsiLogger } from 'matterbridge/logger';
|
|
2
|
+
|
|
3
|
+
import { toThinqDevice } from '../../core/domain/entities/ThinqDevice.js';
|
|
4
|
+
import { ThinqSession } from '../../services/thinq/session.js';
|
|
5
|
+
import { ThinqApiClient } from '../../services/thinq/thinqApiClient.js';
|
|
6
|
+
import type { CliSession } from '../types.js';
|
|
7
|
+
|
|
8
|
+
export async function cmdDevices(
|
|
9
|
+
session: CliSession,
|
|
10
|
+
logger: AnsiLogger,
|
|
11
|
+
dumpSnapshotDeviceId?: string,
|
|
12
|
+
probeFilterDeviceId?: string,
|
|
13
|
+
): Promise<void> {
|
|
14
|
+
const { userData, country, language } = session;
|
|
15
|
+
|
|
16
|
+
const thinqSession = new ThinqSession(userData.accessToken, userData.refreshToken, userData.expiresAtEpochSeconds);
|
|
17
|
+
const apiClient = new ThinqApiClient(thinqSession, country, language, logger);
|
|
18
|
+
if (userData.userNumber) {
|
|
19
|
+
apiClient.setUserNumber(userData.userNumber);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (probeFilterDeviceId) {
|
|
23
|
+
const response = await apiClient.sendCommandAndGetResponse(probeFilterDeviceId, {
|
|
24
|
+
ctrlKey: 'filterMngStateCtrl',
|
|
25
|
+
command: 'Get',
|
|
26
|
+
dataGetList: [
|
|
27
|
+
'airState.filterMngState.useTime',
|
|
28
|
+
'airState.filterMngState.remainTime',
|
|
29
|
+
'airState.filterMngState.maxTime',
|
|
30
|
+
'airState.filterMngState.changeDate',
|
|
31
|
+
'airState.filterMngState.type',
|
|
32
|
+
],
|
|
33
|
+
});
|
|
34
|
+
console.log(JSON.stringify(response, null, 2));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const rawDevices = await apiClient.getListDevices();
|
|
39
|
+
const devices = rawDevices.map(toThinqDevice);
|
|
40
|
+
|
|
41
|
+
if (devices.length === 0) {
|
|
42
|
+
console.log('No devices found.');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (dumpSnapshotDeviceId) {
|
|
47
|
+
const device = devices.find((d) => d.id === dumpSnapshotDeviceId);
|
|
48
|
+
if (!device) {
|
|
49
|
+
console.error(`Device not found: ${dumpSnapshotDeviceId}`);
|
|
50
|
+
process.exitCode = 1;
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
console.log(JSON.stringify(device.snapshot.raw, null, 2));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
console.log(`Found ${devices.length} device(s):\n`);
|
|
58
|
+
devices.forEach((device, index) => {
|
|
59
|
+
console.log(`${index + 1}. ${device.name}`);
|
|
60
|
+
console.log(` Device ID: ${device.id}`);
|
|
61
|
+
console.log(` Type: ${device.type}`);
|
|
62
|
+
console.log(` Platform Type: ${device.platformType ?? 'unknown'}`);
|
|
63
|
+
console.log(` Model Name: ${device.modelName}`);
|
|
64
|
+
console.log(` Online: ${device.online}`);
|
|
65
|
+
console.log('');
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { AnsiLogger } from 'matterbridge/logger';
|
|
2
|
+
|
|
3
|
+
import { isAirConditionerDevice, type ThinqDevice, toThinqDevice } from '../../core/domain/entities/ThinqDevice.js';
|
|
4
|
+
import { ThinqSession } from '../../services/thinq/session.js';
|
|
5
|
+
import { ThinqApiClient } from '../../services/thinq/thinqApiClient.js';
|
|
6
|
+
import type { CliSession } from '../types.js';
|
|
7
|
+
|
|
8
|
+
const ENERGY_KEY = 'airState.energy.onCurrent';
|
|
9
|
+
const OPERATION_KEY = 'airState.operation';
|
|
10
|
+
const KEEP_ALIVE_WAIT_MS = 2000;
|
|
11
|
+
|
|
12
|
+
export interface EnergyOptions {
|
|
13
|
+
readonly deviceId: string | undefined;
|
|
14
|
+
readonly samples: number;
|
|
15
|
+
readonly intervalSeconds: number;
|
|
16
|
+
readonly keepAlive: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function parsePositiveNumber(name: string, raw: string | undefined, fallback: number, integer: boolean): number {
|
|
20
|
+
if (raw === undefined) {
|
|
21
|
+
return fallback;
|
|
22
|
+
}
|
|
23
|
+
const value = Number(raw);
|
|
24
|
+
if (!Number.isFinite(value) || value <= 0 || (integer && !Number.isInteger(value))) {
|
|
25
|
+
throw new Error(`Invalid --${name} value "${raw}": expected a positive ${integer ? 'integer' : 'number'}.`);
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Builds `EnergyOptions` from the flat `parseArgs` record. Throws on invalid numeric values. */
|
|
31
|
+
export function parseEnergyOptions(args: Record<string, string>): EnergyOptions {
|
|
32
|
+
const deviceArg = args['device'];
|
|
33
|
+
return {
|
|
34
|
+
deviceId: deviceArg && deviceArg !== 'true' ? deviceArg : undefined,
|
|
35
|
+
samples: parsePositiveNumber('samples', args['samples'], 1, true),
|
|
36
|
+
intervalSeconds: parsePositiveNumber('interval', args['interval'], 5, false),
|
|
37
|
+
keepAlive: args['keep-alive'] === 'true',
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function sleep(ms: number): Promise<void> {
|
|
42
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function describe(value: unknown): string {
|
|
46
|
+
return `${JSON.stringify(value)} (typeof ${typeof value})`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function listDeviceIds(devices: ThinqDevice[]): void {
|
|
50
|
+
if (devices.length === 0) {
|
|
51
|
+
console.log(' (no devices discovered)');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
for (const device of devices) {
|
|
55
|
+
console.log(` ${device.id} type=${device.type} name=${device.name}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function printSample(device: ThinqDevice, index: number, total: number): void {
|
|
60
|
+
const { snapshot } = device;
|
|
61
|
+
const raw = snapshot.raw;
|
|
62
|
+
|
|
63
|
+
console.log(`--- Sample ${index}/${total} @ ${new Date().toISOString()} ---`);
|
|
64
|
+
console.log(`online (device): ${device.online}`);
|
|
65
|
+
console.log(`online (snapshot): ${describe(raw['online'])}`);
|
|
66
|
+
console.log(`${OPERATION_KEY}: ${describe(raw[OPERATION_KEY])} -> isPowerOn=${snapshot.isPowerOn}`);
|
|
67
|
+
|
|
68
|
+
const keys = Object.keys(raw)
|
|
69
|
+
.filter((key) => {
|
|
70
|
+
const lower = key.toLowerCase();
|
|
71
|
+
return lower.startsWith('airstate.energy.') || lower.includes('energy') || lower.includes('power');
|
|
72
|
+
})
|
|
73
|
+
.sort();
|
|
74
|
+
|
|
75
|
+
console.log('Energy/power related keys:');
|
|
76
|
+
if (keys.length === 0) {
|
|
77
|
+
console.log(' (none found)');
|
|
78
|
+
}
|
|
79
|
+
for (const key of keys) {
|
|
80
|
+
console.log(` ${key} = ${describe(raw[key])}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
console.log(`${ENERGY_KEY}:`);
|
|
84
|
+
if (!(ENERGY_KEY in raw)) {
|
|
85
|
+
console.log(' key is ABSENT from this snapshot');
|
|
86
|
+
} else {
|
|
87
|
+
console.log(` raw value: ${describe(raw[ENERGY_KEY])}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const watts = snapshot.powerConsumptionWatts;
|
|
91
|
+
if (watts === undefined) {
|
|
92
|
+
console.log(' powerConsumptionWatts: undefined (plugin would NOT write activePower)');
|
|
93
|
+
} else {
|
|
94
|
+
console.log(` powerConsumptionWatts: ${watts} W`);
|
|
95
|
+
console.log(` activePower the plugin writes: ${Math.round(watts * 1000)} mW`);
|
|
96
|
+
}
|
|
97
|
+
console.log('');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function cmdEnergy(session: CliSession, options: EnergyOptions, logger: AnsiLogger): Promise<void> {
|
|
101
|
+
const { userData, country, language } = session;
|
|
102
|
+
|
|
103
|
+
const thinqSession = new ThinqSession(userData.accessToken, userData.refreshToken, userData.expiresAtEpochSeconds);
|
|
104
|
+
const apiClient = new ThinqApiClient(thinqSession, country, language, logger);
|
|
105
|
+
if (userData.userNumber) {
|
|
106
|
+
apiClient.setUserNumber(userData.userNumber);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const fetchDevices = async (): Promise<ThinqDevice[]> => (await apiClient.getListDevices()).map(toThinqDevice);
|
|
110
|
+
|
|
111
|
+
let devices = await fetchDevices();
|
|
112
|
+
|
|
113
|
+
const target = options.deviceId
|
|
114
|
+
? devices.find((device) => device.id === options.deviceId)
|
|
115
|
+
: devices.find(isAirConditionerDevice);
|
|
116
|
+
|
|
117
|
+
if (!target) {
|
|
118
|
+
console.error(
|
|
119
|
+
options.deviceId
|
|
120
|
+
? `Device "${options.deviceId}" was not found. Discovered device ids:`
|
|
121
|
+
: 'No AC device found. Discovered device ids:',
|
|
122
|
+
);
|
|
123
|
+
listDeviceIds(devices);
|
|
124
|
+
process.exitCode = 1;
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!isAirConditionerDevice(target)) {
|
|
129
|
+
console.warn(`Warning: device ${target.id} is type ${target.type}, not AC; continuing anyway.`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
console.log(`Device: ${target.name} (${target.id}) type=${target.type} model=${target.modelName || 'unknown'}`);
|
|
133
|
+
console.log(
|
|
134
|
+
`Samples: ${options.samples}, interval: ${options.intervalSeconds}s, keep-alive: ${options.keepAlive ? 'yes' : 'no'}\n`,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
if (options.keepAlive) {
|
|
138
|
+
await apiClient.sendKeepAlive(target.id);
|
|
139
|
+
console.log(
|
|
140
|
+
`Keep-alive sent (allEventEnable, airState.mon.timeout=70). Waiting ${KEEP_ALIVE_WAIT_MS / 1000}s...\n`,
|
|
141
|
+
);
|
|
142
|
+
await sleep(KEEP_ALIVE_WAIT_MS);
|
|
143
|
+
devices = await fetchDevices();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
for (let i = 1; i <= options.samples; i++) {
|
|
147
|
+
if (i > 1) {
|
|
148
|
+
await sleep(options.intervalSeconds * 1000);
|
|
149
|
+
devices = await fetchDevices();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const current = devices.find((device) => device.id === target.id);
|
|
153
|
+
if (!current) {
|
|
154
|
+
console.error(`Sample ${i}/${options.samples}: device ${target.id} is missing from the device list.`);
|
|
155
|
+
process.exitCode = 1;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
printSample(current, i, options.samples);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { AnsiLogger } from 'matterbridge/logger';
|
|
2
|
+
|
|
3
|
+
import { AccountAuthStrategy } from '../../services/authentication/AccountAuthStrategy.js';
|
|
4
|
+
import type { ThinqAuthContext } from '../../services/authentication/AuthContext.js';
|
|
5
|
+
import { AuthenticationCoordinator } from '../../services/authentication/AuthenticationCoordinator.js';
|
|
6
|
+
import { TokenAuthStrategy } from '../../services/authentication/TokenAuthStrategy.js';
|
|
7
|
+
import { ThinqSession } from '../../services/thinq/session.js';
|
|
8
|
+
import { ThinqApiClient } from '../../services/thinq/thinqApiClient.js';
|
|
9
|
+
import { saveSession } from '../session.js';
|
|
10
|
+
import { maskSecret } from '../utils.js';
|
|
11
|
+
import { prompt } from '../utils.js';
|
|
12
|
+
|
|
13
|
+
export type CliLoginType = 'account' | 'token';
|
|
14
|
+
|
|
15
|
+
async function buildAuthContext(type: CliLoginType, country: string, language: string): Promise<ThinqAuthContext> {
|
|
16
|
+
if (type === 'token') {
|
|
17
|
+
const refreshToken = await prompt('Refresh token: ');
|
|
18
|
+
return {
|
|
19
|
+
refreshToken,
|
|
20
|
+
country,
|
|
21
|
+
language,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// type === 'account'
|
|
26
|
+
const username = await prompt('Username (email): ');
|
|
27
|
+
const password = await prompt('Password: ');
|
|
28
|
+
return {
|
|
29
|
+
username,
|
|
30
|
+
password,
|
|
31
|
+
country,
|
|
32
|
+
language,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function cmdLogin(
|
|
37
|
+
type: CliLoginType,
|
|
38
|
+
country: string,
|
|
39
|
+
language: string,
|
|
40
|
+
logger: AnsiLogger,
|
|
41
|
+
): Promise<void> {
|
|
42
|
+
const context = await buildAuthContext(type, country, language);
|
|
43
|
+
|
|
44
|
+
const apiClient = new ThinqApiClient(new ThinqSession('', '', 0), country, language, logger);
|
|
45
|
+
const accountStrategy = new AccountAuthStrategy(apiClient, logger);
|
|
46
|
+
const tokenStrategy = new TokenAuthStrategy(apiClient, logger);
|
|
47
|
+
const coordinator = new AuthenticationCoordinator(accountStrategy, tokenStrategy, apiClient, logger);
|
|
48
|
+
|
|
49
|
+
const userData = await coordinator.authenticate(type, context);
|
|
50
|
+
|
|
51
|
+
if (!userData) {
|
|
52
|
+
console.error('Login did not complete: no session data was returned (a manual process may be required).');
|
|
53
|
+
process.exitCode = 1;
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const session = {
|
|
58
|
+
loginType: type,
|
|
59
|
+
country,
|
|
60
|
+
language,
|
|
61
|
+
userData,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
saveSession(session);
|
|
65
|
+
|
|
66
|
+
console.log('Login successful. Session saved to .cli-session.json');
|
|
67
|
+
console.log('Session Summary:');
|
|
68
|
+
console.log(` Login Type: ${session.loginType}`);
|
|
69
|
+
console.log(` Country: ${session.country}`);
|
|
70
|
+
console.log(` Language: ${session.language}`);
|
|
71
|
+
console.log(` Access Token: ${maskSecret(session.userData.accessToken)}`);
|
|
72
|
+
console.log(` Refresh Token: ${maskSecret(session.userData.refreshToken)}`);
|
|
73
|
+
console.log(` Expires At: ${new Date(session.userData.expiresAtEpochSeconds * 1000).toISOString()}`);
|
|
74
|
+
}
|
package/src/cli/help.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const b = '\x1b[1m'; // bold
|
|
2
|
+
const c = '\x1b[36m'; // cyan
|
|
3
|
+
const y = '\x1b[33m'; // yellow
|
|
4
|
+
const g = '\x1b[32m'; // green
|
|
5
|
+
const d = '\x1b[2m'; // dim
|
|
6
|
+
const r = '\x1b[0m'; // reset
|
|
7
|
+
|
|
8
|
+
const NAME_WIDTH = 12;
|
|
9
|
+
|
|
10
|
+
function row(name: string, args: string, desc: string): string {
|
|
11
|
+
const paddedName = name.padEnd(NAME_WIDTH);
|
|
12
|
+
const paddedArgs = args.padEnd(40);
|
|
13
|
+
return ` ${g}${paddedName}${r}${d}${paddedArgs}${r}${desc}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const HELP_TEXT = `
|
|
17
|
+
${b}Usage:${r}
|
|
18
|
+
npm run cli -- --command ${c}<command>${r} [options]
|
|
19
|
+
|
|
20
|
+
${b}Options:${r}
|
|
21
|
+
${y}--type${r} account|token (default: account)
|
|
22
|
+
${y}--country${r} Country code (default: US)
|
|
23
|
+
${y}--language${r} Language code (default: en-US)
|
|
24
|
+
${y}--help${r} Show this help message
|
|
25
|
+
${y}--debug${r} Enable debug logging
|
|
26
|
+
|
|
27
|
+
${b}Devices options:${r}
|
|
28
|
+
${y}--dump-snapshot${r} Device id; prints its full raw ThinQ snapshot JSON instead of the device list
|
|
29
|
+
${y}--probe-filter${r} Device id; sends a filterMngStateCtrl Get probe and prints the raw response JSON
|
|
30
|
+
|
|
31
|
+
${b}Energy options:${r}
|
|
32
|
+
${y}--device${r} Device id (default: first AC device)
|
|
33
|
+
${y}--samples${r} Number of snapshots to fetch (default: 1)
|
|
34
|
+
${y}--interval${r} Seconds between samples (default: 5)
|
|
35
|
+
${y}--keep-alive${r} Send the plugin's keep-alive first, wait 2s, then sample
|
|
36
|
+
|
|
37
|
+
${b}Commands:${r}
|
|
38
|
+
${row('login', '', 'Authenticate with LG ThinQ')}
|
|
39
|
+
${row('devices', '', 'List all discovered ThinQ devices (requires a prior login)')}
|
|
40
|
+
${row('energy', '[energy options]', 'Print raw AC power/energy snapshot fields (requires a prior login)')}
|
|
41
|
+
${row('help', '', 'Show this help message')}
|
|
42
|
+
|
|
43
|
+
${b}Examples:${r}
|
|
44
|
+
npm run cli -- ${y}--help${r}
|
|
45
|
+
npm run cli -- --command ${g}login${r}
|
|
46
|
+
npm run cli -- --command ${g}login${r} --type account --country US --language en-US
|
|
47
|
+
npm run cli -- --command ${g}login${r} --type token
|
|
48
|
+
npm run cli -- --command ${g}devices${r}
|
|
49
|
+
npm run cli -- --command ${g}devices${r} --dump-snapshot <deviceId>
|
|
50
|
+
npm run cli -- --command ${g}devices${r} --probe-filter <deviceId>
|
|
51
|
+
npm run cli -- --command ${g}energy${r}
|
|
52
|
+
npm run cli -- --command ${g}energy${r} --samples 5 --interval 10 --keep-alive
|
|
53
|
+
`;
|