@putdotio/taizn 1.12.0 → 1.13.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 CHANGED
@@ -57,6 +57,9 @@ taizn profile
57
57
  taizn package
58
58
  taizn install
59
59
  taizn run
60
+ taizn tv doctor
61
+ taizn tv doctor --json
62
+ taizn tv doctor --connect --json
60
63
  taizn tv info
61
64
  taizn tv info --json
62
65
  taizn tv pair
@@ -80,9 +83,10 @@ proof output. `profile` imports
80
83
  `package` builds and signs a `.wgt`. `install` packages and sideloads it.
81
84
  `run` launches the configured variant application on the target. `tv` commands use
82
85
  Samsung's websocket remote-control API to inspect a TV,
83
- pair for a remote token, and send remote-control key presses. Add `--json` to
84
- `tv info` for a structured TV capability snapshot, or to `tv press` for a
85
- structured key-sequence receipt. See
86
+ diagnose remote-control readiness, pair for a remote token, and send remote-control
87
+ key presses. Add `--json` to `tv doctor` for structured host/token/connection
88
+ diagnostics, to `tv info` for a structured TV capability snapshot, or to
89
+ `tv press` for a structured key-sequence receipt. See
86
90
  [Samsung TV Remote](./docs/TV_REMOTE.md) for pairing, environment, and limits.
87
91
  `tv press` accepts one key or a sequence of keys.
88
92
 
package/dist/taizn.mjs CHANGED
@@ -484,6 +484,54 @@ const sendSamsungTvKeys = Effect.fn("sendSamsungTvKeys")(function* (env, keys, p
484
484
  }
485
485
  yield* Console.log(keys.length === 1 ? `Sent Samsung TV remote key: ${keys[0]}` : `Sent Samsung TV remote keys: ${keys.join(", ")}`);
486
486
  });
487
+ const diagnoseSamsungTvRemote = Effect.fn("diagnoseSamsungTvRemote")(function* (env, doctorOptions = {}) {
488
+ const stateRead = yield* readRemoteStateDiagnostic();
489
+ const state = stateRead.state;
490
+ const targetHost = hostFromTarget(env.target);
491
+ const host = env.tvHost ?? state?.host ?? targetHost;
492
+ const hostSource = valueSource(env.tvHost, state?.host, targetHost);
493
+ const token = env.tvToken ?? state?.token;
494
+ const tokenSource = valueSource(env.tvToken, state?.token, void 0);
495
+ const remoteOptions = host ? {
496
+ host,
497
+ name: env.tvName ?? state?.name ?? DEFAULT_REMOTE_NAME,
498
+ port: env.tvPort ?? state?.port ?? DEFAULT_REMOTE_PORT,
499
+ protocol: env.tvProtocol ?? state?.protocol ?? DEFAULT_REMOTE_PROTOCOL,
500
+ timeoutMs: env.tvTimeoutMs ?? DEFAULT_TIMEOUT_MS,
501
+ token
502
+ } : void 0;
503
+ const info = yield* readInfoDiagnostic(host, env.tvInfoPort ?? TV_INFO_PORT, remoteOptions?.timeoutMs);
504
+ const connection = yield* readRemoteConnectionDiagnostic(remoteOptions, doctorOptions);
505
+ const result = {
506
+ host,
507
+ hostSource,
508
+ info,
509
+ remote: {
510
+ connection,
511
+ name: remoteOptions?.name ?? env.tvName ?? DEFAULT_REMOTE_NAME,
512
+ port: remoteOptions?.port ?? env.tvPort ?? state?.port ?? DEFAULT_REMOTE_PORT,
513
+ protocol: remoteOptions?.protocol ?? env.tvProtocol ?? state?.protocol ?? DEFAULT_REMOTE_PROTOCOL,
514
+ target: remoteOptions ? remoteTarget(remoteOptions) : void 0,
515
+ timeoutMs: remoteOptions?.timeoutMs ?? env.tvTimeoutMs ?? DEFAULT_TIMEOUT_MS,
516
+ tokenConfigured: Boolean(token),
517
+ tokenSource
518
+ },
519
+ state: stateRead.diagnostic,
520
+ target: env.target
521
+ };
522
+ if (doctorOptions.json) {
523
+ yield* Console.log(JSON.stringify(result));
524
+ return;
525
+ }
526
+ yield* Console.log(`Samsung TV doctor: ${host ?? "missing host"}`);
527
+ yield* Console.log(`host_source: ${hostSource}`);
528
+ yield* Console.log(`info: ${info.ok ? `ok ${info.name}` : `failed ${info.error.type}`}`);
529
+ yield* Console.log(`remote: ${result.remote.target ?? "missing"}`);
530
+ yield* Console.log(`token_configured: ${result.remote.tokenConfigured ? "yes" : "no"}`);
531
+ yield* Console.log(`remote_state: ${result.state.status}`);
532
+ if (connection.tested) yield* Console.log(`remote_connect: ${connection.ok ? "ok" : `failed ${connection.error.type}`}`);
533
+ else yield* Console.log(`remote_connect: skipped (${connection.reason})`);
534
+ });
487
535
  const showSamsungTvInfo = Effect.fn("showSamsungTvInfo")(function* (env, infoOptions = {}) {
488
536
  const options = yield* resolveRemoteOptions(env);
489
537
  const info = yield* fetchSamsungTvInfo(options.host, {
@@ -582,6 +630,33 @@ const saveRemoteState = Effect.fn("saveRemoteState")(function* (options) {
582
630
  path: paths.remoteStatePath
583
631
  })));
584
632
  });
