@overscore/cli 0.13.7 → 0.13.9
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 +71 -10
- package/dist/template-push.js +5 -3
- package/package.json +1 -1
- package/skills/bind-to-my-data/SKILL.md +0 -40
- package/skills/regenerate-clean/SKILL.md +0 -43
package/dist/index.js
CHANGED
|
@@ -41,10 +41,17 @@ function parseEnv(content) {
|
|
|
41
41
|
function safeApiUrl(apiUrl) {
|
|
42
42
|
try {
|
|
43
43
|
const h = new URL(apiUrl).hostname;
|
|
44
|
+
// Also allow the configured hub's host — prod by default, or a staging host
|
|
45
|
+
// only if the operator set OVERSCORE_HUB_URL (a trusted, user-set env var).
|
|
46
|
+
// If OVERSCORE_HUB_URL is unset, hubHost === overscore.dev and behavior is
|
|
47
|
+
// identical to before, so real users are unaffected.
|
|
48
|
+
const hubHost = new URL(HUB_URL).hostname;
|
|
44
49
|
if (h === "overscore.dev" ||
|
|
45
50
|
h.endsWith(".overscore.dev") ||
|
|
46
51
|
h === "localhost" ||
|
|
47
|
-
h === "127.0.0.1"
|
|
52
|
+
h === "127.0.0.1" ||
|
|
53
|
+
h === hubHost ||
|
|
54
|
+
h.endsWith("." + hubHost)) {
|
|
48
55
|
return apiUrl;
|
|
49
56
|
}
|
|
50
57
|
}
|
|
@@ -176,7 +183,17 @@ async function apiRequest(method, urlPath, body, command) {
|
|
|
176
183
|
}
|
|
177
184
|
}
|
|
178
185
|
if (!res.ok) {
|
|
179
|
-
|
|
186
|
+
const errMsg = data.error || `HTTP ${res.status}`;
|
|
187
|
+
console.error(`\n Error: ${errMsg}\n`);
|
|
188
|
+
// The warehouse "no data connection" error is a common onboarding dead-end:
|
|
189
|
+
// a dataset-backed project has no BigQuery connection, but `query run`/`test`
|
|
190
|
+
// only hit BigQuery. Point the user at the path that actually works.
|
|
191
|
+
if (/no data connection/i.test(errMsg)) {
|
|
192
|
+
console.error(" This usually means one of two things:");
|
|
193
|
+
console.error(" • The project uses an uploaded dataset (not BigQuery). `query run`/`test` only hit");
|
|
194
|
+
console.error(" BigQuery — inspect dataset columns and sample rows with: npm run queries");
|
|
195
|
+
console.error(" • The project should use BigQuery but no warehouse is connected yet — connect it in the Hub.\n");
|
|
196
|
+
}
|
|
180
197
|
process.exit(1);
|
|
181
198
|
}
|
|
182
199
|
return data;
|
|
@@ -268,6 +285,10 @@ const SOURCE_EXCLUDE = new Set([
|
|
|
268
285
|
// this baseline exclusion protects the dashboard deploy path too, regardless of
|
|
269
286
|
// whether a project has a .overscoreignore.
|
|
270
287
|
"data",
|
|
288
|
+
// Vendored DuckDB runtime from a local-preview workaround (public/_runtime).
|
|
289
|
+
// Production serves /_runtime from the edge; shipping ~74MB of .wasm/.js would
|
|
290
|
+
// bloat the deploy. The built-files guard in deploy() also blocks it in dist/.
|
|
291
|
+
"_runtime",
|
|
271
292
|
]);
|
|
272
293
|
const SECRET_FILE_EXTENSIONS = new Set([
|
|
273
294
|
".pem",
|
|
@@ -437,6 +458,30 @@ async function deploy() {
|
|
|
437
458
|
}
|
|
438
459
|
const builtFiles = collectFiles(distDir, "");
|
|
439
460
|
console.log(` Found ${builtFiles.length} built files.`);
|
|
461
|
+
// Guard: a vendored DuckDB runtime (public/_runtime) gets copied into dist/ by
|
|
462
|
+
// Vite and would ship ~74MB of .wasm/.js that production already serves from the
|
|
463
|
+
// edge. This is a local-preview workaround that must never deploy. Fail closed
|
|
464
|
+
// unless explicitly overridden with --allow-runtime.
|
|
465
|
+
const runtimeFiles = builtFiles.filter((f) => f === "_runtime" || f.startsWith("_runtime/"));
|
|
466
|
+
if (runtimeFiles.length > 0 && !process.argv.includes("--allow-runtime")) {
|
|
467
|
+
let bytes = 0;
|
|
468
|
+
for (const f of runtimeFiles) {
|
|
469
|
+
try {
|
|
470
|
+
bytes += fs.statSync(path.join(distDir, f)).size;
|
|
471
|
+
}
|
|
472
|
+
catch {
|
|
473
|
+
// ignore unreadable entries when sizing
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
const mb = (bytes / (1024 * 1024)).toFixed(1);
|
|
477
|
+
console.error(`\n Deploy aborted: dist/_runtime contains ${runtimeFiles.length} file(s) (${mb}MB).`);
|
|
478
|
+
console.error(" This is a vendored DuckDB runtime, usually from a public/_runtime local-preview");
|
|
479
|
+
console.error(" workaround. Production serves /_runtime from the edge, so shipping it just bloats");
|
|
480
|
+
console.error(" the deploy.");
|
|
481
|
+
console.error("\n Fix: rm -rf public/_runtime (then deploy again)");
|
|
482
|
+
console.error(" Or pass --allow-runtime to deploy it anyway.\n");
|
|
483
|
+
process.exit(1);
|
|
484
|
+
}
|
|
440
485
|
// Collect source files (skipped entirely with --no-source). Source is saved
|
|
441
486
|
// for versioning/restore; symlinks and secrets are always excluded.
|
|
442
487
|
const noSource = process.argv.includes("--no-source");
|
|
@@ -873,8 +918,8 @@ async function pull(slug) {
|
|
|
873
918
|
process.exit(1);
|
|
874
919
|
}
|
|
875
920
|
}
|
|
876
|
-
const apiUrl =
|
|
877
|
-
const baseUrl =
|
|
921
|
+
const apiUrl = `${HUB_URL}/api`;
|
|
922
|
+
const baseUrl = HUB_URL;
|
|
878
923
|
const versionQuery = versionParam ? `?version=${versionParam}` : "";
|
|
879
924
|
const url = `${baseUrl}/api/dashboards/${slug}/source${versionQuery}`;
|
|
880
925
|
console.log(`\n Pulling source for: ${slug}${versionParam ? ` (v${versionParam})` : ""}...`);
|
|
@@ -925,7 +970,7 @@ async function pull(slug) {
|
|
|
925
970
|
const envContent = `# Overscore Configuration
|
|
926
971
|
VITE_OVERSCORE_PROJECT_SLUG=${projectSlug}
|
|
927
972
|
VITE_OVERSCORE_DASHBOARD_SLUG=${dashboardSlug}
|
|
928
|
-
VITE_OVERSCORE_API_URL
|
|
973
|
+
VITE_OVERSCORE_API_URL=${HUB_URL}/api
|
|
929
974
|
`;
|
|
930
975
|
fs.writeFileSync(path.join(targetDir, ".env"), envContent, { mode: 0o600 });
|
|
931
976
|
const rulesDir = path.join(targetDir, ".claude", "rules");
|
|
@@ -1087,7 +1132,23 @@ function collectFiles(dir, prefix) {
|
|
|
1087
1132
|
return files;
|
|
1088
1133
|
}
|
|
1089
1134
|
// ── Analysis commands ──────────────────────────────────────────────
|
|
1090
|
-
|
|
1135
|
+
// Hub the CLI talks to. Defaults to production. A TRUSTED override — the
|
|
1136
|
+
// OVERSCORE_HUB_URL env var, set deliberately by the operator (e.g. to point at
|
|
1137
|
+
// a staging environment during a security audit) — can repoint it. This is
|
|
1138
|
+
// distinct from the project-.env override (VITE_OVERSCORE_API_URL), which stays
|
|
1139
|
+
// restricted by safeApiUrl() so a poisoned project can't redirect the API key +
|
|
1140
|
+
// bundle to an attacker host (audit L6).
|
|
1141
|
+
const HUB_URL = (process.env.OVERSCORE_HUB_URL || "https://overscore.dev").replace(/\/+$/, "");
|
|
1142
|
+
// Bare host of the hub (overscore.dev, or overscore-test.com on staging) for
|
|
1143
|
+
// building the {slug}.<host> URLs we print back to the user.
|
|
1144
|
+
const HUB_HOST = (() => {
|
|
1145
|
+
try {
|
|
1146
|
+
return new URL(HUB_URL).hostname;
|
|
1147
|
+
}
|
|
1148
|
+
catch {
|
|
1149
|
+
return "overscore.dev";
|
|
1150
|
+
}
|
|
1151
|
+
})();
|
|
1091
1152
|
function slugify(input) {
|
|
1092
1153
|
return input
|
|
1093
1154
|
.toLowerCase()
|
|
@@ -1754,7 +1815,7 @@ async function analysisNew(title) {
|
|
|
1754
1815
|
npx @overscore/cli analysis publish
|
|
1755
1816
|
|
|
1756
1817
|
The analysis will go live at:
|
|
1757
|
-
https://${data.project_slug}
|
|
1818
|
+
https://${data.project_slug}.${HUB_HOST}/analysis/${data.slug}
|
|
1758
1819
|
`);
|
|
1759
1820
|
}
|
|
1760
1821
|
function loadAnalysisMetadata() {
|
|
@@ -2347,7 +2408,7 @@ async function dev() {
|
|
|
2347
2408
|
apiKey = keyData.raw_key ?? null;
|
|
2348
2409
|
}
|
|
2349
2410
|
catch {
|
|
2350
|
-
console.error(
|
|
2411
|
+
console.error(`\n Error: Could not reach ${HUB_HOST} to create dev session key.\n`);
|
|
2351
2412
|
process.exit(1);
|
|
2352
2413
|
}
|
|
2353
2414
|
if (!apiKey) {
|
|
@@ -2606,7 +2667,7 @@ async function templatePull(slug) {
|
|
|
2606
2667
|
? apiSlug.slice(apiSlug.indexOf("_") + 1)
|
|
2607
2668
|
: apiSlug;
|
|
2608
2669
|
const targetDir = path.resolve(process.cwd(), process.argv[5] || localSlug);
|
|
2609
|
-
const url =
|
|
2670
|
+
const url = `${HUB_URL}/api/templates/${apiSlug}/source`;
|
|
2610
2671
|
console.log(`\n Pulling template: ${slug}...`);
|
|
2611
2672
|
let res;
|
|
2612
2673
|
try {
|
|
@@ -2625,7 +2686,7 @@ async function templatePull(slug) {
|
|
|
2625
2686
|
});
|
|
2626
2687
|
}
|
|
2627
2688
|
catch {
|
|
2628
|
-
console.error(`\n Error: Could not reach
|
|
2689
|
+
console.error(`\n Error: Could not reach ${HUB_HOST}\n`);
|
|
2629
2690
|
process.exit(1);
|
|
2630
2691
|
}
|
|
2631
2692
|
if (!res.ok) {
|
package/dist/template-push.js
CHANGED
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
import fs from "fs";
|
|
35
35
|
import path from "path";
|
|
36
36
|
import { execSync } from "child_process";
|
|
37
|
-
|
|
37
|
+
// Trusted operator override (e.g. staging audit); defaults to production.
|
|
38
|
+
const HUB_URL = (process.env.OVERSCORE_HUB_URL || "https://overscore.dev").replace(/\/+$/, "");
|
|
38
39
|
// Mirrors the templates_slug_format CHECK in the marketplace migration.
|
|
39
40
|
// `_` is the handle namespace delimiter in the stored slug ({handle}_{local}).
|
|
40
41
|
const TEMPLATE_SLUG_RE = /^[a-z0-9]([a-z0-9_-]*[a-z0-9])?$/;
|
|
@@ -263,7 +264,7 @@ export async function templatePush(slugArg) {
|
|
|
263
264
|
});
|
|
264
265
|
}
|
|
265
266
|
catch {
|
|
266
|
-
console.error(`\n Error: Could not reach
|
|
267
|
+
console.error(`\n Error: Could not reach ${new URL(HUB_URL).hostname}\n`);
|
|
267
268
|
process.exit(1);
|
|
268
269
|
}
|
|
269
270
|
const data = (await res.json().catch(() => ({})));
|
|
@@ -300,7 +301,8 @@ export async function templatePush(slugArg) {
|
|
|
300
301
|
const sourceCount = sourceFiles.length;
|
|
301
302
|
const listingUrl = data.detail_url
|
|
302
303
|
? `${HUB_URL}${data.detail_url}`
|
|
303
|
-
:
|
|
304
|
+
: // Mirror the hub's templateExploreUrl: /u/{handle}/{slug} (first `_` only).
|
|
305
|
+
`${HUB_URL}/u/${slug.replace("_", "/")}`;
|
|
304
306
|
console.log(`
|
|
305
307
|
Pushed successfully!
|
|
306
308
|
|
package/package.json
CHANGED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: bind-to-my-data
|
|
3
|
-
description: Binds a pulled or forked Overscore template to the user's own warehouse by regenerating SQL against their real schema and registering it under the template's query names, turning a sample-data demo into a live dashboard. Use after pulling or forking a template, when a dashboard shows mock or sample data and the user wants their own numbers, or when the user says "connect this template to my data", "bind to my warehouse", "make this use my real data", "wire up the queries", "regenerate the queries for my schema".
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Bind a template to your data
|
|
7
|
-
|
|
8
|
-
A pulled or forked template ships design + sample data + query **names** — never the author's SQL. To go live, you regenerate SQL against the user's OWN warehouse and register it under the same names. **All SQL generation runs here, on the user's own Claude tokens;** Overscore only stores and executes the result.
|
|
9
|
-
|
|
10
|
-
## Step 0 — detect state
|
|
11
|
-
|
|
12
|
-
| You see… | State | Do |
|
|
13
|
-
| --- | --- | --- |
|
|
14
|
-
| `.overscore/template.json`, no `.env` | **unbound demo** (`template pull`) | a pulled template has no project binding and runs on baked sample data. Land it in a project first: `npx create-overscore --template <handle>/<slug>`, or fork it in the hub and then `npx @overscore/cli pull <dashboard-slug>` to get a bound copy. Then continue. |
|
|
15
|
-
| `.env` with `VITE_OVERSCORE_DASHBOARD_SLUG` | **bound dashboard** (forked + pulled) | ready to bind — continue. |
|
|
16
|
-
|
|
17
|
-
## Process
|
|
18
|
-
|
|
19
|
-
1. **Ensure a data source exists.** The project needs either a BigQuery connection (hub → the project's **Settings** page → **Connect BigQuery**, paste a read-only service-account JSON) or a dataset. New accounts auto-seed a sample dataset; the user can also upload one. Without one of these there is nothing real to bind to.
|
|
20
|
-
2. **Read the contract.** Find every query the template expects. If the template still calls `useQuery("<name>")`, grep those names. If it was already converted for publishing, the `useQuery` calls are gone — the contract now lives in `src/mockData.ts`: each `MOCK_*` export's shape, plus any `// query:` annotation naming it. If names aren't annotated, infer them from the export names and how each component consumes the array. Run `npx @overscore/cli query list` to see what's already registered (a fresh fork has none).
|
|
21
|
-
3. **Inspect the real schema.** Look at the user's actual tables and columns. Design SQL that produces each query's required shape from THEIR data — explore with `npx @overscore/cli query run "<sql>"` (ad-hoc; results land in `./data` and are never deployed).
|
|
22
|
-
4. **Register SQL under the SAME names.** For each query name:
|
|
23
|
-
```bash
|
|
24
|
-
npx @overscore/cli query add <name> "<sql>" # BigQuery-backed
|
|
25
|
-
npx @overscore/cli query add <name> --dataset <name> # or dataset-backed
|
|
26
|
-
```
|
|
27
|
-
The name MUST match the template's `useQuery` call exactly, or that chart stays empty.
|
|
28
|
-
5. **Go live in the components.** If the template renders from `src/mockData.ts` (converted for publishing), rewire each `import { MOCK_X }` back to `const { data } = useQuery("x")`. Keep the charts and layout identical — only the data source changes.
|
|
29
|
-
6. **Verify and deploy.**
|
|
30
|
-
```bash
|
|
31
|
-
npx @overscore/cli dev # resolves the names against your registered SQL
|
|
32
|
-
npx @overscore/cli deploy --message "Bound <template> to my data"
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## What this skill does NOT do
|
|
36
|
-
|
|
37
|
-
- **It does not generate SQL on Overscore's servers.** Every query is reasoned out here, against the user's schema, on the user's own Claude tokens.
|
|
38
|
-
- **It does not invent query names.** Register under the EXACT names the template references — that mapping is the contract.
|
|
39
|
-
- **It does not write SQL inside React.** Queries are referenced by name via `useQuery`; the SQL lives in the registry via `query add` only.
|
|
40
|
-
- **It does not need the author's queries.** Query SQL isn't registered with a template — queries live by name only — so you rebuild it from the design + names against your own data.
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: regenerate-clean
|
|
3
|
-
description: Converts an Overscore dashboard or pulled template into a publishable template by replacing every live query with typed synthetic mock data in src/mockData.ts, so the frozen bundle renders standalone and no real warehouse data ever ships. Use when preparing a dashboard to publish to the marketplace, when a pulled template still shows "Failed to load" or runs live queries, or when the user says "publish this as a template", "convert this to mock data", "regenerate clean", "make this template safe to share", "prep this for the marketplace".
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Regenerate clean — convert a dashboard into a safe template
|
|
7
|
-
|
|
8
|
-
Run this in a pulled template directory (or a from-scratch dashboard) to swap every live `useQuery(...)` for fresh **synthetic** mock data, so the published template renders on its own. A template carries design + sample data + query **names** — never the creator's real SQL, schema, or rows. You generate the mock data here, locally, on the user's own Claude tokens. **Overscore's servers never generate anything.**
|
|
9
|
-
|
|
10
|
-
## Step 0 — detect context
|
|
11
|
-
|
|
12
|
-
| You see… | You're in… | Do |
|
|
13
|
-
| --- | --- | --- |
|
|
14
|
-
| `.overscore/template.json` | a **pulled template** (`template pull`) | convert it in place, then `template push` |
|
|
15
|
-
| `.env` with `VITE_OVERSCORE_DASHBOARD_SLUG` + live `useQuery` calls | a **live dashboard** | DON'T convert in place. Tell the user to open the dashboard's **Publish as template** modal first — for a live-data dashboard it creates a **private draft** copy. Then `npx @overscore/cli template pull <handle>/<slug>` that draft and run this skill on the copy. **Never touch the live dashboard.** |
|
|
16
|
-
| `src/mockData.ts` present, no `useQuery` left | already converted | skip to Verify |
|
|
17
|
-
|
|
18
|
-
The original live dashboard is never modified. If it has live queries, the rule is **convert a private copy, attest, then promote** — never push a live-data dashboard straight to public.
|
|
19
|
-
|
|
20
|
-
## Process
|
|
21
|
-
|
|
22
|
-
1. **Inventory the data contract.** Grep the source for every `useQuery("<name>")` call. For each, record (a) the query **name** and (b) the row **shape** the component consumes — every column key and its type. These names + shapes ARE the template's contract; preserve them exactly.
|
|
23
|
-
2. **Generate synthetic data — locally, on the user's tokens.** Create `src/mockData.ts`: one exported `interface` + one exported `MOCK_<NAME>` array per query, with realistic-but-fabricated rows matching each shape (same column names, types, rough cardinality). Annotate each export with its query name, e.g. `// query: mrr_monthly` — once `useQuery` is gone (step 3) this comment is the only record of the name a consumer needs to rebind. **Scrub every real company name, customer name, and identifying number** — invent plausible substitutes.
|
|
24
|
-
3. **Rewire the components.** Replace each `const { data } = useQuery("x")` with `import { MOCK_X } from "./mockData"`. Change ONLY the data source — keep every chart, metric, filter, and layout byte-for-byte identical.
|
|
25
|
-
4. **Strip real data.** Delete any `data/` directory (raw query dumps) and any `fixtures/`, `dumps/`, or `exports/` holding real rows. Confirm there is no `.env*`, `*.log`, or credential file in the tree.
|
|
26
|
-
5. **Self-review for inline leaks.** The publish floor rejects `data/`, `.env*`, `*.log`, and secret-named files — but it is **content-blind**. Read `src/mockData.ts` and the components and confirm no real value (a real revenue figure, a real account name) was left inline.
|
|
27
|
-
|
|
28
|
-
## Verify, then publish
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
npx @overscore/cli dev # renders on mock data — no API key or warehouse needed
|
|
32
|
-
npm run build # confirm the rewire compiles
|
|
33
|
-
npx @overscore/cli template push # upload the converted bundle (slug auto-read from .overscore/template.json)
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
After push, open the listing's **Update** modal, **read** the mock-data attestation, and only then check it and set the visibility (Public / Unlisted / Private). Attestation is a truthful claim that the data is synthetic — never check it on an unconverted bundle.
|
|
37
|
-
|
|
38
|
-
## What this skill does NOT do
|
|
39
|
-
|
|
40
|
-
- **It does not call any Overscore server-side AI.** All synthetic data is generated here, by you, on the user's own Claude tokens. Never tell the user "Overscore will convert this for you."
|
|
41
|
-
- **It does not modify the original live dashboard.** Conversion always happens on a pulled template copy.
|
|
42
|
-
- **It does not attest or publish for the user.** The user reads and checks the attestation themselves after reviewing the result.
|
|
43
|
-
- **It does not lean on the server strip as the strategy.** The floor is a backstop; keeping real data out of the bundle in the first place is your job.
|