@overscore/cli 0.13.14 → 0.13.16
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
CHANGED
|
@@ -913,7 +913,7 @@ function formatCell(value) {
|
|
|
913
913
|
}
|
|
914
914
|
async function pull(slug) {
|
|
915
915
|
if (!slug) {
|
|
916
|
-
console.error("\n Usage: npx @overscore/cli pull <dashboard-slug> [--version N]\n");
|
|
916
|
+
console.error("\n Usage: npx @overscore/cli pull <dashboard-slug> [--version N] [--force]\n");
|
|
917
917
|
console.error(" Example:");
|
|
918
918
|
console.error(" npx @overscore/cli pull my-dashboard\n");
|
|
919
919
|
process.exit(1);
|
|
@@ -923,35 +923,47 @@ async function pull(slug) {
|
|
|
923
923
|
const versionParam = versionIndex !== -1 && process.argv[versionIndex + 1]
|
|
924
924
|
? process.argv[versionIndex + 1]
|
|
925
925
|
: null;
|
|
926
|
-
//
|
|
926
|
+
// --force skips the "directory exists, overwrite?" confirm — needed for non-interactive
|
|
927
|
+
// re-pulls (scripted rebuilds / `workspace sync`). Source is versioned in the Hub, and the
|
|
928
|
+
// clean step below already preserves node_modules + .env, so overwriting is safe.
|
|
929
|
+
const force = process.argv.includes("--force");
|
|
930
|
+
// Resolve API key: device token → auto-create os_ key, else the saved project key, else prompt.
|
|
927
931
|
let apiKey = "";
|
|
928
932
|
const configPath = path.join(process.env.HOME || "", ".overscore", "config");
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
})
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
if (keyRes.ok) {
|
|
947
|
-
const keyData = (await keyRes.json());
|
|
948
|
-
apiKey = keyData.raw_key || "";
|
|
949
|
-
}
|
|
933
|
+
const cfg = fs.existsSync(configPath)
|
|
934
|
+
? parseEnv(fs.readFileSync(configPath, "utf-8"))
|
|
935
|
+
: {};
|
|
936
|
+
const deviceToken = cfg.OVERSCORE_DEVICE_TOKEN;
|
|
937
|
+
if (deviceToken) {
|
|
938
|
+
try {
|
|
939
|
+
const keyRes = await fetch(`${HUB_URL}/api/projects/${slug}/api-keys`, {
|
|
940
|
+
method: "POST",
|
|
941
|
+
headers: {
|
|
942
|
+
Authorization: `Bearer ${deviceToken}`,
|
|
943
|
+
"Content-Type": "application/json",
|
|
944
|
+
},
|
|
945
|
+
body: JSON.stringify({ name: `${slug} pull key` }),
|
|
946
|
+
});
|
|
947
|
+
if (keyRes.status === 401 || keyRes.status === 403) {
|
|
948
|
+
console.error("\n Error: Device token is invalid or expired. Run 'npx @overscore/cli auth login' to re-authenticate.\n");
|
|
949
|
+
process.exit(1);
|
|
950
950
|
}
|
|
951
|
-
|
|
952
|
-
|
|
951
|
+
if (keyRes.ok) {
|
|
952
|
+
const keyData = (await keyRes.json());
|
|
953
|
+
apiKey = keyData.raw_key || "";
|
|
953
954
|
}
|
|
954
955
|
}
|
|
956
|
+
catch {
|
|
957
|
+
// Network error — fall through to the saved key / prompt
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
// The mint above targets /api/projects/<slug>, but `slug` is a DASHBOARD slug — for any
|
|
961
|
+
// project whose name differs from the dashboard (i.e. any multi-dashboard project) that
|
|
962
|
+
// 404s and leaves apiKey empty. Fall back to the project API key saved by `auth login`:
|
|
963
|
+
// it authorizes every dashboard in the logged-in project, which is the common pull case
|
|
964
|
+
// (and what makes non-interactive `pull` / scripted rebuilds work).
|
|
965
|
+
if (!apiKey && cfg.OVERSCORE_API_KEY) {
|
|
966
|
+
apiKey = cfg.OVERSCORE_API_KEY;
|
|
955
967
|
}
|
|
956
968
|
if (!apiKey) {
|
|
957
969
|
console.log(" Tip: Run 'npx @overscore/cli auth login' to authenticate once and skip this step.");
|
|
@@ -989,7 +1001,7 @@ async function pull(slug) {
|
|
|
989
1001
|
const zipBuffer = Buffer.from(await res.arrayBuffer());
|
|
990
1002
|
// Extract to ./<slug>/
|
|
991
1003
|
const targetDir = path.resolve(process.cwd(), slug);
|
|
992
|
-
if (fs.existsSync(targetDir)) {
|
|
1004
|
+
if (fs.existsSync(targetDir) && !force) {
|
|
993
1005
|
const yes = await confirm(`Directory "${slug}" already exists. Overwrite source files?`);
|
|
994
1006
|
if (!yes) {
|
|
995
1007
|
console.log(" Cancelled.\n");
|
|
@@ -1608,6 +1620,37 @@ function decidePlatformWrite(fullPath, serverContent, serverHash, lastSyncedHash
|
|
|
1608
1620
|
}
|
|
1609
1621
|
return { action: "skip", reason: "user-modified" };
|
|
1610
1622
|
}
|
|
1623
|
+
// The Hub platform-rules channel currently serves dashboard content (React
|
|
1624
|
+
// rules, deploy-dashboard/start-dashboard skills, and dashboard flavors of
|
|
1625
|
+
// skills the analysis template ships its own versions of). Syncing it into
|
|
1626
|
+
// an analysis misleads Claude and marks the analysis's own skills as
|
|
1627
|
+
// "user-modified" — so analyses only receive the artifact-neutral files.
|
|
1628
|
+
const ANALYSIS_PLATFORM_ALLOW = [
|
|
1629
|
+
"platform-capabilities.md",
|
|
1630
|
+
"skills/update-platform/",
|
|
1631
|
+
];
|
|
1632
|
+
function isAnalysisDir(targetDir) {
|
|
1633
|
+
if (fs.existsSync(path.join(targetDir, ".overscore", "analysis.json"))) {
|
|
1634
|
+
return true;
|
|
1635
|
+
}
|
|
1636
|
+
// Pulled bundles don't include .overscore/ — fall back to the CLAUDE.md
|
|
1637
|
+
// heading, the same detection syncClaudeMdImports uses.
|
|
1638
|
+
const claudeMdPath = path.join(targetDir, ".claude", "CLAUDE.md");
|
|
1639
|
+
if (!fs.existsSync(claudeMdPath))
|
|
1640
|
+
return false;
|
|
1641
|
+
return /^#\s+Overscore\s+Analysis/im.test(fs.readFileSync(claudeMdPath, "utf-8"));
|
|
1642
|
+
}
|
|
1643
|
+
function filterPlatformFilesForArtifact(targetDir, files) {
|
|
1644
|
+
if (!isAnalysisDir(targetDir))
|
|
1645
|
+
return files;
|
|
1646
|
+
const kept = {};
|
|
1647
|
+
for (const [relPath, content] of Object.entries(files)) {
|
|
1648
|
+
if (ANALYSIS_PLATFORM_ALLOW.some((p) => relPath === p || relPath.startsWith(p))) {
|
|
1649
|
+
kept[relPath] = content;
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
return kept;
|
|
1653
|
+
}
|
|
1611
1654
|
/**
|
|
1612
1655
|
* Fetch the latest platform-owned Claude Code rules from the Hub and write
|
|
1613
1656
|
* them into the project's .claude/ directory. Called from `deploy` (before
|
|
@@ -1615,6 +1658,7 @@ function decidePlatformWrite(fullPath, serverContent, serverHash, lastSyncedHash
|
|
|
1615
1658
|
* paths). User-owned files (knowledge/, this-dashboard.md) are NOT touched.
|
|
1616
1659
|
*
|
|
1617
1660
|
* Preserves user edits via .claude/.platform-meta.json hash tracking.
|
|
1661
|
+
* Analyses only receive the artifact-neutral subset (see ANALYSIS_PLATFORM_ALLOW).
|
|
1618
1662
|
*/
|
|
1619
1663
|
async function syncPlatformRules(targetDir, baseUrl, apiKey) {
|
|
1620
1664
|
try {
|
|
@@ -1626,10 +1670,11 @@ async function syncPlatformRules(targetDir, baseUrl, apiKey) {
|
|
|
1626
1670
|
const data = (await res.json());
|
|
1627
1671
|
if (!data.files)
|
|
1628
1672
|
return;
|
|
1673
|
+
const files = filterPlatformFilesForArtifact(targetDir, data.files);
|
|
1629
1674
|
const meta = readPlatformMeta(targetDir);
|
|
1630
1675
|
const written = [];
|
|
1631
1676
|
const preserved = [];
|
|
1632
|
-
for (const [relPath, content] of Object.entries(
|
|
1677
|
+
for (const [relPath, content] of Object.entries(files)) {
|
|
1633
1678
|
const fullPath = path.join(targetDir, ".claude", relPath);
|
|
1634
1679
|
const serverHash = data.hashes?.[relPath] ?? md5(content);
|
|
1635
1680
|
const lastSyncedHash = meta.last_synced[relPath] ?? null;
|
|
@@ -1695,10 +1740,11 @@ async function platformUpdate(checkOnly = false, force = false) {
|
|
|
1695
1740
|
console.error("\n Error: No files in platform-rules response.\n");
|
|
1696
1741
|
process.exit(1);
|
|
1697
1742
|
}
|
|
1743
|
+
const files = filterPlatformFilesForArtifact(process.cwd(), data.files);
|
|
1698
1744
|
const meta = readPlatformMeta(process.cwd());
|
|
1699
1745
|
const updated = [];
|
|
1700
1746
|
const preserved = [];
|
|
1701
|
-
for (const [relPath, content] of Object.entries(
|
|
1747
|
+
for (const [relPath, content] of Object.entries(files)) {
|
|
1702
1748
|
const fullPath = path.join(process.cwd(), ".claude", relPath);
|
|
1703
1749
|
const serverHash = data.hashes?.[relPath] ?? md5(content);
|
|
1704
1750
|
const lastSyncedHash = meta.last_synced[relPath] ?? null;
|
|
@@ -1937,6 +1983,9 @@ const ANALYSIS_PUBLISH_BASELINE_EXCLUDE = new Set([
|
|
|
1937
1983
|
".git",
|
|
1938
1984
|
".DS_Store",
|
|
1939
1985
|
".overscore",
|
|
1986
|
+
// Raw query-result cache (real warehouse rows) — never publishable, even
|
|
1987
|
+
// if .overscoreignore is edited. Mirrors the dashboard deploy SOURCE_EXCLUDE.
|
|
1988
|
+
"data",
|
|
1940
1989
|
]);
|
|
1941
1990
|
function isBaselineExcluded(name) {
|
|
1942
1991
|
if (ANALYSIS_PUBLISH_BASELINE_EXCLUDE.has(name))
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@ paths:
|
|
|
11
11
|
|
|
12
12
|
- **Inline all CSS.** No `<link rel="stylesheet">`, no Tailwind CDN, no Google Fonts (or use a `data:` URI / system stack). Use a single `<style>` block in the `<head>`.
|
|
13
13
|
- **Bake the data into the HTML.** No `fetch()`, no `XMLHttpRequest`, no API calls. The numbers must be visible to anyone who opens the file with no logins, no tokens, no broken external dependencies.
|
|
14
|
-
- **Charts are inline.** Inline SVG is the safest option. Embedded base64 PNGs are OK.
|
|
14
|
+
- **Charts are inline.** Inline SVG is the safest option. Embedded base64 PNGs are OK. **No CDN or external `<script src>` — the published report is served under a CSP that blocks all external scripts**, so a CDN-loaded chart library works in local preview but silently breaks after publish. If you must use a JS chart library, paste its source into an inline `<script>` block — but inline SVG is preferred because it survives indefinitely.
|
|
15
15
|
- **No `useQuery`, no `@overscore/client`, no React.** This is a static HTML file, not a React app.
|
|
16
16
|
|
|
17
17
|
## Aesthetic direction
|