633
+ const readRemoteStateDiagnostic = Effect.fn("readRemoteStateDiagnostic")(function* () {
634
+ const paths = yield* getPaths();
635
+ return yield* readRemoteState().pipe(Effect.match({
636
+ onFailure: (error) => ({ diagnostic: {
637
+ error: diagnosticError(error),
638
+ path: paths.remoteStatePath,
639
+ status: "error",
640
+ tokenConfigured: false
641
+ } }),
642
+ onSuccess: (state) => state ? {
643
+ diagnostic: {
644
+ host: state.host,
645
+ name: state.name,
646
+ path: paths.remoteStatePath,
647
+ port: state.port,
648
+ protocol: state.protocol,
649
+ status: "valid",
650
+ tokenConfigured: true
651
+ },
652
+ state
653
+ } : { diagnostic: {
654
+ path: paths.remoteStatePath,
655
+ status: "missing",
656
+ tokenConfigured: false
657
+ } }
658
+ }));
659
+ });
585
660
  const connectRemote = Effect.fn("connectRemote")(function* (options, sequence) {
586
661
  return yield* Effect.tryPromise({
587
662
  try: () => connectRemotePromise(options, sequence),
@@ -683,6 +758,66 @@ const fetchSamsungTvInfo = Effect.fn("fetchSamsungTvInfo")(function* (host, opti
683
758
  });
684
759
  return yield* Schema.decodeUnknownEffect(TvInfo)(json, { errors: "all" }).pipe(Effect.mapError((error) => TvRemoteProtocolError.make({ details: error.message })));
685
760
  });
761
+ const readInfoDiagnostic = Effect.fn("readInfoDiagnostic")(function* (host, port, timeoutMs) {
762
+ if (!host) return {
763
+ error: diagnosticError(MissingTvRemoteHost.make({})),
764
+ ok: false,
765
+ port
766
+ };
767
+ return yield* fetchSamsungTvInfo(host, {
768
+ port,
769
+ timeoutMs
770
+ }).pipe(Effect.match({
771
+ onFailure: (error) => ({
772
+ error: diagnosticError(error),
773
+ ok: false,
774
+ port
775
+ }),
776
+ onSuccess: (info) => {
777
+ const support = info.isSupport ? parseSupport(info.isSupport) : void 0;
778
+ return {
779
+ developer: {
780
+ enabled: stringFlag(info.device.developerMode),
781
+ ip: info.device.developerIP,
782
+ mode: info.device.developerMode
783
+ },
784
+ ip: info.device.ip ?? host,
785
+ model: info.device.modelName,
786
+ name: decodeHtml(info.name),
787
+ ok: true,
788
+ port,
789
+ remote: info.remote,
790
+ remoteAvailable: stringFlag(support?.remote_available),
791
+ tokenAuth: stringFlag(info.device.TokenAuthSupport),
792
+ type: info.type,
793
+ uri: info.uri
794
+ };
795
+ }
796
+ }));
797
+ });
798
+ const readRemoteConnectionDiagnostic = Effect.fn("readRemoteConnectionDiagnostic")(function* (options, doctorOptions) {
799
+ if (!doctorOptions.connect) return {
800
+ reason: "pass --connect to test the websocket endpoint",
801
+ tested: false
802
+ };
803
+ if (!options) return {
804
+ error: diagnosticError(MissingTvRemoteHost.make({})),
805
+ ok: false,
806
+ tested: true
807
+ };
808
+ return yield* connectRemote(options).pipe(Effect.match({
809
+ onFailure: (error) => ({
810
+ error: diagnosticError(error),
811
+ ok: false,
812
+ tested: true
813
+ }),
814
+ onSuccess: (returnedToken) => ({
815
+ ok: true,
816
+ tested: true,
817
+ tokenReturned: returnedToken.length > 0
818
+ })
819
+ }));
820
+ });
686
821
  const isAbortError = (cause) => cause instanceof Error && (cause.name === "AbortError" || cause.name === "TimeoutError");
687
822
  const parseRemoteEvent = (source, options) => {
688
823
  try {
@@ -726,6 +861,53 @@ const normalizeRemoteError = (cause, options) => {
726
861
  target: remoteTarget(options)
727
862
  });
728
863
  };
864
+ const diagnosticError = (error) => {
865
+ if (error instanceof FileSystemFailure) return {
866
+ message: error.message,
867
+ target: error.path,
868
+ type: "FileSystemFailure"
869
+ };
870
+ if (error instanceof InvalidJson) return {
871
+ details: error.details,
872
+ file: error.file,
873
+ message: error.message,
874
+ type: "InvalidJson"
875
+ };
876
+ if (error instanceof MissingTvRemoteHost) return {
877
+ message: error.message,
878
+ type: "MissingTvRemoteHost"
879
+ };
880
+ if (error instanceof MissingTvRemoteToken) return {
881
+ message: error.message,
882
+ type: "MissingTvRemoteToken"
883
+ };
884
+ if (error instanceof TvRemoteConnectionFailed) return {
885
+ message: error.message,
886
+ target: error.target,
887
+ type: "TvRemoteConnectionFailed"
888
+ };
889
+ if (error instanceof TvRemoteProtocolError) return {
890
+ details: error.details,
891
+ message: error.message,
892
+ type: "TvRemoteProtocolError"
893
+ };
894
+ if (error instanceof TvRemoteTimeout) return {
895
+ message: error.message,
896
+ target: error.target,
897
+ type: "TvRemoteTimeout"
898
+ };
899
+ return {
900
+ message: error.message,
901
+ target: error.target,
902
+ type: "TvRemoteUnauthorized"
903
+ };
904
+ };
905
+ const valueSource = (envValue, stateValue, targetValue) => {
906
+ if (envValue) return "env";
907
+ if (stateValue) return "state";
908
+ if (targetValue) return "target";
909
+ return "none";
910
+ };
729
911
  const hostFromTarget = (target) => {
730
912
  if (!target) return;
731
913
  return target.split(":").at(0);
@@ -1262,6 +1444,15 @@ const run = Command.make("run", {}, () => withContext((context) => runWidget(con
1262
1444
  const tvPair = Command.make("pair", {}, () => Effect.gen(function* () {
1263
1445
  yield* pairSamsungTvRemote(yield* loadEnv());
1264
1446
  }));
1447
+ const tvDoctor = Command.make("doctor", {
1448
+ connect: Flag.boolean("connect"),
1449
+ json: Flag.boolean("json")
1450
+ }, ({ connect, json }) => Effect.gen(function* () {
1451
+ yield* diagnoseSamsungTvRemote(yield* loadEnv(), {
1452
+ connect,
1453
+ json
1454
+ });
1455
+ }));
1265
1456
  const tvPress = Command.make("press", {
1266
1457
  delayMs: Flag.integer("delay-ms").pipe(Flag.withDefault(250)),
1267
1458
  json: Flag.boolean("json"),
@@ -1276,6 +1467,7 @@ const tvInfo = Command.make("info", { json: Flag.boolean("json") }, ({ json }) =
1276
1467
  yield* showSamsungTvInfo(yield* loadEnv(), { json });
1277
1468
  }));
1278
1469
  const tv = Command.make("tv", {}).pipe(Command.withSubcommands([
1470
+ tvDoctor,
1279
1471
  tvPair,
1280
1472
  tvPress,
1281
1473
  tvInfo
package/docs/TV_REMOTE.md CHANGED
@@ -6,6 +6,9 @@ and smoke checks against a physical TV or monitor.
6
6
  ## Commands
7
7
 
8
8
  ```bash
9
+ taizn tv doctor
10
+ taizn tv doctor --json
11
+ taizn tv doctor --connect --json
9
12
  taizn tv info
10
13
  taizn tv info --json
11
14
  taizn tv pair
@@ -14,6 +17,10 @@ taizn tv press --json KEY_ENTER
14
17
  taizn tv press --delay-ms 250 KEY_HOME KEY_DOWN KEY_ENTER
15
18
  ```
16
19
 
20
+ - `doctor` reports the resolved host, local `.taizn/remote.json` state, token
21
+ presence, HTTP metadata status, and websocket endpoint. Add `--json` for a
22
+ structured diagnostic. Add `--connect` only when you want to test the
23
+ websocket; without a token this can trigger the TV's allow/deny prompt.
17
24
  - `info` reads the TV's local `/api/v2/` metadata and reports remote-control
18
25
  support. Add `--json` to emit a structured TV capability snapshot for agents
19
26
  and scripts.
@@ -68,7 +75,8 @@ The `.taizn/` directory is local state and must stay ignored.
68
75
 
69
76
  ## Scope
70
77
 
71
- Remote commands send key presses only. They do not install widgets, launch
72
- applications, or capture screenshots. Use the regular `taizn install` and Tizen
73
- CLI commands for app lifecycle work. Use logs, app-level probes, Samsung Web
74
- Inspector, Remote Test Lab, or external capture when visual proof is needed.
78
+ Remote commands only inspect metadata, diagnose remote readiness, pair, and send
79
+ key presses. They do not install widgets, launch applications, or capture
80
+ screenshots. Use the regular `taizn install` and Tizen CLI commands for app
81
+ lifecycle work. Use logs, app-level probes, Samsung Web Inspector, Remote Test
82
+ Lab, or external capture when visual proof is needed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@putdotio/taizn",
3
- "version": "1.12.0",
3
+ "version": "1.13.0",
4
4
  "description": "A tiny CLI companion for interacting with Tizen ecosystem.",
5
5
  "keywords": [
6
6
  "cli",