unified-tvdevelopment-cli 1.0.1 → 1.0.2

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.
Files changed (4) hide show
  1. package/dist/cli.mjs +36 -19
  2. package/install.ps1 +197 -82
  3. package/install.sh +130 -60
  4. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -41648,26 +41648,43 @@ function Devices({ focused, platform: platform3, onDeviceChange }) {
41648
41648
  setMode(MODES.LIST);
41649
41649
  return;
41650
41650
  }
41651
- if (key.tab || key.return) {
41652
- if (formField < fields.length - 1) {
41653
- setFormField((f) => f + 1);
41654
- } else {
41655
- setWorking(true);
41656
- try {
41657
- if (platform3 === "webos") {
41658
- await utils.addDevice(form);
41659
- setStatus({ ok: true, msg: `Device '${form.name}' added` });
41660
- } else {
41661
- const out = await utils.connectDevice({ host: form.host, port: parseInt(form.port) || void 0 });
41662
- setStatus({ ok: true, msg: out || `Connected to ${form.host}:${form.port}` });
41663
- }
41664
- await load2();
41665
- setMode(MODES.LIST);
41666
- } catch (e) {
41667
- setStatus({ ok: false, msg: e.message });
41651
+ const goBack = key.upArrow || key.shift && key.tab;
41652
+ const goNext = !goBack && (key.tab || key.return && formField < fields.length - 1);
41653
+ const submit = !goBack && key.return && formField === fields.length - 1;
41654
+ if (goBack) {
41655
+ setFormField((f) => Math.max(0, f - 1));
41656
+ return;
41657
+ }
41658
+ if (goNext) {
41659
+ setFormField((f) => Math.min(fields.length - 1, f + 1));
41660
+ return;
41661
+ }
41662
+ if (submit) {
41663
+ const missingName = platform3 === "webos" && !form.name.trim();
41664
+ const missingHost = !form.host.trim();
41665
+ if (missingName) {
41666
+ setStatus({ ok: false, msg: "Name is required" });
41667
+ return;
41668
+ }
41669
+ if (missingHost) {
41670
+ setStatus({ ok: false, msg: "Host is required" });
41671
+ return;
41672
+ }
41673
+ setWorking(true);
41674
+ try {
41675
+ if (platform3 === "webos") {
41676
+ await utils.addDevice(form);
41677
+ setStatus({ ok: true, msg: `Device '${form.name}' added` });
41678
+ } else {
41679
+ const out = await utils.connectDevice({ host: form.host, port: parseInt(form.port) || void 0 });
41680
+ setStatus({ ok: true, msg: out || `Connected to ${form.host}:${form.port}` });
41668
41681
  }
41669
- setWorking(false);
41682
+ await load2();
41683
+ setMode(MODES.LIST);
41684
+ } catch (e) {
41685
+ setStatus({ ok: false, msg: e.message });
41670
41686
  }
41687
+ setWorking(false);
41671
41688
  }
41672
41689
  }
41673
41690
  if (mode === MODES.CONFIRM_REMOVE) {
@@ -41712,7 +41729,7 @@ function Devices({ focused, platform: platform3, onDeviceChange }) {
41712
41729
  onChange: (v) => setForm((f) => ({ ...f, [field]: v })),
41713
41730
  placeholder: field === "port" ? platform3 === "tizen" ? "26101" : "5555" : `Enter ${field}...`
41714
41731
  }
41715
- ) : /* @__PURE__ */ import_react29.default.createElement(Text, { color: "white" }, form[field] || /* @__PURE__ */ import_react29.default.createElement(Text, { dimColor: true }, "(empty)")))), /* @__PURE__ */ import_react29.default.createElement(Text, { dimColor: true }, "Tab/Enter next Last field Enter submits Esc cancel")), mode === MODES.CONFIRM_REMOVE && devices[cursor] && /* @__PURE__ */ import_react29.default.createElement(Box_default, { borderStyle: "round", borderColor: "red", paddingX: 2, paddingY: 1 }, /* @__PURE__ */ import_react29.default.createElement(Text, { color: "red" }, removeLabel, " "), /* @__PURE__ */ import_react29.default.createElement(Text, { bold: true, color: "white" }, devices[cursor].name ?? devices[cursor].serial), /* @__PURE__ */ import_react29.default.createElement(Text, { color: "red" }, "? "), /* @__PURE__ */ import_react29.default.createElement(Text, { color: "yellow" }, "[y] Yes [n] No")));
41732
+ ) : /* @__PURE__ */ import_react29.default.createElement(Text, { color: "white" }, form[field] || /* @__PURE__ */ import_react29.default.createElement(Text, { dimColor: true }, "(empty)")))), /* @__PURE__ */ import_react29.default.createElement(Text, { dimColor: true }, "Tab/\u2193 next \u2191 back Last field Enter submits Esc cancel")), mode === MODES.CONFIRM_REMOVE && devices[cursor] && /* @__PURE__ */ import_react29.default.createElement(Box_default, { borderStyle: "round", borderColor: "red", paddingX: 2, paddingY: 1 }, /* @__PURE__ */ import_react29.default.createElement(Text, { color: "red" }, removeLabel, " "), /* @__PURE__ */ import_react29.default.createElement(Text, { bold: true, color: "white" }, devices[cursor].name ?? devices[cursor].serial), /* @__PURE__ */ import_react29.default.createElement(Text, { color: "red" }, "? "), /* @__PURE__ */ import_react29.default.createElement(Text, { color: "yellow" }, "[y] Yes [n] No")));
41716
41733
  }
