@overscore/cli 0.10.2 → 0.11.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 +258 -106
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
|
+
import http from "http";
|
|
5
|
+
import net from "net";
|
|
4
6
|
import { execSync } from "child_process";
|
|
5
7
|
import { createInterface } from "readline";
|
|
6
|
-
import { createHash } from "crypto";
|
|
8
|
+
import { createHash, randomBytes } from "crypto";
|
|
7
9
|
import yauzl from "yauzl";
|
|
8
10
|
// ── Shared helpers ──────────────────────────────────────────────────
|
|
9
11
|
function parseEnv(content) {
|
|
@@ -641,11 +643,43 @@ async function pull(slug) {
|
|
|
641
643
|
const versionParam = versionIndex !== -1 && process.argv[versionIndex + 1]
|
|
642
644
|
? process.argv[versionIndex + 1]
|
|
643
645
|
: null;
|
|
644
|
-
//
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
646
|
+
// Resolve API key: device token → auto-create os_ key, else prompt
|
|
647
|
+
let apiKey = "";
|
|
648
|
+
const configPath = path.join(process.env.HOME || "", ".overscore", "config");
|
|
649
|
+
if (fs.existsSync(configPath)) {
|
|
650
|
+
const cfg = parseEnv(fs.readFileSync(configPath, "utf-8"));
|
|
651
|
+
const deviceToken = cfg.OVERSCORE_DEVICE_TOKEN;
|
|
652
|
+
if (deviceToken) {
|
|
653
|
+
try {
|
|
654
|
+
const keyRes = await fetch(`${HUB_URL}/api/projects/${slug}/api-keys`, {
|
|
655
|
+
method: "POST",
|
|
656
|
+
headers: {
|
|
657
|
+
Authorization: `Bearer ${deviceToken}`,
|
|
658
|
+
"Content-Type": "application/json",
|
|
659
|
+
},
|
|
660
|
+
body: JSON.stringify({ name: `${slug} pull key` }),
|
|
661
|
+
});
|
|
662
|
+
if (keyRes.status === 401 || keyRes.status === 403) {
|
|
663
|
+
console.error("\n Error: Device token is invalid or expired. Run 'npx @overscore/cli auth login' to re-authenticate.\n");
|
|
664
|
+
process.exit(1);
|
|
665
|
+
}
|
|
666
|
+
if (keyRes.ok) {
|
|
667
|
+
const keyData = (await keyRes.json());
|
|
668
|
+
apiKey = keyData.raw_key || "";
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
catch {
|
|
672
|
+
// Network error — fall through to prompt
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
if (!apiKey) {
|
|
677
|
+
console.log(" Tip: Run 'npx @overscore/cli auth login' to authenticate once and skip this step.");
|
|
678
|
+
apiKey = await prompt("API key (from the Hub):");
|
|
679
|
+
if (!apiKey || !apiKey.startsWith("os_")) {
|
|
680
|
+
console.error("\n Error: API key should start with os_\n");
|
|
681
|
+
process.exit(1);
|
|
682
|
+
}
|
|
649
683
|
}
|
|
650
684
|
const apiUrl = "https://overscore.dev/api";
|
|
651
685
|
const baseUrl = apiUrl.replace(/\/api$/, "");
|
|
@@ -667,10 +701,10 @@ async function pull(slug) {
|
|
|
667
701
|
console.error(`\n Error: ${errorMsg}\n`);
|
|
668
702
|
process.exit(1);
|
|
669
703
|
}
|
|
670
|
-
const projectSlug = res.headers.get("X-
|
|
671
|
-
const dashboardSlug = res.headers.get("X-
|
|
672
|
-
const version = res.headers.get("X-
|
|
673
|
-
const commitMsg = res.headers.get("X-
|
|
704
|
+
const projectSlug = res.headers.get("X-OS-Project-Slug") || "";
|
|
705
|
+
const dashboardSlug = res.headers.get("X-OS-Dashboard-Slug") || slug;
|
|
706
|
+
const version = res.headers.get("X-OS-Version") || "?";
|
|
707
|
+
const commitMsg = res.headers.get("X-OS-Commit-Message") || "";
|
|
674
708
|
// Download the zip
|
|
675
709
|
const zipBuffer = Buffer.from(await res.arrayBuffer());
|
|
676
710
|
// Extract to ./<slug>/
|
|
@@ -695,11 +729,10 @@ async function pull(slug) {
|
|
|
695
729
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
696
730
|
}
|
|
697
731
|
await extractZip(zipBuffer, targetDir);
|
|
698
|
-
// Generate .env (
|
|
732
|
+
// Generate .env (no API key — 'npx @overscore/cli dev' injects it at runtime)
|
|
699
733
|
const envContent = `# Overscore Configuration
|
|
700
734
|
VITE_OVERSCORE_PROJECT_SLUG=${projectSlug}
|
|
701
735
|
VITE_OVERSCORE_DASHBOARD_SLUG=${dashboardSlug}
|
|
702
|
-
VITE_OVERSCORE_API_KEY=${apiKey}
|
|
703
736
|
VITE_OVERSCORE_API_URL=https://overscore.dev/api
|
|
704
737
|
`;
|
|
705
738
|
fs.writeFileSync(path.join(targetDir, ".env"), envContent, { mode: 0o600 });
|
|
@@ -752,11 +785,7 @@ VITE_OVERSCORE_API_URL=https://overscore.dev/api
|
|
|
752
785
|
|
|
753
786
|
cd ${slug}
|
|
754
787
|
npm install
|
|
755
|
-
|
|
756
|
-
claude
|
|
757
|
-
|
|
758
|
-
Note: If this dashboard uses third-party APIs, add your
|
|
759
|
-
API keys to .env before running.
|
|
788
|
+
npx @overscore/cli dev # starts dev server with auth injected
|
|
760
789
|
|
|
761
790
|
When ready to deploy:
|
|
762
791
|
|
|
@@ -1758,67 +1787,122 @@ function analysisPreview() {
|
|
|
1758
1787
|
process.exit(1);
|
|
1759
1788
|
}
|
|
1760
1789
|
}
|
|
1790
|
+
// ── dev ─────────────────────────────────────────────────────────────
|
|
1791
|
+
async function dev() {
|
|
1792
|
+
// Must be run from inside a dashboard project folder
|
|
1793
|
+
const envPath = path.resolve(process.cwd(), ".env");
|
|
1794
|
+
if (!fs.existsSync(envPath)) {
|
|
1795
|
+
console.error("\n Error: No .env file found. Run this from inside a dashboard project folder.\n");
|
|
1796
|
+
process.exit(1);
|
|
1797
|
+
}
|
|
1798
|
+
const localEnv = parseEnv(fs.readFileSync(envPath, "utf-8"));
|
|
1799
|
+
const projectSlug = localEnv.VITE_OVERSCORE_PROJECT_SLUG;
|
|
1800
|
+
if (!projectSlug) {
|
|
1801
|
+
console.error("\n Error: VITE_OVERSCORE_PROJECT_SLUG not set in .env.\n");
|
|
1802
|
+
process.exit(1);
|
|
1803
|
+
}
|
|
1804
|
+
// Get device token from global config
|
|
1805
|
+
const configPath = path.join(process.env.HOME || "", ".overscore", "config");
|
|
1806
|
+
if (!fs.existsSync(configPath)) {
|
|
1807
|
+
console.error("\n Error: Not authenticated. Run 'npx @overscore/cli auth login' first.\n");
|
|
1808
|
+
process.exit(1);
|
|
1809
|
+
}
|
|
1810
|
+
const cfg = parseEnv(fs.readFileSync(configPath, "utf-8"));
|
|
1811
|
+
const deviceToken = cfg.OVERSCORE_DEVICE_TOKEN;
|
|
1812
|
+
if (!deviceToken) {
|
|
1813
|
+
console.error("\n Error: No device token found. Run 'npx @overscore/cli auth login' first.\n");
|
|
1814
|
+
process.exit(1);
|
|
1815
|
+
}
|
|
1816
|
+
// Create a temporary dev session key
|
|
1817
|
+
console.log("\n Creating dev session key...");
|
|
1818
|
+
let keyId = null;
|
|
1819
|
+
let apiKey = null;
|
|
1820
|
+
try {
|
|
1821
|
+
const keyRes = await fetch(`${HUB_URL}/api/projects/${projectSlug}/api-keys`, {
|
|
1822
|
+
method: "POST",
|
|
1823
|
+
headers: {
|
|
1824
|
+
Authorization: `Bearer ${deviceToken}`,
|
|
1825
|
+
"Content-Type": "application/json",
|
|
1826
|
+
},
|
|
1827
|
+
body: JSON.stringify({ name: "dev session (auto-cleanup)" }),
|
|
1828
|
+
});
|
|
1829
|
+
if (keyRes.status === 401 || keyRes.status === 403) {
|
|
1830
|
+
console.error("\n Error: Device token is invalid or expired. Run 'npx @overscore/cli auth login' to re-authenticate.\n");
|
|
1831
|
+
process.exit(1);
|
|
1832
|
+
}
|
|
1833
|
+
if (!keyRes.ok) {
|
|
1834
|
+
const errData = await keyRes.json().catch(() => ({}));
|
|
1835
|
+
console.error(`\n Error: ${errData.error || `HTTP ${keyRes.status}`}\n`);
|
|
1836
|
+
process.exit(1);
|
|
1837
|
+
}
|
|
1838
|
+
const keyData = (await keyRes.json());
|
|
1839
|
+
keyId = keyData.id ?? null;
|
|
1840
|
+
apiKey = keyData.raw_key ?? null;
|
|
1841
|
+
}
|
|
1842
|
+
catch {
|
|
1843
|
+
console.error("\n Error: Could not reach overscore.dev to create dev session key.\n");
|
|
1844
|
+
process.exit(1);
|
|
1845
|
+
}
|
|
1846
|
+
if (!apiKey) {
|
|
1847
|
+
console.error("\n Error: Failed to get API key for dev session.\n");
|
|
1848
|
+
process.exit(1);
|
|
1849
|
+
}
|
|
1850
|
+
// Best-effort cleanup: revoke the session key on exit
|
|
1851
|
+
let cleaned = false;
|
|
1852
|
+
async function cleanup() {
|
|
1853
|
+
if (cleaned || !keyId)
|
|
1854
|
+
return;
|
|
1855
|
+
cleaned = true;
|
|
1856
|
+
try {
|
|
1857
|
+
await fetch(`${HUB_URL}/api/projects/${projectSlug}/api-keys`, {
|
|
1858
|
+
method: "DELETE",
|
|
1859
|
+
headers: {
|
|
1860
|
+
Authorization: `Bearer ${deviceToken}`,
|
|
1861
|
+
"Content-Type": "application/json",
|
|
1862
|
+
},
|
|
1863
|
+
body: JSON.stringify({ id: keyId }),
|
|
1864
|
+
});
|
|
1865
|
+
}
|
|
1866
|
+
catch {
|
|
1867
|
+
// Best effort — key will remain but that's acceptable
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
// Spawn npm run dev with the key injected into the environment
|
|
1871
|
+
const { spawn } = await import("child_process");
|
|
1872
|
+
const child = spawn("npm", ["run", "dev"], {
|
|
1873
|
+
stdio: "inherit",
|
|
1874
|
+
env: { ...process.env, VITE_OVERSCORE_API_KEY: apiKey },
|
|
1875
|
+
});
|
|
1876
|
+
child.on("exit", async (code) => {
|
|
1877
|
+
await cleanup();
|
|
1878
|
+
process.exit(code ?? 0);
|
|
1879
|
+
});
|
|
1880
|
+
process.on("SIGINT", async () => {
|
|
1881
|
+
await cleanup();
|
|
1882
|
+
process.exit(0);
|
|
1883
|
+
});
|
|
1884
|
+
process.on("SIGTERM", async () => {
|
|
1885
|
+
await cleanup();
|
|
1886
|
+
process.exit(0);
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1761
1889
|
// ── auth ────────────────────────────────────────────────────────────
|
|
1762
1890
|
async function authStatus() {
|
|
1763
|
-
const
|
|
1764
|
-
const configPath = path.join(configDir, "config");
|
|
1765
|
-
// Check global config
|
|
1891
|
+
const configPath = path.join(process.env.HOME || "", ".overscore", "config");
|
|
1766
1892
|
if (fs.existsSync(configPath)) {
|
|
1767
1893
|
const cfg = parseEnv(fs.readFileSync(configPath, "utf-8"));
|
|
1768
|
-
const
|
|
1894
|
+
const deviceToken = cfg.OVERSCORE_DEVICE_TOKEN;
|
|
1769
1895
|
const project = cfg.OVERSCORE_PROJECT_SLUG;
|
|
1770
|
-
if (
|
|
1771
|
-
|
|
1772
|
-
let valid = false;
|
|
1773
|
-
try {
|
|
1774
|
-
const hubUrl = cfg.OVERSCORE_API_URL?.replace(/\/api$/, "") || HUB_URL;
|
|
1775
|
-
const res = await fetch(`${hubUrl}/api/projects/verify-key`, {
|
|
1776
|
-
method: "POST",
|
|
1777
|
-
headers: { "Content-Type": "application/json" },
|
|
1778
|
-
body: JSON.stringify({ key }),
|
|
1779
|
-
});
|
|
1780
|
-
if (res.ok) {
|
|
1781
|
-
const data = (await res.json());
|
|
1782
|
-
valid = true;
|
|
1783
|
-
console.log(`
|
|
1896
|
+
if (deviceToken) {
|
|
1897
|
+
console.log(`
|
|
1784
1898
|
Authenticated
|
|
1785
1899
|
|
|
1786
|
-
|
|
1787
|
-
Project:
|
|
1788
|
-
Config:
|
|
1789
|
-
Status: Valid
|
|
1790
|
-
`);
|
|
1791
|
-
}
|
|
1792
|
-
}
|
|
1793
|
-
catch {
|
|
1794
|
-
// network error
|
|
1795
|
-
}
|
|
1796
|
-
if (!valid) {
|
|
1797
|
-
console.log(`
|
|
1798
|
-
Authentication problem
|
|
1799
|
-
|
|
1800
|
-
Key: ${key.slice(0, 10)}...
|
|
1801
|
-
Project: ${project}
|
|
1802
|
-
Config: ~/.overscore/config
|
|
1803
|
-
Status: Invalid or expired
|
|
1804
|
-
|
|
1805
|
-
Run 'npx @overscore/cli auth login' to update your key.
|
|
1806
|
-
`);
|
|
1807
|
-
}
|
|
1808
|
-
return;
|
|
1809
|
-
}
|
|
1810
|
-
}
|
|
1811
|
-
// Check local .env
|
|
1812
|
-
const envPath = path.resolve(process.cwd(), ".env");
|
|
1813
|
-
if (fs.existsSync(envPath)) {
|
|
1814
|
-
const env = parseEnv(fs.readFileSync(envPath, "utf-8"));
|
|
1815
|
-
if (env.VITE_OVERSCORE_API_KEY) {
|
|
1816
|
-
console.log(`
|
|
1817
|
-
Using local .env (this project only)
|
|
1900
|
+
Device token: ${deviceToken.slice(0, 12)}...
|
|
1901
|
+
Project: ${project || "—"}
|
|
1902
|
+
Config: ~/.overscore/config
|
|
1818
1903
|
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
Dashboard: ${env.VITE_OVERSCORE_DASHBOARD_SLUG || "—"}
|
|
1904
|
+
Run 'npx @overscore/cli dev' to start the dev server.
|
|
1905
|
+
Run 'npx @overscore/cli auth logout' to sign out.
|
|
1822
1906
|
`);
|
|
1823
1907
|
return;
|
|
1824
1908
|
}
|
|
@@ -1826,64 +1910,126 @@ async function authStatus() {
|
|
|
1826
1910
|
console.log(`
|
|
1827
1911
|
Not authenticated
|
|
1828
1912
|
|
|
1829
|
-
|
|
1913
|
+
Run 'npx @overscore/cli auth login' to authenticate.
|
|
1830
1914
|
`);
|
|
1831
1915
|
}
|
|
1832
1916
|
async function authLogin() {
|
|
1833
1917
|
const configDir = path.join(process.env.HOME || "", ".overscore");
|
|
1834
1918
|
const configPath = path.join(configDir, "config");
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1919
|
+
const state = randomBytes(16).toString("hex");
|
|
1920
|
+
// Find a free port in range 9876–9886
|
|
1921
|
+
let chosenPort = null;
|
|
1922
|
+
for (let p = 9876; p <= 9886; p++) {
|
|
1923
|
+
const free = await new Promise((resolve) => {
|
|
1924
|
+
const srv = net.createServer();
|
|
1925
|
+
srv.once("error", () => resolve(false));
|
|
1926
|
+
srv.once("listening", () => { srv.close(() => resolve(true)); });
|
|
1927
|
+
srv.listen(p, "127.0.0.1");
|
|
1928
|
+
});
|
|
1929
|
+
if (free) {
|
|
1930
|
+
chosenPort = p;
|
|
1931
|
+
break;
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
if (!chosenPort) {
|
|
1935
|
+
console.error("\n Error: No free port found in range 9876–9886.\n");
|
|
1839
1936
|
process.exit(1);
|
|
1840
1937
|
}
|
|
1841
|
-
//
|
|
1842
|
-
let
|
|
1938
|
+
// Start local callback server
|
|
1939
|
+
let resolveCallback;
|
|
1940
|
+
const callbackPromise = new Promise((resolve) => {
|
|
1941
|
+
resolveCallback = resolve;
|
|
1942
|
+
});
|
|
1943
|
+
const server = http.createServer((req, res) => {
|
|
1944
|
+
const reqUrl = new URL(req.url ?? "/", `http://localhost:${chosenPort}`);
|
|
1945
|
+
if (reqUrl.pathname !== "/callback") {
|
|
1946
|
+
res.writeHead(404);
|
|
1947
|
+
res.end();
|
|
1948
|
+
return;
|
|
1949
|
+
}
|
|
1950
|
+
const token = reqUrl.searchParams.get("token") ?? "";
|
|
1951
|
+
const returnedState = reqUrl.searchParams.get("state") ?? "";
|
|
1952
|
+
const project = reqUrl.searchParams.get("project") ?? undefined;
|
|
1953
|
+
if (returnedState !== state || !token.startsWith("ocli_")) {
|
|
1954
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
1955
|
+
res.end("Invalid callback");
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
1959
|
+
res.end(`<!DOCTYPE html><html><head><title>Overscore CLI</title></head>` +
|
|
1960
|
+
`<body style="font-family:sans-serif;text-align:center;padding:3rem">` +
|
|
1961
|
+
`<h2>Authenticated! You can close this tab.</h2></body></html>`);
|
|
1962
|
+
resolveCallback({ token, project });
|
|
1963
|
+
});
|
|
1964
|
+
server.listen(chosenPort, "127.0.0.1");
|
|
1965
|
+
const loginUrl = `${HUB_URL}/login?next=${encodeURIComponent(`/api/cli/auth/finalize?port=${chosenPort}&state=${state}`)}&mode=signin`;
|
|
1966
|
+
console.log(`\n Opening browser to authenticate...`);
|
|
1967
|
+
console.log(`\n If your browser doesn't open automatically, visit:\n ${loginUrl}\n`);
|
|
1843
1968
|
try {
|
|
1844
|
-
const
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
const data = (await res.json());
|
|
1851
|
-
projectSlug = data.project_slug || null;
|
|
1852
|
-
if (projectSlug) {
|
|
1853
|
-
console.log(`\n Detected project: ${projectSlug}`);
|
|
1854
|
-
}
|
|
1969
|
+
const platform = process.platform;
|
|
1970
|
+
if (platform === "darwin") {
|
|
1971
|
+
execSync(`open '${loginUrl}'`);
|
|
1972
|
+
}
|
|
1973
|
+
else if (platform === "win32") {
|
|
1974
|
+
execSync(`start "" "${loginUrl}"`);
|
|
1855
1975
|
}
|
|
1856
1976
|
else {
|
|
1857
|
-
|
|
1858
|
-
process.exit(1);
|
|
1977
|
+
execSync(`xdg-open '${loginUrl}'`);
|
|
1859
1978
|
}
|
|
1860
1979
|
}
|
|
1861
1980
|
catch {
|
|
1862
|
-
|
|
1863
|
-
process.exit(1);
|
|
1981
|
+
// Browser open failed — user can follow the printed URL
|
|
1864
1982
|
}
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1983
|
+
// Wait up to 120 seconds for the callback
|
|
1984
|
+
const timeout = setTimeout(() => {
|
|
1985
|
+
console.error("\n Error: Timed out waiting for browser login (120s).\n");
|
|
1986
|
+
server.close();
|
|
1987
|
+
process.exit(1);
|
|
1988
|
+
}, 120000);
|
|
1989
|
+
const { token: deviceToken, project: projectSlug } = await callbackPromise;
|
|
1990
|
+
clearTimeout(timeout);
|
|
1991
|
+
server.close();
|
|
1992
|
+
// Preserve any existing os_ API key so dashboard .env fallback still works
|
|
1993
|
+
let existingApiKey = "";
|
|
1994
|
+
if (fs.existsSync(configPath)) {
|
|
1995
|
+
const existing = parseEnv(fs.readFileSync(configPath, "utf-8"));
|
|
1996
|
+
existingApiKey = existing.OVERSCORE_API_KEY || "";
|
|
1871
1997
|
}
|
|
1872
1998
|
fs.mkdirSync(configDir, { recursive: true });
|
|
1873
|
-
|
|
1999
|
+
const lines = [
|
|
2000
|
+
`OVERSCORE_API_URL=${HUB_URL}/api`,
|
|
2001
|
+
`OVERSCORE_DEVICE_TOKEN=${deviceToken}`,
|
|
2002
|
+
];
|
|
2003
|
+
if (projectSlug)
|
|
2004
|
+
lines.push(`OVERSCORE_PROJECT_SLUG=${projectSlug}`);
|
|
2005
|
+
if (existingApiKey)
|
|
2006
|
+
lines.push(`OVERSCORE_API_KEY=${existingApiKey}`);
|
|
2007
|
+
fs.writeFileSync(configPath, lines.join("\n") + "\n", { mode: 0o600 });
|
|
1874
2008
|
console.log(`
|
|
1875
2009
|
Authenticated!
|
|
1876
2010
|
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
You're ready to use the CLI.
|
|
2011
|
+
Device token saved to ~/.overscore/config.
|
|
2012
|
+
You won't need to copy API keys manually — create-overscore and pull
|
|
2013
|
+
will authenticate automatically from now on.
|
|
1882
2014
|
`);
|
|
1883
2015
|
}
|
|
1884
2016
|
async function authLogout() {
|
|
1885
2017
|
const configPath = path.join(process.env.HOME || "", ".overscore", "config");
|
|
1886
2018
|
if (fs.existsSync(configPath)) {
|
|
2019
|
+
// Attempt server-side revocation of the device token (non-fatal)
|
|
2020
|
+
try {
|
|
2021
|
+
const cfg = parseEnv(fs.readFileSync(configPath, "utf-8"));
|
|
2022
|
+
const deviceToken = cfg.OVERSCORE_DEVICE_TOKEN;
|
|
2023
|
+
if (deviceToken) {
|
|
2024
|
+
await fetch(`${HUB_URL}/api/cli/auth/revoke`, {
|
|
2025
|
+
method: "POST",
|
|
2026
|
+
headers: { Authorization: `Bearer ${deviceToken}` },
|
|
2027
|
+
});
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
catch {
|
|
2031
|
+
// Ignore — Hub may be unreachable
|
|
2032
|
+
}
|
|
1887
2033
|
fs.unlinkSync(configPath);
|
|
1888
2034
|
console.log("\n Logged out. Global config removed.\n");
|
|
1889
2035
|
}
|
|
@@ -1930,7 +2076,10 @@ ${installed.map((s) => ` - ${s}`).join("\n")}
|
|
|
1930
2076
|
// ── Routing ─────────────────────────────────────────────────────────
|
|
1931
2077
|
const command = process.argv[2];
|
|
1932
2078
|
const subcommand = process.argv[3];
|
|
1933
|
-
if (command === "
|
|
2079
|
+
if (command === "dev") {
|
|
2080
|
+
dev();
|
|
2081
|
+
}
|
|
2082
|
+
else if (command === "deploy") {
|
|
1934
2083
|
deploy();
|
|
1935
2084
|
}
|
|
1936
2085
|
else if (command === "pull") {
|
|
@@ -2039,8 +2188,9 @@ else {
|
|
|
2039
2188
|
|
|
2040
2189
|
Commands:
|
|
2041
2190
|
auth Check authentication status
|
|
2042
|
-
auth login
|
|
2191
|
+
auth login Authenticate via browser (one time per machine)
|
|
2043
2192
|
auth logout Remove saved credentials
|
|
2193
|
+
dev Start the dev server (injects auth automatically)
|
|
2044
2194
|
list List all dashboards in the project
|
|
2045
2195
|
deploy Build and deploy the dashboard
|
|
2046
2196
|
pull <slug> Pull source code from the hub
|
|
@@ -2050,6 +2200,8 @@ else {
|
|
|
2050
2200
|
install-skills Install the Overscore skill library into ~/.claude/skills/
|
|
2051
2201
|
|
|
2052
2202
|
Usage:
|
|
2203
|
+
npx @overscore/cli auth login
|
|
2204
|
+
npx @overscore/cli dev
|
|
2053
2205
|
npx @overscore/cli list
|
|
2054
2206
|
npx @overscore/cli deploy --message "description"
|
|
2055
2207
|
npx @overscore/cli pull <slug>
|