@overscore/cli 0.11.1 → 0.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +98 -63
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -23,7 +23,7 @@ function parseEnv(content) {
|
|
|
23
23
|
}
|
|
24
24
|
return env;
|
|
25
25
|
}
|
|
26
|
-
function loadEnv() {
|
|
26
|
+
async function loadEnv() {
|
|
27
27
|
// (1) Local .env (inside a dashboard project)
|
|
28
28
|
const envPath = path.resolve(process.cwd(), ".env");
|
|
29
29
|
if (fs.existsSync(envPath)) {
|
|
@@ -49,33 +49,32 @@ function loadEnv() {
|
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
-
// No creds
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
52
|
+
// No creds — trigger browser auth inline, then re-check
|
|
53
|
+
console.log("\n No Overscore credentials found. Launching browser login...\n");
|
|
54
|
+
await authLogin();
|
|
55
|
+
const configPath2 = path.join(process.env.HOME || "", ".overscore", "config");
|
|
56
|
+
if (fs.existsSync(configPath2)) {
|
|
57
|
+
const cfg2 = parseEnv(fs.readFileSync(configPath2, "utf-8"));
|
|
58
|
+
if (cfg2.OVERSCORE_API_KEY) {
|
|
59
|
+
return {
|
|
60
|
+
apiUrl: cfg2.OVERSCORE_API_URL || `${HUB_URL}/api`,
|
|
61
|
+
apiKey: cfg2.OVERSCORE_API_KEY,
|
|
62
|
+
dashboardSlug: path.basename(process.cwd()),
|
|
63
|
+
projectSlug: cfg2.OVERSCORE_PROJECT_SLUG || "",
|
|
64
|
+
};
|
|
63
65
|
}
|
|
64
|
-
console.error("\n Error: No credentials found in this analysis folder.");
|
|
65
|
-
console.error(" This usually means the analysis hasn't been linked to your account yet.");
|
|
66
|
-
console.error(" Run:");
|
|
67
|
-
console.error(` npx @overscore/cli analysis pull ${analysisSlug || "<slug>"}`);
|
|
68
|
-
console.error(" to refresh credentials and link this folder, or:");
|
|
69
|
-
console.error(" npx @overscore/cli auth login");
|
|
70
|
-
console.error(" if you've never set up the CLI on this machine.\n");
|
|
71
66
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
67
|
+
// Logged in but no project API key yet — the user needs to run setup first
|
|
68
|
+
console.log(`
|
|
69
|
+
Logged in! To get your first project set up:
|
|
70
|
+
|
|
71
|
+
Create a dashboard: npx create-overscore --dashboard <name> --project <slug>
|
|
72
|
+
Pull an analysis: npx @overscore/cli analysis pull <slug>
|
|
73
|
+
`);
|
|
74
|
+
process.exit(0);
|
|
76
75
|
}
|
|
77
76
|
async function apiRequest(method, urlPath, body, command) {
|
|
78
|
-
const { apiUrl, apiKey } = loadEnv();
|
|
77
|
+
const { apiUrl, apiKey } = await loadEnv();
|
|
79
78
|
const baseUrl = apiUrl.replace(/\/api$/, "");
|
|
80
79
|
const url = `${baseUrl}${urlPath}`;
|
|
81
80
|
const headers = {
|
|
@@ -195,7 +194,7 @@ function extractZip(zipBuffer, targetDir) {
|
|
|
195
194
|
}
|
|
196
195
|
// ── Commands ────────────────────────────────────────────────────────
|
|
197
196
|
async function deploy() {
|
|
198
|
-
const { apiUrl, apiKey, dashboardSlug } = loadEnv();
|
|
197
|
+
const { apiUrl, apiKey, dashboardSlug } = await loadEnv();
|
|
199
198
|
// Parse --message flag
|
|
200
199
|
const msgIndex = process.argv.indexOf("--message");
|
|
201
200
|
const commitMessage = msgIndex !== -1 && process.argv[msgIndex + 1]
|
|
@@ -264,7 +263,7 @@ async function deploy() {
|
|
|
264
263
|
URL: ${data.url}
|
|
265
264
|
`);
|
|
266
265
|
// Sync context files to Hub
|
|
267
|
-
const { projectSlug } = loadEnv();
|
|
266
|
+
const { projectSlug } = await loadEnv();
|
|
268
267
|
const baseUrl = apiUrl.replace(/\/api$/, "");
|
|
269
268
|
// Upload knowledge base (project-level, shared across all artifacts)
|
|
270
269
|
await uploadKnowledgeBase(process.cwd(), baseUrl, projectSlug, apiKey);
|
|
@@ -306,7 +305,7 @@ async function deploy() {
|
|
|
306
305
|
await syncPlatformRules(process.cwd(), baseUrl, apiKey);
|
|
307
306
|
}
|
|
308
307
|
async function queryList() {
|
|
309
|
-
const { dashboardSlug } = loadEnv();
|
|
308
|
+
const { dashboardSlug } = await loadEnv();
|
|
310
309
|
const data = (await apiRequest("GET", `/api/dashboards/${dashboardSlug}/queries`, undefined, "query-list"));
|
|
311
310
|
console.log(`\n Dashboard: ${data.dashboard}\n`);
|
|
312
311
|
// Registered queries
|
|
@@ -353,7 +352,7 @@ async function queryShow(name) {
|
|
|
353
352
|
console.error("\n Usage: npx @overscore/cli query show <name>\n");
|
|
354
353
|
process.exit(1);
|
|
355
354
|
}
|
|
356
|
-
const { dashboardSlug } = loadEnv();
|
|
355
|
+
const { dashboardSlug } = await loadEnv();
|
|
357
356
|
const data = (await apiRequest("GET", `/api/dashboards/${dashboardSlug}/queries`));
|
|
358
357
|
const query = data.queries.find((q) => q.query_name === name);
|
|
359
358
|
if (!query) {
|
|
@@ -421,7 +420,7 @@ async function queryAdd(name, sql) {
|
|
|
421
420
|
console.error("\n Error: Query name must start with a lowercase letter and contain only lowercase letters, numbers, and underscores.\n");
|
|
422
421
|
process.exit(1);
|
|
423
422
|
}
|
|
424
|
-
const { dashboardSlug } = loadEnv();
|
|
423
|
+
const { dashboardSlug } = await loadEnv();
|
|
425
424
|
let body;
|
|
426
425
|
if (datasetName) {
|
|
427
426
|
// Look up the dataset UUID from project_datasets in the queries endpoint
|
|
@@ -454,7 +453,7 @@ async function queryUpdate(name, sql) {
|
|
|
454
453
|
console.error(" npx @overscore/cli query update <name> --file query.sql\n");
|
|
455
454
|
process.exit(1);
|
|
456
455
|
}
|
|
457
|
-
const { dashboardSlug } = loadEnv();
|
|
456
|
+
const { dashboardSlug } = await loadEnv();
|
|
458
457
|
const data = (await apiRequest("PUT", `/api/dashboards/${dashboardSlug}/queries/${encodeURIComponent(name)}`, { sql_text: resolvedSql }));
|
|
459
458
|
console.log(`\n Query "${data.query_name}" updated.`);
|
|
460
459
|
console.log(" Cache has been invalidated — fresh data on next load.\n");
|
|
@@ -469,7 +468,7 @@ async function queryRemove(name) {
|
|
|
469
468
|
console.log(" Cancelled.\n");
|
|
470
469
|
return;
|
|
471
470
|
}
|
|
472
|
-
const { dashboardSlug } = loadEnv();
|
|
471
|
+
const { dashboardSlug } = await loadEnv();
|
|
473
472
|
await apiRequest("DELETE", `/api/dashboards/${dashboardSlug}/queries/${encodeURIComponent(name)}`);
|
|
474
473
|
console.log(`\n Query "${name}" deleted.\n`);
|
|
475
474
|
}
|
|
@@ -527,7 +526,7 @@ async function queryTest(sql) {
|
|
|
527
526
|
console.error(" npx @overscore/cli query run --file queries/foo.sql\n");
|
|
528
527
|
process.exit(1);
|
|
529
528
|
}
|
|
530
|
-
const { projectSlug } = loadEnv();
|
|
529
|
+
const { projectSlug } = await loadEnv();
|
|
531
530
|
if (!projectSlug) {
|
|
532
531
|
const analysisMetaPath = path.resolve(process.cwd(), ".overscore", "analysis.json");
|
|
533
532
|
if (fs.existsSync(analysisMetaPath)) {
|
|
@@ -797,7 +796,7 @@ VITE_OVERSCORE_API_URL=https://overscore.dev/api
|
|
|
797
796
|
`);
|
|
798
797
|
}
|
|
799
798
|
async function versions() {
|
|
800
|
-
const { dashboardSlug } = loadEnv();
|
|
799
|
+
const { dashboardSlug } = await loadEnv();
|
|
801
800
|
const data = (await apiRequest("GET", `/api/dashboards/${dashboardSlug}/versions`));
|
|
802
801
|
console.log(`\n Dashboard: ${dashboardSlug}\n`);
|
|
803
802
|
if (data.versions.length === 0) {
|
|
@@ -821,7 +820,7 @@ async function versions() {
|
|
|
821
820
|
console.log(`\n Pull a version: npx @overscore/cli pull ${dashboardSlug} --version N\n`);
|
|
822
821
|
}
|
|
823
822
|
async function listDashboards() {
|
|
824
|
-
const { apiUrl, apiKey } = loadEnv();
|
|
823
|
+
const { apiUrl, apiKey } = await loadEnv();
|
|
825
824
|
const baseUrl = apiUrl.replace(/\/api$/, "");
|
|
826
825
|
const res = await fetch(`${baseUrl}/api/dashboards`, {
|
|
827
826
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
@@ -927,44 +926,80 @@ async function loadAnalysisConfig() {
|
|
|
927
926
|
}
|
|
928
927
|
}
|
|
929
928
|
}
|
|
930
|
-
// (3)
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
929
|
+
// (3) No valid API key — use an existing device token or trigger browser auth
|
|
930
|
+
let existingCfg = fs.existsSync(configPath)
|
|
931
|
+
? parseEnv(fs.readFileSync(configPath, "utf-8"))
|
|
932
|
+
: {};
|
|
933
|
+
let deviceToken = existingCfg.OVERSCORE_DEVICE_TOKEN;
|
|
934
|
+
if (!deviceToken) {
|
|
935
|
+
console.log("\n No Overscore credentials found. Launching browser login...\n");
|
|
936
|
+
await authLogin();
|
|
937
|
+
existingCfg = parseEnv(fs.readFileSync(configPath, "utf-8"));
|
|
938
|
+
deviceToken = existingCfg.OVERSCORE_DEVICE_TOKEN;
|
|
939
|
+
if (!deviceToken) {
|
|
940
|
+
console.error("\n Error: Login did not complete. Please try again.\n");
|
|
941
|
+
process.exit(1);
|
|
942
|
+
}
|
|
936
943
|
}
|
|
937
|
-
//
|
|
938
|
-
let
|
|
939
|
-
|
|
940
|
-
|
|
944
|
+
// Resolve project slug
|
|
945
|
+
let targetProjectSlug = existingCfg.OVERSCORE_PROJECT_SLUG || "";
|
|
946
|
+
if (!targetProjectSlug) {
|
|
947
|
+
targetProjectSlug = await prompt("Project slug:");
|
|
948
|
+
if (!targetProjectSlug) {
|
|
949
|
+
console.error("\n Error: Project slug is required.\n");
|
|
950
|
+
process.exit(1);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
// Generate an API key via device token (retry once on expired token)
|
|
954
|
+
let generatedKey;
|
|
955
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
956
|
+
const keyGenRes = await fetch(`${HUB_URL}/api/projects/${targetProjectSlug}/api-keys`, {
|
|
941
957
|
method: "POST",
|
|
942
|
-
headers: {
|
|
943
|
-
|
|
958
|
+
headers: {
|
|
959
|
+
Authorization: `Bearer ${deviceToken}`,
|
|
960
|
+
"Content-Type": "application/json",
|
|
961
|
+
},
|
|
962
|
+
body: JSON.stringify({ name: `cli-${Date.now()}` }),
|
|
944
963
|
});
|
|
945
|
-
if (
|
|
946
|
-
const data = (await
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
964
|
+
if (keyGenRes.ok) {
|
|
965
|
+
const data = (await keyGenRes.json());
|
|
966
|
+
generatedKey = data.raw_key;
|
|
967
|
+
break;
|
|
968
|
+
}
|
|
969
|
+
if ((keyGenRes.status === 401 || keyGenRes.status === 403) && attempt === 0) {
|
|
970
|
+
console.log("\n Session expired. Launching browser login...\n");
|
|
971
|
+
await authLogin();
|
|
972
|
+
existingCfg = parseEnv(fs.readFileSync(configPath, "utf-8"));
|
|
973
|
+
deviceToken = existingCfg.OVERSCORE_DEVICE_TOKEN;
|
|
974
|
+
if (!deviceToken) {
|
|
975
|
+
console.error("\n Error: Login did not complete. Please try again.\n");
|
|
976
|
+
process.exit(1);
|
|
950
977
|
}
|
|
978
|
+
continue;
|
|
951
979
|
}
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
// Fall back to manual entry
|
|
955
|
-
}
|
|
956
|
-
if (!projectSlug) {
|
|
957
|
-
projectSlug = await prompt("Project slug:");
|
|
958
|
-
if (!projectSlug) {
|
|
959
|
-
console.error("\n Error: Project slug is required\n");
|
|
960
|
-
process.exit(1);
|
|
980
|
+
if (keyGenRes.status === 403) {
|
|
981
|
+
console.error(`\n Error: Access denied for project "${targetProjectSlug}". Make sure you have access.\n`);
|
|
961
982
|
}
|
|
983
|
+
else {
|
|
984
|
+
console.error(`\n Error: Failed to generate API key (HTTP ${keyGenRes.status}).\n`);
|
|
985
|
+
}
|
|
986
|
+
process.exit(1);
|
|
962
987
|
}
|
|
963
|
-
|
|
988
|
+
if (!generatedKey) {
|
|
989
|
+
console.error("\n Error: Failed to generate API key.\n");
|
|
990
|
+
process.exit(1);
|
|
991
|
+
}
|
|
992
|
+
// Persist the new key
|
|
993
|
+
const newLines = [
|
|
994
|
+
`OVERSCORE_API_URL=${HUB_URL}/api`,
|
|
995
|
+
`OVERSCORE_DEVICE_TOKEN=${deviceToken}`,
|
|
996
|
+
`OVERSCORE_API_KEY=${generatedKey}`,
|
|
997
|
+
`OVERSCORE_PROJECT_SLUG=${targetProjectSlug}`,
|
|
998
|
+
];
|
|
964
999
|
fs.mkdirSync(configDir, { recursive: true });
|
|
965
|
-
fs.writeFileSync(configPath,
|
|
1000
|
+
fs.writeFileSync(configPath, newLines.join("\n") + "\n", { mode: 0o600 });
|
|
966
1001
|
console.log(`\n Saved to ~/.overscore/config\n`);
|
|
967
|
-
return { apiUrl: `${HUB_URL}/api`, apiKey, projectSlug };
|
|
1002
|
+
return { apiUrl: `${HUB_URL}/api`, apiKey: generatedKey, projectSlug: targetProjectSlug };
|
|
968
1003
|
}
|
|
969
1004
|
async function analysisApiRequest(cfg, method, urlPath, body) {
|
|
970
1005
|
const baseUrl = cfg.apiUrl.replace(/\/api$/, "");
|