41717
41734
 
41718
41735
  // src/screens/Generate.js
package/install.ps1 CHANGED
@@ -1,29 +1,27 @@
1
- # unified-tvdevelopment-cli — Windows installer
1
+ # tvdev-cli — Windows installer
2
2
  # Run:
3
3
  # iwr -useb https://raw.githubusercontent.com/tvdev-cli/tvdev-cli/main/install.ps1 | iex
4
- # iwr -useb https://raw.githubusercontent.com/tvdev-cli/tvdev-cli/main/install.ps1 | iex # then pass -Beta
5
-
4
+ # iwr -useb https://raw.githubusercontent.com/tvdev-cli/tvdev-cli/main/install.ps1 | iex -Beta
5
+ #
6
6
  param(
7
- [switch]$Beta,
8
- [string]$Tag = "latest"
7
+ [switch]$Beta
9
8
  )
10
9
 
11
10
  $ErrorActionPreference = 'Stop'
12
- $Package = "unified-tvdevelopment-cli"
13
- $Bin = "tvdev"
14
- $RequiredNodeMajor = 18
15
11
 
16
- if ($Beta) { $Tag = "beta" }
12
+ $Repo = "tvdev-cli/tvdev-cli"
13
+ $Bin = "tvdev"
14
+ $Channel = if ($Beta) { "beta" } else { "stable" }
15
+ $RequiredNodeMajor = 18
16
+ $InstallDir = Join-Path $env:LOCALAPPDATA "tvdev\bin"
17
+ $BinPath = Join-Path $InstallDir "$Bin"
18
+ $VersionFile = Join-Path $InstallDir ".tvdev-version"
17
19
 
18
20
  # ── Helpers ───────────────────────────────────────────────────────────────────
