@bigstrider/transcodes-cli 0.9.0 → 0.10.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 +32 -4
- package/dist/index.js +1143 -308
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// ../packages/core/dist/stepup/client.js
|
|
4
4
|
var REQUEST_TIMEOUT_MS = 3e4;
|
|
5
5
|
async function request(config, input) {
|
|
6
|
-
const
|
|
6
|
+
const path6 = input.path.startsWith("/") ? input.path : `/${input.path}`;
|
|
7
7
|
const params = new URLSearchParams();
|
|
8
8
|
if (input.query) {
|
|
9
9
|
for (const [k, v] of Object.entries(input.query)) {
|
|
@@ -13,7 +13,7 @@ async function request(config, input) {
|
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
const qs = params.toString();
|
|
16
|
-
const url = `${config.apiBaseV1}${
|
|
16
|
+
const url = `${config.apiBaseV1}${path6}${qs ? `?${qs}` : ""}`;
|
|
17
17
|
const headers = {
|
|
18
18
|
"x-transcodes-token": config.token,
|
|
19
19
|
Accept: "application/json"
|
|
@@ -60,6 +60,13 @@ async function request(config, input) {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
// ../packages/core/dist/contract/messages.js
|
|
64
|
+
var TRANSCODES_GUARD_REPO_URL = "https://github.com/transcodings/transcodes-guard";
|
|
65
|
+
var CLI_INSTALL_SH_URL = "https://raw.githubusercontent.com/transcodings/transcodes-guard/prod/cli/install.sh";
|
|
66
|
+
var CLI_INSTALL_PS1_URL = "https://raw.githubusercontent.com/transcodings/transcodes-guard/prod/cli/install.ps1";
|
|
67
|
+
var CLI_INSTALL_HINT_UNIX = `curl -fsSL ${CLI_INSTALL_SH_URL} | bash && transcodes install`;
|
|
68
|
+
var CLI_INSTALL_HINT_WIN = `irm ${CLI_INSTALL_PS1_URL} | iex; transcodes install`;
|
|
69
|
+
|
|
63
70
|
// ../packages/core/dist/stepup/jwt.js
|
|
64
71
|
var REQUIRED_AUDIENCE = "transcodes-mcp";
|
|
65
72
|
function isPlainObject(v) {
|
|
@@ -80,8 +87,8 @@ function readString(rec, key) {
|
|
|
80
87
|
const v = rec[key];
|
|
81
88
|
if (typeof v !== "string")
|
|
82
89
|
return void 0;
|
|
83
|
-
const
|
|
84
|
-
return
|
|
90
|
+
const t2 = v.trim();
|
|
91
|
+
return t2 || void 0;
|
|
85
92
|
}
|
|
86
93
|
function readNumericDate(rec, key) {
|
|
87
94
|
const v = rec[key];
|
|
@@ -91,8 +98,8 @@ function readNumericDate(rec, key) {
|
|
|
91
98
|
function readAudience(rec) {
|
|
92
99
|
const aud = rec.aud;
|
|
93
100
|
if (typeof aud === "string") {
|
|
94
|
-
const
|
|
95
|
-
return
|
|
101
|
+
const t2 = aud.trim();
|
|
102
|
+
return t2 ? [t2] : void 0;
|
|
96
103
|
}
|
|
97
104
|
if (Array.isArray(aud)) {
|
|
98
105
|
const list = aud.filter((x) => typeof x === "string").map((x) => x.trim()).filter((x) => x.length > 0);
|
|
@@ -271,12 +278,12 @@ function writeTokenToFile(token, label) {
|
|
|
271
278
|
throw new Error("token is empty");
|
|
272
279
|
}
|
|
273
280
|
const nextLabel = normalizeLabel(label);
|
|
274
|
-
const
|
|
275
|
-
const existing =
|
|
281
|
+
const current2 = readConfig();
|
|
282
|
+
const existing = current2.tokenList.find((r) => r.token === trimmed);
|
|
276
283
|
if (!existing && !nextLabel) {
|
|
277
284
|
throw new Error("label is required");
|
|
278
285
|
}
|
|
279
|
-
const tokenList = existing ?
|
|
286
|
+
const tokenList = existing ? current2.tokenList.map((r) => r.token === trimmed ? { token: trimmed, label: nextLabel ?? r.label } : r) : [...current2.tokenList, { token: trimmed, label: nextLabel }];
|
|
280
287
|
writeConfig({ token: trimmed, tokenList });
|
|
281
288
|
}
|
|
282
289
|
function setActiveToken(token) {
|
|
@@ -288,22 +295,22 @@ function setTokenLabel(token, label) {
|
|
|
288
295
|
if (!nextLabel) {
|
|
289
296
|
throw new Error("label is required");
|
|
290
297
|
}
|
|
291
|
-
const
|
|
292
|
-
if (!
|
|
298
|
+
const current2 = readConfig();
|
|
299
|
+
if (!current2.tokenList.some((r) => r.token === trimmed)) {
|
|
293
300
|
throw new Error("token not found");
|
|
294
301
|
}
|
|
295
|
-
const tokenList =
|
|
296
|
-
writeConfig({ token:
|
|
302
|
+
const tokenList = current2.tokenList.map((r) => r.token === trimmed ? { token: r.token, label: nextLabel } : r);
|
|
303
|
+
writeConfig({ token: current2.token, tokenList });
|
|
297
304
|
}
|
|
298
305
|
function removeTokenFromFile(token) {
|
|
299
306
|
const trimmed = token.trim();
|
|
300
|
-
const
|
|
301
|
-
const tokenList =
|
|
307
|
+
const current2 = readConfig();
|
|
308
|
+
const tokenList = current2.tokenList.filter((r) => r.token !== trimmed);
|
|
302
309
|
if (tokenList.length === 0) {
|
|
303
310
|
clearTokenFile();
|
|
304
311
|
return;
|
|
305
312
|
}
|
|
306
|
-
const active =
|
|
313
|
+
const active = current2.token && tokenList.some((r) => r.token === current2.token) ? current2.token : tokenList[0].token;
|
|
307
314
|
writeConfig({ token: active, tokenList });
|
|
308
315
|
}
|
|
309
316
|
function clearTokenFile() {
|
|
@@ -333,7 +340,7 @@ function loadStepupConfig() {
|
|
|
333
340
|
}
|
|
334
341
|
const { token: tokenRaw } = resolveToken();
|
|
335
342
|
if (!tokenRaw) {
|
|
336
|
-
throw new Error(
|
|
343
|
+
throw new Error(`No Transcodes token found. Install the CLI (macOS/Linux: \`${CLI_INSTALL_HINT_UNIX}\`; Windows: \`${CLI_INSTALL_HINT_WIN}\`), then run \`transcodes login\` to sign in and choose an organization. Manual fallback: \`transcodes set <token> -l <label>\`.`);
|
|
337
344
|
}
|
|
338
345
|
const parsed = parseMemberAccessToken(tokenRaw);
|
|
339
346
|
for (const w of parsed.warnings) {
|
|
@@ -490,11 +497,25 @@ async function openConsoleSession(options) {
|
|
|
490
497
|
};
|
|
491
498
|
}
|
|
492
499
|
|
|
500
|
+
// ../packages/core/dist/paths/index.js
|
|
501
|
+
import { copyFileSync, existsSync, mkdirSync as mkdirSync2, renameSync } from "fs";
|
|
502
|
+
import os2 from "os";
|
|
503
|
+
import path2 from "path";
|
|
504
|
+
function transcodesDir() {
|
|
505
|
+
return path2.join(os2.homedir(), ".transcodes");
|
|
506
|
+
}
|
|
507
|
+
function stateDir() {
|
|
508
|
+
return path2.join(transcodesDir(), "state");
|
|
509
|
+
}
|
|
510
|
+
function dataDir() {
|
|
511
|
+
return stateDir();
|
|
512
|
+
}
|
|
513
|
+
|
|
493
514
|
// src/commands.ts
|
|
494
515
|
var CLI_COMMAND_SPECS = [
|
|
495
516
|
{
|
|
496
517
|
usage: "transcodes install",
|
|
497
|
-
description: "Guided setup: pick host plugins to install,
|
|
518
|
+
description: "Guided setup: pick host plugins to install, sign in, then open the dashboard (--all / <platforms>)"
|
|
498
519
|
},
|
|
499
520
|
{
|
|
500
521
|
usage: "transcodes update",
|
|
@@ -502,7 +523,15 @@ var CLI_COMMAND_SPECS = [
|
|
|
502
523
|
},
|
|
503
524
|
{
|
|
504
525
|
usage: "transcodes",
|
|
505
|
-
description: "Open the local dashboard (
|
|
526
|
+
description: "Open the local dashboard in the background (reuses a running server; default port 3847; --port N / --no-open)"
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
usage: "transcodes stop",
|
|
530
|
+
description: "Stop the background local dashboard daemon"
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
usage: "transcodes login",
|
|
534
|
+
description: "Sign in with Google in your browser, choose an organization, and save the issued member token (-l <label> / --no-open)"
|
|
506
535
|
},
|
|
507
536
|
{
|
|
508
537
|
usage: "transcodes set <token> -l <label>",
|
|
@@ -561,6 +590,20 @@ function renderCliCommandsHtml() {
|
|
|
561
590
|
).join("\n ");
|
|
562
591
|
}
|
|
563
592
|
|
|
593
|
+
// src/dashboard-lifecycle.ts
|
|
594
|
+
import { execFile as execFile2, spawn as spawn3 } from "child_process";
|
|
595
|
+
import {
|
|
596
|
+
closeSync,
|
|
597
|
+
mkdirSync as mkdirSync3,
|
|
598
|
+
openSync,
|
|
599
|
+
readFileSync as readFileSync2,
|
|
600
|
+
unlinkSync,
|
|
601
|
+
writeFileSync as writeFileSync2
|
|
602
|
+
} from "fs";
|
|
603
|
+
import http from "http";
|
|
604
|
+
import path4 from "path";
|
|
605
|
+
import { promisify as promisify2 } from "util";
|
|
606
|
+
|
|
564
607
|
// src/dashboard.ts
|
|
565
608
|
import { execFile, spawn as spawn2 } from "child_process";
|
|
566
609
|
import { createHash } from "crypto";
|
|
@@ -569,9 +612,6 @@ import {
|
|
|
569
612
|
} from "http";
|
|
570
613
|
import { promisify } from "util";
|
|
571
614
|
|
|
572
|
-
// ../packages/core/dist/contract/messages.js
|
|
573
|
-
var TRANSCODES_GUARD_REPO_URL = "https://github.com/transcodings/transcodes-guard";
|
|
574
|
-
|
|
575
615
|
// src/logo.ts
|
|
576
616
|
var LOGO_DATA_URI = "data:image/png;base64,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";
|
|
577
617
|
|
|
@@ -623,11 +663,16 @@ var CLI_PACKAGE_NAME = pkg.name;
|
|
|
623
663
|
var CLI_VERSION = pkg.version;
|
|
624
664
|
|
|
625
665
|
// src/dashboard.ts
|
|
626
|
-
var
|
|
627
|
-
var
|
|
628
|
-
var
|
|
666
|
+
var DEFAULT_DASHBOARD_PORT = 3847;
|
|
667
|
+
var DASHBOARD_HOST = "127.0.0.1";
|
|
668
|
+
var DASHBOARD_PORT_ATTEMPTS = 10;
|
|
669
|
+
var DEFAULT_PORT = DEFAULT_DASHBOARD_PORT;
|
|
670
|
+
var HOST = DASHBOARD_HOST;
|
|
671
|
+
var PORT_ATTEMPTS = DASHBOARD_PORT_ATTEMPTS;
|
|
672
|
+
var DASHBOARD_HEALTH_MARKER = "transcodes-dashboard";
|
|
629
673
|
var GUIDELINE_MUX_PLAYBACK_ID = "h1vOCPmFDA02fGhcout01FZWD4lpKNTzLFk7vybxvrc3M";
|
|
630
674
|
var APP_ORG_URL = "https://app.transcodes.io/en/org";
|
|
675
|
+
var APP_HOME_URL = "https://app.transcodes.io";
|
|
631
676
|
var execFileAsync = promisify(execFile);
|
|
632
677
|
function fingerprint(token) {
|
|
633
678
|
return createHash("sha256").update(token).digest("hex").slice(0, 12);
|
|
@@ -708,7 +753,7 @@ async function buildStatus() {
|
|
|
708
753
|
};
|
|
709
754
|
}
|
|
710
755
|
function tokenById(id) {
|
|
711
|
-
return readTokenList().find((
|
|
756
|
+
return readTokenList().find((t2) => fingerprint(t2) === id);
|
|
712
757
|
}
|
|
713
758
|
function sendJson(res, status, body) {
|
|
714
759
|
res.writeHead(status, {
|
|
@@ -912,6 +957,28 @@ function dashboardHtml() {
|
|
|
912
957
|
opacity: 0.55;
|
|
913
958
|
cursor: not-allowed;
|
|
914
959
|
}
|
|
960
|
+
.btn-manage-tokens {
|
|
961
|
+
margin-left: auto;
|
|
962
|
+
flex-shrink: 0;
|
|
963
|
+
display: inline-flex;
|
|
964
|
+
align-items: center;
|
|
965
|
+
gap: 6px;
|
|
966
|
+
border: none;
|
|
967
|
+
border-radius: 10px;
|
|
968
|
+
padding: 10px 16px;
|
|
969
|
+
font-size: var(--text-sm);
|
|
970
|
+
font-weight: 600;
|
|
971
|
+
color: #fff;
|
|
972
|
+
background: var(--accent);
|
|
973
|
+
cursor: pointer;
|
|
974
|
+
transition: opacity 0.15s ease;
|
|
975
|
+
}
|
|
976
|
+
.btn-manage-tokens svg {
|
|
977
|
+
width: 20px;
|
|
978
|
+
height: 20px;
|
|
979
|
+
flex-shrink: 0;
|
|
980
|
+
}
|
|
981
|
+
.btn-manage-tokens:hover { opacity: 0.92; }
|
|
915
982
|
.header-profile-actions {
|
|
916
983
|
flex-shrink: 0;
|
|
917
984
|
display: flex;
|
|
@@ -988,6 +1055,18 @@ function dashboardHtml() {
|
|
|
988
1055
|
margin: 0 0 4px;
|
|
989
1056
|
letter-spacing: -0.01em;
|
|
990
1057
|
}
|
|
1058
|
+
.section-title--spaced {
|
|
1059
|
+
margin: 26px 0 4px;
|
|
1060
|
+
}
|
|
1061
|
+
.section-title-row {
|
|
1062
|
+
display: flex;
|
|
1063
|
+
align-items: center;
|
|
1064
|
+
justify-content: space-between;
|
|
1065
|
+
gap: 12px;
|
|
1066
|
+
margin: 0 0 4px;
|
|
1067
|
+
}
|
|
1068
|
+
.section-title-row .section-title { margin: 0; }
|
|
1069
|
+
.section-title-row .guide-video-toggle { flex-shrink: 0; }
|
|
991
1070
|
.section-sub {
|
|
992
1071
|
font-size: var(--text-base);
|
|
993
1072
|
color: var(--muted);
|
|
@@ -1560,7 +1639,7 @@ function dashboardHtml() {
|
|
|
1560
1639
|
margin: 0;
|
|
1561
1640
|
}
|
|
1562
1641
|
.guide-video-wrap {
|
|
1563
|
-
margin: 0
|
|
1642
|
+
margin: 0;
|
|
1564
1643
|
}
|
|
1565
1644
|
.guide-video-toggle {
|
|
1566
1645
|
display: inline-flex;
|
|
@@ -1586,7 +1665,7 @@ function dashboardHtml() {
|
|
|
1586
1665
|
background: var(--accent-soft);
|
|
1587
1666
|
}
|
|
1588
1667
|
.guide-video {
|
|
1589
|
-
margin:
|
|
1668
|
+
margin: 0 0 22px;
|
|
1590
1669
|
border-radius: 14px;
|
|
1591
1670
|
overflow: hidden;
|
|
1592
1671
|
border: 1px solid var(--line);
|
|
@@ -1787,13 +1866,49 @@ function dashboardHtml() {
|
|
|
1787
1866
|
}
|
|
1788
1867
|
.guide-help {
|
|
1789
1868
|
margin: 0 0 18px;
|
|
1790
|
-
padding:
|
|
1869
|
+
padding: 0;
|
|
1791
1870
|
background: var(--accent-soft);
|
|
1792
1871
|
border: 1px solid rgba(91, 84, 230, 0.18);
|
|
1793
1872
|
border-radius: 14px;
|
|
1873
|
+
overflow: hidden;
|
|
1794
1874
|
}
|
|
1795
|
-
.guide-help-
|
|
1875
|
+
.guide-help-accordion { margin: 0; }
|
|
1876
|
+
.guide-help-summary {
|
|
1877
|
+
display: flex;
|
|
1878
|
+
align-items: center;
|
|
1879
|
+
gap: 10px;
|
|
1880
|
+
padding: 14px 18px;
|
|
1881
|
+
cursor: pointer;
|
|
1882
|
+
list-style: none;
|
|
1883
|
+
user-select: none;
|
|
1884
|
+
}
|
|
1885
|
+
.guide-help-summary::-webkit-details-marker { display: none; }
|
|
1886
|
+
.guide-help-summary::marker { content: ''; }
|
|
1887
|
+
.guide-help-summary:hover { background: rgba(91, 84, 230, 0.06); }
|
|
1888
|
+
.guide-help-summary .guide-help-heading {
|
|
1796
1889
|
margin: 0;
|
|
1890
|
+
flex: 1;
|
|
1891
|
+
min-width: 0;
|
|
1892
|
+
font-size: var(--text-sm);
|
|
1893
|
+
font-weight: 700;
|
|
1894
|
+
color: var(--ink);
|
|
1895
|
+
}
|
|
1896
|
+
.guide-help-chevron {
|
|
1897
|
+
width: 18px;
|
|
1898
|
+
height: 18px;
|
|
1899
|
+
flex-shrink: 0;
|
|
1900
|
+
color: var(--muted);
|
|
1901
|
+
transition: transform 0.15s ease;
|
|
1902
|
+
}
|
|
1903
|
+
.guide-help-accordion[open] .guide-help-chevron {
|
|
1904
|
+
transform: rotate(180deg);
|
|
1905
|
+
}
|
|
1906
|
+
.guide-help-body {
|
|
1907
|
+
padding: 0 18px 16px;
|
|
1908
|
+
border-top: 1px solid rgba(91, 84, 230, 0.12);
|
|
1909
|
+
}
|
|
1910
|
+
.guide-help-line {
|
|
1911
|
+
margin: 12px 0 0;
|
|
1797
1912
|
font-size: var(--text-sm);
|
|
1798
1913
|
color: #4a4a52;
|
|
1799
1914
|
line-height: 1.6;
|
|
@@ -1813,7 +1928,7 @@ function dashboardHtml() {
|
|
|
1813
1928
|
color: #4a4a52;
|
|
1814
1929
|
line-height: 1.6;
|
|
1815
1930
|
}
|
|
1816
|
-
.guide-help-heading:first-child { margin-top:
|
|
1931
|
+
.guide-help-body > .guide-help-heading:first-child { margin-top: 12px; }
|
|
1817
1932
|
.guide-help-list {
|
|
1818
1933
|
margin: 6px 0 0;
|
|
1819
1934
|
padding-left: 18px;
|
|
@@ -1979,11 +2094,10 @@ function dashboardHtml() {
|
|
|
1979
2094
|
</div>
|
|
1980
2095
|
</div>
|
|
1981
2096
|
<div id="header-token-empty" class="header-token-empty" hidden>
|
|
1982
|
-
<p class="header-token-empty-title">
|
|
1983
|
-
<p class="header-token-empty-title">
|
|
2097
|
+
<p class="header-token-empty-title">No Transcodes CLI authorization found</p>
|
|
2098
|
+
<p class="header-token-empty-title">Sign in with Google and choose an organization:</p>
|
|
1984
2099
|
<div class="header-token-empty-cmds">
|
|
1985
|
-
<code>
|
|
1986
|
-
<code>transcodes install</code>
|
|
2100
|
+
<code>transcodes login</code>
|
|
1987
2101
|
</div>
|
|
1988
2102
|
</div>
|
|
1989
2103
|
<div class="header-profile" id="header-profile" hidden>
|
|
@@ -2010,12 +2124,14 @@ function dashboardHtml() {
|
|
|
2010
2124
|
</div>
|
|
2011
2125
|
|
|
2012
2126
|
<div class="panel active" id="panel-guideline">
|
|
2013
|
-
<
|
|
2014
|
-
|
|
2015
|
-
<div class="guide-video-wrap">
|
|
2127
|
+
<div class="section-title-row">
|
|
2128
|
+
<p class="section-title">Getting Started</p>
|
|
2016
2129
|
<button type="button" class="guide-video-toggle" id="guide-video-toggle" aria-expanded="false" aria-controls="guide-video">
|
|
2017
2130
|
Watch Intro Video
|
|
2018
2131
|
</button>
|
|
2132
|
+
</div>
|
|
2133
|
+
<p class="section-sub">Transcodes CLI Dashboard,Set it up visually \u2014 no terminal required. New to Transcodes? Watch the video.</p>
|
|
2134
|
+
<div class="guide-video-wrap">
|
|
2019
2135
|
<div class="guide-video" id="guide-video" hidden>
|
|
2020
2136
|
<mux-player
|
|
2021
2137
|
playback-id="${GUIDELINE_MUX_PLAYBACK_ID}"
|
|
@@ -2026,41 +2142,51 @@ function dashboardHtml() {
|
|
|
2026
2142
|
></mux-player>
|
|
2027
2143
|
</div>
|
|
2028
2144
|
</div>
|
|
2029
|
-
<p class="
|
|
2145
|
+
<p class="section-title section-title--spaced">Quick Demo</p>
|
|
2030
2146
|
<p class="guide-prefix-note">Try the quick demo to see how Transcodes works</p>
|
|
2031
2147
|
<p class="guide-prefix-note">Prefix prompts with <code class="cli-cmd">/transcodes</code> on Claude, Cursor, and Antigravity \u2014 use <code class="cli-cmd">$transcodes</code> on ChatGPT (Codex).</p>
|
|
2032
2148
|
<div class="guide-groups">
|
|
2033
2149
|
<section class="guide-group guide-group--panel">
|
|
2034
2150
|
<ol class="guide-steps">
|
|
2035
|
-
<li
|
|
2036
|
-
<
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2151
|
+
<li>
|
|
2152
|
+
<details class="guide-step guide-step--accordion">
|
|
2153
|
+
<summary class="guide-step-summary">
|
|
2154
|
+
<span class="guide-step-num">1</span>
|
|
2155
|
+
<span class="guide-step-title">Open your CLI or desktop app (Claude, Cursor, Antigravity, ChatGPT)</span>
|
|
2156
|
+
<svg class="guide-step-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>
|
|
2157
|
+
</summary>
|
|
2158
|
+
<div class="guide-step-body">
|
|
2159
|
+
<p class="guide-step-desc">If it's already running, restart it so the latest plugin and token are loaded</p>
|
|
2160
|
+
</div>
|
|
2161
|
+
</details>
|
|
2041
2162
|
</li>
|
|
2042
|
-
<li
|
|
2043
|
-
<
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2163
|
+
<li>
|
|
2164
|
+
<details class="guide-step guide-step--accordion">
|
|
2165
|
+
<summary class="guide-step-summary">
|
|
2166
|
+
<span class="guide-step-num">2</span>
|
|
2167
|
+
<span class="guide-step-title">Trigger a step-up test</span>
|
|
2168
|
+
<svg class="guide-step-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>
|
|
2169
|
+
</summary>
|
|
2170
|
+
<div class="guide-step-body">
|
|
2171
|
+
<p class="guide-step-desc">Ask the agent to run a protected action \u2014 it should prompt for step-up authentication</p>
|
|
2172
|
+
<div class="guide-help-examples">
|
|
2173
|
+
<div class="guide-cmd-row">
|
|
2174
|
+
<span class="guide-cmd-hosts">Claude \xB7 Cursor \xB7 Antigravity</span>
|
|
2175
|
+
<code class="cli-cmd">/transcodes open step-up authentication for testing</code>
|
|
2176
|
+
</div>
|
|
2177
|
+
<div class="guide-cmd-row">
|
|
2178
|
+
<span class="guide-cmd-hosts">ChatGPT (Codex)</span>
|
|
2179
|
+
<code class="cli-cmd">$transcodes open step-up authentication for testing</code>
|
|
2180
|
+
</div>
|
|
2055
2181
|
</div>
|
|
2056
2182
|
</div>
|
|
2057
|
-
</
|
|
2183
|
+
</details>
|
|
2058
2184
|
</li>
|
|
2059
2185
|
</ol>
|
|
2060
2186
|
</section>
|
|
2061
2187
|
</div>
|
|
2062
2188
|
|
|
2063
|
-
<p class="
|
|
2189
|
+
<p class="section-title section-title--spaced">Steps</p>
|
|
2064
2190
|
<div class="guide-groups">
|
|
2065
2191
|
<section class="guide-group guide-group--panel">
|
|
2066
2192
|
<ol class="guide-steps">
|
|
@@ -2166,8 +2292,14 @@ function dashboardHtml() {
|
|
|
2166
2292
|
<p class="cli-map-row cli-map-row--section">
|
|
2167
2293
|
<span class="cli-map-label cli-map-label--title">MCP Access Token</span>
|
|
2168
2294
|
<code>transcodes set <token> -l <label></code>
|
|
2295
|
+
<button type="button" class="btn-manage-tokens" id="manage-tokens-btn" aria-label="Open Transcodes members page">
|
|
2296
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
|
2297
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1 1 21.75 8.25Z" />
|
|
2298
|
+
</svg>
|
|
2299
|
+
Manage Tokens
|
|
2300
|
+
</button>
|
|
2169
2301
|
</p>
|
|
2170
|
-
<p class="section-sub">
|
|
2302
|
+
<p class="section-sub">Browser login is recommended: <code>transcodes login</code>. Manual token entry remains available for scripts and recovery.</p>
|
|
2171
2303
|
<textarea id="token" placeholder="eyJhbGciOi\u2026" spellcheck="false" autocomplete="off"></textarea>
|
|
2172
2304
|
<input type="text" id="label" class="label-input" placeholder="Label (required) \u2014 e.g. transcodes-{project_name}-{env}" autocomplete="off" required />
|
|
2173
2305
|
<div class="actions">
|
|
@@ -2190,7 +2322,7 @@ function dashboardHtml() {
|
|
|
2190
2322
|
</div>
|
|
2191
2323
|
<button type="button" class="btn-danger" id="reset-all">Reset all</button>
|
|
2192
2324
|
</div>
|
|
2193
|
-
<p class="hint">
|
|
2325
|
+
<p class="hint">Please read the Guideline tab to learn how to use Transcodes CLI.<br />Stop the background dashboard with <code>transcodes stop</code>.</p>
|
|
2194
2326
|
</div>
|
|
2195
2327
|
|
|
2196
2328
|
<div class="panel" id="panel-cli">
|
|
@@ -2199,21 +2331,40 @@ function dashboardHtml() {
|
|
|
2199
2331
|
<div class="cmd-list">
|
|
2200
2332
|
${renderCliCommandsHtml()}
|
|
2201
2333
|
</div>
|
|
2202
|
-
<p class="hint">
|
|
2334
|
+
<p class="hint">Please read the Guideline tab first if you are new to Transcodes CLI.</p>
|
|
2203
2335
|
</div>
|
|
2204
2336
|
|
|
2205
2337
|
<div class="panel" id="panel-rbac">
|
|
2206
2338
|
<p id="rbac-token-warning" class="rbac-token-warning" hidden>Save a Transcodes token(MAT) first</p>
|
|
2207
2339
|
<div class="guide-help">
|
|
2208
|
-
<
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
<
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2340
|
+
<details class="guide-help-accordion">
|
|
2341
|
+
<summary class="guide-help-summary">
|
|
2342
|
+
<span class="guide-help-heading">How it works</span>
|
|
2343
|
+
<svg class="guide-help-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>
|
|
2344
|
+
</summary>
|
|
2345
|
+
<div class="guide-help-body">
|
|
2346
|
+
<p class="guide-help-line">Try an action in your CLI or desktop app. Transcodes classifies each prompt into a <code class="cli-cmd">resource:action</code> pair, then applies the permission matrix \u2014 <strong>0 deny</strong> \xB7 <strong>1 allow (pass)</strong> \xB7 <strong>2 step-up MFA</strong>.</p>
|
|
2347
|
+
<p class="guide-help-line">Resources are matched by <strong>name</strong> and <strong>description</strong>. If nothing matches, it falls back to <code class="cli-cmd">system</code>. Write a concrete description (what the resource covers \u2014 e.g. tools, domains, verbs) so the classifier can match accurately. When integrating with your own back office, you can pass <code>resource</code> and <code>action</code> as params to control permissions directly.</p>
|
|
2348
|
+
<p class="guide-help-heading">Action Classification</p>
|
|
2349
|
+
<ul class="guide-help-list">
|
|
2350
|
+
<li><strong>read</strong> \u2014 inspect data without changing it (list, get, query, cat, grep, status, describe)</li>
|
|
2351
|
+
<li><strong>update</strong> \u2014 modify existing data (edit, patch, set, rename, move, config change)</li>
|
|
2352
|
+
<li><strong>delete</strong> \u2014 remove or destroy data (rm, drop, truncate, purge). Also used for destructive / irreversible mutations (force-push, reset --hard, DROP/TRUNCATE, prod overwrite)</li>
|
|
2353
|
+
<li><strong>create</strong> \u2014 any other state-changing action (new records, sends, posts, uploads, installs)</li>
|
|
2354
|
+
</ul>
|
|
2355
|
+
<p class="guide-help-heading">Examples</p>
|
|
2356
|
+
<ul class="guide-classify-list">
|
|
2357
|
+
<li><span class="guide-classify-prompt">"Create a Google Calendar event"</span> <span class="guide-classify-arrow">\u2192</span> <code class="cli-cmd">google:create</code> <span class="guide-classify-arrow">if <code>google</code> is registered</span> \xB7 otherwise <code class="cli-cmd">system:create</code></li>
|
|
2358
|
+
<li><span class="guide-classify-prompt">"Post a message to #eng on Slack"</span> <span class="guide-classify-arrow">\u2192</span> <code class="cli-cmd">slack:create</code> <span class="guide-classify-arrow">if <code>slack</code> is registered</span> \xB7 otherwise <code class="cli-cmd">system:create</code></li>
|
|
2359
|
+
<li><span class="guide-classify-prompt">"Change James's role in Transcodes"</span> <span class="guide-classify-arrow">\u2192</span> <code class="cli-cmd">transcodes:update</code></li>
|
|
2360
|
+
<li><span class="guide-classify-prompt">"Retire a Transcodes member"</span> <span class="guide-classify-arrow">\u2192</span> <code class="cli-cmd">transcodes:delete</code></li>
|
|
2361
|
+
<li><span class="guide-classify-prompt">"Push this branch to GitHub"</span> <span class="guide-classify-arrow">\u2192</span> <code class="cli-cmd">github:update</code> <span class="guide-classify-arrow">if <code>github</code> is registered</span> \xB7 otherwise <code class="cli-cmd">system:update</code></li>
|
|
2362
|
+
<li><span class="guide-classify-prompt">"Delete files on my computer"</span> <span class="guide-classify-arrow">\u2192</span> <code class="cli-cmd">system:delete</code></li>
|
|
2363
|
+
<li><span class="guide-classify-prompt">"Bash/Shell cat .env" / "read ~/.ssh/id_rsa"</span> <span class="guide-classify-arrow">\u2192</span> <code class="cli-cmd">system:update</code></li>
|
|
2364
|
+
</ul>
|
|
2365
|
+
<p class="guide-classify-note">Registered resources only. Name/description must match \u2014 if the project has no <code>google</code> / <code>slack</code> / <code>github</code> resource, that prompt is classified under <code class="cli-cmd">system</code> instead.</p>
|
|
2366
|
+
</div>
|
|
2367
|
+
</details>
|
|
2217
2368
|
</div>
|
|
2218
2369
|
<div class="rbac-legend">
|
|
2219
2370
|
<p class="rbac-legend-title">Permission matrix</p>
|
|
@@ -2281,6 +2432,7 @@ function dashboardHtml() {
|
|
|
2281
2432
|
const headerProfileNameEl = document.getElementById("header-profile-name");
|
|
2282
2433
|
const headerProfileMetaEl = document.getElementById("header-profile-meta");
|
|
2283
2434
|
const APP_ORG_URL = ${JSON.stringify(APP_ORG_URL)};
|
|
2435
|
+
const APP_HOME_URL = ${JSON.stringify(APP_HOME_URL)};
|
|
2284
2436
|
|
|
2285
2437
|
let lastStatus = { tokens: [], activeMember: null };
|
|
2286
2438
|
let editingId = null;
|
|
@@ -2289,12 +2441,33 @@ function dashboardHtml() {
|
|
|
2289
2441
|
return Array.isArray(s.tokens) && s.tokens.length > 0;
|
|
2290
2442
|
}
|
|
2291
2443
|
|
|
2444
|
+
function appTabToSegment(tab) {
|
|
2445
|
+
if (tab === "rbac") return "permissions";
|
|
2446
|
+
if (tab === "settings" || tab === "logs" || tab === "members") return tab;
|
|
2447
|
+
return "members";
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
function appWorkspaceUrl(organizationId, projectId, tab) {
|
|
2451
|
+
return (
|
|
2452
|
+
APP_ORG_URL +
|
|
2453
|
+
"/" +
|
|
2454
|
+
encodeURIComponent(organizationId) +
|
|
2455
|
+
"/project/" +
|
|
2456
|
+
encodeURIComponent(projectId) +
|
|
2457
|
+
"/" +
|
|
2458
|
+
encodeURIComponent(appTabToSegment(tab))
|
|
2459
|
+
);
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2292
2462
|
function updateAppDeepLinks(s) {
|
|
2463
|
+
const oid = s && s.activeMember && s.activeMember.organizationId;
|
|
2293
2464
|
const pid = s && s.activeMember && s.activeMember.projectId;
|
|
2294
2465
|
document.querySelectorAll("[data-app-tab]").forEach((a) => {
|
|
2295
2466
|
const tab = a.getAttribute("data-app-tab");
|
|
2296
|
-
if (pid && tab) {
|
|
2297
|
-
a.href =
|
|
2467
|
+
if (oid && pid && tab) {
|
|
2468
|
+
a.href = appWorkspaceUrl(oid, pid, tab);
|
|
2469
|
+
} else if (oid) {
|
|
2470
|
+
a.href = APP_ORG_URL + "/" + encodeURIComponent(oid);
|
|
2298
2471
|
} else {
|
|
2299
2472
|
a.href = APP_ORG_URL;
|
|
2300
2473
|
}
|
|
@@ -2329,6 +2502,19 @@ function dashboardHtml() {
|
|
|
2329
2502
|
}
|
|
2330
2503
|
}
|
|
2331
2504
|
|
|
2505
|
+
function openManageTokens() {
|
|
2506
|
+
const member = lastStatus && lastStatus.activeMember;
|
|
2507
|
+
const oid = member && member.organizationId;
|
|
2508
|
+
const pid = member && member.projectId;
|
|
2509
|
+
const url =
|
|
2510
|
+
oid && pid
|
|
2511
|
+
? appWorkspaceUrl(oid, pid, "members")
|
|
2512
|
+
: oid
|
|
2513
|
+
? APP_ORG_URL + "/" + encodeURIComponent(oid)
|
|
2514
|
+
: APP_HOME_URL;
|
|
2515
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2332
2518
|
const guideVideoToggle = document.getElementById("guide-video-toggle");
|
|
2333
2519
|
const guideVideo = document.getElementById("guide-video");
|
|
2334
2520
|
if (guideVideoToggle && guideVideo) {
|
|
@@ -2455,6 +2641,11 @@ function dashboardHtml() {
|
|
|
2455
2641
|
btn.addEventListener("click", () => { openConsole(); });
|
|
2456
2642
|
});
|
|
2457
2643
|
|
|
2644
|
+
const manageTokensBtn = document.getElementById("manage-tokens-btn");
|
|
2645
|
+
if (manageTokensBtn) {
|
|
2646
|
+
manageTokensBtn.addEventListener("click", () => { openManageTokens(); });
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2458
2649
|
async function saveLabel(id) {
|
|
2459
2650
|
const input = listEl.querySelector('[data-edit-input="' + id + '"]');
|
|
2460
2651
|
const label = input ? input.value.trim() : "";
|
|
@@ -2719,7 +2910,7 @@ function dashboardHtml() {
|
|
|
2719
2910
|
</body>
|
|
2720
2911
|
</html>`;
|
|
2721
2912
|
}
|
|
2722
|
-
function
|
|
2913
|
+
function openDashboardBrowser(url) {
|
|
2723
2914
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
2724
2915
|
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
2725
2916
|
try {
|
|
@@ -2742,6 +2933,16 @@ function listen(port) {
|
|
|
2742
2933
|
return;
|
|
2743
2934
|
}
|
|
2744
2935
|
try {
|
|
2936
|
+
if (method === "GET" && url === "/health") {
|
|
2937
|
+
res.writeHead(200, {
|
|
2938
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
2939
|
+
"Cache-Control": "no-store",
|
|
2940
|
+
Connection: "close",
|
|
2941
|
+
"X-Transcodes-Dashboard": DASHBOARD_HEALTH_MARKER
|
|
2942
|
+
});
|
|
2943
|
+
res.end(JSON.stringify({ ok: true }));
|
|
2944
|
+
return;
|
|
2945
|
+
}
|
|
2745
2946
|
if (method === "GET" && (url === "/" || url === "/index.html")) {
|
|
2746
2947
|
res.writeHead(200, {
|
|
2747
2948
|
"Content-Type": "text/html; charset=utf-8",
|
|
@@ -2864,48 +3065,6 @@ function listen(port) {
|
|
|
2864
3065
|
server.listen(port, HOST, () => resolve(server));
|
|
2865
3066
|
});
|
|
2866
3067
|
}
|
|
2867
|
-
function waitForDashboardShutdown(server) {
|
|
2868
|
-
return new Promise((resolve) => {
|
|
2869
|
-
let shuttingDown = false;
|
|
2870
|
-
let forceTimer;
|
|
2871
|
-
const cleanup = () => {
|
|
2872
|
-
if (forceTimer) clearTimeout(forceTimer);
|
|
2873
|
-
process.removeListener("SIGINT", onSignal);
|
|
2874
|
-
process.removeListener("SIGTERM", onSignal);
|
|
2875
|
-
};
|
|
2876
|
-
const finish = () => {
|
|
2877
|
-
cleanup();
|
|
2878
|
-
resolve();
|
|
2879
|
-
};
|
|
2880
|
-
const forceExit = () => {
|
|
2881
|
-
cleanup();
|
|
2882
|
-
process.stderr.write("\nForce stopping dashboard.\n");
|
|
2883
|
-
process.exit(0);
|
|
2884
|
-
};
|
|
2885
|
-
const onSignal = () => {
|
|
2886
|
-
if (shuttingDown) {
|
|
2887
|
-
forceExit();
|
|
2888
|
-
return;
|
|
2889
|
-
}
|
|
2890
|
-
shuttingDown = true;
|
|
2891
|
-
process.stderr.write("\nStopping dashboard\u2026\n");
|
|
2892
|
-
forceTimer = setTimeout(forceExit, 1500);
|
|
2893
|
-
forceTimer.unref();
|
|
2894
|
-
server.closeAllConnections?.();
|
|
2895
|
-
server.close((err) => {
|
|
2896
|
-
if (err) {
|
|
2897
|
-
process.stderr.write(
|
|
2898
|
-
`Shutdown error: ${err instanceof Error ? err.message : String(err)}
|
|
2899
|
-
`
|
|
2900
|
-
);
|
|
2901
|
-
}
|
|
2902
|
-
finish();
|
|
2903
|
-
});
|
|
2904
|
-
};
|
|
2905
|
-
process.on("SIGINT", onSignal);
|
|
2906
|
-
process.on("SIGTERM", onSignal);
|
|
2907
|
-
});
|
|
2908
|
-
}
|
|
2909
3068
|
async function pidsListeningOnPorts(from, to) {
|
|
2910
3069
|
const pids = /* @__PURE__ */ new Set();
|
|
2911
3070
|
if (process.platform === "win32") {
|
|
@@ -2979,7 +3138,7 @@ async function tryBindPortRange(preferred) {
|
|
|
2979
3138
|
}
|
|
2980
3139
|
return null;
|
|
2981
3140
|
}
|
|
2982
|
-
async function
|
|
3141
|
+
async function serveDashboardHttp(options) {
|
|
2983
3142
|
const preferred = options.port ?? DEFAULT_PORT;
|
|
2984
3143
|
const last = preferred + PORT_ATTEMPTS - 1;
|
|
2985
3144
|
let bound = await tryBindPortRange(preferred);
|
|
@@ -2990,39 +3149,591 @@ async function runDashboard(options) {
|
|
|
2990
3149
|
if (!bound) {
|
|
2991
3150
|
throw new Error(
|
|
2992
3151
|
`could not find a free port in ${preferred}-${last} (all in use).
|
|
2993
|
-
|
|
2994
|
-
Tip: Ctrl+Z only suspends the process (port stays taken) \u2014 run \`kill %1\` or \`fg\` then Ctrl+C.
|
|
2995
|
-
Free the ports and retry:
|
|
3152
|
+
Stop a previous dashboard with: transcodes stop
|
|
2996
3153
|
macOS/Linux: lsof -ti tcp:${preferred}-${last} | xargs kill -9
|
|
2997
|
-
any platform: npx kill-port ${preferred} ${preferred + 1} # repeat per port
|
|
2998
3154
|
Or choose another port: transcodes --port <N>`
|
|
2999
3155
|
);
|
|
3000
3156
|
}
|
|
3001
|
-
|
|
3002
|
-
|
|
3157
|
+
return bound;
|
|
3158
|
+
}
|
|
3159
|
+
|
|
3160
|
+
// src/i18n.ts
|
|
3161
|
+
import fs from "fs";
|
|
3162
|
+
import os3 from "os";
|
|
3163
|
+
import path3 from "path";
|
|
3164
|
+
var LOCALE_FILE = path3.join(os3.homedir(), ".transcodes", "locale");
|
|
3165
|
+
var current = "en";
|
|
3166
|
+
var MESSAGES = {
|
|
3167
|
+
en: {
|
|
3168
|
+
installBanner: "transcodes install \u2014 set up plugins and dashboard.",
|
|
3169
|
+
langTitle: "Select language / \uC5B8\uC5B4\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
|
|
3170
|
+
chooseKeys: "\u2191/\u2193 move \xB7 enter select",
|
|
3171
|
+
platformTitle: "Please select platforms that you want to install:",
|
|
3172
|
+
platformHint1: "Installing a plugin enables both its CLI and desktop app.",
|
|
3173
|
+
platformInstalledApps: "Currently installed applications: {list}",
|
|
3174
|
+
platformInstalledNone: "No applications are installed yet.",
|
|
3175
|
+
nextStep: "Next Step \u2192",
|
|
3176
|
+
platformKeys: "\u2191/\u2193 move \xB7 space select \xB7 a all/none \xB7 enter confirm \xB7 q quit/exit",
|
|
3177
|
+
platformNumberedHint: 'Enter numbers to install (e.g. 1,2), "all" or Enter to install all.',
|
|
3178
|
+
platformNumberedNext: 'Type {n} (or "next") for Next Step, "exit"/"q" to quit.',
|
|
3179
|
+
installed: "[Installed \u2713]",
|
|
3180
|
+
nothingSelected: "Nothing selected.",
|
|
3181
|
+
noPlatforms: "No platforms selected \u2014 nothing to install.",
|
|
3182
|
+
installing: "Installing: {list}",
|
|
3183
|
+
cancelHint: "To open the dashboard later, type `transcodes`",
|
|
3184
|
+
nonInteractive: "Non-interactive shell detected. Specify platforms explicitly, e.g.:",
|
|
3185
|
+
loginPleaseSignIn: "Please sign in to Transcodes in your browser first.",
|
|
3186
|
+
loginWaiting: "Waiting for authorization to finish\u2026",
|
|
3187
|
+
loginBrowserOpenFailed: "Could not open the browser automatically. Run `transcodes login` again and complete sign-in when the browser opens.",
|
|
3188
|
+
loginNoOpenHint: "Browser open was skipped (--no-open). Run `transcodes login` without --no-open to continue.",
|
|
3189
|
+
tokenWhy: "The MAT is the ID card / key attached to every request to Transcodes. It verifies your identity so you can use Transcodes.",
|
|
3190
|
+
tokenQuestion: "Do you have a Member Access Token (MAT) to access Transcodes?",
|
|
3191
|
+
tokenYes: "Yes",
|
|
3192
|
+
tokenNo: "No",
|
|
3193
|
+
tokenSkip: "Skip \u2014 token already configured",
|
|
3194
|
+
tokenChoose123: "Please choose 1, 2, or 3.",
|
|
3195
|
+
tokenSkipLog: "Skipping token setup \u2014 using the token already configured.",
|
|
3196
|
+
tokenNoIntro1: "No token yet \u2014 open the Transcodes console to create a project and issue a MAT.",
|
|
3197
|
+
tokenNoIntro2: "When you return, paste the token here (or run `transcodes install` again).",
|
|
3198
|
+
tokenPressEnterConsole: "Press ENTER to open the console ({url}) \u2026",
|
|
3199
|
+
tokenOpening: "Opening {url} \u2026",
|
|
3200
|
+
tokenSteps1: "1) After sign-in, create a new project on the dashboard.",
|
|
3201
|
+
tokenSteps2: '2) Open the "Members & Tokens" tab and add a new member.',
|
|
3202
|
+
tokenSteps3: "3) Generate a new token for that member, or ask your manager to get a token.",
|
|
3203
|
+
tokenSteps4: "4) Paste it here.",
|
|
3204
|
+
registerTitle: "\u2500\u2500 Register MAT token \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
3205
|
+
pasteToken: "Paste your token: ",
|
|
3206
|
+
tokenRequired: "Token is required.",
|
|
3207
|
+
tokenRejected: "Token rejected: {err}",
|
|
3208
|
+
labelPrompt: "Label (e.g. transcodes-myproject-prod): ",
|
|
3209
|
+
labelRequired: "Label is required.",
|
|
3210
|
+
tokenSaved: "Saved token to {path} (label={label}).",
|
|
3211
|
+
tokenTooMany: "Too many invalid attempts \u2014 run `transcodes install` again later.",
|
|
3212
|
+
congratsBar: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
3213
|
+
congrats1: "Authenticated successfully.",
|
|
3214
|
+
congrats2: "You are ready to use Transcodes.",
|
|
3215
|
+
congratsBody: "Plugins are installed and authenticated successfully",
|
|
3216
|
+
pressEnterDashboard: "Press ENTER to open the transcodes CLI dashboard \u2026 ",
|
|
3217
|
+
installSummary: "\u2500\u2500 Install summary \u2500\u2500",
|
|
3218
|
+
dashboardOpened: "Transcodes dashboard is open: {url}",
|
|
3219
|
+
dashboardHowToUse: "Please read how to use Transcodes in the CLI dashboard.",
|
|
3220
|
+
dashboardStopHint: "Running in the background \u2014 stop with: transcodes stop",
|
|
3221
|
+
dashboardStopped: "Transcodes dashboard stopped.",
|
|
3222
|
+
dashboardNotRunning: "Transcodes dashboard is not running.",
|
|
3223
|
+
loginTokenSaved: "Login complete. Your Transcodes access is ready."
|
|
3224
|
+
},
|
|
3225
|
+
ko: {
|
|
3226
|
+
installBanner: "transcodes install \u2014 \uD50C\uB7EC\uADF8\uC778\uACFC \uB300\uC2DC\uBCF4\uB4DC\uB97C \uC124\uC815\uD569\uB2C8\uB2E4.",
|
|
3227
|
+
langTitle: "Select language / \uC5B8\uC5B4\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
|
|
3228
|
+
chooseKeys: "\u2191/\u2193 \uC774\uB3D9 \xB7 enter \uC120\uD0DD",
|
|
3229
|
+
platformTitle: "\uC124\uCE58\uD560 \uD50C\uB7AB\uD3FC\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
|
|
3230
|
+
platformHint1: "\uD50C\uB7EC\uADF8\uC778 \uC124\uCE58 \uC2DC CLI\xB7\uB370\uC2A4\uD06C\uD1B1 \uC571\uC5D0 \uBAA8\uB450 \uC801\uC6A9\uB429\uB2C8\uB2E4.",
|
|
3231
|
+
platformInstalledApps: "\uD604\uC7AC \uC124\uCE58\uB418\uC5B4 \uC788\uB294 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158: {list}",
|
|
3232
|
+
platformInstalledNone: "\uC544\uC9C1 \uC124\uCE58\uB41C Application\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
3233
|
+
nextStep: "\uB2E4\uC74C \uB2E8\uACC4 \u2192",
|
|
3234
|
+
platformKeys: "\u2191/\u2193 \uC774\uB3D9 \xB7 space \uC120\uD0DD \xB7 a \uC804\uCCB4 \xB7 enter \uD655\uC778 \xB7 q \uC885\uB8CC",
|
|
3235
|
+
platformNumberedHint: '\uC124\uCE58\uD560 \uBC88\uD638\uB97C \uC785\uB825\uD558\uC138\uC694 (\uC608: 1,2). "all" \uB610\uB294 Enter\uBA74 \uC804\uCCB4 \uC124\uCE58.',
|
|
3236
|
+
platformNumberedNext: '{n} (\uB610\uB294 "next")\uB294 \uB2E4\uC74C \uB2E8\uACC4, "exit"/"q"\uB294 \uC885\uB8CC\uC785\uB2C8\uB2E4.',
|
|
3237
|
+
installed: "[\uC124\uCE58\uB428 \u2713]",
|
|
3238
|
+
nothingSelected: "\uC120\uD0DD\uB41C \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
3239
|
+
noPlatforms: "\uC120\uD0DD\uB41C \uD50C\uB7AB\uD3FC\uC774 \uC5C6\uC5B4 \uC124\uCE58\uB97C \uAC74\uB108\uB701\uB2C8\uB2E4.",
|
|
3240
|
+
installing: "\uC124\uCE58 \uC911: {list}",
|
|
3241
|
+
cancelHint: "\uB098\uC911\uC5D0 \uB300\uC2DC\uBCF4\uB4DC\uB97C \uC5F4\uB824\uBA74 `transcodes`\uB97C \uC785\uB825\uD558\uC138\uC694",
|
|
3242
|
+
nonInteractive: "\uBE44\uB300\uD654\uD615 \uC178\uC785\uB2C8\uB2E4. \uD50C\uB7AB\uD3FC\uC744 \uC9C1\uC811 \uC9C0\uC815\uD558\uC138\uC694. \uC608:",
|
|
3243
|
+
loginPleaseSignIn: "\uBA3C\uC800 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C Transcodes\uC5D0 \uB85C\uADF8\uC778\uD574 \uC8FC\uC138\uC694.",
|
|
3244
|
+
loginWaiting: "\uC2B9\uC778 \uC644\uB8CC\uB97C \uAE30\uB2E4\uB9AC\uB294 \uC911\u2026",
|
|
3245
|
+
loginBrowserOpenFailed: "\uBE0C\uB77C\uC6B0\uC800\uB97C \uC790\uB3D9\uC73C\uB85C \uC5F4\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. `transcodes login`\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD55C \uB4A4, \uC5F4\uB9B0 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB85C\uADF8\uC778\uC744 \uC644\uB8CC\uD574 \uC8FC\uC138\uC694.",
|
|
3246
|
+
loginNoOpenHint: "\uBE0C\uB77C\uC6B0\uC800 \uC790\uB3D9 \uC5F4\uAE30\uAC00 \uAC74\uB108\uB6F0\uC5B4\uC84C\uC2B5\uB2C8\uB2E4 (--no-open). `--no-open` \uC5C6\uC774 `transcodes login`\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC138\uC694.",
|
|
3247
|
+
tokenWhy: "MAT\uB294 Transcodes\uC5D0 \uC694\uCCAD\uD560 \uB54C \uAC19\uC774 \uCCA8\uBD80\uB418\uB294 ID Card / Key\uC785\uB2C8\uB2E4. Transcodes\uB97C \uC774\uC6A9\uD558\uAE30 \uC704\uD574 \uC2E0\uC6D0\uC744 \uD655\uC778\uD558\uB294 \uB370 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
3248
|
+
tokenQuestion: "Transcodes \uC5D0 \uC811\uC18D\uD558\uAE30 \uC704\uD55C Member Access Token(MAT)\uC774 \uC788\uB098\uC694?",
|
|
3249
|
+
tokenYes: "\uC608",
|
|
3250
|
+
tokenNo: "\uC544\uB2C8\uC624",
|
|
3251
|
+
tokenSkip: "\uAC74\uB108\uB6F0\uAE30 \u2014 \uD1A0\uD070\uC774 \uC774\uBBF8 \uC124\uC815\uB418\uC5B4 \uC788\uC74C",
|
|
3252
|
+
tokenChoose123: "1, 2, \uB610\uB294 3\uC744 \uC120\uD0DD\uD558\uC138\uC694.",
|
|
3253
|
+
tokenSkipLog: "\uD1A0\uD070 \uC124\uC815\uC744 \uAC74\uB108\uB701\uB2C8\uB2E4 \u2014 \uC774\uBBF8 \uC124\uC815\uB41C \uD1A0\uD070\uC744 \uC0AC\uC6A9\uD569\uB2C8\uB2E4.",
|
|
3254
|
+
tokenNoIntro1: "\uD1A0\uD070\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 Transcodes \uCF58\uC194\uC5D0\uC11C \uD504\uB85C\uC81D\uD2B8\uB97C \uB9CC\uB4E4\uACE0 MAT\uB97C \uBC1C\uAE09\uD558\uC138\uC694.",
|
|
3255
|
+
tokenNoIntro2: "\uB3CC\uC544\uC628 \uB4A4 \uC5EC\uAE30\uC5D0 \uD1A0\uD070\uC744 \uBD99\uC5EC\uB123\uAC70\uB098 `transcodes install`\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.",
|
|
3256
|
+
tokenPressEnterConsole: "ENTER\uB97C \uB204\uB974\uBA74 \uCF58\uC194\uC744 \uC5FD\uB2C8\uB2E4 ({url}) \u2026",
|
|
3257
|
+
tokenOpening: "{url} \uC5EC\uB294 \uC911 \u2026",
|
|
3258
|
+
tokenSteps1: "1) \uB85C\uADF8\uC778 \uD6C4 \uB300\uC2DC\uBCF4\uB4DC\uC5D0\uC11C \uC0C8 \uD504\uB85C\uC81D\uD2B8\uB97C \uB9CC\uB4DC\uC138\uC694.",
|
|
3259
|
+
tokenSteps2: '2) "Members & Tokens" \uD0ED\uC5D0\uC11C \uBA64\uBC84\uB97C \uCD94\uAC00\uD558\uC138\uC694.',
|
|
3260
|
+
tokenSteps3: "3) \uD574\uB2F9 \uBA64\uBC84\uC758 \uD1A0\uD070\uC744 \uBC1C\uAE09\uD558\uAC70\uB098, \uAD00\uB9AC\uC790\uC5D0\uAC8C \uD1A0\uD070\uC744 \uC694\uCCAD\uD558\uC138\uC694.",
|
|
3261
|
+
tokenSteps4: "4) \uC5EC\uAE30\uC5D0 \uBD99\uC5EC\uB123\uC73C\uC138\uC694.",
|
|
3262
|
+
registerTitle: "\u2500\u2500 MAT \uD1A0\uD070 \uB4F1\uB85D \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
3263
|
+
pasteToken: "\uD1A0\uD070\uC744 \uBD99\uC5EC\uB123\uC73C\uC138\uC694: ",
|
|
3264
|
+
tokenRequired: "\uD1A0\uD070\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
3265
|
+
tokenRejected: "\uD1A0\uD070\uC774 \uAC70\uBD80\uB418\uC5C8\uC2B5\uB2C8\uB2E4: {err}",
|
|
3266
|
+
labelPrompt: "\uB77C\uBCA8 (\uC608: transcodes-myproject-prod): ",
|
|
3267
|
+
labelRequired: "\uB77C\uBCA8\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
3268
|
+
tokenSaved: "\uD1A0\uD070\uC744 {path}\uC5D0 \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4 (label={label}).",
|
|
3269
|
+
tokenTooMany: "\uC798\uBABB\uB41C \uC785\uB825\uC774 \uB108\uBB34 \uB9CE\uC2B5\uB2C8\uB2E4 \u2014 \uB098\uC911\uC5D0 `transcodes install`\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.",
|
|
3270
|
+
congratsBar: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
3271
|
+
congrats1: "Authenticated successfully.",
|
|
3272
|
+
congrats2: "\uC774\uC81C Transcodes\uB97C \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
|
|
3273
|
+
congratsBody: "\uC124\uC815\uC774 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.",
|
|
3274
|
+
pressEnterDashboard: "ENTER\uB97C \uB204\uB974\uBA74 transcodes CLI \uB300\uC2DC\uBCF4\uB4DC\uB97C \uC5FD\uB2C8\uB2E4 \u2026 ",
|
|
3275
|
+
installSummary: "\u2500\u2500 \uC124\uCE58 \uC694\uC57D \u2500\u2500",
|
|
3276
|
+
dashboardOpened: "Transcodes \uB300\uC2DC\uBCF4\uB4DC\uAC00 \uC5F4\uB838\uC2B5\uB2C8\uB2E4: {url}",
|
|
3277
|
+
dashboardHowToUse: "CLI \uB300\uC2DC\uBCF4\uB4DC\uC5D0\uC11C \uC0AC\uC6A9 \uBC29\uBC95\uC744 \uC77D\uC5B4\uBCF4\uC2DC\uAE30 \uBC14\uB78D\uB2C8\uB2E4.",
|
|
3278
|
+
dashboardStopHint: "\uBC31\uADF8\uB77C\uC6B4\uB4DC\uC5D0\uC11C \uC2E4\uD589 \uC911 \u2014 \uC885\uB8CC: transcodes stop",
|
|
3279
|
+
dashboardStopped: "Transcodes \uB300\uC2DC\uBCF4\uB4DC\uB97C \uC885\uB8CC\uD588\uC2B5\uB2C8\uB2E4.",
|
|
3280
|
+
dashboardNotRunning: "\uC2E4\uD589 \uC911\uC778 Transcodes \uB300\uC2DC\uBCF4\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
3281
|
+
loginTokenSaved: "\uB85C\uADF8\uC778\uC774 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC774\uC81C Transcodes\uB97C \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4."
|
|
3282
|
+
}
|
|
3283
|
+
};
|
|
3284
|
+
function setLocale(locale) {
|
|
3285
|
+
current = locale;
|
|
3286
|
+
try {
|
|
3287
|
+
const dir = path3.dirname(LOCALE_FILE);
|
|
3288
|
+
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
3289
|
+
fs.writeFileSync(LOCALE_FILE, `${locale}
|
|
3290
|
+
`, { mode: 384 });
|
|
3291
|
+
} catch {
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
function readSavedLocale() {
|
|
3295
|
+
try {
|
|
3296
|
+
const raw = fs.readFileSync(LOCALE_FILE, "utf8").trim().toLowerCase();
|
|
3297
|
+
if (raw === "en" || raw === "ko") return raw;
|
|
3298
|
+
} catch {
|
|
3299
|
+
}
|
|
3300
|
+
return null;
|
|
3301
|
+
}
|
|
3302
|
+
function t(key, vars) {
|
|
3303
|
+
let s = MESSAGES[current][key] ?? MESSAGES.en[key];
|
|
3304
|
+
if (vars) {
|
|
3305
|
+
for (const [k, v] of Object.entries(vars)) {
|
|
3306
|
+
s = s.replaceAll(`{${k}}`, String(v));
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
return s;
|
|
3310
|
+
}
|
|
3311
|
+
|
|
3312
|
+
// src/dashboard-lifecycle.ts
|
|
3313
|
+
var DASHBOARD_MARKER = "transcodes-dashboard";
|
|
3314
|
+
var HEALTH_PATH = "/health";
|
|
3315
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
3316
|
+
function pidFilePath() {
|
|
3317
|
+
return path4.join(dataDir(), "dashboard.pid");
|
|
3318
|
+
}
|
|
3319
|
+
function sleep(ms) {
|
|
3320
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3321
|
+
}
|
|
3322
|
+
function readPidRecord() {
|
|
3323
|
+
try {
|
|
3324
|
+
const raw = readFileSync2(pidFilePath(), "utf8");
|
|
3325
|
+
const parsed = JSON.parse(raw);
|
|
3326
|
+
if (typeof parsed.pid === "number" && Number.isInteger(parsed.pid) && parsed.pid > 0 && typeof parsed.port === "number" && Number.isInteger(parsed.port) && parsed.port > 0) {
|
|
3327
|
+
return { pid: parsed.pid, port: parsed.port };
|
|
3328
|
+
}
|
|
3329
|
+
} catch {
|
|
3330
|
+
}
|
|
3331
|
+
return null;
|
|
3332
|
+
}
|
|
3333
|
+
function writePidRecord(record) {
|
|
3334
|
+
mkdirSync3(dataDir(), { recursive: true });
|
|
3335
|
+
writeFileSync2(pidFilePath(), `${JSON.stringify(record)}
|
|
3336
|
+
`, { mode: 384 });
|
|
3337
|
+
}
|
|
3338
|
+
function clearPidRecord() {
|
|
3339
|
+
try {
|
|
3340
|
+
unlinkSync(pidFilePath());
|
|
3341
|
+
} catch {
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
function isProcessAlive(pid) {
|
|
3345
|
+
try {
|
|
3346
|
+
process.kill(pid, 0);
|
|
3347
|
+
return true;
|
|
3348
|
+
} catch {
|
|
3349
|
+
return false;
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
function probeHealth(port) {
|
|
3353
|
+
return new Promise((resolve) => {
|
|
3354
|
+
const req = http.get(
|
|
3355
|
+
{
|
|
3356
|
+
host: DASHBOARD_HOST,
|
|
3357
|
+
port,
|
|
3358
|
+
path: HEALTH_PATH,
|
|
3359
|
+
timeout: 500
|
|
3360
|
+
},
|
|
3361
|
+
(res) => {
|
|
3362
|
+
const marker = res.headers["x-transcodes-dashboard"];
|
|
3363
|
+
res.resume();
|
|
3364
|
+
resolve(res.statusCode === 200 && marker === DASHBOARD_MARKER);
|
|
3365
|
+
}
|
|
3366
|
+
);
|
|
3367
|
+
req.on("error", () => resolve(false));
|
|
3368
|
+
req.on("timeout", () => {
|
|
3369
|
+
req.destroy();
|
|
3370
|
+
resolve(false);
|
|
3371
|
+
});
|
|
3372
|
+
});
|
|
3373
|
+
}
|
|
3374
|
+
async function findRunningDashboard(preferred) {
|
|
3375
|
+
const record = readPidRecord();
|
|
3376
|
+
if (record && isProcessAlive(record.pid)) {
|
|
3377
|
+
if (await probeHealth(record.port)) {
|
|
3378
|
+
return {
|
|
3379
|
+
port: record.port,
|
|
3380
|
+
url: `http://${DASHBOARD_HOST}:${record.port}/`
|
|
3381
|
+
};
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
for (let i = 0; i < DASHBOARD_PORT_ATTEMPTS; i++) {
|
|
3385
|
+
const port = preferred + i;
|
|
3386
|
+
if (await probeHealth(port)) {
|
|
3387
|
+
return { port, url: `http://${DASHBOARD_HOST}:${port}/` };
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
return null;
|
|
3391
|
+
}
|
|
3392
|
+
function spawnDaemon(port) {
|
|
3393
|
+
const entry = process.argv[1];
|
|
3394
|
+
if (!entry) {
|
|
3395
|
+
throw new Error("cannot resolve CLI entry path to spawn dashboard daemon");
|
|
3396
|
+
}
|
|
3397
|
+
mkdirSync3(dataDir(), { recursive: true });
|
|
3398
|
+
const logPath = path4.join(dataDir(), "dashboard.log");
|
|
3399
|
+
const logFd = openSync(logPath, "a");
|
|
3400
|
+
try {
|
|
3401
|
+
const child = spawn3(
|
|
3402
|
+
process.execPath,
|
|
3403
|
+
[entry, "dashboard", "--daemon", "--port", String(port)],
|
|
3404
|
+
{
|
|
3405
|
+
detached: true,
|
|
3406
|
+
stdio: ["ignore", logFd, logFd],
|
|
3407
|
+
env: process.env
|
|
3408
|
+
}
|
|
3409
|
+
);
|
|
3410
|
+
child.unref();
|
|
3411
|
+
} finally {
|
|
3412
|
+
try {
|
|
3413
|
+
closeSync(logFd);
|
|
3414
|
+
} catch {
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
async function killPortListener(port) {
|
|
3419
|
+
try {
|
|
3420
|
+
if (process.platform === "win32") {
|
|
3421
|
+
await execFileAsync2(
|
|
3422
|
+
"powershell",
|
|
3423
|
+
[
|
|
3424
|
+
"-NoProfile",
|
|
3425
|
+
"-Command",
|
|
3426
|
+
`$p=(Get-NetTCPConnection -LocalPort ${port} -State Listen -ErrorAction SilentlyContinue).OwningProcess; if($p){ Stop-Process -Id $p -Force }`
|
|
3427
|
+
],
|
|
3428
|
+
{ timeout: 5e3 }
|
|
3429
|
+
);
|
|
3430
|
+
return;
|
|
3431
|
+
}
|
|
3432
|
+
const { stdout } = await execFileAsync2(
|
|
3433
|
+
"lsof",
|
|
3434
|
+
["-ti", `tcp:${port}`, "-sTCP:LISTEN"],
|
|
3435
|
+
{ timeout: 5e3 }
|
|
3436
|
+
);
|
|
3437
|
+
for (const token of stdout.trim().split(/\s+/)) {
|
|
3438
|
+
const pid = Number(token);
|
|
3439
|
+
if (Number.isInteger(pid) && pid > 0) {
|
|
3440
|
+
try {
|
|
3441
|
+
process.kill(pid, "SIGKILL");
|
|
3442
|
+
} catch {
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
} catch {
|
|
3447
|
+
}
|
|
3448
|
+
}
|
|
3449
|
+
async function serveDashboard(options) {
|
|
3450
|
+
const preferred = options.port ?? DEFAULT_DASHBOARD_PORT;
|
|
3451
|
+
const bound = await serveDashboardHttp({ port: preferred });
|
|
3452
|
+
writePidRecord({ pid: process.pid, port: bound.port });
|
|
3453
|
+
const cleanup = () => clearPidRecord();
|
|
3454
|
+
process.once("exit", cleanup);
|
|
3455
|
+
await new Promise((resolve) => {
|
|
3456
|
+
let shuttingDown = false;
|
|
3457
|
+
const onSignal = () => {
|
|
3458
|
+
if (shuttingDown) {
|
|
3459
|
+
cleanup();
|
|
3460
|
+
process.exit(0);
|
|
3461
|
+
}
|
|
3462
|
+
shuttingDown = true;
|
|
3463
|
+
bound.server.closeAllConnections?.();
|
|
3464
|
+
bound.server.close(() => {
|
|
3465
|
+
cleanup();
|
|
3466
|
+
resolve();
|
|
3467
|
+
});
|
|
3468
|
+
setTimeout(() => {
|
|
3469
|
+
cleanup();
|
|
3470
|
+
process.exit(0);
|
|
3471
|
+
}, 1500).unref();
|
|
3472
|
+
};
|
|
3473
|
+
process.on("SIGINT", onSignal);
|
|
3474
|
+
process.on("SIGTERM", onSignal);
|
|
3475
|
+
});
|
|
3476
|
+
}
|
|
3477
|
+
async function killRunningDashboard(preferred) {
|
|
3478
|
+
const record = readPidRecord();
|
|
3479
|
+
if (record && isProcessAlive(record.pid)) {
|
|
3480
|
+
try {
|
|
3481
|
+
process.kill(record.pid, "SIGTERM");
|
|
3482
|
+
for (let i = 0; i < 20; i++) {
|
|
3483
|
+
await sleep(100);
|
|
3484
|
+
if (!isProcessAlive(record.pid)) break;
|
|
3485
|
+
}
|
|
3486
|
+
if (isProcessAlive(record.pid)) {
|
|
3487
|
+
process.kill(record.pid, "SIGKILL");
|
|
3488
|
+
}
|
|
3489
|
+
} catch {
|
|
3490
|
+
}
|
|
3491
|
+
}
|
|
3492
|
+
for (let i = 0; i < DASHBOARD_PORT_ATTEMPTS; i++) {
|
|
3493
|
+
const port = preferred + i;
|
|
3494
|
+
if (!await probeHealth(port)) continue;
|
|
3495
|
+
await killPortListener(port);
|
|
3496
|
+
}
|
|
3497
|
+
clearPidRecord();
|
|
3498
|
+
}
|
|
3499
|
+
async function ensureDashboard(options) {
|
|
3500
|
+
const preferred = options.port ?? DEFAULT_DASHBOARD_PORT;
|
|
3501
|
+
const open = options.open !== false;
|
|
3502
|
+
await killRunningDashboard(preferred);
|
|
3503
|
+
spawnDaemon(preferred);
|
|
3504
|
+
let running = null;
|
|
3505
|
+
for (let i = 0; i < 50; i++) {
|
|
3506
|
+
await sleep(100);
|
|
3507
|
+
running = await findRunningDashboard(preferred);
|
|
3508
|
+
if (running) break;
|
|
3509
|
+
}
|
|
3510
|
+
if (!running) {
|
|
3511
|
+
throw new Error(
|
|
3512
|
+
`could not start dashboard on ${DASHBOARD_HOST}:${preferred}.
|
|
3513
|
+
Check ${path4.join(dataDir(), "dashboard.log")} or run:
|
|
3514
|
+
transcodes dashboard --daemon --port ${preferred}
|
|
3515
|
+
Stop a stuck instance with: transcodes stop`
|
|
3516
|
+
);
|
|
3517
|
+
}
|
|
3003
3518
|
process.stdout.write(
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3519
|
+
`${t("dashboardOpened", { url: running.url })}
|
|
3520
|
+
${t("dashboardHowToUse")}
|
|
3521
|
+
${t("dashboardStopHint")}
|
|
3007
3522
|
`
|
|
3008
3523
|
);
|
|
3009
|
-
if (
|
|
3010
|
-
|
|
3524
|
+
if (open) {
|
|
3525
|
+
openDashboardBrowser(running.url);
|
|
3011
3526
|
}
|
|
3012
|
-
|
|
3527
|
+
}
|
|
3528
|
+
async function stopDashboard() {
|
|
3529
|
+
const preferred = readPidRecord()?.port ?? DEFAULT_DASHBOARD_PORT;
|
|
3530
|
+
const record = readPidRecord();
|
|
3531
|
+
const wasRunning = record !== null && isProcessAlive(record.pid) || await findRunningDashboard(preferred) !== null;
|
|
3532
|
+
await killRunningDashboard(preferred);
|
|
3533
|
+
process.stdout.write(
|
|
3534
|
+
wasRunning ? `${t("dashboardStopped")}
|
|
3535
|
+
` : `${t("dashboardNotRunning")}
|
|
3536
|
+
`
|
|
3537
|
+
);
|
|
3013
3538
|
}
|
|
3014
3539
|
|
|
3015
3540
|
// src/install.ts
|
|
3016
|
-
import { spawn as
|
|
3017
|
-
import
|
|
3018
|
-
import
|
|
3019
|
-
import
|
|
3541
|
+
import { spawn as spawn5 } from "child_process";
|
|
3542
|
+
import fs2 from "fs";
|
|
3543
|
+
import os5 from "os";
|
|
3544
|
+
import path5 from "path";
|
|
3020
3545
|
import { createInterface, moveCursor } from "readline";
|
|
3546
|
+
|
|
3547
|
+
// src/login.ts
|
|
3548
|
+
import { spawn as spawn4 } from "child_process";
|
|
3549
|
+
import { createHash as createHash2, randomBytes } from "crypto";
|
|
3550
|
+
import os4 from "os";
|
|
3551
|
+
var REQUEST_TIMEOUT_MS2 = 3e4;
|
|
3552
|
+
var TransientPollError = class extends Error {
|
|
3553
|
+
constructor(message) {
|
|
3554
|
+
super(message);
|
|
3555
|
+
this.name = "TransientPollError";
|
|
3556
|
+
}
|
|
3557
|
+
};
|
|
3558
|
+
var FatalPollError = class extends Error {
|
|
3559
|
+
constructor(message) {
|
|
3560
|
+
super(message);
|
|
3561
|
+
this.name = "FatalPollError";
|
|
3562
|
+
}
|
|
3563
|
+
};
|
|
3564
|
+
function parseOptions(args) {
|
|
3565
|
+
let label = `transcodes-${os4.hostname() || "cli"}`;
|
|
3566
|
+
let open = true;
|
|
3567
|
+
for (let i = 0; i < args.length; i++) {
|
|
3568
|
+
const arg = args[i];
|
|
3569
|
+
if (arg === "-l" || arg === "--label") {
|
|
3570
|
+
const value = args[++i]?.trim();
|
|
3571
|
+
if (!value) {
|
|
3572
|
+
throw new Error("missing label. Usage: transcodes login [-l <label>]");
|
|
3573
|
+
}
|
|
3574
|
+
label = value;
|
|
3575
|
+
} else if (arg === "--no-open") {
|
|
3576
|
+
open = false;
|
|
3577
|
+
} else {
|
|
3578
|
+
throw new Error(
|
|
3579
|
+
`unknown flag "${arg}". Usage: transcodes login [-l <label>] [--no-open]`
|
|
3580
|
+
);
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3583
|
+
return { label, open };
|
|
3584
|
+
}
|
|
3585
|
+
function openBrowser2(url) {
|
|
3586
|
+
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
3587
|
+
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
3588
|
+
try {
|
|
3589
|
+
const child = spawn4(opener, args, { stdio: "ignore", detached: true });
|
|
3590
|
+
child.on("error", () => {
|
|
3591
|
+
});
|
|
3592
|
+
child.unref();
|
|
3593
|
+
return true;
|
|
3594
|
+
} catch {
|
|
3595
|
+
return false;
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
function apiBaseV1() {
|
|
3599
|
+
const backend = process.env.TRANSCODES_BACKEND_URL?.trim() || DEFAULT_BACKEND_URL;
|
|
3600
|
+
return `${backend.replace(/\/$/, "")}/v1`;
|
|
3601
|
+
}
|
|
3602
|
+
async function postJson(path6, body) {
|
|
3603
|
+
const controller = new AbortController();
|
|
3604
|
+
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS2);
|
|
3605
|
+
try {
|
|
3606
|
+
const response = await fetch(`${apiBaseV1()}${path6}`, {
|
|
3607
|
+
method: "POST",
|
|
3608
|
+
headers: {
|
|
3609
|
+
Accept: "application/json",
|
|
3610
|
+
"Content-Type": "application/json"
|
|
3611
|
+
},
|
|
3612
|
+
body: JSON.stringify(body),
|
|
3613
|
+
signal: controller.signal
|
|
3614
|
+
});
|
|
3615
|
+
let envelope;
|
|
3616
|
+
try {
|
|
3617
|
+
envelope = await response.json();
|
|
3618
|
+
} catch {
|
|
3619
|
+
throw new TransientPollError(
|
|
3620
|
+
`CLI authorization response was not JSON (${response.status})`
|
|
3621
|
+
);
|
|
3622
|
+
}
|
|
3623
|
+
return { status: response.status, envelope };
|
|
3624
|
+
} catch (error) {
|
|
3625
|
+
if (error instanceof TransientPollError) throw error;
|
|
3626
|
+
const aborted = error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
|
|
3627
|
+
throw new TransientPollError(
|
|
3628
|
+
aborted ? "CLI authorization request timed out" : error instanceof Error ? error.message : "CLI authorization request failed"
|
|
3629
|
+
);
|
|
3630
|
+
} finally {
|
|
3631
|
+
clearTimeout(timer);
|
|
3632
|
+
}
|
|
3633
|
+
}
|
|
3634
|
+
async function createSession(body) {
|
|
3635
|
+
const { status, envelope } = await postJson(
|
|
3636
|
+
"/auth/temp-session/cli/session",
|
|
3637
|
+
body
|
|
3638
|
+
);
|
|
3639
|
+
const value = envelope.payload?.[0];
|
|
3640
|
+
if (status < 200 || status >= 300 || !value) {
|
|
3641
|
+
throw new Error(
|
|
3642
|
+
envelope.message || envelope.error || `CLI authorization request failed (${status})`
|
|
3643
|
+
);
|
|
3644
|
+
}
|
|
3645
|
+
return value;
|
|
3646
|
+
}
|
|
3647
|
+
async function pollToken(params) {
|
|
3648
|
+
const { status, envelope } = await postJson(
|
|
3649
|
+
`/auth/temp-session/cli/session/${encodeURIComponent(params.sid)}/token`,
|
|
3650
|
+
{ code_verifier: params.verifier }
|
|
3651
|
+
);
|
|
3652
|
+
if (status === 404) {
|
|
3653
|
+
throw new FatalPollError(
|
|
3654
|
+
envelope.message || envelope.error || "CLI authorization session expired or was already used"
|
|
3655
|
+
);
|
|
3656
|
+
}
|
|
3657
|
+
if (status === 403) {
|
|
3658
|
+
throw new FatalPollError(
|
|
3659
|
+
envelope.message || envelope.error || "CLI authorization verifier was rejected"
|
|
3660
|
+
);
|
|
3661
|
+
}
|
|
3662
|
+
if (status >= 500 || status === 0) {
|
|
3663
|
+
throw new TransientPollError(
|
|
3664
|
+
envelope.message || envelope.error || `CLI authorization temporary failure (${status})`
|
|
3665
|
+
);
|
|
3666
|
+
}
|
|
3667
|
+
const value = envelope.payload?.[0];
|
|
3668
|
+
if (status < 200 || status >= 300 || !value) {
|
|
3669
|
+
throw new FatalPollError(
|
|
3670
|
+
envelope.message || envelope.error || `CLI authorization request failed (${status})`
|
|
3671
|
+
);
|
|
3672
|
+
}
|
|
3673
|
+
return value;
|
|
3674
|
+
}
|
|
3675
|
+
function delay(ms) {
|
|
3676
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3677
|
+
}
|
|
3678
|
+
async function waitForToken(params) {
|
|
3679
|
+
const deadline = new Date(params.expiresAt).getTime();
|
|
3680
|
+
if (!Number.isFinite(deadline)) {
|
|
3681
|
+
throw new Error("CLI authorization session has an invalid expiry");
|
|
3682
|
+
}
|
|
3683
|
+
while (Date.now() < deadline) {
|
|
3684
|
+
try {
|
|
3685
|
+
const result = await pollToken({
|
|
3686
|
+
sid: params.sid,
|
|
3687
|
+
verifier: params.verifier
|
|
3688
|
+
});
|
|
3689
|
+
if (result.status === "authorized" && result.token) return result;
|
|
3690
|
+
} catch (error) {
|
|
3691
|
+
if (error instanceof FatalPollError) throw error;
|
|
3692
|
+
}
|
|
3693
|
+
await delay(Math.max(1, params.interval) * 1e3);
|
|
3694
|
+
}
|
|
3695
|
+
throw new Error("CLI authorization timed out");
|
|
3696
|
+
}
|
|
3697
|
+
async function cmdLogin(args) {
|
|
3698
|
+
const options = parseOptions(args);
|
|
3699
|
+
const verifier = randomBytes(32).toString("base64url");
|
|
3700
|
+
const challenge = createHash2("sha256").update(verifier).digest("base64url");
|
|
3701
|
+
const session = await createSession({
|
|
3702
|
+
code_challenge: challenge,
|
|
3703
|
+
label: options.label
|
|
3704
|
+
});
|
|
3705
|
+
process.stdout.write(`${t("loginPleaseSignIn")}
|
|
3706
|
+
`);
|
|
3707
|
+
if (!options.open) {
|
|
3708
|
+
process.stdout.write(`${t("loginNoOpenHint")}
|
|
3709
|
+
`);
|
|
3710
|
+
} else if (!openBrowser2(session.url)) {
|
|
3711
|
+
process.stdout.write(`${t("loginBrowserOpenFailed")}
|
|
3712
|
+
`);
|
|
3713
|
+
} else {
|
|
3714
|
+
process.stdout.write(`${t("loginWaiting")}
|
|
3715
|
+
`);
|
|
3716
|
+
}
|
|
3717
|
+
const result = await waitForToken({
|
|
3718
|
+
sid: session.sid,
|
|
3719
|
+
verifier,
|
|
3720
|
+
expiresAt: session.expires_at,
|
|
3721
|
+
interval: session.interval
|
|
3722
|
+
});
|
|
3723
|
+
const token = result.token;
|
|
3724
|
+
if (!token) throw new Error("CLI authorization completed without a token");
|
|
3725
|
+
const label = result.label || options.label;
|
|
3726
|
+
parseMemberAccessToken(token);
|
|
3727
|
+
writeTokenToFile(token, label);
|
|
3728
|
+
process.stdout.write(`${t("loginTokenSaved")}
|
|
3729
|
+
`);
|
|
3730
|
+
}
|
|
3731
|
+
|
|
3732
|
+
// src/install.ts
|
|
3021
3733
|
var REPO_SLUG = "transcodings/transcodes-guard";
|
|
3022
3734
|
var REPO_GIT_URL = "https://github.com/transcodings/transcodes-guard.git";
|
|
3023
3735
|
var MARKETPLACE = "bigstrider";
|
|
3024
3736
|
var PLUGIN_NAME = "transcodes-guard";
|
|
3025
|
-
var APP_CONSOLE_URL = "https://app.transcodes.io";
|
|
3026
3737
|
var MIN_NODE_MAJOR = 20;
|
|
3027
3738
|
var PLATFORMS = [
|
|
3028
3739
|
{ id: "claude", label: "Claude Code" },
|
|
@@ -3045,6 +3756,36 @@ function log(line = "") {
|
|
|
3045
3756
|
function clearScreen() {
|
|
3046
3757
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
3047
3758
|
}
|
|
3759
|
+
function displayWidth(s) {
|
|
3760
|
+
let w = 0;
|
|
3761
|
+
for (const ch of s) {
|
|
3762
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
3763
|
+
if (code >= 4352 && code <= 4447 || code >= 11904 && code <= 42191 || code >= 44032 && code <= 55203 || code >= 63744 && code <= 64255 || code >= 65040 && code <= 65135 || code >= 65280 && code <= 65376 || code >= 65504 && code <= 65510) {
|
|
3764
|
+
w += 2;
|
|
3765
|
+
} else {
|
|
3766
|
+
w += 1;
|
|
3767
|
+
}
|
|
3768
|
+
}
|
|
3769
|
+
return w;
|
|
3770
|
+
}
|
|
3771
|
+
function physicalRowCount(lines) {
|
|
3772
|
+
const cols = Math.max(1, process.stdout.columns || 80);
|
|
3773
|
+
let rows = 0;
|
|
3774
|
+
for (const line of lines) {
|
|
3775
|
+
const w = Math.max(1, displayWidth(line));
|
|
3776
|
+
rows += Math.ceil(w / cols);
|
|
3777
|
+
}
|
|
3778
|
+
return rows;
|
|
3779
|
+
}
|
|
3780
|
+
function redrawBlock(stdout, lines, previousPhysicalRows) {
|
|
3781
|
+
if (previousPhysicalRows > 0) {
|
|
3782
|
+
moveCursor(stdout, 0, -previousPhysicalRows);
|
|
3783
|
+
stdout.write("\x1B[J");
|
|
3784
|
+
}
|
|
3785
|
+
stdout.write(`${lines.map((line) => `${line}\x1B[K`).join("\n")}
|
|
3786
|
+
`);
|
|
3787
|
+
return physicalRowCount(lines);
|
|
3788
|
+
}
|
|
3048
3789
|
function isTty() {
|
|
3049
3790
|
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
3050
3791
|
}
|
|
@@ -3052,7 +3793,7 @@ function openUrl(url) {
|
|
|
3052
3793
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
3053
3794
|
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
3054
3795
|
try {
|
|
3055
|
-
const child =
|
|
3796
|
+
const child = spawn5(opener, args, { stdio: "ignore", detached: true });
|
|
3056
3797
|
child.on("error", () => {
|
|
3057
3798
|
});
|
|
3058
3799
|
child.unref();
|
|
@@ -3060,25 +3801,25 @@ function openUrl(url) {
|
|
|
3060
3801
|
}
|
|
3061
3802
|
}
|
|
3062
3803
|
function refreshPathHints() {
|
|
3063
|
-
const home =
|
|
3804
|
+
const home = os5.homedir();
|
|
3064
3805
|
const extras = [
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3806
|
+
path5.join(home, ".local", "bin"),
|
|
3807
|
+
path5.join(home, ".claude", "bin"),
|
|
3808
|
+
path5.join(home, ".codex", "bin"),
|
|
3809
|
+
path5.join(home, ".cursor", "bin"),
|
|
3810
|
+
path5.join(home, ".gemini", "bin"),
|
|
3811
|
+
path5.join(home, "bin"),
|
|
3071
3812
|
"/usr/local/bin",
|
|
3072
3813
|
"/opt/homebrew/bin"
|
|
3073
3814
|
];
|
|
3074
|
-
const
|
|
3075
|
-
const parts =
|
|
3815
|
+
const current2 = process.env.PATH ?? "";
|
|
3816
|
+
const parts = current2.split(path5.delimiter).filter(Boolean);
|
|
3076
3817
|
const merged = [...extras.filter((p) => !parts.includes(p)), ...parts];
|
|
3077
|
-
process.env.PATH = merged.join(
|
|
3818
|
+
process.env.PATH = merged.join(path5.delimiter);
|
|
3078
3819
|
}
|
|
3079
3820
|
function commandExists(cmd) {
|
|
3080
3821
|
return new Promise((resolve) => {
|
|
3081
|
-
const child = process.platform === "win32" ?
|
|
3822
|
+
const child = process.platform === "win32" ? spawn5("where", [cmd], { stdio: "ignore" }) : spawn5("sh", ["-c", `command -v ${JSON.stringify(cmd)}`], {
|
|
3082
3823
|
stdio: "ignore"
|
|
3083
3824
|
});
|
|
3084
3825
|
child.on("error", () => resolve(false));
|
|
@@ -3095,7 +3836,7 @@ function run(cmd, args) {
|
|
|
3095
3836
|
return new Promise((resolve) => {
|
|
3096
3837
|
log(`
|
|
3097
3838
|
$ ${cmd} ${args.join(" ")}`);
|
|
3098
|
-
const child =
|
|
3839
|
+
const child = spawn5(cmd, args, { stdio: "inherit", env: process.env });
|
|
3099
3840
|
child.on("error", (err) => {
|
|
3100
3841
|
log(` (failed to start: ${err.message})`);
|
|
3101
3842
|
resolve(1);
|
|
@@ -3107,7 +3848,7 @@ function runShell(script) {
|
|
|
3107
3848
|
return new Promise((resolve) => {
|
|
3108
3849
|
log(`
|
|
3109
3850
|
$ ${script}`);
|
|
3110
|
-
const child =
|
|
3851
|
+
const child = spawn5("sh", ["-c", script], {
|
|
3111
3852
|
stdio: "inherit",
|
|
3112
3853
|
env: process.env
|
|
3113
3854
|
});
|
|
@@ -3169,20 +3910,20 @@ async function ensureNode() {
|
|
|
3169
3910
|
installed = await runShell(nvmInstall) === 0;
|
|
3170
3911
|
}
|
|
3171
3912
|
refreshPathHints();
|
|
3172
|
-
const nvmDefaultBin =
|
|
3173
|
-
if (
|
|
3913
|
+
const nvmDefaultBin = path5.join(os5.homedir(), ".nvm", "versions", "node");
|
|
3914
|
+
if (fs2.existsSync(nvmDefaultBin)) {
|
|
3174
3915
|
try {
|
|
3175
|
-
const versions =
|
|
3916
|
+
const versions = fs2.readdirSync(nvmDefaultBin).filter((v) => v.startsWith("v")).sort().reverse();
|
|
3176
3917
|
if (versions[0]) {
|
|
3177
|
-
const bin =
|
|
3178
|
-
process.env.PATH = `${bin}${
|
|
3918
|
+
const bin = path5.join(nvmDefaultBin, versions[0], "bin");
|
|
3919
|
+
process.env.PATH = `${bin}${path5.delimiter}${process.env.PATH ?? ""}`;
|
|
3179
3920
|
}
|
|
3180
3921
|
} catch {
|
|
3181
3922
|
}
|
|
3182
3923
|
}
|
|
3183
3924
|
if (await commandExists("node")) {
|
|
3184
3925
|
const ver = await new Promise((resolve) => {
|
|
3185
|
-
const child =
|
|
3926
|
+
const child = spawn5("node", ["-v"], {
|
|
3186
3927
|
stdio: ["ignore", "pipe", "ignore"]
|
|
3187
3928
|
});
|
|
3188
3929
|
let out = "";
|
|
@@ -3288,70 +4029,159 @@ function promptLine(question) {
|
|
|
3288
4029
|
}
|
|
3289
4030
|
function safeExists(p) {
|
|
3290
4031
|
try {
|
|
3291
|
-
return
|
|
4032
|
+
return fs2.existsSync(p);
|
|
3292
4033
|
} catch {
|
|
3293
4034
|
return false;
|
|
3294
4035
|
}
|
|
3295
4036
|
}
|
|
3296
4037
|
function dirHasMatch(dir, needles) {
|
|
3297
4038
|
try {
|
|
3298
|
-
return
|
|
4039
|
+
return fs2.readdirSync(dir).some((entry) => needles.some((n) => entry.includes(n)));
|
|
3299
4040
|
} catch {
|
|
3300
4041
|
return false;
|
|
3301
4042
|
}
|
|
3302
4043
|
}
|
|
3303
4044
|
function fileHas(file, needle) {
|
|
3304
4045
|
try {
|
|
3305
|
-
return
|
|
4046
|
+
return fs2.readFileSync(file, "utf8").includes(needle);
|
|
3306
4047
|
} catch {
|
|
3307
4048
|
return false;
|
|
3308
4049
|
}
|
|
3309
4050
|
}
|
|
3310
4051
|
function isPluginInstalled(id) {
|
|
3311
|
-
const home =
|
|
4052
|
+
const home = os5.homedir();
|
|
3312
4053
|
switch (id) {
|
|
3313
4054
|
case "cursor":
|
|
3314
4055
|
return safeExists(
|
|
3315
|
-
|
|
4056
|
+
path5.join(home, ".cursor", "plugins", "local", "transcodes-guard")
|
|
3316
4057
|
);
|
|
3317
4058
|
case "antigravity":
|
|
3318
4059
|
return safeExists(
|
|
3319
|
-
|
|
4060
|
+
path5.join(home, ".gemini", "config", "plugins", "transcodes-guard")
|
|
3320
4061
|
);
|
|
3321
4062
|
case "claude":
|
|
3322
|
-
return dirHasMatch(
|
|
4063
|
+
return dirHasMatch(path5.join(home, ".claude", "plugins"), [
|
|
3323
4064
|
"transcodes-guard",
|
|
3324
4065
|
"bigstrider"
|
|
3325
|
-
]) || dirHasMatch(
|
|
4066
|
+
]) || dirHasMatch(path5.join(home, ".claude", "plugins", "marketplaces"), [
|
|
3326
4067
|
"bigstrider",
|
|
3327
4068
|
"transcodes-guard"
|
|
3328
4069
|
]) || fileHas(
|
|
3329
|
-
|
|
4070
|
+
path5.join(home, ".claude", "settings.json"),
|
|
3330
4071
|
"transcodes-guard"
|
|
3331
|
-
) || fileHas(
|
|
4072
|
+
) || fileHas(path5.join(home, ".claude.json"), "transcodes-guard");
|
|
3332
4073
|
case "codex":
|
|
3333
|
-
return dirHasMatch(
|
|
4074
|
+
return dirHasMatch(path5.join(home, ".codex", "plugins"), [
|
|
3334
4075
|
"transcodes-guard",
|
|
3335
4076
|
"bigstrider"
|
|
3336
|
-
]) || fileHas(
|
|
4077
|
+
]) || fileHas(path5.join(home, ".codex", "config.toml"), "transcodes-guard") || fileHas(path5.join(home, ".codex", "config.json"), "transcodes-guard");
|
|
3337
4078
|
}
|
|
3338
4079
|
}
|
|
4080
|
+
function commandExistsSync(cmd) {
|
|
4081
|
+
const pathEnv = process.env.PATH ?? "";
|
|
4082
|
+
const exts = process.platform === "win32" ? [".exe", ".cmd", ".bat", ".ps1", ""] : [""];
|
|
4083
|
+
for (const dir of pathEnv.split(path5.delimiter)) {
|
|
4084
|
+
if (!dir) continue;
|
|
4085
|
+
for (const ext of exts) {
|
|
4086
|
+
if (safeExists(path5.join(dir, `${cmd}${ext}`))) return true;
|
|
4087
|
+
}
|
|
4088
|
+
}
|
|
4089
|
+
return false;
|
|
4090
|
+
}
|
|
4091
|
+
function hostDesktopPresent(id) {
|
|
4092
|
+
const home = os5.homedir();
|
|
4093
|
+
if (process.platform === "darwin") {
|
|
4094
|
+
const candidates = {
|
|
4095
|
+
claude: [
|
|
4096
|
+
"/Applications/Claude.app",
|
|
4097
|
+
"/Applications/Claude Code.app",
|
|
4098
|
+
path5.join(home, "Applications", "Claude.app"),
|
|
4099
|
+
path5.join(home, "Applications", "Claude Code.app")
|
|
4100
|
+
],
|
|
4101
|
+
cursor: [
|
|
4102
|
+
"/Applications/Cursor.app",
|
|
4103
|
+
path5.join(home, "Applications", "Cursor.app")
|
|
4104
|
+
],
|
|
4105
|
+
codex: [
|
|
4106
|
+
"/Applications/ChatGPT.app",
|
|
4107
|
+
"/Applications/Codex.app",
|
|
4108
|
+
path5.join(home, "Applications", "ChatGPT.app"),
|
|
4109
|
+
path5.join(home, "Applications", "Codex.app")
|
|
4110
|
+
],
|
|
4111
|
+
antigravity: [
|
|
4112
|
+
"/Applications/Antigravity.app",
|
|
4113
|
+
"/Applications/Google Antigravity.app",
|
|
4114
|
+
path5.join(home, "Applications", "Antigravity.app"),
|
|
4115
|
+
path5.join(home, "Applications", "Google Antigravity.app")
|
|
4116
|
+
]
|
|
4117
|
+
};
|
|
4118
|
+
return candidates[id].some(safeExists);
|
|
4119
|
+
}
|
|
4120
|
+
if (process.platform === "win32") {
|
|
4121
|
+
const local = process.env.LOCALAPPDATA ?? "";
|
|
4122
|
+
const prog = process.env.PROGRAMFILES ?? "C:\\Program Files";
|
|
4123
|
+
const candidates = {
|
|
4124
|
+
claude: [
|
|
4125
|
+
path5.join(local, "Programs", "Claude", "Claude.exe"),
|
|
4126
|
+
path5.join(local, "AnthropicClaude", "claude.exe")
|
|
4127
|
+
],
|
|
4128
|
+
cursor: [
|
|
4129
|
+
path5.join(local, "Programs", "cursor", "Cursor.exe"),
|
|
4130
|
+
path5.join(prog, "Cursor", "Cursor.exe")
|
|
4131
|
+
],
|
|
4132
|
+
codex: [
|
|
4133
|
+
path5.join(local, "Programs", "ChatGPT", "ChatGPT.exe"),
|
|
4134
|
+
path5.join(local, "Programs", "codex", "Codex.exe")
|
|
4135
|
+
],
|
|
4136
|
+
antigravity: [
|
|
4137
|
+
path5.join(local, "Programs", "Antigravity", "Antigravity.exe"),
|
|
4138
|
+
path5.join(local, "Programs", "Google Antigravity", "Antigravity.exe")
|
|
4139
|
+
]
|
|
4140
|
+
};
|
|
4141
|
+
return candidates[id].some(safeExists);
|
|
4142
|
+
}
|
|
4143
|
+
return false;
|
|
4144
|
+
}
|
|
4145
|
+
function isHostAppInstalled(id) {
|
|
4146
|
+
refreshPathHints();
|
|
4147
|
+
if (HOST_CLIS[id].binaries.some(commandExistsSync)) return true;
|
|
4148
|
+
return hostDesktopPresent(id);
|
|
4149
|
+
}
|
|
4150
|
+
function platformShortName(id) {
|
|
4151
|
+
switch (id) {
|
|
4152
|
+
case "claude":
|
|
4153
|
+
return "Claude";
|
|
4154
|
+
case "codex":
|
|
4155
|
+
return "ChatGPT";
|
|
4156
|
+
case "cursor":
|
|
4157
|
+
return "Cursor";
|
|
4158
|
+
case "antigravity":
|
|
4159
|
+
return "Antigravity";
|
|
4160
|
+
}
|
|
4161
|
+
}
|
|
4162
|
+
function installedAppsHint() {
|
|
4163
|
+
const names = PLATFORMS.filter((p) => isHostAppInstalled(p.id)).map(
|
|
4164
|
+
(p) => platformShortName(p.id)
|
|
4165
|
+
);
|
|
4166
|
+
if (names.length === 0) return t("platformInstalledNone");
|
|
4167
|
+
return t("platformInstalledApps", { list: names.join(", ") });
|
|
4168
|
+
}
|
|
3339
4169
|
function renderMenu() {
|
|
3340
4170
|
log("");
|
|
3341
|
-
log("
|
|
3342
|
-
log("
|
|
3343
|
-
log(
|
|
4171
|
+
log(t("platformTitle"));
|
|
4172
|
+
log(t("platformHint1"));
|
|
4173
|
+
log(installedAppsHint());
|
|
3344
4174
|
log("");
|
|
3345
4175
|
PLATFORMS.forEach((p, i) => {
|
|
3346
|
-
const
|
|
3347
|
-
|
|
4176
|
+
const onDevice = isHostAppInstalled(p.id);
|
|
4177
|
+
const dot = onDevice ? "\u25CF" : "\u25CB";
|
|
4178
|
+
const mark = onDevice ? ` ${t("installed")}` : "";
|
|
4179
|
+
log(` ${i + 1}. ${dot} ${p.label}${mark}`);
|
|
3348
4180
|
});
|
|
3349
|
-
log(` ${PLATFORMS.length + 1}.
|
|
4181
|
+
log(` ${PLATFORMS.length + 1}. ${t("nextStep")}`);
|
|
3350
4182
|
log("");
|
|
3351
|
-
log(
|
|
3352
|
-
log(
|
|
3353
|
-
`Type ${PLATFORMS.length + 1} (or "next") for Next Step, "exit"/"q" to quit.`
|
|
3354
|
-
);
|
|
4183
|
+
log(t("platformNumberedHint"));
|
|
4184
|
+
log(t("platformNumberedNext", { n: String(PLATFORMS.length + 1) }));
|
|
3355
4185
|
}
|
|
3356
4186
|
async function promptMenu() {
|
|
3357
4187
|
const nextNum = String(PLATFORMS.length + 1);
|
|
@@ -3394,27 +4224,22 @@ function arrowSelect(checked) {
|
|
|
3394
4224
|
const { stdin, stdout } = process;
|
|
3395
4225
|
const draw = () => {
|
|
3396
4226
|
const lines = [];
|
|
3397
|
-
lines.push("
|
|
3398
|
-
lines.push("
|
|
3399
|
-
lines.push(
|
|
4227
|
+
lines.push(t("platformTitle"));
|
|
4228
|
+
lines.push(t("platformHint1"));
|
|
4229
|
+
lines.push(installedAppsHint());
|
|
3400
4230
|
lines.push("");
|
|
3401
4231
|
PLATFORMS.forEach((p, i) => {
|
|
3402
4232
|
const pointer = cursor === i ? "\u276F" : " ";
|
|
3403
|
-
const
|
|
3404
|
-
const
|
|
4233
|
+
const onDevice = isHostAppInstalled(p.id);
|
|
4234
|
+
const box = checked.has(p.id) ? "\u25CF" : "\u25EF";
|
|
4235
|
+
const mark = onDevice ? ` ${t("installed")}` : "";
|
|
3405
4236
|
lines.push(`${pointer} ${box} ${p.label}${mark}`);
|
|
3406
4237
|
});
|
|
3407
4238
|
const nextPointer = cursor === PLATFORMS.length ? "\u276F" : " ";
|
|
3408
|
-
lines.push(`${nextPointer}
|
|
4239
|
+
lines.push(`${nextPointer} ${t("nextStep")}`);
|
|
3409
4240
|
lines.push("");
|
|
3410
|
-
lines.push(
|
|
3411
|
-
|
|
3412
|
-
);
|
|
3413
|
-
if (rendered > 0) moveCursor(stdout, 0, -rendered);
|
|
3414
|
-
const frame = lines.map((line) => `${line}\x1B[K`).join("\n");
|
|
3415
|
-
stdout.write(`${frame}
|
|
3416
|
-
`);
|
|
3417
|
-
rendered = lines.length;
|
|
4241
|
+
lines.push(t("platformKeys"));
|
|
4242
|
+
rendered = redrawBlock(stdout, lines, rendered);
|
|
3418
4243
|
};
|
|
3419
4244
|
const cleanup = () => {
|
|
3420
4245
|
stdout.write("\x1B[?25h");
|
|
@@ -3480,11 +4305,8 @@ function arrowChoose(title, options, defaultIndex = 0) {
|
|
|
3480
4305
|
lines.push(`${pointer} ${radio} ${opt}`);
|
|
3481
4306
|
});
|
|
3482
4307
|
lines.push("");
|
|
3483
|
-
lines.push("
|
|
3484
|
-
|
|
3485
|
-
stdout.write(`${lines.map((line) => `${line}\x1B[K`).join("\n")}
|
|
3486
|
-
`);
|
|
3487
|
-
rendered = lines.length;
|
|
4308
|
+
lines.push(t("chooseKeys"));
|
|
4309
|
+
rendered = redrawBlock(stdout, lines, rendered);
|
|
3488
4310
|
};
|
|
3489
4311
|
const cleanup = () => {
|
|
3490
4312
|
stdout.write("\x1B[?25h");
|
|
@@ -3521,24 +4343,6 @@ function arrowChoose(title, options, defaultIndex = 0) {
|
|
|
3521
4343
|
draw();
|
|
3522
4344
|
});
|
|
3523
4345
|
}
|
|
3524
|
-
async function chooseTokenAction(question) {
|
|
3525
|
-
const options = ["Yes", "No", "Skip \u2014 token already configured"];
|
|
3526
|
-
if (supportsArrowSelect()) {
|
|
3527
|
-
const idx = await arrowChoose(question, options, 0);
|
|
3528
|
-
return idx === 2 ? "skip" : idx === 0 ? "yes" : "no";
|
|
3529
|
-
}
|
|
3530
|
-
for (; ; ) {
|
|
3531
|
-
log(question);
|
|
3532
|
-
for (let i = 0; i < options.length; i++) {
|
|
3533
|
-
log(` ${i + 1}. ${options[i]}`);
|
|
3534
|
-
}
|
|
3535
|
-
const answer = (await promptLine("> ")).trim().toLowerCase();
|
|
3536
|
-
if (answer === "1" || answer === "y" || answer === "yes") return "yes";
|
|
3537
|
-
if (answer === "2" || answer === "n" || answer === "no") return "no";
|
|
3538
|
-
if (answer === "3" || answer === "skip" || answer === "s") return "skip";
|
|
3539
|
-
log(" Please choose 1, 2, or 3.");
|
|
3540
|
-
}
|
|
3541
|
-
}
|
|
3542
4346
|
async function runInstalls(ids) {
|
|
3543
4347
|
const clonedRepoDir = await cloneRepoIfNeeded(ids);
|
|
3544
4348
|
const results = /* @__PURE__ */ new Map();
|
|
@@ -3551,10 +4355,11 @@ async function runInstalls(ids) {
|
|
|
3551
4355
|
}
|
|
3552
4356
|
} finally {
|
|
3553
4357
|
if (clonedRepoDir) {
|
|
3554
|
-
|
|
4358
|
+
fs2.rmSync(clonedRepoDir, { recursive: true, force: true });
|
|
3555
4359
|
}
|
|
3556
4360
|
}
|
|
3557
|
-
log(
|
|
4361
|
+
log(`
|
|
4362
|
+
${t("installSummary")}`);
|
|
3558
4363
|
for (const id of ids) {
|
|
3559
4364
|
const label = PLATFORMS.find((p) => p.id === id)?.label ?? id;
|
|
3560
4365
|
log(` ${results.get(id) ? "\u2713" : "\u2717"} ${label}`);
|
|
@@ -3591,8 +4396,8 @@ async function installPlatform(id, clonedRepoDir) {
|
|
|
3591
4396
|
log(` Skipped ${platform?.label ?? id} \u2014 repo clone unavailable.`);
|
|
3592
4397
|
return false;
|
|
3593
4398
|
}
|
|
3594
|
-
const installer =
|
|
3595
|
-
if (!
|
|
4399
|
+
const installer = path5.join(clonedRepoDir, platform.installerRel);
|
|
4400
|
+
if (!fs2.existsSync(installer)) {
|
|
3596
4401
|
log(` Skipped ${platform.label} \u2014 installer missing at ${installer}.`);
|
|
3597
4402
|
return false;
|
|
3598
4403
|
}
|
|
@@ -3608,92 +4413,75 @@ async function cloneRepoIfNeeded(platforms) {
|
|
|
3608
4413
|
log("Install git and re-run, or use the one-liners from the README.");
|
|
3609
4414
|
return null;
|
|
3610
4415
|
}
|
|
3611
|
-
const tmpDir =
|
|
4416
|
+
const tmpDir = fs2.mkdtempSync(path5.join(os5.tmpdir(), "tg-install-"));
|
|
3612
4417
|
const ok = await run("git", ["clone", "--depth", "1", REPO_GIT_URL, tmpDir]) === 0;
|
|
3613
4418
|
if (!ok) {
|
|
3614
|
-
|
|
4419
|
+
fs2.rmSync(tmpDir, { recursive: true, force: true });
|
|
3615
4420
|
log("\nRepo clone failed \u2014 skipping Cursor / Antigravity.");
|
|
3616
4421
|
return null;
|
|
3617
4422
|
}
|
|
3618
4423
|
return tmpDir;
|
|
3619
4424
|
}
|
|
3620
|
-
async function registerToken() {
|
|
3621
|
-
log("");
|
|
3622
|
-
log("\u2500\u2500 Register MAT token \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
3623
|
-
for (let attempt = 0; attempt < 3; attempt++) {
|
|
3624
|
-
const token = (await promptLine("Paste your token: ")).trim();
|
|
3625
|
-
if (!token) {
|
|
3626
|
-
log(" Token is required.");
|
|
3627
|
-
continue;
|
|
3628
|
-
}
|
|
3629
|
-
try {
|
|
3630
|
-
parseMemberAccessToken(token);
|
|
3631
|
-
} catch (err) {
|
|
3632
|
-
log(
|
|
3633
|
-
` Token rejected: ${err instanceof Error ? err.message : String(err)}`
|
|
3634
|
-
);
|
|
3635
|
-
continue;
|
|
3636
|
-
}
|
|
3637
|
-
let label = "";
|
|
3638
|
-
while (!label) {
|
|
3639
|
-
label = (await promptLine("Label (e.g. transcodes-myproject-prod): ")).trim();
|
|
3640
|
-
if (!label) log(" Label is required.");
|
|
3641
|
-
}
|
|
3642
|
-
writeTokenToFile(token, label);
|
|
3643
|
-
log(`
|
|
3644
|
-
Saved token to ${transcodesConfigFile()} (label=${label}).`);
|
|
3645
|
-
log("");
|
|
3646
|
-
log(
|
|
3647
|
-
"Please restart your CLI or desktop app to activate transcodes plugins."
|
|
3648
|
-
);
|
|
3649
|
-
return true;
|
|
3650
|
-
}
|
|
3651
|
-
log("\nToo many invalid attempts \u2014 run `transcodes install` again later.");
|
|
3652
|
-
return false;
|
|
3653
|
-
}
|
|
3654
4425
|
function printSetupComplete() {
|
|
3655
4426
|
clearScreen();
|
|
3656
|
-
log("
|
|
3657
|
-
log(
|
|
3658
|
-
log(
|
|
3659
|
-
log("
|
|
4427
|
+
log(t("congratsBar"));
|
|
4428
|
+
log(` ${t("congrats1")}`);
|
|
4429
|
+
log(` ${t("congrats2")}`);
|
|
4430
|
+
log(t("congratsBar"));
|
|
3660
4431
|
log("");
|
|
3661
|
-
log("
|
|
4432
|
+
log(t("congratsBody"));
|
|
3662
4433
|
log("");
|
|
3663
4434
|
}
|
|
3664
4435
|
async function tokenFlow() {
|
|
3665
4436
|
log("");
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
"Do you have a Transcodes project MAT (Member Access Token)?"
|
|
3669
|
-
);
|
|
3670
|
-
if (choice === "skip") {
|
|
3671
|
-
log("");
|
|
3672
|
-
log("Skipping token setup \u2014 using the token already configured.");
|
|
4437
|
+
try {
|
|
4438
|
+
await cmdLogin([]);
|
|
3673
4439
|
return true;
|
|
4440
|
+
} catch (error) {
|
|
4441
|
+
log(` ${error instanceof Error ? error.message : String(error)}`);
|
|
4442
|
+
return false;
|
|
3674
4443
|
}
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
4444
|
+
}
|
|
4445
|
+
async function promptLocale() {
|
|
4446
|
+
if (!isTty()) {
|
|
4447
|
+
setLocale(readSavedLocale() ?? "en");
|
|
4448
|
+
return;
|
|
4449
|
+
}
|
|
4450
|
+
const saved = readSavedLocale();
|
|
4451
|
+
const defaultIndex = saved === "ko" ? 1 : 0;
|
|
4452
|
+
const options = ["English", "\uD55C\uAD6D\uC5B4"];
|
|
4453
|
+
if (supportsArrowSelect()) {
|
|
4454
|
+
const idx = await arrowChoose(t("langTitle"), [...options], defaultIndex);
|
|
4455
|
+
if (idx < 0) {
|
|
4456
|
+
setLocale(saved ?? "en");
|
|
4457
|
+
return;
|
|
4458
|
+
}
|
|
4459
|
+
setLocale(idx === 1 ? "ko" : "en");
|
|
3688
4460
|
log("");
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
);
|
|
3694
|
-
log("
|
|
4461
|
+
return;
|
|
4462
|
+
}
|
|
4463
|
+
for (; ; ) {
|
|
4464
|
+
log(t("langTitle"));
|
|
4465
|
+
log(" 1. English");
|
|
4466
|
+
log(" 2. \uD55C\uAD6D\uC5B4");
|
|
4467
|
+
const answer = (await promptLine("> ")).trim().toLowerCase();
|
|
4468
|
+
if (answer === "1" || answer === "en" || answer === "english") {
|
|
4469
|
+
setLocale("en");
|
|
4470
|
+
log("");
|
|
4471
|
+
return;
|
|
4472
|
+
}
|
|
4473
|
+
if (answer === "2" || answer === "ko" || answer === "kr" || answer === "\uD55C\uAD6D\uC5B4") {
|
|
4474
|
+
setLocale("ko");
|
|
4475
|
+
log("");
|
|
4476
|
+
return;
|
|
4477
|
+
}
|
|
4478
|
+
if (answer === "" && saved) {
|
|
4479
|
+
setLocale(saved);
|
|
4480
|
+
log("");
|
|
4481
|
+
return;
|
|
4482
|
+
}
|
|
4483
|
+
log(" 1 or 2 / 1 \uB610\uB294 2");
|
|
3695
4484
|
}
|
|
3696
|
-
return registerToken();
|
|
3697
4485
|
}
|
|
3698
4486
|
function parseSelection(args) {
|
|
3699
4487
|
const ids = [];
|
|
@@ -3713,7 +4501,9 @@ function parseSelection(args) {
|
|
|
3713
4501
|
return explicit ? ids : "interactive";
|
|
3714
4502
|
}
|
|
3715
4503
|
async function cmdInstall(args) {
|
|
3716
|
-
|
|
4504
|
+
await promptLocale();
|
|
4505
|
+
log(`${t("installBanner")}
|
|
4506
|
+
`);
|
|
3717
4507
|
if (!await ensureNode()) {
|
|
3718
4508
|
process.exit(1);
|
|
3719
4509
|
}
|
|
@@ -3722,19 +4512,17 @@ async function cmdInstall(args) {
|
|
|
3722
4512
|
let openDashboard = false;
|
|
3723
4513
|
if (selection === "interactive") {
|
|
3724
4514
|
if (!isTty()) {
|
|
3725
|
-
log(
|
|
3726
|
-
"Non-interactive shell detected. Specify platforms explicitly, e.g.:"
|
|
3727
|
-
);
|
|
4515
|
+
log(t("nonInteractive"));
|
|
3728
4516
|
log(" transcodes install --all");
|
|
3729
4517
|
log(" transcodes install claude codex cursor antigravity");
|
|
3730
4518
|
process.exit(1);
|
|
3731
4519
|
}
|
|
3732
4520
|
const useArrows = supportsArrowSelect();
|
|
3733
|
-
const checked =
|
|
3734
|
-
|
|
4521
|
+
const checked = new Set(
|
|
4522
|
+
PLATFORMS.filter((p) => isHostAppInstalled(p.id)).map((p) => p.id)
|
|
4523
|
+
);
|
|
3735
4524
|
for (; ; ) {
|
|
3736
|
-
|
|
3737
|
-
firstMenu = false;
|
|
4525
|
+
clearScreen();
|
|
3738
4526
|
let choice;
|
|
3739
4527
|
if (useArrows) {
|
|
3740
4528
|
choice = await arrowSelect(checked);
|
|
@@ -3744,9 +4532,7 @@ async function cmdInstall(args) {
|
|
|
3744
4532
|
}
|
|
3745
4533
|
if (choice.kind === "cancel") {
|
|
3746
4534
|
log("");
|
|
3747
|
-
log(
|
|
3748
|
-
"If you want to manage tokens directly, please type `transcodes` to open the dashboard"
|
|
3749
|
-
);
|
|
4535
|
+
log(t("cancelHint"));
|
|
3750
4536
|
process.exit(0);
|
|
3751
4537
|
}
|
|
3752
4538
|
if (choice.kind === "next") {
|
|
@@ -3754,7 +4540,7 @@ async function cmdInstall(args) {
|
|
|
3754
4540
|
break;
|
|
3755
4541
|
}
|
|
3756
4542
|
if (choice.ids.length === 0) {
|
|
3757
|
-
log(
|
|
4543
|
+
log(` ${t("nothingSelected")}`);
|
|
3758
4544
|
continue;
|
|
3759
4545
|
}
|
|
3760
4546
|
checked.clear();
|
|
@@ -3763,12 +4549,14 @@ async function cmdInstall(args) {
|
|
|
3763
4549
|
}
|
|
3764
4550
|
} else {
|
|
3765
4551
|
if (selection.length === 0) {
|
|
3766
|
-
log("
|
|
4552
|
+
log(t("noPlatforms"));
|
|
3767
4553
|
process.exit(0);
|
|
3768
4554
|
}
|
|
3769
4555
|
log(
|
|
3770
4556
|
`
|
|
3771
|
-
|
|
4557
|
+
${t("installing", {
|
|
4558
|
+
list: selection.map((id) => PLATFORMS.find((p) => p.id === id)?.label ?? id).join(", ")
|
|
4559
|
+
})}`
|
|
3772
4560
|
);
|
|
3773
4561
|
await runInstalls(selection);
|
|
3774
4562
|
clearScreen();
|
|
@@ -3778,8 +4566,8 @@ Installing: ${selection.map((id) => PLATFORMS.find((p) => p.id === id)?.label ??
|
|
|
3778
4566
|
process.exit(0);
|
|
3779
4567
|
}
|
|
3780
4568
|
printSetupComplete();
|
|
3781
|
-
await promptLine("
|
|
3782
|
-
await
|
|
4569
|
+
await promptLine(t("pressEnterDashboard"));
|
|
4570
|
+
await ensureDashboard({});
|
|
3783
4571
|
process.exit(0);
|
|
3784
4572
|
}
|
|
3785
4573
|
function installedPlatforms() {
|
|
@@ -3866,7 +4654,7 @@ async function cmdUpdate(args) {
|
|
|
3866
4654
|
}
|
|
3867
4655
|
} finally {
|
|
3868
4656
|
if (clonedRepoDir) {
|
|
3869
|
-
|
|
4657
|
+
fs2.rmSync(clonedRepoDir, { recursive: true, force: true });
|
|
3870
4658
|
}
|
|
3871
4659
|
}
|
|
3872
4660
|
}
|
|
@@ -4017,9 +4805,10 @@ async function cmdConsole(args) {
|
|
|
4017
4805
|
}
|
|
4018
4806
|
process.exit(0);
|
|
4019
4807
|
}
|
|
4020
|
-
|
|
4808
|
+
function parseDashboardFlags(args, usage) {
|
|
4021
4809
|
let port;
|
|
4022
4810
|
let open = true;
|
|
4811
|
+
let daemon = false;
|
|
4023
4812
|
for (let i = 0; i < args.length; i++) {
|
|
4024
4813
|
if (args[i] === "--port" && args[i + 1]) {
|
|
4025
4814
|
port = Number(args[++i]);
|
|
@@ -4028,14 +4817,49 @@ async function cmdDashboard(args) {
|
|
|
4028
4817
|
}
|
|
4029
4818
|
} else if (args[i] === "--no-open") {
|
|
4030
4819
|
open = false;
|
|
4820
|
+
} else if (args[i] === "--daemon") {
|
|
4821
|
+
daemon = true;
|
|
4031
4822
|
} else {
|
|
4032
|
-
fail(
|
|
4033
|
-
|
|
4034
|
-
|
|
4823
|
+
fail(`unknown flag "${args[i]}". Usage: ${usage}`);
|
|
4824
|
+
}
|
|
4825
|
+
}
|
|
4826
|
+
return { port, open, daemon };
|
|
4827
|
+
}
|
|
4828
|
+
async function cmdDashboard(args) {
|
|
4829
|
+
const { port, open, daemon } = parseDashboardFlags(
|
|
4830
|
+
args,
|
|
4831
|
+
"transcodes [--port N] [--no-open]"
|
|
4832
|
+
);
|
|
4833
|
+
if (daemon) {
|
|
4834
|
+
fail("use `transcodes dashboard --daemon` to run the server process");
|
|
4835
|
+
}
|
|
4836
|
+
try {
|
|
4837
|
+
await ensureDashboard({ port, open });
|
|
4838
|
+
} catch (err) {
|
|
4839
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
4840
|
+
}
|
|
4841
|
+
process.exit(0);
|
|
4842
|
+
}
|
|
4843
|
+
async function cmdDashboardSub(args) {
|
|
4844
|
+
const { port, open, daemon } = parseDashboardFlags(
|
|
4845
|
+
args,
|
|
4846
|
+
"transcodes dashboard [--daemon] [--port N] [--no-open]"
|
|
4847
|
+
);
|
|
4848
|
+
try {
|
|
4849
|
+
if (daemon) {
|
|
4850
|
+
await serveDashboard({ port });
|
|
4851
|
+
process.exit(0);
|
|
4852
|
+
return;
|
|
4035
4853
|
}
|
|
4854
|
+
await ensureDashboard({ port, open });
|
|
4855
|
+
} catch (err) {
|
|
4856
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
4036
4857
|
}
|
|
4858
|
+
process.exit(0);
|
|
4859
|
+
}
|
|
4860
|
+
async function cmdStop() {
|
|
4037
4861
|
try {
|
|
4038
|
-
await
|
|
4862
|
+
await stopDashboard();
|
|
4039
4863
|
} catch (err) {
|
|
4040
4864
|
fail(err instanceof Error ? err.message : String(err));
|
|
4041
4865
|
}
|
|
@@ -4062,9 +4886,20 @@ function main() {
|
|
|
4062
4886
|
case "tokens":
|
|
4063
4887
|
cmdTokens();
|
|
4064
4888
|
break;
|
|
4889
|
+
case "login":
|
|
4890
|
+
void cmdLogin(rest).catch((error) => {
|
|
4891
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
4892
|
+
});
|
|
4893
|
+
break;
|
|
4065
4894
|
case "console":
|
|
4066
4895
|
void cmdConsole(rest);
|
|
4067
4896
|
break;
|
|
4897
|
+
case "dashboard":
|
|
4898
|
+
void cmdDashboardSub(rest);
|
|
4899
|
+
break;
|
|
4900
|
+
case "stop":
|
|
4901
|
+
void cmdStop();
|
|
4902
|
+
break;
|
|
4068
4903
|
case "version":
|
|
4069
4904
|
case "--version":
|
|
4070
4905
|
case "-V":
|