@bigstrider/transcodes-cli 0.7.0 → 0.9.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/dist/index.js +1330 -118
- package/package.json +1 -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 path3 = 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,14 +13,11 @@ 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}${path3}${qs ? `?${qs}` : ""}`;
|
|
17
17
|
const headers = {
|
|
18
18
|
"x-transcodes-token": config.token,
|
|
19
19
|
Accept: "application/json"
|
|
20
20
|
};
|
|
21
|
-
if (input.stepUpSid) {
|
|
22
|
-
headers["X-Step-Up-Session-Id"] = input.stepUpSid;
|
|
23
|
-
}
|
|
24
21
|
let body;
|
|
25
22
|
const sendsBody = input.method !== "GET" && !input.omitBody;
|
|
26
23
|
if (sendsBody) {
|
|
@@ -495,6 +492,14 @@ async function openConsoleSession(options) {
|
|
|
495
492
|
|
|
496
493
|
// src/commands.ts
|
|
497
494
|
var CLI_COMMAND_SPECS = [
|
|
495
|
+
{
|
|
496
|
+
usage: "transcodes install",
|
|
497
|
+
description: "Guided setup: pick host plugins to install, save a token, then open the dashboard (--all / <platforms>)"
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
usage: "transcodes update",
|
|
501
|
+
description: "Update installed host plugins in place and bump this CLI from npm (--cli-only / --plugins-only / --all)"
|
|
502
|
+
},
|
|
498
503
|
{
|
|
499
504
|
usage: "transcodes",
|
|
500
505
|
description: "Open the local dashboard (URL printed in terminal; default port 3847; --port N / --no-open)"
|
|
@@ -557,11 +562,12 @@ function renderCliCommandsHtml() {
|
|
|
557
562
|
}
|
|
558
563
|
|
|
559
564
|
// src/dashboard.ts
|
|
560
|
-
import { spawn as spawn2 } from "child_process";
|
|
565
|
+
import { execFile, spawn as spawn2 } from "child_process";
|
|
561
566
|
import { createHash } from "crypto";
|
|
562
567
|
import {
|
|
563
568
|
createServer
|
|
564
569
|
} from "http";
|
|
570
|
+
import { promisify } from "util";
|
|
565
571
|
|
|
566
572
|
// ../packages/core/dist/contract/messages.js
|
|
567
573
|
var TRANSCODES_GUARD_REPO_URL = "https://github.com/transcodings/transcodes-guard";
|
|
@@ -619,7 +625,10 @@ var CLI_VERSION = pkg.version;
|
|
|
619
625
|
// src/dashboard.ts
|
|
620
626
|
var DEFAULT_PORT = 3847;
|
|
621
627
|
var HOST = "127.0.0.1";
|
|
622
|
-
var
|
|
628
|
+
var PORT_ATTEMPTS = 10;
|
|
629
|
+
var GUIDELINE_MUX_PLAYBACK_ID = "h1vOCPmFDA02fGhcout01FZWD4lpKNTzLFk7vybxvrc3M";
|
|
630
|
+
var APP_ORG_URL = "https://app.transcodes.io/en/org";
|
|
631
|
+
var execFileAsync = promisify(execFile);
|
|
623
632
|
function fingerprint(token) {
|
|
624
633
|
return createHash("sha256").update(token).digest("hex").slice(0, 12);
|
|
625
634
|
}
|
|
@@ -827,6 +836,24 @@ function dashboardHtml() {
|
|
|
827
836
|
font-weight: 600;
|
|
828
837
|
line-height: 1.45;
|
|
829
838
|
}
|
|
839
|
+
.header-token-empty-title { margin: 0; }
|
|
840
|
+
.header-token-empty-cmds {
|
|
841
|
+
margin: 10px 0 0;
|
|
842
|
+
display: flex;
|
|
843
|
+
flex-direction: column;
|
|
844
|
+
align-items: flex-start;
|
|
845
|
+
gap: 6px;
|
|
846
|
+
}
|
|
847
|
+
.header-token-empty-cmds code {
|
|
848
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
849
|
+
font-size: var(--text-2xs);
|
|
850
|
+
font-weight: 600;
|
|
851
|
+
color: var(--accent);
|
|
852
|
+
background: #fff;
|
|
853
|
+
border: 1px solid #f5c6cb;
|
|
854
|
+
padding: 3px 9px;
|
|
855
|
+
border-radius: 7px;
|
|
856
|
+
}
|
|
830
857
|
.header-profile {
|
|
831
858
|
display: flex;
|
|
832
859
|
align-items: center;
|
|
@@ -862,6 +889,9 @@ function dashboardHtml() {
|
|
|
862
889
|
}
|
|
863
890
|
.btn-manage-auth {
|
|
864
891
|
flex-shrink: 0;
|
|
892
|
+
display: inline-flex;
|
|
893
|
+
align-items: center;
|
|
894
|
+
gap: 6px;
|
|
865
895
|
border: none;
|
|
866
896
|
border-radius: 10px;
|
|
867
897
|
padding: 10px 16px;
|
|
@@ -872,6 +902,11 @@ function dashboardHtml() {
|
|
|
872
902
|
cursor: pointer;
|
|
873
903
|
transition: opacity 0.15s ease, transform 0.15s ease;
|
|
874
904
|
}
|
|
905
|
+
.btn-manage-auth svg {
|
|
906
|
+
width: 20px;
|
|
907
|
+
height: 20px;
|
|
908
|
+
flex-shrink: 0;
|
|
909
|
+
}
|
|
875
910
|
.btn-manage-auth:hover:not(:disabled) { opacity: 0.92; }
|
|
876
911
|
.btn-manage-auth:disabled {
|
|
877
912
|
opacity: 0.55;
|
|
@@ -958,6 +993,12 @@ function dashboardHtml() {
|
|
|
958
993
|
color: var(--muted);
|
|
959
994
|
margin: 0 0 16px;
|
|
960
995
|
}
|
|
996
|
+
.section-sub a {
|
|
997
|
+
color: var(--accent);
|
|
998
|
+
font-weight: 600;
|
|
999
|
+
text-decoration: none;
|
|
1000
|
+
}
|
|
1001
|
+
.section-sub a:hover { text-decoration: underline; }
|
|
961
1002
|
.cli-map-row {
|
|
962
1003
|
display: flex;
|
|
963
1004
|
align-items: center;
|
|
@@ -1217,8 +1258,8 @@ function dashboardHtml() {
|
|
|
1217
1258
|
.list-label {
|
|
1218
1259
|
margin: 26px 0 10px;
|
|
1219
1260
|
font-size: var(--text-sm);
|
|
1220
|
-
font-weight:
|
|
1221
|
-
color: var(--
|
|
1261
|
+
font-weight: 700;
|
|
1262
|
+
color: var(--ink);
|
|
1222
1263
|
letter-spacing: 0.01em;
|
|
1223
1264
|
}
|
|
1224
1265
|
.token-list { display: flex; flex-direction: column; gap: 10px; }
|
|
@@ -1518,8 +1559,34 @@ function dashboardHtml() {
|
|
|
1518
1559
|
line-height: 1.5;
|
|
1519
1560
|
margin: 0;
|
|
1520
1561
|
}
|
|
1521
|
-
.guide-video {
|
|
1562
|
+
.guide-video-wrap {
|
|
1522
1563
|
margin: 0 0 22px;
|
|
1564
|
+
}
|
|
1565
|
+
.guide-video-toggle {
|
|
1566
|
+
display: inline-flex;
|
|
1567
|
+
align-items: center;
|
|
1568
|
+
gap: 6px;
|
|
1569
|
+
border: 1px solid var(--line);
|
|
1570
|
+
border-radius: 999px;
|
|
1571
|
+
padding: 6px 12px;
|
|
1572
|
+
font-size: var(--text-xs);
|
|
1573
|
+
font-weight: 600;
|
|
1574
|
+
color: var(--ink);
|
|
1575
|
+
background: #fff;
|
|
1576
|
+
cursor: pointer;
|
|
1577
|
+
transition: background 0.15s ease, border-color 0.15s ease;
|
|
1578
|
+
}
|
|
1579
|
+
.guide-video-toggle:hover {
|
|
1580
|
+
background: #f7f7f9;
|
|
1581
|
+
border-color: #d8d8de;
|
|
1582
|
+
}
|
|
1583
|
+
.guide-video-toggle[aria-expanded="true"] {
|
|
1584
|
+
color: var(--accent);
|
|
1585
|
+
border-color: rgba(91, 84, 230, 0.35);
|
|
1586
|
+
background: var(--accent-soft);
|
|
1587
|
+
}
|
|
1588
|
+
.guide-video {
|
|
1589
|
+
margin: 10px 0 0;
|
|
1523
1590
|
border-radius: 14px;
|
|
1524
1591
|
overflow: hidden;
|
|
1525
1592
|
border: 1px solid var(--line);
|
|
@@ -1527,6 +1594,7 @@ function dashboardHtml() {
|
|
|
1527
1594
|
aspect-ratio: 16 / 9;
|
|
1528
1595
|
box-shadow: 0 8px 28px rgba(22, 22, 26, 0.08);
|
|
1529
1596
|
}
|
|
1597
|
+
.guide-video[hidden] { display: none !important; }
|
|
1530
1598
|
.guide-video mux-player {
|
|
1531
1599
|
width: 100%;
|
|
1532
1600
|
height: 100%;
|
|
@@ -1592,6 +1660,37 @@ function dashboardHtml() {
|
|
|
1592
1660
|
border: 1px solid var(--line);
|
|
1593
1661
|
border-radius: 14px;
|
|
1594
1662
|
}
|
|
1663
|
+
.guide-step--accordion {
|
|
1664
|
+
display: block;
|
|
1665
|
+
padding: 0;
|
|
1666
|
+
overflow: hidden;
|
|
1667
|
+
}
|
|
1668
|
+
.guide-step-summary {
|
|
1669
|
+
display: flex;
|
|
1670
|
+
align-items: center;
|
|
1671
|
+
gap: 14px;
|
|
1672
|
+
padding: 14px 16px;
|
|
1673
|
+
cursor: pointer;
|
|
1674
|
+
list-style: none;
|
|
1675
|
+
user-select: none;
|
|
1676
|
+
}
|
|
1677
|
+
.guide-step-summary::-webkit-details-marker { display: none; }
|
|
1678
|
+
.guide-step-summary::marker { content: ''; }
|
|
1679
|
+
.guide-step-summary:hover { background: #f7f7f9; }
|
|
1680
|
+
.guide-step--accordion[open] .guide-step-summary {
|
|
1681
|
+
border-bottom: 1px solid var(--line);
|
|
1682
|
+
}
|
|
1683
|
+
.guide-step-chevron {
|
|
1684
|
+
margin-left: auto;
|
|
1685
|
+
flex-shrink: 0;
|
|
1686
|
+
width: 16px;
|
|
1687
|
+
height: 16px;
|
|
1688
|
+
color: var(--muted);
|
|
1689
|
+
transition: transform 0.15s ease;
|
|
1690
|
+
}
|
|
1691
|
+
.guide-step--accordion[open] .guide-step-chevron {
|
|
1692
|
+
transform: rotate(180deg);
|
|
1693
|
+
}
|
|
1595
1694
|
.guide-step-num {
|
|
1596
1695
|
flex: 0 0 28px;
|
|
1597
1696
|
width: 28px;
|
|
@@ -1606,18 +1705,35 @@ function dashboardHtml() {
|
|
|
1606
1705
|
justify-content: center;
|
|
1607
1706
|
}
|
|
1608
1707
|
.guide-step-body { min-width: 0; }
|
|
1708
|
+
.guide-step--accordion > .guide-step-body {
|
|
1709
|
+
padding: 12px 16px 14px 58px;
|
|
1710
|
+
}
|
|
1609
1711
|
.guide-step-title {
|
|
1610
1712
|
font-size: var(--text-sm);
|
|
1611
1713
|
font-weight: 700;
|
|
1612
1714
|
color: var(--ink);
|
|
1613
1715
|
margin: 0 0 4px;
|
|
1614
1716
|
}
|
|
1717
|
+
.guide-step-summary .guide-step-title { margin: 0; flex: 1; min-width: 0; }
|
|
1615
1718
|
.guide-step-desc {
|
|
1616
1719
|
font-size: var(--text-sm);
|
|
1617
1720
|
color: var(--muted);
|
|
1618
1721
|
line-height: 1.5;
|
|
1619
1722
|
margin: 0;
|
|
1620
1723
|
}
|
|
1724
|
+
.guide-step-desc a,
|
|
1725
|
+
.guide-console-link {
|
|
1726
|
+
color: var(--accent);
|
|
1727
|
+
font-weight: 600;
|
|
1728
|
+
text-decoration: underline;
|
|
1729
|
+
background: none;
|
|
1730
|
+
border: none;
|
|
1731
|
+
padding: 0;
|
|
1732
|
+
font: inherit;
|
|
1733
|
+
cursor: pointer;
|
|
1734
|
+
}
|
|
1735
|
+
.guide-step-desc a:hover,
|
|
1736
|
+
.guide-console-link:hover { opacity: 0.88; }
|
|
1621
1737
|
.guide-step-desc code {
|
|
1622
1738
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
1623
1739
|
font-size: var(--text-2xs);
|
|
@@ -1711,12 +1827,56 @@ function dashboardHtml() {
|
|
|
1711
1827
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
1712
1828
|
font-size: var(--text-xs);
|
|
1713
1829
|
}
|
|
1830
|
+
.guide-prefix-note {
|
|
1831
|
+
margin: 0 0 14px;
|
|
1832
|
+
font-size: var(--text-sm);
|
|
1833
|
+
color: var(--muted);
|
|
1834
|
+
line-height: 1.6;
|
|
1835
|
+
}
|
|
1836
|
+
.guide-prefix-note code.cli-cmd { white-space: nowrap; }
|
|
1714
1837
|
.guide-help-examples {
|
|
1715
1838
|
margin: 10px 0 0;
|
|
1716
1839
|
display: flex;
|
|
1717
1840
|
flex-direction: column;
|
|
1718
1841
|
gap: 8px;
|
|
1719
1842
|
}
|
|
1843
|
+
.guide-cmd-row {
|
|
1844
|
+
display: flex;
|
|
1845
|
+
flex-direction: column;
|
|
1846
|
+
gap: 4px;
|
|
1847
|
+
}
|
|
1848
|
+
.guide-cmd-hosts {
|
|
1849
|
+
font-size: var(--text-2xs);
|
|
1850
|
+
font-weight: 600;
|
|
1851
|
+
color: var(--muted);
|
|
1852
|
+
text-transform: uppercase;
|
|
1853
|
+
letter-spacing: 0.04em;
|
|
1854
|
+
}
|
|
1855
|
+
.guide-classify-list {
|
|
1856
|
+
margin: 10px 0 0;
|
|
1857
|
+
padding: 0;
|
|
1858
|
+
list-style: none;
|
|
1859
|
+
display: flex;
|
|
1860
|
+
flex-direction: column;
|
|
1861
|
+
gap: 8px;
|
|
1862
|
+
}
|
|
1863
|
+
.guide-classify-list li {
|
|
1864
|
+
display: flex;
|
|
1865
|
+
align-items: center;
|
|
1866
|
+
flex-wrap: wrap;
|
|
1867
|
+
gap: 8px;
|
|
1868
|
+
font-size: var(--text-sm);
|
|
1869
|
+
color: var(--muted);
|
|
1870
|
+
line-height: 1.5;
|
|
1871
|
+
}
|
|
1872
|
+
.guide-classify-prompt { color: var(--ink); }
|
|
1873
|
+
.guide-classify-arrow { color: var(--muted); }
|
|
1874
|
+
.guide-classify-note {
|
|
1875
|
+
margin: 10px 0 0;
|
|
1876
|
+
font-size: var(--text-sm);
|
|
1877
|
+
color: var(--muted);
|
|
1878
|
+
line-height: 1.55;
|
|
1879
|
+
}
|
|
1720
1880
|
.guide-help-examples code.cli-cmd {
|
|
1721
1881
|
display: block;
|
|
1722
1882
|
width: fit-content;
|
|
@@ -1776,12 +1936,27 @@ function dashboardHtml() {
|
|
|
1776
1936
|
display: flex;
|
|
1777
1937
|
align-items: center;
|
|
1778
1938
|
justify-content: center;
|
|
1939
|
+
flex-wrap: wrap;
|
|
1779
1940
|
gap: 8px;
|
|
1780
1941
|
font-size: var(--text-2xs);
|
|
1781
1942
|
color: var(--muted);
|
|
1782
1943
|
text-align: center;
|
|
1783
1944
|
line-height: 1.6;
|
|
1784
1945
|
}
|
|
1946
|
+
.dashboard-footer a {
|
|
1947
|
+
display: inline-flex;
|
|
1948
|
+
align-items: center;
|
|
1949
|
+
gap: 5px;
|
|
1950
|
+
color: var(--accent);
|
|
1951
|
+
font-weight: 600;
|
|
1952
|
+
text-decoration: none;
|
|
1953
|
+
}
|
|
1954
|
+
.dashboard-footer a:hover { text-decoration: underline; }
|
|
1955
|
+
.dashboard-footer-github-icon {
|
|
1956
|
+
width: 14px;
|
|
1957
|
+
height: 14px;
|
|
1958
|
+
flex-shrink: 0;
|
|
1959
|
+
}
|
|
1785
1960
|
</style>
|
|
1786
1961
|
<script type="module" src="https://cdn.jsdelivr.net/npm/@mux/mux-player"></script>
|
|
1787
1962
|
</head>
|
|
@@ -1803,14 +1978,26 @@ function dashboardHtml() {
|
|
|
1803
1978
|
<p class="header-tagline">Manage credentials and view RBAC from one panel \u2014 no CLI typing required</p>
|
|
1804
1979
|
</div>
|
|
1805
1980
|
</div>
|
|
1806
|
-
<
|
|
1981
|
+
<div id="header-token-empty" class="header-token-empty" hidden>
|
|
1982
|
+
<p class="header-token-empty-title">Invalid Token Configuration. Please register a Transcodes Member Access Token</p>
|
|
1983
|
+
<p class="header-token-empty-title">To get started, run the following commands:</p>
|
|
1984
|
+
<div class="header-token-empty-cmds">
|
|
1985
|
+
<code>npm install -g @bigstrider/transcodes-cli</code>
|
|
1986
|
+
<code>transcodes install</code>
|
|
1987
|
+
</div>
|
|
1988
|
+
</div>
|
|
1807
1989
|
<div class="header-profile" id="header-profile" hidden>
|
|
1808
1990
|
<div class="header-profile-info">
|
|
1809
1991
|
<div class="header-profile-name" id="header-profile-name"></div>
|
|
1810
1992
|
<div class="header-profile-meta" id="header-profile-meta"></div>
|
|
1811
1993
|
</div>
|
|
1812
1994
|
<div class="header-profile-actions">
|
|
1813
|
-
<button type="button" class="btn-manage-auth" id="manage-auth-btn"
|
|
1995
|
+
<button type="button" class="btn-manage-auth" id="manage-auth-btn" data-console-open aria-label="Open Transcodes console">
|
|
1996
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
|
1997
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M7.864 4.243A7.5 7.5 0 0 1 19.5 10.5c0 2.92-.556 5.709-1.568 8.268M5.742 6.364A7.465 7.465 0 0 0 4.5 10.5a7.464 7.464 0 0 1-1.15 3.993m1.989 3.559A11.209 11.209 0 0 0 8.25 10.5a3.75 3.75 0 1 1 7.5 0c0 .527-.021 1.049-.064 1.565M12 10.5a14.94 14.94 0 0 1-3.6 9.75m6.633-4.596a18.666 18.666 0 0 1-2.485 5.33" />
|
|
1998
|
+
</svg>
|
|
1999
|
+
Console
|
|
2000
|
+
</button>
|
|
1814
2001
|
<p class="header-profile-cli-hint"><code>transcodes console</code></p>
|
|
1815
2002
|
</div>
|
|
1816
2003
|
</div>
|
|
@@ -1824,108 +2011,147 @@ function dashboardHtml() {
|
|
|
1824
2011
|
|
|
1825
2012
|
<div class="panel active" id="panel-guideline">
|
|
1826
2013
|
<p class="section-title">Getting Started</p>
|
|
1827
|
-
<p class="section-sub">New to Transcodes? Watch the
|
|
1828
|
-
<div class="guide-
|
|
1829
|
-
<
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
2014
|
+
<p class="section-sub">Transcodes CLI Dashboard,Set it up visually \u2014 no terminal required. New to Transcodes? Watch the video.</p>
|
|
2015
|
+
<div class="guide-video-wrap">
|
|
2016
|
+
<button type="button" class="guide-video-toggle" id="guide-video-toggle" aria-expanded="false" aria-controls="guide-video">
|
|
2017
|
+
Watch Intro Video
|
|
2018
|
+
</button>
|
|
2019
|
+
<div class="guide-video" id="guide-video" hidden>
|
|
2020
|
+
<mux-player
|
|
2021
|
+
playback-id="${GUIDELINE_MUX_PLAYBACK_ID}"
|
|
2022
|
+
stream-type="on-demand"
|
|
2023
|
+
accent-color="#5b54e6"
|
|
2024
|
+
primary-color="#ffffff"
|
|
2025
|
+
metadata-video-title="Transcodes CLI onboarding"
|
|
2026
|
+
></mux-player>
|
|
1833
2027
|
</div>
|
|
1834
2028
|
</div>
|
|
1835
|
-
<
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
stream-type="on-demand"
|
|
1839
|
-
accent-color="#5b54e6"
|
|
1840
|
-
primary-color="#ffffff"
|
|
1841
|
-
metadata-video-title="Transcodes CLI onboarding"
|
|
1842
|
-
></mux-player>
|
|
1843
|
-
</div>
|
|
1844
|
-
<p class="list-label">Quick setup</p>
|
|
2029
|
+
<p class="list-label">Quick Demo</p>
|
|
2030
|
+
<p class="guide-prefix-note">Try the quick demo to see how Transcodes works</p>
|
|
2031
|
+
<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>
|
|
1845
2032
|
<div class="guide-groups">
|
|
1846
|
-
<section class="guide-group guide-group--
|
|
1847
|
-
<a class="guide-group-label" href="https://app.transcodes.io/" target="_blank" rel="noopener noreferrer">Transcodes Console<span class="guide-group-link-icon" aria-hidden="true"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></span></a>
|
|
2033
|
+
<section class="guide-group guide-group--panel">
|
|
1848
2034
|
<ol class="guide-steps">
|
|
1849
2035
|
<li class="guide-step">
|
|
1850
2036
|
<span class="guide-step-num">1</span>
|
|
1851
2037
|
<div class="guide-step-body">
|
|
1852
|
-
<p class="guide-step-title">
|
|
1853
|
-
<p class="guide-step-desc">
|
|
2038
|
+
<p class="guide-step-title">Open your CLI or desktop app (Claude, Cursor, Antigravity, ChatGPT)</p>
|
|
2039
|
+
<p class="guide-step-desc">If it's already running, restart it so the latest plugin and token are loaded</p>
|
|
1854
2040
|
</div>
|
|
1855
2041
|
</li>
|
|
1856
2042
|
<li class="guide-step">
|
|
1857
2043
|
<span class="guide-step-num">2</span>
|
|
1858
2044
|
<div class="guide-step-body">
|
|
1859
|
-
<p class="guide-step-title">
|
|
1860
|
-
<p class="guide-step-desc">
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
<span class="guide-step-num">4</span>
|
|
1872
|
-
<div class="guide-step-body">
|
|
1873
|
-
<p class="guide-step-title">Issue an access token</p>
|
|
1874
|
-
<p class="guide-step-desc">Open the member detail page and issue a Member Access Token (MAT) for the agent</p>
|
|
1875
|
-
</div>
|
|
1876
|
-
</li>
|
|
1877
|
-
<li class="guide-step">
|
|
1878
|
-
<span class="guide-step-num">5</span>
|
|
1879
|
-
<div class="guide-step-body">
|
|
1880
|
-
<p class="guide-step-title">Configure RBAC</p>
|
|
1881
|
-
<p class="guide-step-desc">In Role Management, add resources (key, name, description), create roles, and set the permission matrix \u2014 0 deny \xB7 1 allow \xB7 2 step-up MFA</p>
|
|
2045
|
+
<p class="guide-step-title">Trigger a step-up test</p>
|
|
2046
|
+
<p class="guide-step-desc">Ask the agent to run a protected action \u2014 it should prompt for step-up authentication</p>
|
|
2047
|
+
<div class="guide-help-examples">
|
|
2048
|
+
<div class="guide-cmd-row">
|
|
2049
|
+
<span class="guide-cmd-hosts">Claude \xB7 Cursor \xB7 Antigravity</span>
|
|
2050
|
+
<code class="cli-cmd">/transcodes open step-up authentication for testing</code>
|
|
2051
|
+
</div>
|
|
2052
|
+
<div class="guide-cmd-row">
|
|
2053
|
+
<span class="guide-cmd-hosts">ChatGPT (Codex)</span>
|
|
2054
|
+
<code class="cli-cmd">$transcodes open step-up authentication for testing</code>
|
|
2055
|
+
</div>
|
|
2056
|
+
</div>
|
|
1882
2057
|
</div>
|
|
1883
2058
|
</li>
|
|
1884
2059
|
</ol>
|
|
1885
2060
|
</section>
|
|
2061
|
+
</div>
|
|
1886
2062
|
|
|
2063
|
+
<p class="list-label">Steps</p>
|
|
2064
|
+
<div class="guide-groups">
|
|
1887
2065
|
<section class="guide-group guide-group--panel">
|
|
1888
|
-
<p class="guide-group-label">This CLI panel</p>
|
|
1889
2066
|
<ol class="guide-steps">
|
|
1890
|
-
<li
|
|
1891
|
-
<
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
2067
|
+
<li>
|
|
2068
|
+
<details class="guide-step guide-step--accordion">
|
|
2069
|
+
<summary class="guide-step-summary">
|
|
2070
|
+
<span class="guide-step-num">1</span>
|
|
2071
|
+
<span class="guide-step-title">Configure RBAC in the Transcodes app</span>
|
|
2072
|
+
<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>
|
|
2073
|
+
</summary>
|
|
2074
|
+
<div class="guide-step-body">
|
|
2075
|
+
<p class="guide-step-desc">Set up resources, actions, and roles in the <a href="${APP_ORG_URL}" data-app-tab="rbac" target="_blank" rel="noopener noreferrer">Transcodes app</a>. You can review the resulting RBAC permissions read-only in this CLI panel's RBAC tab</p>
|
|
2076
|
+
</div>
|
|
2077
|
+
</details>
|
|
1896
2078
|
</li>
|
|
1897
|
-
<li
|
|
1898
|
-
<
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
2079
|
+
<li>
|
|
2080
|
+
<details class="guide-step guide-step--accordion">
|
|
2081
|
+
<summary class="guide-step-summary">
|
|
2082
|
+
<span class="guide-step-num">2</span>
|
|
2083
|
+
<span class="guide-step-title">Register a passkey or biometrics</span>
|
|
2084
|
+
<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>
|
|
2085
|
+
</summary>
|
|
2086
|
+
<div class="guide-step-body">
|
|
2087
|
+
<p class="guide-step-desc">Click the <button type="button" class="guide-console-link" data-console-open>Console</button> button (or run <code class="cli-cmd">transcodes console</code> in your terminal), sign in, then register a passkey or biometrics \u2014 these are used for step-up authentication</p>
|
|
2088
|
+
</div>
|
|
2089
|
+
</details>
|
|
1903
2090
|
</li>
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
<p class="guide-step-opensource">
|
|
1916
|
-
<a class="guide-opensource-link" href="${TRANSCODES_GUARD_REPO_URL}" target="_blank" rel="noopener noreferrer" aria-label="transcodes-guard on GitHub">
|
|
1917
|
-
<svg class="guide-opensource-icon" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z"/></svg>
|
|
1918
|
-
We support Open Source, <span class="guide-opensource-accent">More Info</span>
|
|
1919
|
-
</a>
|
|
1920
|
-
</p>
|
|
1921
|
-
</div>
|
|
2091
|
+
<li>
|
|
2092
|
+
<details class="guide-step guide-step--accordion">
|
|
2093
|
+
<summary class="guide-step-summary">
|
|
2094
|
+
<span class="guide-step-num">3</span>
|
|
2095
|
+
<span class="guide-step-title">Open your CLI or desktop app (Claude, Cursor, Antigravity, ChatGPT)</span>
|
|
2096
|
+
<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>
|
|
2097
|
+
</summary>
|
|
2098
|
+
<div class="guide-step-body">
|
|
2099
|
+
<p class="guide-step-desc">If it's already running, restart it so the latest plugin and token are loaded</p>
|
|
2100
|
+
</div>
|
|
2101
|
+
</details>
|
|
1922
2102
|
</li>
|
|
1923
|
-
<li
|
|
1924
|
-
<
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
2103
|
+
<li>
|
|
2104
|
+
<details class="guide-step guide-step--accordion">
|
|
2105
|
+
<summary class="guide-step-summary">
|
|
2106
|
+
<span class="guide-step-num">4</span>
|
|
2107
|
+
<span class="guide-step-title">Run an action from the agent</span>
|
|
2108
|
+
<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>
|
|
2109
|
+
</summary>
|
|
2110
|
+
<div class="guide-step-body">
|
|
2111
|
+
<p class="guide-step-desc">Try an action in your CLI or desktop app. Transcodes classifies each prompt into a <code>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>
|
|
2112
|
+
<ul class="guide-classify-list">
|
|
2113
|
+
<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></li>
|
|
2114
|
+
<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>
|
|
2115
|
+
<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>
|
|
2116
|
+
</ul>
|
|
2117
|
+
<p class="guide-classify-note">No matching resource? It falls back to <code class="cli-cmd">system</code> automatically \u2014 e.g. if there's no <code>google</code> resource, "Create a Google Calendar event" becomes <code class="cli-cmd">system:create</code>.</p>
|
|
2118
|
+
</div>
|
|
2119
|
+
</details>
|
|
2120
|
+
</li>
|
|
2121
|
+
<li>
|
|
2122
|
+
<details class="guide-step guide-step--accordion">
|
|
2123
|
+
<summary class="guide-step-summary">
|
|
2124
|
+
<span class="guide-step-num">5</span>
|
|
2125
|
+
<span class="guide-step-title">Pull the audit log report</span>
|
|
2126
|
+
<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>
|
|
2127
|
+
</summary>
|
|
2128
|
+
<div class="guide-step-body">
|
|
2129
|
+
<p class="guide-step-desc">Ask the agent for a member audit / security log report \u2014 Transcodes maps the prompt to the right resource and enforces RBAC</p>
|
|
2130
|
+
<p class="guide-step-desc">Every action is logged \u2014 whether it was 0 deny, 1 allow, or 2 step-up MFA.</p>
|
|
2131
|
+
<div class="guide-help-examples">
|
|
2132
|
+
<div class="guide-cmd-row">
|
|
2133
|
+
<span class="guide-cmd-hosts">Claude \xB7 Cursor \xB7 Antigravity</span>
|
|
2134
|
+
<code class="cli-cmd">/transcodes show audit logs report</code>
|
|
2135
|
+
</div>
|
|
2136
|
+
<div class="guide-cmd-row">
|
|
2137
|
+
<span class="guide-cmd-hosts">ChatGPT (Codex)</span>
|
|
2138
|
+
<code class="cli-cmd">$transcodes show audit logs report</code>
|
|
2139
|
+
</div>
|
|
2140
|
+
</div>
|
|
2141
|
+
</div>
|
|
2142
|
+
</details>
|
|
2143
|
+
</li>
|
|
2144
|
+
<li>
|
|
2145
|
+
<details class="guide-step guide-step--accordion">
|
|
2146
|
+
<summary class="guide-step-summary">
|
|
2147
|
+
<span class="guide-step-num">6</span>
|
|
2148
|
+
<span class="guide-step-title">Receive step-up links and notifications on Slack or Discord</span>
|
|
2149
|
+
<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>
|
|
2150
|
+
</summary>
|
|
2151
|
+
<div class="guide-step-body">
|
|
2152
|
+
<p class="guide-step-desc">Want step-up links delivered to Slack or Discord? Go to <a href="${APP_ORG_URL}" data-app-tab="settings" target="_blank" rel="noopener noreferrer">Transcodes Settings</a> and set up a webhook. (More channels coming soon.)</p>
|
|
2153
|
+
</div>
|
|
2154
|
+
</details>
|
|
1929
2155
|
</li>
|
|
1930
2156
|
</ol>
|
|
1931
2157
|
</section>
|
|
@@ -1977,7 +2203,7 @@ function dashboardHtml() {
|
|
|
1977
2203
|
</div>
|
|
1978
2204
|
|
|
1979
2205
|
<div class="panel" id="panel-rbac">
|
|
1980
|
-
<p id="rbac-token-warning" class="rbac-token-warning" hidden>Save a token
|
|
2206
|
+
<p id="rbac-token-warning" class="rbac-token-warning" hidden>Save a Transcodes token(MAT) first</p>
|
|
1981
2207
|
<div class="guide-help">
|
|
1982
2208
|
<p class="guide-help-heading">How it works</p>
|
|
1983
2209
|
<p class="guide-help-line">Transcodes AI interprets the prompt and maps it to a resource / action pair. Based on the member's role, it applies deny / allow / step-up from the permission matrix. Resources are matched by name and description \u2014 if nothing matches, it falls back to <code>system</code>. When integrating with your own back office, you can pass <code>resource</code> and <code>action</code> as params to control permissions directly.</p>
|
|
@@ -2002,7 +2228,7 @@ function dashboardHtml() {
|
|
|
2002
2228
|
<button type="button" class="tab" data-rbac="roles" role="tab">Roles</button>
|
|
2003
2229
|
</div>
|
|
2004
2230
|
<div class="rbac-pane active" id="rbac-pane-resources">
|
|
2005
|
-
<p class="section-sub">Configure resources, roles, and permissions
|
|
2231
|
+
<p class="section-sub">Configure resources, roles, and permissions. <a href="${APP_ORG_URL}" data-app-tab="rbac" target="_blank" rel="noopener noreferrer">Edit RBAC Permissions</a></p>
|
|
2006
2232
|
<div class="rbac-table-wrap">
|
|
2007
2233
|
<table class="rbac-table" id="resources-table">
|
|
2008
2234
|
<thead><tr><th>Resource</th><th>Description</th></tr></thead>
|
|
@@ -2011,7 +2237,7 @@ function dashboardHtml() {
|
|
|
2011
2237
|
</div>
|
|
2012
2238
|
</div>
|
|
2013
2239
|
<div class="rbac-pane" id="rbac-pane-roles">
|
|
2014
|
-
<p class="section-sub">Select a role to view its permission matrix</p>
|
|
2240
|
+
<p class="section-sub">Select a role to view its permission matrix \xB7 <a href="${APP_ORG_URL}" data-app-tab="rbac" target="_blank" rel="noopener noreferrer">Edit RBAC Permissions</a></p>
|
|
2015
2241
|
<div class="role-picker" id="role-picker"></div>
|
|
2016
2242
|
<div id="matrix-wrap" hidden>
|
|
2017
2243
|
<p class="list-label" id="matrix-role-label"></p>
|
|
@@ -2035,7 +2261,14 @@ function dashboardHtml() {
|
|
|
2035
2261
|
</div>
|
|
2036
2262
|
</div>
|
|
2037
2263
|
|
|
2038
|
-
<p class="dashboard-footer">
|
|
2264
|
+
<p class="dashboard-footer">
|
|
2265
|
+
Ver ${CLI_VERSION} <code class="cli-cmd">transcodes version</code>
|
|
2266
|
+
<span aria-hidden="true">\xB7</span>
|
|
2267
|
+
<a href="${TRANSCODES_GUARD_REPO_URL}" target="_blank" rel="noopener noreferrer">
|
|
2268
|
+
<svg class="dashboard-footer-github-icon" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z"/></svg>
|
|
2269
|
+
GitHub
|
|
2270
|
+
</a>
|
|
2271
|
+
</p>
|
|
2039
2272
|
<script>
|
|
2040
2273
|
const tokenEl = document.getElementById("token");
|
|
2041
2274
|
const labelEl = document.getElementById("label");
|
|
@@ -2047,7 +2280,7 @@ function dashboardHtml() {
|
|
|
2047
2280
|
const headerProfileEl = document.getElementById("header-profile");
|
|
2048
2281
|
const headerProfileNameEl = document.getElementById("header-profile-name");
|
|
2049
2282
|
const headerProfileMetaEl = document.getElementById("header-profile-meta");
|
|
2050
|
-
const
|
|
2283
|
+
const APP_ORG_URL = ${JSON.stringify(APP_ORG_URL)};
|
|
2051
2284
|
|
|
2052
2285
|
let lastStatus = { tokens: [], activeMember: null };
|
|
2053
2286
|
let editingId = null;
|
|
@@ -2056,14 +2289,58 @@ function dashboardHtml() {
|
|
|
2056
2289
|
return Array.isArray(s.tokens) && s.tokens.length > 0;
|
|
2057
2290
|
}
|
|
2058
2291
|
|
|
2292
|
+
function updateAppDeepLinks(s) {
|
|
2293
|
+
const pid = s && s.activeMember && s.activeMember.projectId;
|
|
2294
|
+
document.querySelectorAll("[data-app-tab]").forEach((a) => {
|
|
2295
|
+
const tab = a.getAttribute("data-app-tab");
|
|
2296
|
+
if (pid && tab) {
|
|
2297
|
+
a.href = APP_ORG_URL + "?pid=" + encodeURIComponent(pid) + "&tab=" + encodeURIComponent(tab);
|
|
2298
|
+
} else {
|
|
2299
|
+
a.href = APP_ORG_URL;
|
|
2300
|
+
}
|
|
2301
|
+
});
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2059
2304
|
function updateTokenEmptyState() {
|
|
2060
2305
|
const empty = !hasSavedTokens(lastStatus);
|
|
2061
2306
|
headerTokenEmptyEl.hidden = !empty;
|
|
2062
2307
|
if (empty) {
|
|
2063
2308
|
headerProfileEl.hidden = true;
|
|
2309
|
+
updateAppDeepLinks(lastStatus);
|
|
2064
2310
|
return;
|
|
2065
2311
|
}
|
|
2066
2312
|
renderHeaderProfile(lastStatus);
|
|
2313
|
+
updateAppDeepLinks(lastStatus);
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
async function openConsole() {
|
|
2317
|
+
const consoleBtns = document.querySelectorAll("[data-console-open]");
|
|
2318
|
+
consoleBtns.forEach((btn) => { btn.disabled = true; });
|
|
2319
|
+
try {
|
|
2320
|
+
const res = await fetch("/api/console/open", { method: "POST" });
|
|
2321
|
+
const data = await res.json();
|
|
2322
|
+
if (!res.ok) throw new Error(data.error || "Could not open auth settings");
|
|
2323
|
+
if (data.browserUrl) window.open(data.browserUrl, "_blank", "noopener,noreferrer");
|
|
2324
|
+
showToast("Opening auth settings in your browser", "success");
|
|
2325
|
+
} catch (e) {
|
|
2326
|
+
showToast(e.message || "Failed to open auth settings", "error");
|
|
2327
|
+
} finally {
|
|
2328
|
+
consoleBtns.forEach((btn) => { btn.disabled = false; });
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
const guideVideoToggle = document.getElementById("guide-video-toggle");
|
|
2333
|
+
const guideVideo = document.getElementById("guide-video");
|
|
2334
|
+
if (guideVideoToggle && guideVideo) {
|
|
2335
|
+
guideVideoToggle.addEventListener("click", () => {
|
|
2336
|
+
const open = guideVideoToggle.getAttribute("aria-expanded") === "true";
|
|
2337
|
+
const next = !open;
|
|
2338
|
+
guideVideoToggle.setAttribute("aria-expanded", String(next));
|
|
2339
|
+
guideVideo.hidden = !next;
|
|
2340
|
+
guideVideoToggle.textContent = next
|
|
2341
|
+
? "Hide Intro Video"
|
|
2342
|
+
: "Watch Intro Video";
|
|
2343
|
+
});
|
|
2067
2344
|
}
|
|
2068
2345
|
|
|
2069
2346
|
document.querySelectorAll(".tab[data-tab]").forEach((tab) => {
|
|
@@ -2174,19 +2451,8 @@ function dashboardHtml() {
|
|
|
2174
2451
|
renderTokens(lastStatus);
|
|
2175
2452
|
}
|
|
2176
2453
|
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
try {
|
|
2180
|
-
const res = await fetch("/api/console/open", { method: "POST" });
|
|
2181
|
-
const data = await res.json();
|
|
2182
|
-
if (!res.ok) throw new Error(data.error || "Could not open auth settings");
|
|
2183
|
-
if (data.browserUrl) window.open(data.browserUrl, "_blank", "noopener,noreferrer");
|
|
2184
|
-
showToast("Opening auth settings in your browser", "success");
|
|
2185
|
-
} catch (e) {
|
|
2186
|
-
showToast(e.message || "Failed to open auth settings", "error");
|
|
2187
|
-
} finally {
|
|
2188
|
-
manageAuthBtn.disabled = false;
|
|
2189
|
-
}
|
|
2454
|
+
document.querySelectorAll("[data-console-open]").forEach((btn) => {
|
|
2455
|
+
btn.addEventListener("click", () => { openConsole(); });
|
|
2190
2456
|
});
|
|
2191
2457
|
|
|
2192
2458
|
async function saveLabel(id) {
|
|
@@ -2640,14 +2906,68 @@ function waitForDashboardShutdown(server) {
|
|
|
2640
2906
|
process.on("SIGTERM", onSignal);
|
|
2641
2907
|
});
|
|
2642
2908
|
}
|
|
2643
|
-
async function
|
|
2644
|
-
const
|
|
2645
|
-
|
|
2909
|
+
async function pidsListeningOnPorts(from, to) {
|
|
2910
|
+
const pids = /* @__PURE__ */ new Set();
|
|
2911
|
+
if (process.platform === "win32") {
|
|
2912
|
+
for (let port = from; port <= to; port++) {
|
|
2913
|
+
try {
|
|
2914
|
+
const { stdout } = await execFileAsync(
|
|
2915
|
+
"powershell",
|
|
2916
|
+
[
|
|
2917
|
+
"-NoProfile",
|
|
2918
|
+
"-Command",
|
|
2919
|
+
`(Get-NetTCPConnection -LocalPort ${port} -State Listen -ErrorAction SilentlyContinue).OwningProcess`
|
|
2920
|
+
],
|
|
2921
|
+
{ timeout: 5e3 }
|
|
2922
|
+
);
|
|
2923
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
2924
|
+
const pid = Number(line.trim());
|
|
2925
|
+
if (Number.isInteger(pid) && pid > 0) pids.add(pid);
|
|
2926
|
+
}
|
|
2927
|
+
} catch {
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
return [...pids];
|
|
2931
|
+
}
|
|
2932
|
+
for (let port = from; port <= to; port++) {
|
|
2933
|
+
try {
|
|
2934
|
+
const { stdout } = await execFileAsync(
|
|
2935
|
+
"lsof",
|
|
2936
|
+
["-ti", `tcp:${port}`, "-sTCP:LISTEN"],
|
|
2937
|
+
{ timeout: 5e3 }
|
|
2938
|
+
);
|
|
2939
|
+
for (const token of stdout.trim().split(/\s+/)) {
|
|
2940
|
+
const pid = Number(token);
|
|
2941
|
+
if (Number.isInteger(pid) && pid > 0) pids.add(pid);
|
|
2942
|
+
}
|
|
2943
|
+
} catch {
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
return [...pids];
|
|
2947
|
+
}
|
|
2948
|
+
async function freePortRange(from, to) {
|
|
2949
|
+
process.stdout.write(`Freeing ports ${from}\u2013${to} \u2026
|
|
2950
|
+
`);
|
|
2951
|
+
const pids = await pidsListeningOnPorts(from, to);
|
|
2952
|
+
if (pids.length === 0) {
|
|
2953
|
+
process.stdout.write(" (no listeners found)\n");
|
|
2954
|
+
return;
|
|
2955
|
+
}
|
|
2956
|
+
for (const pid of pids) {
|
|
2957
|
+
try {
|
|
2958
|
+
process.kill(pid, "SIGKILL");
|
|
2959
|
+
process.stdout.write(` killed pid ${pid}
|
|
2960
|
+
`);
|
|
2961
|
+
} catch {
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
async function tryBindPortRange(preferred) {
|
|
2646
2966
|
let port = preferred;
|
|
2647
|
-
for (let attempt = 0; attempt <
|
|
2967
|
+
for (let attempt = 0; attempt < PORT_ATTEMPTS; attempt++) {
|
|
2648
2968
|
try {
|
|
2649
|
-
server = await listen(port);
|
|
2650
|
-
|
|
2969
|
+
const server = await listen(port);
|
|
2970
|
+
return { server, port };
|
|
2651
2971
|
} catch (err) {
|
|
2652
2972
|
const code = err.code;
|
|
2653
2973
|
if (code === "EADDRINUSE") {
|
|
@@ -2657,8 +2977,17 @@ async function runDashboard(options) {
|
|
|
2657
2977
|
throw err;
|
|
2658
2978
|
}
|
|
2659
2979
|
}
|
|
2660
|
-
|
|
2661
|
-
|
|
2980
|
+
return null;
|
|
2981
|
+
}
|
|
2982
|
+
async function runDashboard(options) {
|
|
2983
|
+
const preferred = options.port ?? DEFAULT_PORT;
|
|
2984
|
+
const last = preferred + PORT_ATTEMPTS - 1;
|
|
2985
|
+
let bound = await tryBindPortRange(preferred);
|
|
2986
|
+
if (!bound) {
|
|
2987
|
+
await freePortRange(preferred, last);
|
|
2988
|
+
bound = await tryBindPortRange(preferred);
|
|
2989
|
+
}
|
|
2990
|
+
if (!bound) {
|
|
2662
2991
|
throw new Error(
|
|
2663
2992
|
`could not find a free port in ${preferred}-${last} (all in use).
|
|
2664
2993
|
A previous dashboard is probably still running.
|
|
@@ -2669,6 +2998,7 @@ async function runDashboard(options) {
|
|
|
2669
2998
|
Or choose another port: transcodes --port <N>`
|
|
2670
2999
|
);
|
|
2671
3000
|
}
|
|
3001
|
+
const { server, port } = bound;
|
|
2672
3002
|
const url = `http://${HOST}:${port}/`;
|
|
2673
3003
|
process.stdout.write(
|
|
2674
3004
|
`Transcodes dashboard running at ${url}
|
|
@@ -2682,6 +3012,882 @@ async function runDashboard(options) {
|
|
|
2682
3012
|
await waitForDashboardShutdown(server);
|
|
2683
3013
|
}
|
|
2684
3014
|
|
|
3015
|
+
// src/install.ts
|
|
3016
|
+
import { spawn as spawn3 } from "child_process";
|
|
3017
|
+
import fs from "fs";
|
|
3018
|
+
import os2 from "os";
|
|
3019
|
+
import path2 from "path";
|
|
3020
|
+
import { createInterface, moveCursor } from "readline";
|
|
3021
|
+
var REPO_SLUG = "transcodings/transcodes-guard";
|
|
3022
|
+
var REPO_GIT_URL = "https://github.com/transcodings/transcodes-guard.git";
|
|
3023
|
+
var MARKETPLACE = "bigstrider";
|
|
3024
|
+
var PLUGIN_NAME = "transcodes-guard";
|
|
3025
|
+
var APP_CONSOLE_URL = "https://app.transcodes.io";
|
|
3026
|
+
var MIN_NODE_MAJOR = 20;
|
|
3027
|
+
var PLATFORMS = [
|
|
3028
|
+
{ id: "claude", label: "Claude Code" },
|
|
3029
|
+
{ id: "codex", label: "ChatGPT (Codex)" },
|
|
3030
|
+
{
|
|
3031
|
+
id: "cursor",
|
|
3032
|
+
label: "Cursor",
|
|
3033
|
+
installerRel: "plugins/cursor/install.mjs"
|
|
3034
|
+
},
|
|
3035
|
+
{
|
|
3036
|
+
id: "antigravity",
|
|
3037
|
+
label: "Antigravity",
|
|
3038
|
+
installerRel: "plugins/antigravity/install.mjs"
|
|
3039
|
+
}
|
|
3040
|
+
];
|
|
3041
|
+
function log(line = "") {
|
|
3042
|
+
process.stdout.write(`${line}
|
|
3043
|
+
`);
|
|
3044
|
+
}
|
|
3045
|
+
function clearScreen() {
|
|
3046
|
+
process.stdout.write("\x1B[2J\x1B[H");
|
|
3047
|
+
}
|
|
3048
|
+
function isTty() {
|
|
3049
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
3050
|
+
}
|
|
3051
|
+
function openUrl(url) {
|
|
3052
|
+
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
3053
|
+
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
3054
|
+
try {
|
|
3055
|
+
const child = spawn3(opener, args, { stdio: "ignore", detached: true });
|
|
3056
|
+
child.on("error", () => {
|
|
3057
|
+
});
|
|
3058
|
+
child.unref();
|
|
3059
|
+
} catch {
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
function refreshPathHints() {
|
|
3063
|
+
const home = os2.homedir();
|
|
3064
|
+
const extras = [
|
|
3065
|
+
path2.join(home, ".local", "bin"),
|
|
3066
|
+
path2.join(home, ".claude", "bin"),
|
|
3067
|
+
path2.join(home, ".codex", "bin"),
|
|
3068
|
+
path2.join(home, ".cursor", "bin"),
|
|
3069
|
+
path2.join(home, ".gemini", "bin"),
|
|
3070
|
+
path2.join(home, "bin"),
|
|
3071
|
+
"/usr/local/bin",
|
|
3072
|
+
"/opt/homebrew/bin"
|
|
3073
|
+
];
|
|
3074
|
+
const current = process.env.PATH ?? "";
|
|
3075
|
+
const parts = current.split(path2.delimiter).filter(Boolean);
|
|
3076
|
+
const merged = [...extras.filter((p) => !parts.includes(p)), ...parts];
|
|
3077
|
+
process.env.PATH = merged.join(path2.delimiter);
|
|
3078
|
+
}
|
|
3079
|
+
function commandExists(cmd) {
|
|
3080
|
+
return new Promise((resolve) => {
|
|
3081
|
+
const child = process.platform === "win32" ? spawn3("where", [cmd], { stdio: "ignore" }) : spawn3("sh", ["-c", `command -v ${JSON.stringify(cmd)}`], {
|
|
3082
|
+
stdio: "ignore"
|
|
3083
|
+
});
|
|
3084
|
+
child.on("error", () => resolve(false));
|
|
3085
|
+
child.on("close", (code) => resolve(code === 0));
|
|
3086
|
+
});
|
|
3087
|
+
}
|
|
3088
|
+
async function anyCommandExists(cmds) {
|
|
3089
|
+
for (const cmd of cmds) {
|
|
3090
|
+
if (await commandExists(cmd)) return cmd;
|
|
3091
|
+
}
|
|
3092
|
+
return null;
|
|
3093
|
+
}
|
|
3094
|
+
function run(cmd, args) {
|
|
3095
|
+
return new Promise((resolve) => {
|
|
3096
|
+
log(`
|
|
3097
|
+
$ ${cmd} ${args.join(" ")}`);
|
|
3098
|
+
const child = spawn3(cmd, args, { stdio: "inherit", env: process.env });
|
|
3099
|
+
child.on("error", (err) => {
|
|
3100
|
+
log(` (failed to start: ${err.message})`);
|
|
3101
|
+
resolve(1);
|
|
3102
|
+
});
|
|
3103
|
+
child.on("close", (code) => resolve(code ?? 1));
|
|
3104
|
+
});
|
|
3105
|
+
}
|
|
3106
|
+
function runShell(script) {
|
|
3107
|
+
return new Promise((resolve) => {
|
|
3108
|
+
log(`
|
|
3109
|
+
$ ${script}`);
|
|
3110
|
+
const child = spawn3("sh", ["-c", script], {
|
|
3111
|
+
stdio: "inherit",
|
|
3112
|
+
env: process.env
|
|
3113
|
+
});
|
|
3114
|
+
child.on("error", (err) => {
|
|
3115
|
+
log(` (failed to start: ${err.message})`);
|
|
3116
|
+
resolve(1);
|
|
3117
|
+
});
|
|
3118
|
+
child.on("close", (code) => resolve(code ?? 1));
|
|
3119
|
+
});
|
|
3120
|
+
}
|
|
3121
|
+
async function runAll(steps) {
|
|
3122
|
+
for (const step of steps) {
|
|
3123
|
+
const code = await run(step.cmd, step.args);
|
|
3124
|
+
if (code !== 0) return false;
|
|
3125
|
+
}
|
|
3126
|
+
return true;
|
|
3127
|
+
}
|
|
3128
|
+
function nodeMajor(version = process.versions.node) {
|
|
3129
|
+
const major = Number(version.split(".")[0]);
|
|
3130
|
+
return Number.isFinite(major) ? major : 0;
|
|
3131
|
+
}
|
|
3132
|
+
async function ensureNode() {
|
|
3133
|
+
log("\u2500\u2500 Prerequisites \u2500\u2500");
|
|
3134
|
+
const major = nodeMajor();
|
|
3135
|
+
const onPath = await commandExists("node");
|
|
3136
|
+
if (major >= MIN_NODE_MAJOR && onPath) {
|
|
3137
|
+
log(` \u2713 Node.js v${process.versions.node} (>= ${MIN_NODE_MAJOR})`);
|
|
3138
|
+
return true;
|
|
3139
|
+
}
|
|
3140
|
+
if (major >= MIN_NODE_MAJOR && !onPath) {
|
|
3141
|
+
log(
|
|
3142
|
+
` ! Node.js v${process.versions.node} is running this CLI but \`node\` is not on PATH.`
|
|
3143
|
+
);
|
|
3144
|
+
log(" Prepending common bin dirs and continuing\u2026");
|
|
3145
|
+
refreshPathHints();
|
|
3146
|
+
if (await commandExists("node")) {
|
|
3147
|
+
log(" \u2713 Node.js now on PATH");
|
|
3148
|
+
return true;
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
log(
|
|
3152
|
+
major > 0 && major < MIN_NODE_MAJOR ? ` Node.js v${process.versions.node} is below ${MIN_NODE_MAJOR} \u2014 installing LTS\u2026` : " Node.js not found \u2014 installing LTS\u2026"
|
|
3153
|
+
);
|
|
3154
|
+
let installed = false;
|
|
3155
|
+
if (process.platform === "darwin" && await commandExists("brew")) {
|
|
3156
|
+
installed = await run("brew", ["install", "node"]) === 0;
|
|
3157
|
+
}
|
|
3158
|
+
if (!installed && await commandExists("nvm")) {
|
|
3159
|
+
installed = await runShell("nvm install --lts && nvm use --lts") === 0;
|
|
3160
|
+
}
|
|
3161
|
+
if (!installed) {
|
|
3162
|
+
const nvmInstall = [
|
|
3163
|
+
"curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash",
|
|
3164
|
+
'export NVM_DIR="$HOME/.nvm"',
|
|
3165
|
+
'[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"',
|
|
3166
|
+
"nvm install --lts",
|
|
3167
|
+
'nvm alias default "lts/*"'
|
|
3168
|
+
].join(" && ");
|
|
3169
|
+
installed = await runShell(nvmInstall) === 0;
|
|
3170
|
+
}
|
|
3171
|
+
refreshPathHints();
|
|
3172
|
+
const nvmDefaultBin = path2.join(os2.homedir(), ".nvm", "versions", "node");
|
|
3173
|
+
if (fs.existsSync(nvmDefaultBin)) {
|
|
3174
|
+
try {
|
|
3175
|
+
const versions = fs.readdirSync(nvmDefaultBin).filter((v) => v.startsWith("v")).sort().reverse();
|
|
3176
|
+
if (versions[0]) {
|
|
3177
|
+
const bin = path2.join(nvmDefaultBin, versions[0], "bin");
|
|
3178
|
+
process.env.PATH = `${bin}${path2.delimiter}${process.env.PATH ?? ""}`;
|
|
3179
|
+
}
|
|
3180
|
+
} catch {
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
if (await commandExists("node")) {
|
|
3184
|
+
const ver = await new Promise((resolve) => {
|
|
3185
|
+
const child = spawn3("node", ["-v"], {
|
|
3186
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
3187
|
+
});
|
|
3188
|
+
let out = "";
|
|
3189
|
+
child.stdout?.on("data", (c) => {
|
|
3190
|
+
out += c.toString();
|
|
3191
|
+
});
|
|
3192
|
+
child.on("close", () => resolve(out.trim().replace(/^v/, "")));
|
|
3193
|
+
child.on("error", () => resolve(""));
|
|
3194
|
+
});
|
|
3195
|
+
const installedMajor = nodeMajor(ver || "0");
|
|
3196
|
+
if (installedMajor >= MIN_NODE_MAJOR) {
|
|
3197
|
+
log(` \u2713 Node.js v${ver} installed`);
|
|
3198
|
+
return true;
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
log("");
|
|
3202
|
+
log("Could not install Node.js LTS automatically.");
|
|
3203
|
+
log(
|
|
3204
|
+
`Install Node.js ${MIN_NODE_MAJOR}+ from https://nodejs.org then re-run:`
|
|
3205
|
+
);
|
|
3206
|
+
log(" transcodes install");
|
|
3207
|
+
openUrl("https://nodejs.org");
|
|
3208
|
+
return false;
|
|
3209
|
+
}
|
|
3210
|
+
var HOST_CLIS = {
|
|
3211
|
+
claude: {
|
|
3212
|
+
binaries: ["claude"],
|
|
3213
|
+
label: "Claude Code CLI (`claude`)",
|
|
3214
|
+
install: async () => {
|
|
3215
|
+
if (process.platform === "win32") {
|
|
3216
|
+
return await run("npm", ["install", "-g", "@anthropic-ai/claude-code"]) === 0;
|
|
3217
|
+
}
|
|
3218
|
+
const native = await runShell("curl -fsSL https://claude.ai/install.sh | bash") === 0;
|
|
3219
|
+
if (native) return true;
|
|
3220
|
+
return await run("npm", ["install", "-g", "@anthropic-ai/claude-code"]) === 0;
|
|
3221
|
+
}
|
|
3222
|
+
},
|
|
3223
|
+
codex: {
|
|
3224
|
+
binaries: ["codex"],
|
|
3225
|
+
label: "Codex CLI (`codex`)",
|
|
3226
|
+
install: async () => {
|
|
3227
|
+
if (process.platform !== "win32") {
|
|
3228
|
+
const native = await runShell(
|
|
3229
|
+
"curl -fsSL https://chatgpt.com/codex/install.sh | sh"
|
|
3230
|
+
) === 0;
|
|
3231
|
+
if (native) return true;
|
|
3232
|
+
}
|
|
3233
|
+
return await run("npm", ["install", "-g", "@openai/codex"]) === 0;
|
|
3234
|
+
}
|
|
3235
|
+
},
|
|
3236
|
+
cursor: {
|
|
3237
|
+
// Docs: `agent` is primary; `cursor-agent` is the backward-compatible name.
|
|
3238
|
+
binaries: ["cursor-agent", "agent"],
|
|
3239
|
+
label: "Cursor Agent CLI (`cursor-agent` / `agent`)",
|
|
3240
|
+
install: async () => {
|
|
3241
|
+
if (process.platform === "win32") {
|
|
3242
|
+
return await runShell(
|
|
3243
|
+
`powershell -NoProfile -Command "irm 'https://cursor.com/install?win32=true' | iex"`
|
|
3244
|
+
) === 0;
|
|
3245
|
+
}
|
|
3246
|
+
return await runShell("curl https://cursor.com/install -fsS | bash") === 0;
|
|
3247
|
+
}
|
|
3248
|
+
},
|
|
3249
|
+
antigravity: {
|
|
3250
|
+
binaries: ["agy"],
|
|
3251
|
+
label: "Antigravity CLI (`agy`)",
|
|
3252
|
+
install: async () => await runShell(
|
|
3253
|
+
"curl -fsSL https://antigravity.google/cli/install.sh | bash"
|
|
3254
|
+
) === 0
|
|
3255
|
+
}
|
|
3256
|
+
};
|
|
3257
|
+
async function ensureHostCli(id) {
|
|
3258
|
+
const spec = HOST_CLIS[id];
|
|
3259
|
+
refreshPathHints();
|
|
3260
|
+
const found = await anyCommandExists(spec.binaries);
|
|
3261
|
+
if (found) {
|
|
3262
|
+
log(` \u2713 ${spec.label} found (\`${found}\`)`);
|
|
3263
|
+
return true;
|
|
3264
|
+
}
|
|
3265
|
+
log(` ${spec.label} not found \u2014 installing\u2026`);
|
|
3266
|
+
const ok = await spec.install();
|
|
3267
|
+
refreshPathHints();
|
|
3268
|
+
const after = await anyCommandExists(spec.binaries);
|
|
3269
|
+
if (ok && after) {
|
|
3270
|
+
log(` \u2713 ${spec.label} installed (\`${after}\`)`);
|
|
3271
|
+
return true;
|
|
3272
|
+
}
|
|
3273
|
+
log(` \u2717 Failed to install ${spec.label}.`);
|
|
3274
|
+
log(" Install it manually, then re-run `transcodes install`.");
|
|
3275
|
+
return false;
|
|
3276
|
+
}
|
|
3277
|
+
function promptLine(question) {
|
|
3278
|
+
return new Promise((resolve) => {
|
|
3279
|
+
const rl = createInterface({
|
|
3280
|
+
input: process.stdin,
|
|
3281
|
+
output: process.stdout
|
|
3282
|
+
});
|
|
3283
|
+
rl.question(question, (answer) => {
|
|
3284
|
+
rl.close();
|
|
3285
|
+
resolve(answer);
|
|
3286
|
+
});
|
|
3287
|
+
});
|
|
3288
|
+
}
|
|
3289
|
+
function safeExists(p) {
|
|
3290
|
+
try {
|
|
3291
|
+
return fs.existsSync(p);
|
|
3292
|
+
} catch {
|
|
3293
|
+
return false;
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
function dirHasMatch(dir, needles) {
|
|
3297
|
+
try {
|
|
3298
|
+
return fs.readdirSync(dir).some((entry) => needles.some((n) => entry.includes(n)));
|
|
3299
|
+
} catch {
|
|
3300
|
+
return false;
|
|
3301
|
+
}
|
|
3302
|
+
}
|
|
3303
|
+
function fileHas(file, needle) {
|
|
3304
|
+
try {
|
|
3305
|
+
return fs.readFileSync(file, "utf8").includes(needle);
|
|
3306
|
+
} catch {
|
|
3307
|
+
return false;
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
function isPluginInstalled(id) {
|
|
3311
|
+
const home = os2.homedir();
|
|
3312
|
+
switch (id) {
|
|
3313
|
+
case "cursor":
|
|
3314
|
+
return safeExists(
|
|
3315
|
+
path2.join(home, ".cursor", "plugins", "local", "transcodes-guard")
|
|
3316
|
+
);
|
|
3317
|
+
case "antigravity":
|
|
3318
|
+
return safeExists(
|
|
3319
|
+
path2.join(home, ".gemini", "config", "plugins", "transcodes-guard")
|
|
3320
|
+
);
|
|
3321
|
+
case "claude":
|
|
3322
|
+
return dirHasMatch(path2.join(home, ".claude", "plugins"), [
|
|
3323
|
+
"transcodes-guard",
|
|
3324
|
+
"bigstrider"
|
|
3325
|
+
]) || dirHasMatch(path2.join(home, ".claude", "plugins", "marketplaces"), [
|
|
3326
|
+
"bigstrider",
|
|
3327
|
+
"transcodes-guard"
|
|
3328
|
+
]) || fileHas(
|
|
3329
|
+
path2.join(home, ".claude", "settings.json"),
|
|
3330
|
+
"transcodes-guard"
|
|
3331
|
+
) || fileHas(path2.join(home, ".claude.json"), "transcodes-guard");
|
|
3332
|
+
case "codex":
|
|
3333
|
+
return dirHasMatch(path2.join(home, ".codex", "plugins"), [
|
|
3334
|
+
"transcodes-guard",
|
|
3335
|
+
"bigstrider"
|
|
3336
|
+
]) || fileHas(path2.join(home, ".codex", "config.toml"), "transcodes-guard") || fileHas(path2.join(home, ".codex", "config.json"), "transcodes-guard");
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
function renderMenu() {
|
|
3340
|
+
log("");
|
|
3341
|
+
log("Please select platforms that you want to install:");
|
|
3342
|
+
log("Installing a plugin enables both its CLI and desktop app.");
|
|
3343
|
+
log("(Claude only supports the Claude Code tab)");
|
|
3344
|
+
log("");
|
|
3345
|
+
PLATFORMS.forEach((p, i) => {
|
|
3346
|
+
const mark = isPluginInstalled(p.id) ? " [Installed \u2713]" : "";
|
|
3347
|
+
log(` ${i + 1}. ${p.label}${mark}`);
|
|
3348
|
+
});
|
|
3349
|
+
log(` ${PLATFORMS.length + 1}. Next Step \u2192`);
|
|
3350
|
+
log("");
|
|
3351
|
+
log('Enter numbers to install (e.g. 1,2), "all" or Enter to install all.');
|
|
3352
|
+
log(
|
|
3353
|
+
`Type ${PLATFORMS.length + 1} (or "next") for Next Step, "exit"/"q" to quit.`
|
|
3354
|
+
);
|
|
3355
|
+
}
|
|
3356
|
+
async function promptMenu() {
|
|
3357
|
+
const nextNum = String(PLATFORMS.length + 1);
|
|
3358
|
+
for (; ; ) {
|
|
3359
|
+
const answer = (await promptLine("> ")).trim().toLowerCase();
|
|
3360
|
+
if (answer === "exit" || answer === "q") return { kind: "cancel" };
|
|
3361
|
+
if (answer === "next" || answer === "n" || answer === nextNum) {
|
|
3362
|
+
return { kind: "next" };
|
|
3363
|
+
}
|
|
3364
|
+
if (answer === "" || answer === "all" || answer === "a") {
|
|
3365
|
+
return { kind: "install", ids: PLATFORMS.map((p) => p.id) };
|
|
3366
|
+
}
|
|
3367
|
+
const tokens = answer.split(/[\s,]+/).filter(Boolean);
|
|
3368
|
+
const ids = [];
|
|
3369
|
+
let invalid = false;
|
|
3370
|
+
for (const token of tokens) {
|
|
3371
|
+
const num = Number(token);
|
|
3372
|
+
if (!Number.isInteger(num) || num < 1 || num > PLATFORMS.length) {
|
|
3373
|
+
log(
|
|
3374
|
+
` Invalid choice "${token}". Use 1\u2013${PLATFORMS.length}, ${nextNum} (Next Step), all, or exit.`
|
|
3375
|
+
);
|
|
3376
|
+
invalid = true;
|
|
3377
|
+
break;
|
|
3378
|
+
}
|
|
3379
|
+
const pid = PLATFORMS[num - 1].id;
|
|
3380
|
+
if (!ids.includes(pid)) ids.push(pid);
|
|
3381
|
+
}
|
|
3382
|
+
if (invalid) continue;
|
|
3383
|
+
return { kind: "install", ids };
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
function supportsArrowSelect() {
|
|
3387
|
+
return isTty() && typeof process.stdin.setRawMode === "function";
|
|
3388
|
+
}
|
|
3389
|
+
function arrowSelect(checked) {
|
|
3390
|
+
return new Promise((resolve) => {
|
|
3391
|
+
const rowCount = PLATFORMS.length + 1;
|
|
3392
|
+
let cursor = 0;
|
|
3393
|
+
let rendered = 0;
|
|
3394
|
+
const { stdin, stdout } = process;
|
|
3395
|
+
const draw = () => {
|
|
3396
|
+
const lines = [];
|
|
3397
|
+
lines.push("Please select platforms that you want to install:");
|
|
3398
|
+
lines.push("Installing a plugin enables both its CLI and desktop app.");
|
|
3399
|
+
lines.push("(Claude only supports the Claude Code tab)");
|
|
3400
|
+
lines.push("");
|
|
3401
|
+
PLATFORMS.forEach((p, i) => {
|
|
3402
|
+
const pointer = cursor === i ? "\u276F" : " ";
|
|
3403
|
+
const box = checked.has(p.id) ? "\u25C9" : "\u25EF";
|
|
3404
|
+
const mark = isPluginInstalled(p.id) ? " [Installed \u2713]" : "";
|
|
3405
|
+
lines.push(`${pointer} ${box} ${p.label}${mark}`);
|
|
3406
|
+
});
|
|
3407
|
+
const nextPointer = cursor === PLATFORMS.length ? "\u276F" : " ";
|
|
3408
|
+
lines.push(`${nextPointer} Next Step \u2192`);
|
|
3409
|
+
lines.push("");
|
|
3410
|
+
lines.push(
|
|
3411
|
+
"\u2191/\u2193 move \xB7 space select \xB7 a all/none \xB7 enter confirm \xB7 q quit/exit"
|
|
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;
|
|
3418
|
+
};
|
|
3419
|
+
const cleanup = () => {
|
|
3420
|
+
stdout.write("\x1B[?25h");
|
|
3421
|
+
stdin.setRawMode?.(false);
|
|
3422
|
+
stdin.pause();
|
|
3423
|
+
stdin.removeListener("data", onData);
|
|
3424
|
+
};
|
|
3425
|
+
const onData = (buf) => {
|
|
3426
|
+
const key = buf.toString();
|
|
3427
|
+
if (key === "" || key === "q") {
|
|
3428
|
+
cleanup();
|
|
3429
|
+
resolve({ kind: "cancel" });
|
|
3430
|
+
return;
|
|
3431
|
+
}
|
|
3432
|
+
if (key === "\r" || key === "\n") {
|
|
3433
|
+
cleanup();
|
|
3434
|
+
if (cursor === PLATFORMS.length) {
|
|
3435
|
+
resolve({ kind: "next" });
|
|
3436
|
+
} else {
|
|
3437
|
+
resolve({
|
|
3438
|
+
kind: "install",
|
|
3439
|
+
ids: PLATFORMS.filter((p) => checked.has(p.id)).map((p) => p.id)
|
|
3440
|
+
});
|
|
3441
|
+
}
|
|
3442
|
+
return;
|
|
3443
|
+
}
|
|
3444
|
+
if (key === " ") {
|
|
3445
|
+
if (cursor < PLATFORMS.length) {
|
|
3446
|
+
const id = PLATFORMS[cursor].id;
|
|
3447
|
+
if (checked.has(id)) checked.delete(id);
|
|
3448
|
+
else checked.add(id);
|
|
3449
|
+
}
|
|
3450
|
+
} else if (key === "a") {
|
|
3451
|
+
if (PLATFORMS.every((p) => checked.has(p.id))) checked.clear();
|
|
3452
|
+
else for (const p of PLATFORMS) checked.add(p.id);
|
|
3453
|
+
} else if (key === "\x1B[A" || key === "k") {
|
|
3454
|
+
cursor = (cursor - 1 + rowCount) % rowCount;
|
|
3455
|
+
} else if (key === "\x1B[B" || key === "j") {
|
|
3456
|
+
cursor = (cursor + 1) % rowCount;
|
|
3457
|
+
} else {
|
|
3458
|
+
return;
|
|
3459
|
+
}
|
|
3460
|
+
draw();
|
|
3461
|
+
};
|
|
3462
|
+
stdin.setRawMode?.(true);
|
|
3463
|
+
stdin.resume();
|
|
3464
|
+
stdin.setEncoding("utf8");
|
|
3465
|
+
stdin.on("data", onData);
|
|
3466
|
+
stdout.write("\x1B[?25l");
|
|
3467
|
+
draw();
|
|
3468
|
+
});
|
|
3469
|
+
}
|
|
3470
|
+
function arrowChoose(title, options, defaultIndex = 0) {
|
|
3471
|
+
return new Promise((resolve) => {
|
|
3472
|
+
let cursor = Math.max(0, Math.min(defaultIndex, options.length - 1));
|
|
3473
|
+
let rendered = 0;
|
|
3474
|
+
const { stdin, stdout } = process;
|
|
3475
|
+
const draw = () => {
|
|
3476
|
+
const lines = [title];
|
|
3477
|
+
options.forEach((opt, i) => {
|
|
3478
|
+
const pointer = cursor === i ? "\u276F" : " ";
|
|
3479
|
+
const radio = cursor === i ? "\u25C9" : "\u25EF";
|
|
3480
|
+
lines.push(`${pointer} ${radio} ${opt}`);
|
|
3481
|
+
});
|
|
3482
|
+
lines.push("");
|
|
3483
|
+
lines.push("\u2191/\u2193 move \xB7 enter select");
|
|
3484
|
+
if (rendered > 0) moveCursor(stdout, 0, -rendered);
|
|
3485
|
+
stdout.write(`${lines.map((line) => `${line}\x1B[K`).join("\n")}
|
|
3486
|
+
`);
|
|
3487
|
+
rendered = lines.length;
|
|
3488
|
+
};
|
|
3489
|
+
const cleanup = () => {
|
|
3490
|
+
stdout.write("\x1B[?25h");
|
|
3491
|
+
stdin.setRawMode?.(false);
|
|
3492
|
+
stdin.pause();
|
|
3493
|
+
stdin.removeListener("data", onData);
|
|
3494
|
+
};
|
|
3495
|
+
const onData = (buf) => {
|
|
3496
|
+
const key = buf.toString();
|
|
3497
|
+
if (key === "") {
|
|
3498
|
+
cleanup();
|
|
3499
|
+
resolve(-1);
|
|
3500
|
+
return;
|
|
3501
|
+
}
|
|
3502
|
+
if (key === "\r" || key === "\n") {
|
|
3503
|
+
cleanup();
|
|
3504
|
+
resolve(cursor);
|
|
3505
|
+
return;
|
|
3506
|
+
}
|
|
3507
|
+
if (key === "\x1B[A" || key === "k") {
|
|
3508
|
+
cursor = (cursor - 1 + options.length) % options.length;
|
|
3509
|
+
} else if (key === "\x1B[B" || key === "j") {
|
|
3510
|
+
cursor = (cursor + 1) % options.length;
|
|
3511
|
+
} else {
|
|
3512
|
+
return;
|
|
3513
|
+
}
|
|
3514
|
+
draw();
|
|
3515
|
+
};
|
|
3516
|
+
stdin.setRawMode?.(true);
|
|
3517
|
+
stdin.resume();
|
|
3518
|
+
stdin.setEncoding("utf8");
|
|
3519
|
+
stdin.on("data", onData);
|
|
3520
|
+
stdout.write("\x1B[?25l");
|
|
3521
|
+
draw();
|
|
3522
|
+
});
|
|
3523
|
+
}
|
|
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
|
+
async function runInstalls(ids) {
|
|
3543
|
+
const clonedRepoDir = await cloneRepoIfNeeded(ids);
|
|
3544
|
+
const results = /* @__PURE__ */ new Map();
|
|
3545
|
+
try {
|
|
3546
|
+
for (const id of ids) {
|
|
3547
|
+
const label = PLATFORMS.find((p) => p.id === id)?.label ?? id;
|
|
3548
|
+
log(`
|
|
3549
|
+
\u2500\u2500 ${label} \u2500\u2500`);
|
|
3550
|
+
results.set(id, await installPlatform(id, clonedRepoDir));
|
|
3551
|
+
}
|
|
3552
|
+
} finally {
|
|
3553
|
+
if (clonedRepoDir) {
|
|
3554
|
+
fs.rmSync(clonedRepoDir, { recursive: true, force: true });
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
log("\n\u2500\u2500 Install summary \u2500\u2500");
|
|
3558
|
+
for (const id of ids) {
|
|
3559
|
+
const label = PLATFORMS.find((p) => p.id === id)?.label ?? id;
|
|
3560
|
+
log(` ${results.get(id) ? "\u2713" : "\u2717"} ${label}`);
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3563
|
+
async function installPlatform(id, clonedRepoDir) {
|
|
3564
|
+
if (!await ensureHostCli(id)) return false;
|
|
3565
|
+
if (id === "claude") {
|
|
3566
|
+
return runAll([
|
|
3567
|
+
{ cmd: "claude", args: ["plugin", "marketplace", "add", REPO_SLUG] },
|
|
3568
|
+
{
|
|
3569
|
+
cmd: "claude",
|
|
3570
|
+
args: [
|
|
3571
|
+
"plugin",
|
|
3572
|
+
"install",
|
|
3573
|
+
`${PLUGIN_NAME}@${MARKETPLACE}`,
|
|
3574
|
+
"--scope",
|
|
3575
|
+
"user"
|
|
3576
|
+
]
|
|
3577
|
+
}
|
|
3578
|
+
]);
|
|
3579
|
+
}
|
|
3580
|
+
if (id === "codex") {
|
|
3581
|
+
return runAll([
|
|
3582
|
+
{ cmd: "codex", args: ["plugin", "marketplace", "add", REPO_SLUG] },
|
|
3583
|
+
{
|
|
3584
|
+
cmd: "codex",
|
|
3585
|
+
args: ["plugin", "add", `${PLUGIN_NAME}@${MARKETPLACE}`]
|
|
3586
|
+
}
|
|
3587
|
+
]);
|
|
3588
|
+
}
|
|
3589
|
+
const platform = PLATFORMS.find((p) => p.id === id);
|
|
3590
|
+
if (!clonedRepoDir || !platform?.installerRel) {
|
|
3591
|
+
log(` Skipped ${platform?.label ?? id} \u2014 repo clone unavailable.`);
|
|
3592
|
+
return false;
|
|
3593
|
+
}
|
|
3594
|
+
const installer = path2.join(clonedRepoDir, platform.installerRel);
|
|
3595
|
+
if (!fs.existsSync(installer)) {
|
|
3596
|
+
log(` Skipped ${platform.label} \u2014 installer missing at ${installer}.`);
|
|
3597
|
+
return false;
|
|
3598
|
+
}
|
|
3599
|
+
return await run("node", [installer]) === 0;
|
|
3600
|
+
}
|
|
3601
|
+
async function cloneRepoIfNeeded(platforms) {
|
|
3602
|
+
const needsClone = platforms.some(
|
|
3603
|
+
(id) => PLATFORMS.find((p) => p.id === id)?.installerRel
|
|
3604
|
+
);
|
|
3605
|
+
if (!needsClone) return null;
|
|
3606
|
+
if (!await commandExists("git")) {
|
|
3607
|
+
log("\n`git` not found on PATH \u2014 cannot install Cursor / Antigravity.");
|
|
3608
|
+
log("Install git and re-run, or use the one-liners from the README.");
|
|
3609
|
+
return null;
|
|
3610
|
+
}
|
|
3611
|
+
const tmpDir = fs.mkdtempSync(path2.join(os2.tmpdir(), "tg-install-"));
|
|
3612
|
+
const ok = await run("git", ["clone", "--depth", "1", REPO_GIT_URL, tmpDir]) === 0;
|
|
3613
|
+
if (!ok) {
|
|
3614
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
3615
|
+
log("\nRepo clone failed \u2014 skipping Cursor / Antigravity.");
|
|
3616
|
+
return null;
|
|
3617
|
+
}
|
|
3618
|
+
return tmpDir;
|
|
3619
|
+
}
|
|
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
|
+
function printSetupComplete() {
|
|
3655
|
+
clearScreen();
|
|
3656
|
+
log("\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");
|
|
3657
|
+
log(" Congratulations!");
|
|
3658
|
+
log(" All setup is completed.");
|
|
3659
|
+
log("\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");
|
|
3660
|
+
log("");
|
|
3661
|
+
log("Plugins are installed and your MAT token is saved.");
|
|
3662
|
+
log("");
|
|
3663
|
+
}
|
|
3664
|
+
async function tokenFlow() {
|
|
3665
|
+
log("");
|
|
3666
|
+
log("\u2500\u2500 Token setup \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");
|
|
3667
|
+
const choice = await chooseTokenAction(
|
|
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.");
|
|
3673
|
+
return true;
|
|
3674
|
+
}
|
|
3675
|
+
if (choice === "no") {
|
|
3676
|
+
log("");
|
|
3677
|
+
log(
|
|
3678
|
+
"You can create a MAT (Member Access Token) in the Transcodes console."
|
|
3679
|
+
);
|
|
3680
|
+
log(
|
|
3681
|
+
"If you don't have access, ask your project administrator to issue one for you."
|
|
3682
|
+
);
|
|
3683
|
+
log("");
|
|
3684
|
+
await promptLine(`Press ENTER to open the console (${APP_CONSOLE_URL}) \u2026 `);
|
|
3685
|
+
log(`
|
|
3686
|
+
Opening ${APP_CONSOLE_URL} \u2026`);
|
|
3687
|
+
openUrl(APP_CONSOLE_URL);
|
|
3688
|
+
log("");
|
|
3689
|
+
log(" 1) After sign-in, create a new project on the dashboard.");
|
|
3690
|
+
log(' 2) Open the "Members & Tokens" tab and add a new member.');
|
|
3691
|
+
log(
|
|
3692
|
+
" 3) Generate a new token for that member, or ask your manager to get a token."
|
|
3693
|
+
);
|
|
3694
|
+
log(" 4) Paste it here.");
|
|
3695
|
+
}
|
|
3696
|
+
return registerToken();
|
|
3697
|
+
}
|
|
3698
|
+
function parseSelection(args) {
|
|
3699
|
+
const ids = [];
|
|
3700
|
+
let explicit = false;
|
|
3701
|
+
for (const arg of args) {
|
|
3702
|
+
if (arg === "--all") {
|
|
3703
|
+
return PLATFORMS.map((p) => p.id);
|
|
3704
|
+
}
|
|
3705
|
+
const match = PLATFORMS.find(
|
|
3706
|
+
(p) => p.id === arg || arg === "chatgpt" && p.id === "codex"
|
|
3707
|
+
);
|
|
3708
|
+
if (match) {
|
|
3709
|
+
explicit = true;
|
|
3710
|
+
if (!ids.includes(match.id)) ids.push(match.id);
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3713
|
+
return explicit ? ids : "interactive";
|
|
3714
|
+
}
|
|
3715
|
+
async function cmdInstall(args) {
|
|
3716
|
+
log("transcodes install \u2014 set up plugins, token, and dashboard.\n");
|
|
3717
|
+
if (!await ensureNode()) {
|
|
3718
|
+
process.exit(1);
|
|
3719
|
+
}
|
|
3720
|
+
log("");
|
|
3721
|
+
const selection = parseSelection(args);
|
|
3722
|
+
let openDashboard = false;
|
|
3723
|
+
if (selection === "interactive") {
|
|
3724
|
+
if (!isTty()) {
|
|
3725
|
+
log(
|
|
3726
|
+
"Non-interactive shell detected. Specify platforms explicitly, e.g.:"
|
|
3727
|
+
);
|
|
3728
|
+
log(" transcodes install --all");
|
|
3729
|
+
log(" transcodes install claude codex cursor antigravity");
|
|
3730
|
+
process.exit(1);
|
|
3731
|
+
}
|
|
3732
|
+
const useArrows = supportsArrowSelect();
|
|
3733
|
+
const checked = /* @__PURE__ */ new Set();
|
|
3734
|
+
let firstMenu = true;
|
|
3735
|
+
for (; ; ) {
|
|
3736
|
+
if (!firstMenu) clearScreen();
|
|
3737
|
+
firstMenu = false;
|
|
3738
|
+
let choice;
|
|
3739
|
+
if (useArrows) {
|
|
3740
|
+
choice = await arrowSelect(checked);
|
|
3741
|
+
} else {
|
|
3742
|
+
renderMenu();
|
|
3743
|
+
choice = await promptMenu();
|
|
3744
|
+
}
|
|
3745
|
+
if (choice.kind === "cancel") {
|
|
3746
|
+
log("");
|
|
3747
|
+
log(
|
|
3748
|
+
"If you want to manage tokens directly, please type `transcodes` to open the dashboard"
|
|
3749
|
+
);
|
|
3750
|
+
process.exit(0);
|
|
3751
|
+
}
|
|
3752
|
+
if (choice.kind === "next") {
|
|
3753
|
+
clearScreen();
|
|
3754
|
+
break;
|
|
3755
|
+
}
|
|
3756
|
+
if (choice.ids.length === 0) {
|
|
3757
|
+
log(" Nothing selected.");
|
|
3758
|
+
continue;
|
|
3759
|
+
}
|
|
3760
|
+
checked.clear();
|
|
3761
|
+
for (const id of choice.ids) checked.add(id);
|
|
3762
|
+
await runInstalls(choice.ids);
|
|
3763
|
+
}
|
|
3764
|
+
} else {
|
|
3765
|
+
if (selection.length === 0) {
|
|
3766
|
+
log("No platforms selected \u2014 nothing to install.");
|
|
3767
|
+
process.exit(0);
|
|
3768
|
+
}
|
|
3769
|
+
log(
|
|
3770
|
+
`
|
|
3771
|
+
Installing: ${selection.map((id) => PLATFORMS.find((p) => p.id === id)?.label ?? id).join(", ")}`
|
|
3772
|
+
);
|
|
3773
|
+
await runInstalls(selection);
|
|
3774
|
+
clearScreen();
|
|
3775
|
+
}
|
|
3776
|
+
openDashboard = await tokenFlow();
|
|
3777
|
+
if (!openDashboard) {
|
|
3778
|
+
process.exit(0);
|
|
3779
|
+
}
|
|
3780
|
+
printSetupComplete();
|
|
3781
|
+
await promptLine("Press ENTER to open the transcodes CLI dashboard \u2026 ");
|
|
3782
|
+
await runDashboard({});
|
|
3783
|
+
process.exit(0);
|
|
3784
|
+
}
|
|
3785
|
+
function installedPlatforms() {
|
|
3786
|
+
return PLATFORMS.filter((p) => isPluginInstalled(p.id)).map((p) => p.id);
|
|
3787
|
+
}
|
|
3788
|
+
async function updateCliPackage() {
|
|
3789
|
+
log(`
|
|
3790
|
+
\u2500\u2500 ${CLI_PACKAGE_NAME} (CLI) \u2500\u2500`);
|
|
3791
|
+
log(` Current version: ${CLI_VERSION}`);
|
|
3792
|
+
if (!await commandExists("npm")) {
|
|
3793
|
+
log(" \u2717 `npm` not found on PATH \u2014 cannot update the CLI.");
|
|
3794
|
+
log(
|
|
3795
|
+
` Install Node.js / npm, then: npm install -g ${CLI_PACKAGE_NAME}@latest`
|
|
3796
|
+
);
|
|
3797
|
+
return false;
|
|
3798
|
+
}
|
|
3799
|
+
const ok = await run("npm", ["install", "-g", `${CLI_PACKAGE_NAME}@latest`]) === 0;
|
|
3800
|
+
if (ok) {
|
|
3801
|
+
log(" \u2713 CLI updated (run `transcodes version` to confirm).");
|
|
3802
|
+
} else {
|
|
3803
|
+
log(" \u2717 CLI update failed.");
|
|
3804
|
+
}
|
|
3805
|
+
return ok;
|
|
3806
|
+
}
|
|
3807
|
+
async function cmdUpdate(args) {
|
|
3808
|
+
log("transcodes update \u2014 refresh plugins and CLI.\n");
|
|
3809
|
+
if (!await ensureNode()) {
|
|
3810
|
+
process.exit(1);
|
|
3811
|
+
}
|
|
3812
|
+
let cliOnly = false;
|
|
3813
|
+
let pluginsOnly = false;
|
|
3814
|
+
let forceAll = false;
|
|
3815
|
+
const platformArgs = [];
|
|
3816
|
+
for (const arg of args) {
|
|
3817
|
+
if (arg === "--cli-only") cliOnly = true;
|
|
3818
|
+
else if (arg === "--plugins-only") pluginsOnly = true;
|
|
3819
|
+
else if (arg === "--all") forceAll = true;
|
|
3820
|
+
else if (arg.startsWith("-")) {
|
|
3821
|
+
log(`Unknown flag "${arg}".`);
|
|
3822
|
+
log(
|
|
3823
|
+
"Usage: transcodes update [--cli-only|--plugins-only|--all] [claude|codex|cursor|antigravity \u2026]"
|
|
3824
|
+
);
|
|
3825
|
+
process.exit(1);
|
|
3826
|
+
} else {
|
|
3827
|
+
platformArgs.push(arg);
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3830
|
+
if (cliOnly && pluginsOnly) {
|
|
3831
|
+
log("Cannot combine --cli-only and --plugins-only.");
|
|
3832
|
+
process.exit(1);
|
|
3833
|
+
}
|
|
3834
|
+
const results = [];
|
|
3835
|
+
if (!cliOnly) {
|
|
3836
|
+
let targets;
|
|
3837
|
+
if (forceAll) {
|
|
3838
|
+
targets = PLATFORMS.map((p) => p.id);
|
|
3839
|
+
} else if (platformArgs.length > 0) {
|
|
3840
|
+
const parsed = parseSelection(platformArgs);
|
|
3841
|
+
if (parsed === "interactive" || parsed.length === 0) {
|
|
3842
|
+
log(
|
|
3843
|
+
"No valid platforms. Use: claude, codex, cursor, antigravity (or chatgpt)."
|
|
3844
|
+
);
|
|
3845
|
+
process.exit(1);
|
|
3846
|
+
}
|
|
3847
|
+
targets = parsed;
|
|
3848
|
+
} else {
|
|
3849
|
+
targets = installedPlatforms();
|
|
3850
|
+
}
|
|
3851
|
+
if (targets.length === 0) {
|
|
3852
|
+
log("No installed plugins detected.");
|
|
3853
|
+
log(" Run `transcodes install` first, or `transcodes update --all`.");
|
|
3854
|
+
} else {
|
|
3855
|
+
log(
|
|
3856
|
+
`Updating plugins: ${targets.map((id) => PLATFORMS.find((p) => p.id === id)?.label ?? id).join(", ")}`
|
|
3857
|
+
);
|
|
3858
|
+
const clonedRepoDir = await cloneRepoIfNeeded(targets);
|
|
3859
|
+
try {
|
|
3860
|
+
for (const id of targets) {
|
|
3861
|
+
const label = PLATFORMS.find((p) => p.id === id)?.label ?? id;
|
|
3862
|
+
log(`
|
|
3863
|
+
\u2500\u2500 ${label} \u2500\u2500`);
|
|
3864
|
+
const ok = await installPlatform(id, clonedRepoDir);
|
|
3865
|
+
results.push({ name: label, ok });
|
|
3866
|
+
}
|
|
3867
|
+
} finally {
|
|
3868
|
+
if (clonedRepoDir) {
|
|
3869
|
+
fs.rmSync(clonedRepoDir, { recursive: true, force: true });
|
|
3870
|
+
}
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
}
|
|
3874
|
+
if (!pluginsOnly) {
|
|
3875
|
+
const ok = await updateCliPackage();
|
|
3876
|
+
results.push({ name: `${CLI_PACKAGE_NAME} (CLI)`, ok });
|
|
3877
|
+
}
|
|
3878
|
+
log("\n\u2500\u2500 Update summary \u2500\u2500");
|
|
3879
|
+
if (results.length === 0) {
|
|
3880
|
+
log(" (nothing to update)");
|
|
3881
|
+
} else {
|
|
3882
|
+
for (const r of results) {
|
|
3883
|
+
log(` ${r.ok ? "\u2713" : "\u2717"} ${r.name}`);
|
|
3884
|
+
}
|
|
3885
|
+
}
|
|
3886
|
+
log("");
|
|
3887
|
+
log("Done. Restart your host apps/CLIs so they pick up the new plugin.");
|
|
3888
|
+
process.exit(results.some((r) => !r.ok) ? 1 : 0);
|
|
3889
|
+
}
|
|
3890
|
+
|
|
2685
3891
|
// src/index.ts
|
|
2686
3892
|
function fail(message) {
|
|
2687
3893
|
process.stderr.write(`transcodes: ${message}
|
|
@@ -2838,6 +4044,12 @@ async function cmdDashboard(args) {
|
|
|
2838
4044
|
function main() {
|
|
2839
4045
|
const [command, ...rest] = process.argv.slice(2);
|
|
2840
4046
|
switch (command) {
|
|
4047
|
+
case "install":
|
|
4048
|
+
void cmdInstall(rest);
|
|
4049
|
+
break;
|
|
4050
|
+
case "update":
|
|
4051
|
+
void cmdUpdate(rest);
|
|
4052
|
+
break;
|
|
2841
4053
|
case "set":
|
|
2842
4054
|
cmdSet(rest);
|
|
2843
4055
|
break;
|