19
21
  function Write-Banner {
20
22
  Write-Host ""
21
23
  Write-Host " [TV Dev Manager]" -ForegroundColor Cyan -NoNewline
22
- if ($Tag -ne "latest") {
23
- Write-Host " tag: $Tag" -ForegroundColor Yellow
24
- } else {
25
- Write-Host ""
26
- }
24
+ if ($Channel -eq "beta") { Write-Host " channel: beta" -ForegroundColor Yellow } else { Write-Host "" }
27
25
  Write-Host " Universal Smart TV Development CLI" -ForegroundColor DarkGray
28
26
  Write-Host " LG webOS · Samsung Tizen · Amazon Fire TV · Android TV" -ForegroundColor DarkGray
29
27
  Write-Host ""
@@ -35,50 +33,81 @@ function Write-Info { param($msg) Write-Host " [..] $msg" -ForegroundColor Blue
35
33
  function Write-Warn { param($msg) Write-Host " [!!] $msg" -ForegroundColor Yellow }
36
34
  function Write-Fail { param($msg) Write-Host "`n [XX] $msg`n" -ForegroundColor Red; exit 1 }
37
35
 
38
- function Get-SemverParts {
39
- param([string]$v)
40
- $core = ($v -split '-')[0]
41
- return ($core -split '\.' | ForEach-Object { [int]$_ })
42
- }
43
-
44
- function Compare-Version {
45
- param([string]$installed, [string]$latest)
46
- $a = Get-SemverParts $installed
47
- $b = Get-SemverParts $latest
48
- for ($i = 0; $i -lt [Math]::Max($a.Count, $b.Count); $i++) {
49
- $av = if ($i -lt $a.Count) { $a[$i] } else { 0 }
50
- $bv = if ($i -lt $b.Count) { $b[$i] } else { 0 }
51
- if ($av -gt $bv) { return 1 }
52
- if ($av -lt $bv) { return -1 }
36
+ function Invoke-GhApi {
37
+ param([string]$Url)
38
+ $headers = @{
39
+ 'Accept' = 'application/vnd.github+json'
40
+ 'X-GitHub-Api-Version' = '2022-11-28'
41
+ 'User-Agent' = 'tvdev-cli-installer'
53
42
  }
54
- return 0
43
+ Invoke-RestMethod -Uri $Url -Headers $headers
44
+ }
45
+
46
+ function Compare-Semver {
47
+ param([string]$A, [string]$B)
48
+ # returns $true if A >= B (strip pre-release suffix)
49
+ $av = [Version](($A -split '-')[0] -replace '^v','')
50
+ $bv = [Version](($B -split '-')[0] -replace '^v','')
51
+ return $av -ge $bv
52
+ }
53
+
54
+ function Add-ToUserPath {
55
+ param([string]$Dir)
56
+ $current = [Environment]::GetEnvironmentVariable("PATH", "User")
57
+ if ($current -split ';' -contains $Dir) { return $false }
58
+ [Environment]::SetEnvironmentVariable("PATH", "$current;$Dir", "User")
59
+ $env:PATH = "$env:PATH;$Dir"
60
+ return $true
61
+ }
62
+
63
+ function Test-Command {
64
+ param([string]$Name)
65
+ return [bool](Get-Command $Name -ErrorAction SilentlyContinue)
55
66
  }
56
67
 
57
68
  Write-Banner
58
69
 
59
- # ── Idempotency ───────────────────────────────────────────────────────────────
60
- Write-Step "Checking existing installation"
70
+ # ── Resolve latest release from GitHub ───────────────────────────────────────
71
+ Write-Step "Resolving latest $Channel release from GitHub"
72
+
73
+ try {
74
+ if ($Channel -eq "beta") {
75
+ $releases = Invoke-GhApi "https://api.github.com/repos/$Repo/releases"
76
+ $release = $releases | Where-Object { $_.prerelease -eq $true } | Select-Object -First 1
77
+ } else {
78
+ $release = Invoke-GhApi "https://api.github.com/repos/$Repo/releases/latest"
79
+ }
61
80
 
62
- $installedVer = ""
63
- $latestVer = ""
81
+ if (-not $release -or -not $release.tag_name) {
82
+ Write-Fail "Could not resolve release from GitHub. Visit https://github.com/$Repo/releases"
83
+ }
64
84
 
65
- if (Get-Command $Bin -ErrorAction SilentlyContinue) {
66
- try {
67
- $installedVer = (npm list -g --depth=0 $Package 2>$null | Select-String $Package) `
68
- -replace ".*@", "" | ForEach-Object { $_.Trim() }
69
- $latestVer = (npm show "${Package}@${Tag}" version 2>$null).Trim()
70
- } catch {}
85
+ $ReleaseTag = $release.tag_name
86
+ $ReleaseVersion = $ReleaseTag -replace '^v', ''
87
+ } catch {
88
+ Write-Fail "GitHub API request failed: $_"
89
+ }
90
+
91
+ Write-Info "Latest release : $ReleaseTag"
92
+
93
+ # ── Idempotency check ─────────────────────────────────────────────────────────
94
+ Write-Step "Checking existing installation"
71
95
 
96
+ $InstalledVer = ""
97
+ if (Test-Path $VersionFile) {
98
+ $InstalledVer = (Get-Content $VersionFile -Raw).Trim().TrimStart('v')
99
+ }
100
+
101
+ if ($InstalledVer -and (Test-Path $BinPath)) {
72
102
  Write-Ok "$Bin already installed"
73
- if ($installedVer) { Write-Info "Installed : $installedVer" }
74
- if ($latestVer) { Write-Info "Latest : $latestVer ($Tag)" }
103
+ Write-Info "Installed : v$InstalledVer"
104
+ Write-Info "Latest : v$ReleaseVersion ($Channel)"
75
105
 
76
- if ($installedVer -and $latestVer -and (Compare-Version $installedVer $latestVer) -ge 0) {
106
+ if (Compare-Semver $InstalledVer $ReleaseVersion) {
77
107
  Write-Ok "Already up to date — nothing to do"
78
108
  Write-Host "`n Run: $Bin`n" -ForegroundColor Cyan
79
109
  exit 0
80
110
  }
81
-
82
111
  Write-Info "Update available — reinstalling"
83
112
  } else {
84
113
  Write-Info "$Bin not yet installed — starting fresh install"
@@ -87,11 +116,23 @@ if (Get-Command $Bin -ErrorAction SilentlyContinue) {
87
116
  # ── Node.js ───────────────────────────────────────────────────────────────────
88
117
  Write-Step "Checking Node.js"
89
118
 
90
- if (-not (Get-Command node -ErrorAction SilentlyContinue)) {
91
- Write-Warn "Node.js not found."
92
- Write-Host " Install from https://nodejs.org (LTS recommended)" -ForegroundColor DarkGray
93
- Write-Host " Or via winget: winget install OpenJS.NodeJS.LTS" -ForegroundColor DarkGray
94
- Write-Fail "Node.js $RequiredNodeMajor+ required."
119
+ if (-not (Test-Command "node")) {
120
+ Write-Warn "Node.js not found — attempting install via winget"
121
+ try {
122
+ winget install --id OpenJS.NodeJS.LTS --accept-source-agreements --accept-package-agreements --silent
123
+ # reload PATH for current session
124
+ $env:PATH = [Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" +
125
+ [Environment]::GetEnvironmentVariable("PATH", "User")
126
+ Write-Ok "Node.js installed via winget"
127
+ } catch {
128
+ Write-Host " Install Node.js manually from https://nodejs.org (LTS recommended)" -ForegroundColor DarkGray
129
+ Write-Host " Or via winget: winget install OpenJS.NodeJS.LTS" -ForegroundColor DarkGray
130
+ Write-Fail "Node.js $RequiredNodeMajor+ required."
131
+ }
132
+ }
133
+
134
+ if (-not (Test-Command "node")) {
135
+ Write-Fail "Node.js not found after install. Restart terminal and re-run this script."
95
136
  }
96
137
 
97
138
  $nodeVer = (node --version).TrimStart('v')
@@ -103,65 +144,127 @@ if ($nodeMajor -lt $RequiredNodeMajor) {
103
144
 
104
145
  Write-Ok "Node.js v$nodeVer"
105
146
 
106
- # ── npm ───────────────────────────────────────────────────────────────────────
107
- Write-Step "Checking npm"
147
+ # ── Download binary from GitHub release ───────────────────────────────────────
148
+ Write-Step "Downloading $Bin $ReleaseTag"
108
149
 
109
- if (-not (Get-Command npm -ErrorAction SilentlyContinue)) {
110
- Write-Fail "npm not found. Reinstall Node.js from https://nodejs.org"
150
+ New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
151
+
152
+ # Find the cli.mjs asset in the release
153
+ $asset = $release.assets | Where-Object { $_.name -eq "cli.mjs" } | Select-Object -First 1
154
+
155
+ if ($asset) {
156
+ $DownloadUrl = $asset.browser_download_url
157
+ } else {
158
+ $DownloadUrl = "https://github.com/$Repo/releases/download/$ReleaseTag/cli.mjs"
111
159
  }
112
160
 
113
- Write-Ok "npm $(npm --version)"
161
+ Write-Info "Source : $DownloadUrl"
114
162
 
115
- # ── Install ───────────────────────────────────────────────────────────────────
116
- Write-Step "Installing ${Package}@${Tag}"
117
- Write-Info "Running: npm install -g ${Package}@${Tag}"
118
- Write-Host ""
163
+ $downloaded = $false
164
+ try {
165
+ Invoke-WebRequest -Uri $DownloadUrl -OutFile $BinPath -UseBasicParsing
166
+ $downloaded = $true
167
+ Write-Ok "Binary downloaded to $BinPath"
168
+ } catch {
169
+ Write-Warn "GitHub download failed — falling back to npm"
170
+ }
119
171
 
120
- npm install -g "${Package}@${Tag}" 2>&1 | Where-Object { $_ -notmatch "^npm warn" -and $_.Trim() -ne "" }
172
+ if (-not $downloaded) {
173
+ if (-not (Test-Command "npm")) {
174
+ Write-Fail "npm not found and GitHub download failed. Install Node.js from https://nodejs.org"
175
+ }
176
+ $NpmTag = if ($Channel -eq "beta") { "beta" } else { "latest" }
177
+ Write-Info "Running: npm install -g unified-tvdevelopment-cli@$NpmTag"
178
+ npm install -g "unified-tvdevelopment-cli@$NpmTag" 2>&1 | Where-Object { $_ -notmatch "^npm warn" -and $_.Trim() -ne "" }
179
+ # use npm global bin path
180
+ $BinPath = Join-Path (npm prefix -g) "bin\$Bin"
181
+ }
121
182
 
122
- Write-Host ""
123
- Write-Ok "$Package installed"
183
+ # Create a wrapper .cmd so Windows can execute the .mjs without typing 'node'
184
+ $WrapperPath = Join-Path $InstallDir "$Bin.cmd"
185
+ $WrapperContent = "@echo off`r`nnode `"%~dp0$Bin`" %*"
186
+ Set-Content -Path $WrapperPath -Value $WrapperContent -Encoding ASCII
124
187
 
125
- # ── Verify ────────────────────────────────────────────────────────────────────
126
- Write-Step "Verifying"
188
+ # Save installed version
189
+ Set-Content -Path $VersionFile -Value $ReleaseVersion -Encoding UTF8
190
+ Write-Ok "Installed to $InstallDir"
127
191
 
128
- try {
129
- $binPath = (Get-Command $Bin -ErrorAction Stop).Path
130
- Write-Ok "$Bin → $binPath"
131
- } catch {
132
- Write-Warn "$Bin not found in PATH. Restart your terminal and try again."
192
+ # ── PATH ──────────────────────────────────────────────────────────────────────
193
+ Write-Step "Setting up PATH"
194
+
195
+ if (Add-ToUserPath $InstallDir) {
196
+ Write-Ok "Added $InstallDir to user PATH"
197
+ Write-Warn "Restart your terminal for PATH to take effect in new sessions"
198
+ } else {
199
+ Write-Ok "PATH already contains $InstallDir"
200
+ }
201
+
202
+ if (Test-Command $Bin) {
203
+ Write-Ok "$Bin is available in PATH"
204
+ } else {
205
+ Write-Warn "$Bin not in PATH for this session — restart terminal to use it"
133
206
  }
134
207
 
135
208
  # ── Platform tools ────────────────────────────────────────────────────────────
136
- Write-Step "Checking platform-specific tools"
209
+ Write-Step "Checking and installing platform-specific tools"
210
+
211
+ function Install-NpmPackage {
212
+ param([string]$Package, [string]$Label)
213
+ Write-Info "Installing $Label via npm..."
214
+ try {
215
+ npm install -g $Package 2>&1 | Where-Object { $_ -notmatch "^npm warn" -and $_.Trim() -ne "" }
216
+ Write-Ok "$Label installed"
217
+ } catch {
218
+ Write-Warn "$Label install failed — run manually: npm install -g $Package"
219
+ }
220
+ }
137
221
 
138
- if (Get-Command "ares-setup-device" -ErrorAction SilentlyContinue) {
139
- Write-Ok "ares-cli (LG webOS) found"
222
+ function Install-AdbViaWinget {
223
+ Write-Info "Installing adb via winget (Android Platform Tools)..."
224
+ try {
225
+ winget install --id Google.PlatformTools --accept-source-agreements --accept-package-agreements --silent
226
+ $env:PATH = [Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" +
227
+ [Environment]::GetEnvironmentVariable("PATH", "User")
228
+ Write-Ok "adb (Fire TV/Android TV) installed"
229
+ } catch {
230
+ Write-Warn "winget adb install failed — install Android SDK Platform Tools: https://developer.android.com/studio/releases/platform-tools"
231
+ }
232
+ }
233
+
234
+ # ares-cli (LG webOS) — auto-install via npm
235
+ if (Test-Command "ares-setup-device") {
236
+ Write-Ok "ares-cli (LG webOS) — found"
140
237
  } else {
141
- Write-Warn "ares-cli not found npm install -g @webosose/ares-cli"
238
+ Write-Warn "ares-cli (LG webOS) not found installing..."
239
+ Install-NpmPackage "@webosose/ares-cli" "ares-cli"
142
240
  }
143
241
 
144
- if (Get-Command "sdb" -ErrorAction SilentlyContinue) {
145
- Write-Ok "sdb (Samsung Tizen) found"
242
+ # sdb (Samsung Tizen) — requires Tizen Studio GUI installer, warn only
243
+ if (Test-Command "sdb") {
244
+ Write-Ok "sdb (Samsung Tizen) — found"
146
245
  } else {
147
- Write-Warn "sdb not found install Tizen Studio: developer.samsung.com/smarttv"
246
+ Write-Warn "sdb (Samsung Tizen) not found requires Tizen Studio: https://developer.samsung.com/smarttv"
148
247
  }
149
248
 
150
- if (Get-Command "adb" -ErrorAction SilentlyContinue) {
151
- Write-Ok "adb (Fire TV / Android TV) found"
249
+ # adb (Fire TV / Android TV) — auto-install via winget
250
+ if (Test-Command "adb") {
251
+ Write-Ok "adb (Fire TV/Android TV) — found"
152
252
  } else {
153
- Write-Warn "adb not found install Android SDK Platform Tools"
253
+ Write-Warn "adb (Fire TV/Android TV) not found installing..."
254
+ Install-AdbViaWinget
154
255
  }
155
256
 
156
- if (Get-Command "inputd-cli" -ErrorAction SilentlyContinue) {
157
- Write-Ok "inputd-cli (Fire TV input) found"
257
+ # inputd-cli (optional Fire TV input simulation) — auto-install via npm
258
+ if (Test-Command "inputd-cli") {
259
+ Write-Ok "inputd-cli (Fire TV input) — found"
158
260
  } else {
159
- Write-Warn "inputd-cli not found (optional — Fire TV remote input simulation)"
261
+ Write-Warn "inputd-cli (Fire TV input) not found — installing..."
262
+ Install-NpmPackage "inputd-cli" "inputd-cli"
160
263
  }
161
264
 
162
265
  # ── Done ──────────────────────────────────────────────────────────────────────
163
266
  Write-Host ""
164
- Write-Host " Installation complete!" -ForegroundColor Green
267
+ Write-Host " Installation complete! ($ReleaseTag)" -ForegroundColor Green
165
268
  Write-Host ""
166
269
  Write-Host " Launch TV Dev Manager: " -NoNewline
167
270
  Write-Host $Bin -ForegroundColor Cyan
@@ -169,3 +272,15 @@ Write-Host ""
169
272
  Write-Host " GitHub : https://github.com/tvdev-cli/tvdev-cli" -ForegroundColor DarkGray
170
273
  Write-Host " npm : https://npmjs.com/package/unified-tvdevelopment-cli" -ForegroundColor DarkGray
171
274
  Write-Host ""
275
+
276
+ $currentPath = [Environment]::GetEnvironmentVariable("PATH", "User")
277
+ if (-not ($currentPath -split ';' -contains $InstallDir)) {
278
+ Write-Host " Tip: to make " -NoNewline -ForegroundColor Yellow
279
+ Write-Host "$Bin" -NoNewline -ForegroundColor Cyan
280
+ Write-Host " available in every new terminal, add this to your user PATH:" -ForegroundColor Yellow
281
+ Write-Host ""
282
+ Write-Host " [Environment]::SetEnvironmentVariable('PATH', `$env:PATH + ';$InstallDir', 'User')" -ForegroundColor DarkGray
283
+ Write-Host ""
284
+ Write-Host " Or restart your terminal — the installer already added it." -ForegroundColor DarkGray
285
+ Write-Host ""
286
+ }
package/install.sh CHANGED
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env bash
2
- # tvdev-cli — one-line installer
3
- # Pulls the latest release binary directly from GitHub Releases.
2
+ # tvdev-cli — one-line installer (macOS / Linux)
4
3
  #
5
4
  # curl -fsSL https://raw.githubusercontent.com/tvdev-cli/tvdev-cli/main/install.sh | bash
6
5
  # curl -fsSL https://raw.githubusercontent.com/tvdev-cli/tvdev-cli/main/install.sh | bash -s -- --beta
@@ -12,12 +11,15 @@ BIN="tvdev"
12
11
  REQUIRED_NODE=18
13
12
  NVM_VERSION="v0.39.7"
14
13
  INSTALL_NODE_VERSION="20"
15
- CHANNEL="stable" # stable | beta
14
+ INSTALL_DIR="${HOME}/.local/bin"
15
+ VERSION_FILE="${INSTALL_DIR}/.tvdev-version"
16
+ CHANNEL="stable"
16
17
 
17
18
  # ── Flags ─────────────────────────────────────────────────────────────────────
18
19
  for arg in "${@:-}"; do
19
20
  case "$arg" in
20
- --beta) CHANNEL="beta" ;;
21
+ --beta) CHANNEL="beta" ;;
22
+ --stable) CHANNEL="stable" ;;
21
23
  esac
22
24
  done
23
25
 
@@ -36,9 +38,7 @@ banner() {
36
38
  echo -e " ${INDIGO}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
37
39
  echo -e " ${DIM}Universal Smart TV Development CLI${RESET}"
38
40
  echo -e " ${DIM}LG webOS · Samsung Tizen · Amazon Fire TV · Android TV${RESET}"
39
- if [ "$CHANNEL" = "beta" ]; then
40
- echo -e " ${YELLOW} channel: beta${RESET}"
41
- fi
41
+ [ "$CHANNEL" = "beta" ] && echo -e " ${YELLOW} channel: beta${RESET}"
42
42
  echo ""
43
43
  }
44
44
 
@@ -58,57 +58,58 @@ semver_gte() {
58
58
  detect_shell_rc() {
59
59
  case "${SHELL:-}" in
60
60
  */zsh) echo "$HOME/.zshrc" ;;
61
- */bash)
62
- [ -f "$HOME/.bash_profile" ] && echo "$HOME/.bash_profile" || echo "$HOME/.bashrc" ;;
61
+ */bash) [ -f "$HOME/.bash_profile" ] && echo "$HOME/.bash_profile" || echo "$HOME/.bashrc" ;;
63
62
  */fish) echo "$HOME/.config/fish/config.fish" ;;
64
63
  *) echo "$HOME/.profile" ;;
65
64
  esac
66
65
  }
67
66
 
68
- # ── Resolve GitHub release ────────────────────────────────────────────────────
69
- fetch_release_info() {
70
- local url
67
+ gh_api() {
68
+ curl -fsSL \
69
+ -H "Accept: application/vnd.github+json" \
70
+ -H "X-GitHub-Api-Version: 2022-11-28" \
71
+ "$1"
72
+ }
73
+
74
+ # ── Resolve latest release tag from GitHub ────────────────────────────────────
75
+ resolve_release_tag() {
71
76
  if [ "$CHANNEL" = "beta" ]; then
72
- # latest pre-release (first entry that has prerelease:true)
73
- url="https://api.github.com/repos/${REPO}/releases"
74
- curl -fsSL "$url" \
77
+ gh_api "https://api.github.com/repos/${REPO}/releases" \
75
78
  | grep -E '"tag_name"|"prerelease"' \
76
79
  | paste - - \
77
- | awk -F'"' '/"prerelease": true/{print $4; exit}'
80
+ | awk -F'"' '$0 ~ /"prerelease": true/ { print $4; exit }'
78
81
  else
79
- # latest stable release
80
- curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" \
82
+ gh_api "https://api.github.com/repos/${REPO}/releases/latest" \
81
83
  | grep '"tag_name"' \
82
84
  | sed 's/.*"tag_name": *"\([^"]*\)".*/\1/'
83
85
  fi
84
86
  }
85
87
 
86
- # ── Banner ────────────────────────────────────────────────────────────────────
87
88
  banner
88
89
 
89
- # ── Resolve release tag ───────────────────────────────────────────────────────
90
+ # ── Resolve release ───────────────────────────────────────────────────────────
90
91
  step "Resolving latest ${CHANNEL} release from GitHub"
91
92
 
92
- RELEASE_TAG=$(fetch_release_info)
93
- [ -z "$RELEASE_TAG" ] && fail "Could not resolve release tag from GitHub. Check https://github.com/${REPO}/releases"
93
+ RELEASE_TAG=$(resolve_release_tag)
94
+ [ -z "$RELEASE_TAG" ] && fail "Could not resolve release from GitHub. Visit https://github.com/${REPO}/releases"
94
95
 
95
96
  RELEASE_VERSION="${RELEASE_TAG#v}"
96
- info "Release : ${RELEASE_TAG}"
97
+ info "Latest release : ${RELEASE_TAG}"
97
98
 
98
- # Download URL for the cli.mjs asset attached to the release
99
- DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${RELEASE_TAG}/cli.mjs"
100
-
101
- # ── Idempotency check ─────────────────────────────────────────────────────────
99
+ # ── Idempotency ───────────────────────────────────────────────────────────────
102
100
  step "Checking existing installation"
103
101
 
104
102
  INSTALLED_VER=""
105
- if command -v "$BIN" &>/dev/null; then
106
- INSTALLED_VER=$("$BIN" --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+[^ ]*' | head -1 || true)
103
+ if [ -f "$VERSION_FILE" ]; then
104
+ INSTALLED_VER=$(cat "$VERSION_FILE" 2>/dev/null | tr -d '[:space:]v' || true)
105
+ fi
106
+
107
+ if [ -n "$INSTALLED_VER" ] && command -v "$BIN" &>/dev/null; then
107
108
  ok "${BIN} already installed"
108
- [ -n "$INSTALLED_VER" ] && info "Installed : ${INSTALLED_VER}"
109
- info "Latest : ${RELEASE_VERSION} (${CHANNEL})"
109
+ info "Installed : v${INSTALLED_VER}"
110
+ info "Latest : v${RELEASE_VERSION} (${CHANNEL})"
110
111
 
111
- if [ -n "$INSTALLED_VER" ] && semver_gte "$INSTALLED_VER" "$RELEASE_VERSION"; then
112
+ if semver_gte "$INSTALLED_VER" "$RELEASE_VERSION"; then
112
113
  ok "Already up to date — nothing to do"
113
114
  echo ""
114
115
  echo -e " ${BOLD}Run: ${INDIGO}${BIN}${RESET}"
@@ -138,9 +139,7 @@ if ! command -v node &>/dev/null; then
138
139
  NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
139
140
  if [ -s "$NVM_DIR/nvm.sh" ]; then
140
141
  source "$NVM_DIR/nvm.sh"
141
- if ! command -v node &>/dev/null; then
142
- nvm install "$INSTALL_NODE_VERSION" && nvm use "$INSTALL_NODE_VERSION"
143
- fi
142
+ ! command -v node &>/dev/null && nvm install "$INSTALL_NODE_VERSION" && nvm use "$INSTALL_NODE_VERSION"
144
143
  else
145
144
  install_node_via_nvm
146
145
  fi
@@ -154,46 +153,49 @@ NODE_MAJOR=$(node --version | sed 's/v//' | cut -d. -f1)
154
153
 
155
154
  ok "Node.js $(node --version)"
156
155
 
157
- # ── Download & install binary from GitHub Release ────────────────────────────
158
- step "Downloading ${BIN} ${RELEASE_TAG} from GitHub"
159
- info "Source: ${DOWNLOAD_URL}"
156
+ # ── Download binary from GitHub release ───────────────────────────────────────
157
+ step "Downloading ${BIN} ${RELEASE_TAG}"
160
158
 
161
- INSTALL_DIR="${HOME}/.local/bin"
162
159
  mkdir -p "$INSTALL_DIR"
163
160
  BIN_PATH="${INSTALL_DIR}/${BIN}"
161
+ DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${RELEASE_TAG}/cli.mjs"
162
+
163
+ info "Source : ${DOWNLOAD_URL}"
164
164
 
165
165
  if curl -fsSL --output "$BIN_PATH" "$DOWNLOAD_URL"; then
166
166
  chmod +x "$BIN_PATH"
167
+ echo "$RELEASE_VERSION" > "$VERSION_FILE"
167
168
  ok "Installed to ${BIN_PATH}"
168
169
  else
169
- # fallback: try npm if GitHub download fails
170
170
  warn "GitHub download failed — falling back to npm"
171
- ! command -v npm &>/dev/null && fail "npm not found. Install Node.js from https://nodejs.org"
171
+ command -v npm &>/dev/null || fail "npm not found and GitHub download failed. Install Node.js from https://nodejs.org"
172
172
  NPM_TAG="latest"
173
173
  [ "$CHANNEL" = "beta" ] && NPM_TAG="beta"
174
174
  npm install -g "unified-tvdevelopment-cli@${NPM_TAG}" 2>&1 | grep -v "^npm warn" | grep -v "^$" || true
175
+ # point BIN_PATH to npm global bin for the version file
176
+ BIN_PATH=$(npm prefix -g)/bin/${BIN}
177
+ echo "$RELEASE_VERSION" > "$VERSION_FILE"
175
178
  fi
176
179
 
177
180
  # ── PATH ──────────────────────────────────────────────────────────────────────
178
181
  step "Setting up PATH"
179
182
 
180
183
  rc_file=$(detect_shell_rc)
181
- export_line="export PATH=\"\$PATH:${INSTALL_DIR}\""
182
184
 
183
185
  if echo ":${PATH}:" | grep -q ":${INSTALL_DIR}:"; then
184
186
  ok "PATH already contains ${INSTALL_DIR}"
185
- elif [[ "${SHELL:-}" == */fish ]]; then
186
- echo "set -gx PATH \$PATH ${INSTALL_DIR}" >> "$rc_file"
187
- ok "Added PATH entry to ${rc_file}"
188
187
  else
189
- if ! grep -qF "$INSTALL_DIR" "$rc_file" 2>/dev/null; then
190
- echo "" >> "$rc_file"
191
- echo "# added by tvdev-cli installer" >> "$rc_file"
192
- echo "$export_line" >> "$rc_file"
193
- ok "Added PATH entry to ${rc_file}"
188
+ if [[ "${SHELL:-}" == */fish ]]; then
189
+ grep -qF "$INSTALL_DIR" "$rc_file" 2>/dev/null || \
190
+ echo "set -gx PATH \$PATH ${INSTALL_DIR}" >> "$rc_file"
194
191
  else
195
- ok "PATH entry already in ${rc_file}"
192
+ if ! grep -qF "$INSTALL_DIR" "$rc_file" 2>/dev/null; then
193
+ echo "" >> "$rc_file"
194
+ echo "# added by tvdev-cli installer" >> "$rc_file"
195
+ echo "export PATH=\"\$PATH:${INSTALL_DIR}\"" >> "$rc_file"
196
+ fi
196
197
  fi
198
+ ok "Added ${INSTALL_DIR} to PATH in ${rc_file}"
197
199
  export PATH="${PATH}:${INSTALL_DIR}"
198
200
  fi
199
201
 
@@ -202,35 +204,94 @@ hash -r 2>/dev/null || true
202
204
  if command -v "$BIN" &>/dev/null; then
203
205
  ok "${BIN} is in PATH → $(command -v ${BIN})"
204
206
  else
205
- warn "${BIN} not in PATH for this session. Restart terminal or:"
207
+ warn "${BIN} not in PATH yet restart terminal or run:"
206
208
  echo -e "\n source $(detect_shell_rc)\n"
207
209
  fi
208
210
 
209
211
  # ── Platform tools ────────────────────────────────────────────────────────────
210
- step "Checking platform-specific tools"
212
+ step "Checking and installing platform-specific tools"
211
213
 
214
+ install_ares_cli() {
215
+ info "Installing ares-cli via npm..."
216
+ if npm install -g @webosose/ares-cli 2>&1 | grep -v "^npm warn" | grep -v "^$"; then
217
+ ok "ares-cli (LG webOS) installed → $(command -v ares-setup-device 2>/dev/null || echo 'reload shell')"
218
+ else
219
+ warn "ares-cli install failed — run manually: npm install -g @webosose/ares-cli"
220
+ fi
221
+ }
222
+
223
+ install_adb() {
224
+ if [[ "$(uname)" == "Darwin" ]]; then
225
+ if command -v brew &>/dev/null; then
226
+ info "Installing adb via Homebrew..."
227
+ if brew install --quiet android-platform-tools; then
228
+ ok "adb (Fire TV/Android TV) installed → $(command -v adb)"
229
+ else
230
+ warn "Homebrew adb install failed — install Android Studio SDK manually"
231
+ fi
232
+ else
233
+ warn "adb not found — install Homebrew first, then: brew install android-platform-tools"
234
+ fi
235
+ else
236
+ # Linux
237
+ if command -v apt-get &>/dev/null; then
238
+ info "Installing adb via apt..."
239
+ if sudo apt-get install -y -qq adb 2>/dev/null; then
240
+ ok "adb (Fire TV/Android TV) installed → $(command -v adb)"
241
+ else
242
+ warn "apt adb install failed — install Android SDK Platform Tools manually"
243
+ fi
244
+ elif command -v dnf &>/dev/null; then
245
+ info "Installing adb via dnf..."
246
+ if sudo dnf install -y -q android-tools 2>/dev/null; then
247
+ ok "adb (Fire TV/Android TV) installed → $(command -v adb)"
248
+ else
249
+ warn "dnf adb install failed — install Android SDK Platform Tools manually"
250
+ fi
251
+ else
252
+ warn "adb not found — install Android SDK Platform Tools: https://developer.android.com/studio/releases/platform-tools"
253
+ fi
254
+ fi
255
+ }
256
+
257
+ install_inputd_cli() {
258
+ info "Installing inputd-cli via npm..."
259
+ if npm install -g inputd-cli 2>&1 | grep -v "^npm warn" | grep -v "^$"; then
260
+ ok "inputd-cli (Fire TV input) installed"
261
+ else
262
+ warn "inputd-cli install failed — optional tool, skip if not needed"
263
+ fi
264
+ }
265
+
266
+ # ares-cli (LG webOS) — auto-install via npm
212
267
  if command -v ares-setup-device &>/dev/null; then
213
- ok "ares-cli (LG webOS) → $(command -v ares-setup-device)"
268
+ ok "ares-cli (LG webOS) → $(command -v ares-setup-device)"
214
269
  else
215
- warn "ares-cli not found npm install -g @webosose/ares-cli"
270
+ warn "ares-cli (LG webOS) not found installing..."
271
+ install_ares_cli
216
272
  fi
217
273
 
274
+ # sdb (Samsung Tizen) — requires Tizen Studio GUI installer, warn only
218
275
  if command -v sdb &>/dev/null; then
219
- ok "sdb (Samsung Tizen) → $(command -v sdb)"
276
+ ok "sdb (Samsung Tizen) → $(command -v sdb)"
220
277
  else
221
- warn "sdb not found install Tizen Studio: developer.samsung.com/smarttv"
278
+ warn "sdb (Samsung Tizen) not found requires Tizen Studio: https://developer.samsung.com/smarttv"
222
279
  fi
223
280
 
281
+ # adb (Fire TV / Android TV) — auto-install
224
282
  if command -v adb &>/dev/null; then
225
- ok "adb (Fire TV / Android TV) → $(command -v adb)"
283
+ ok "adb (Fire TV/Android TV) → $(command -v adb)"
226
284
  else
227
- warn "adb not found brew install android-platform-tools"
285
+ warn "adb (Fire TV/Android TV) not found installing..."
286
+ install_adb
228
287
  fi
229
288
 
289
+ # inputd-cli (optional Fire TV input simulation) — auto-install via npm
230
290
  if command -v inputd-cli &>/dev/null; then
231
291
  ok "inputd-cli (Fire TV input) → $(command -v inputd-cli)"
232
292
  else
233
- warn "inputd-cli not found (optional — Fire TV remote input simulation)"
293
+ warn "inputd-cli (Fire TV input) not found — installing..."
294
+ install_inputd_cli
234
295
  fi
235
296
 
236
297
  # ── Done ──────────────────────────────────────────────────────────────────────
@@ -243,3 +304,12 @@ echo ""
243
304
  echo -e " ${DIM}GitHub : https://github.com/tvdev-cli/tvdev-cli${RESET}"
244
305
  echo -e " ${DIM}npm : https://npmjs.com/package/unified-tvdevelopment-cli${RESET}"
245
306
  echo ""
307
+ rc_tip=$(detect_shell_rc)
308
+ if ! echo ":${PATH}:" | grep -q ":${INSTALL_DIR}:"; then
309
+ echo -e " ${YELLOW}Tip:${RESET} to make ${BOLD}${INDIGO}${BIN}${RESET} available in every new terminal, add this to ${rc_tip}:"
310
+ echo ""
311
+ echo -e " ${DIM}export PATH=\"\$PATH:${INSTALL_DIR}\"${RESET}"
312
+ echo ""
313
+ echo -e " Then reload: ${DIM}source ${rc_tip}${RESET}"
314
+ echo ""
315
+ fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unified-tvdevelopment-cli",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Universal TUI manager for Smart TV development — LG webOS, Samsung Tizen, Amazon Fire TV, Android TV",
5
5
  "type": "module",
6
6
  "bin": {