@overscore/cli 0.10.1 → 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.
Files changed (2) hide show
  1. package/dist/index.js +265 -110
  2. 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) {
@@ -72,7 +74,7 @@ function loadEnv() {
72
74
  }
73
75
  process.exit(1);
74
76
  }
75
- async function apiRequest(method, urlPath, body) {
77
+ async function apiRequest(method, urlPath, body, command) {
76
78
  const { apiUrl, apiKey } = loadEnv();
77
79
  const baseUrl = apiUrl.replace(/\/api$/, "");
78
80
  const url = `${baseUrl}${urlPath}`;
@@ -82,6 +84,9 @@ async function apiRequest(method, urlPath, body) {
82
84
  if (body !== undefined) {
83
85
  headers["Content-Type"] = "application/json";
84
86
  }
87
+ if (command) {
88
+ headers["X-Overscore-Command"] = command;
89
+ }
85
90
  const res = await fetch(url, {
86
91
  method,
87
92
  headers,
@@ -332,7 +337,7 @@ async function deploy() {
332
337
  }
333
338
  async function queryList() {
334
339
  const { dashboardSlug } = loadEnv();
335
- const data = (await apiRequest("GET", `/api/dashboards/${dashboardSlug}/queries`));
340
+ const data = (await apiRequest("GET", `/api/dashboards/${dashboardSlug}/queries`, undefined, "query-list"));
336
341
  console.log(`\n Dashboard: ${data.dashboard}\n`);
337
342
  // Registered queries
338
343
  if (data.queries.length === 0) {
@@ -450,7 +455,7 @@ async function queryAdd(name, sql) {
450
455
  let body;
451
456
  if (datasetName) {
452
457
  // Look up the dataset UUID from project_datasets in the queries endpoint
453
- const listData = (await apiRequest("GET", `/api/dashboards/${dashboardSlug}/queries`));
458
+ const listData = (await apiRequest("GET", `/api/dashboards/${dashboardSlug}/queries`, undefined, "query-add"));
454
459
  const dataset = (listData.project_datasets ?? []).find((ds) => ds.name === datasetName);
455
460
  if (!dataset) {
456
461
  console.error(`\n Error: Dataset "${datasetName}" not found in this project.\n`);
@@ -466,7 +471,7 @@ async function queryAdd(name, sql) {
466
471
  else {
467
472
  body = { query_name: name, sql_text: resolvedSql };
468
473
  }
469
- const data = (await apiRequest("POST", `/api/dashboards/${dashboardSlug}/queries`, body));
474
+ const data = (await apiRequest("POST", `/api/dashboards/${dashboardSlug}/queries`, body, "query-add"));
470
475
  console.log(`\n Query "${data.query_name}" created.\n`);
471
476
  console.log(` Use it in your dashboard:`);
472
477
  console.log(` const { data } = useQuery("${data.query_name}");\n`);
@@ -638,11 +643,43 @@ async function pull(slug) {
638
643
  const versionParam = versionIndex !== -1 && process.argv[versionIndex + 1]
639
644
  ? process.argv[versionIndex + 1]
640
645
  : null;
641
- // Interactive prompts for credentials
642
- const apiKey = await prompt("API key (from the Hub):");
643
- if (!apiKey || !apiKey.startsWith("os_")) {
644
- console.error("\n Error: API key should start with os_\n");
645
- process.exit(1);
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
+ }
646
683
  }
647
684
  const apiUrl = "https://overscore.dev/api";
648
685
  const baseUrl = apiUrl.replace(/\/api$/, "");
@@ -664,10 +701,10 @@ async function pull(slug) {
664
701
  console.error(`\n Error: ${errorMsg}\n`);
665
702
  process.exit(1);
666
703
  }
667
- const projectSlug = res.headers.get("X-VD-Project-Slug") || "";
668
- const dashboardSlug = res.headers.get("X-VD-Dashboard-Slug") || slug;
669
- const version = res.headers.get("X-VD-Version") || "?";
670
- const commitMsg = res.headers.get("X-VD-Commit-Message") || "";
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") || "";
671
708
  // Download the zip
672
709
  const zipBuffer = Buffer.from(await res.arrayBuffer());
673
710
  // Extract to ./<slug>/
@@ -692,11 +729,10 @@ async function pull(slug) {
692
729
  fs.mkdirSync(targetDir, { recursive: true });
693
730
  }
694
731
  await extractZip(zipBuffer, targetDir);
695
- // Generate .env (only overscore configuser adds their own API keys)
732
+ // Generate .env (no API key'npx @overscore/cli dev' injects it at runtime)
696
733
  const envContent = `# Overscore Configuration
697
734
  VITE_OVERSCORE_PROJECT_SLUG=${projectSlug}
698
735
  VITE_OVERSCORE_DASHBOARD_SLUG=${dashboardSlug}
699
- VITE_OVERSCORE_API_KEY=${apiKey}
700
736
  VITE_OVERSCORE_API_URL=https://overscore.dev/api
701
737
  `;
702
738
  fs.writeFileSync(path.join(targetDir, ".env"), envContent, { mode: 0o600 });
@@ -749,11 +785,7 @@ VITE_OVERSCORE_API_URL=https://overscore.dev/api
749
785
 
750
786
  cd ${slug}
751
787
  npm install
752
- code .
753
- claude
754
-
755
- Note: If this dashboard uses third-party APIs, add your
756
- API keys to .env before running.
788
+ npx @overscore/cli dev # starts dev server with auth injected
757
789
 
758
790
  When ready to deploy:
759
791
 
@@ -1755,67 +1787,122 @@ function analysisPreview() {
1755
1787
  process.exit(1);
1756
1788
  }
1757
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
+ }
1758
1889
  // ── auth ────────────────────────────────────────────────────────────
1759
1890
  async function authStatus() {
1760
- const configDir = path.join(process.env.HOME || "", ".overscore");
1761
- const configPath = path.join(configDir, "config");
1762
- // Check global config
1891
+ const configPath = path.join(process.env.HOME || "", ".overscore", "config");
1763
1892
  if (fs.existsSync(configPath)) {
1764
1893
  const cfg = parseEnv(fs.readFileSync(configPath, "utf-8"));
1765
- const key = cfg.OVERSCORE_API_KEY;
1894
+ const deviceToken = cfg.OVERSCORE_DEVICE_TOKEN;
1766
1895
  const project = cfg.OVERSCORE_PROJECT_SLUG;
1767
- if (key && project) {
1768
- // Verify the key is still valid
1769
- let valid = false;
1770
- try {
1771
- const hubUrl = cfg.OVERSCORE_API_URL?.replace(/\/api$/, "") || HUB_URL;
1772
- const res = await fetch(`${hubUrl}/api/projects/verify-key`, {
1773
- method: "POST",
1774
- headers: { "Content-Type": "application/json" },
1775
- body: JSON.stringify({ key }),
1776
- });
1777
- if (res.ok) {
1778
- const data = (await res.json());
1779
- valid = true;
1780
- console.log(`
1896
+ if (deviceToken) {
1897
+ console.log(`
1781
1898
  Authenticated
1782
1899
 
1783
- Key: ${key.slice(0, 10)}...
1784
- Project: ${data.project_slug || project}
1785
- Config: ~/.overscore/config
1786
- Status: Valid
1787
- `);
1788
- }
1789
- }
1790
- catch {
1791
- // network error
1792
- }
1793
- if (!valid) {
1794
- console.log(`
1795
- Authentication problem
1796
-
1797
- Key: ${key.slice(0, 10)}...
1798
- Project: ${project}
1799
- Config: ~/.overscore/config
1800
- Status: Invalid or expired
1801
-
1802
- Run 'npx @overscore/cli auth login' to update your key.
1803
- `);
1804
- }
1805
- return;
1806
- }
1807
- }
1808
- // Check local .env
1809
- const envPath = path.resolve(process.cwd(), ".env");
1810
- if (fs.existsSync(envPath)) {
1811
- const env = parseEnv(fs.readFileSync(envPath, "utf-8"));
1812
- if (env.VITE_OVERSCORE_API_KEY) {
1813
- console.log(`
1814
- Using local .env (this project only)
1900
+ Device token: ${deviceToken.slice(0, 12)}...
1901
+ Project: ${project || "—"}
1902
+ Config: ~/.overscore/config
1815
1903
 
1816
- Key: ${env.VITE_OVERSCORE_API_KEY.slice(0, 10)}...
1817
- Project: ${env.VITE_OVERSCORE_PROJECT_SLUG || "—"}
1818
- 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.
1819
1906
  `);
1820
1907
  return;
1821
1908
  }
@@ -1823,64 +1910,126 @@ async function authStatus() {
1823
1910
  console.log(`
1824
1911
  Not authenticated
1825
1912
 
1826
- No API key found. Run 'npx @overscore/cli auth login' to set one up.
1913
+ Run 'npx @overscore/cli auth login' to authenticate.
1827
1914
  `);
1828
1915
  }
1829
1916
  async function authLogin() {
1830
1917
  const configDir = path.join(process.env.HOME || "", ".overscore");
1831
1918
  const configPath = path.join(configDir, "config");
1832
- console.log("\n Set up Overscore CLI authentication.\n");
1833
- const apiKey = await prompt(" API key (from the Hub):");
1834
- if (!apiKey || !apiKey.startsWith("os_")) {
1835
- console.error("\n Error: API key should start with os_\n");
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");
1836
1936
  process.exit(1);
1837
1937
  }
1838
- // Auto-resolve project from key
1839
- let projectSlug = null;
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`);
1840
1968
  try {
1841
- const res = await fetch(`${HUB_URL}/api/projects/verify-key`, {
1842
- method: "POST",
1843
- headers: { "Content-Type": "application/json" },
1844
- body: JSON.stringify({ key: apiKey }),
1845
- });
1846
- if (res.ok) {
1847
- const data = (await res.json());
1848
- projectSlug = data.project_slug || null;
1849
- if (projectSlug) {
1850
- console.log(`\n Detected project: ${projectSlug}`);
1851
- }
1969
+ const platform = process.platform;
1970
+ if (platform === "darwin") {
1971
+ execSync(`open '${loginUrl}'`);
1972
+ }
1973
+ else if (platform === "win32") {
1974
+ execSync(`start "" "${loginUrl}"`);
1852
1975
  }
1853
1976
  else {
1854
- console.error("\n Error: API key is invalid or expired.\n");
1855
- process.exit(1);
1977
+ execSync(`xdg-open '${loginUrl}'`);
1856
1978
  }
1857
1979
  }
1858
1980
  catch {
1859
- console.error("\n Error: Could not reach overscore.dev to verify key.\n");
1860
- process.exit(1);
1981
+ // Browser open failed user can follow the printed URL
1861
1982
  }
1862
- if (!projectSlug) {
1863
- projectSlug = await prompt(" Project slug:");
1864
- if (!projectSlug) {
1865
- console.error("\n Error: Project slug is required\n");
1866
- process.exit(1);
1867
- }
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 || "";
1868
1997
  }
1869
1998
  fs.mkdirSync(configDir, { recursive: true });
1870
- fs.writeFileSync(configPath, `OVERSCORE_API_URL=${HUB_URL}/api\nOVERSCORE_API_KEY=${apiKey}\nOVERSCORE_PROJECT_SLUG=${projectSlug}\n`, { mode: 0o600 });
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 });
1871
2008
  console.log(`
1872
2009
  Authenticated!
1873
2010
 
1874
- Key: ${apiKey.slice(0, 10)}...
1875
- Project: ${projectSlug}
1876
- Config: ~/.overscore/config
1877
-
1878
- 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.
1879
2014
  `);
1880
2015
  }
1881
2016
  async function authLogout() {
1882
2017
  const configPath = path.join(process.env.HOME || "", ".overscore", "config");
1883
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
+ }
1884
2033
  fs.unlinkSync(configPath);
1885
2034
  console.log("\n Logged out. Global config removed.\n");
1886
2035
  }
@@ -1927,7 +2076,10 @@ ${installed.map((s) => ` - ${s}`).join("\n")}
1927
2076
  // ── Routing ─────────────────────────────────────────────────────────
1928
2077
  const command = process.argv[2];
1929
2078
  const subcommand = process.argv[3];
1930
- if (command === "deploy") {
2079
+ if (command === "dev") {
2080
+ dev();
2081
+ }
2082
+ else if (command === "deploy") {
1931
2083
  deploy();
1932
2084
  }
1933
2085
  else if (command === "pull") {
@@ -2036,8 +2188,9 @@ else {
2036
2188
 
2037
2189
  Commands:
2038
2190
  auth Check authentication status
2039
- auth login Set up or update your API key
2191
+ auth login Authenticate via browser (one time per machine)
2040
2192
  auth logout Remove saved credentials
2193
+ dev Start the dev server (injects auth automatically)
2041
2194
  list List all dashboards in the project
2042
2195
  deploy Build and deploy the dashboard
2043
2196
  pull <slug> Pull source code from the hub
@@ -2047,6 +2200,8 @@ else {
2047
2200
  install-skills Install the Overscore skill library into ~/.claude/skills/
2048
2201
 
2049
2202
  Usage:
2203
+ npx @overscore/cli auth login
2204
+ npx @overscore/cli dev
2050
2205
  npx @overscore/cli list
2051
2206
  npx @overscore/cli deploy --message "description"
2052
2207
  npx @overscore/cli pull <slug>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@overscore/cli",
3
- "version": "0.10.1",
3
+ "version": "0.11.0",
4
4
  "description": "CLI for deploying Overscore dashboards and publishing analyses",
5
5
  "bin": {
6
6
  "overscore": "dist/index.js"